Commit 0fc94993f38fc854d2cb608d540701f934f0c35d

Authored by 陆恒
1 parent d5aaa06d
Exists in master

增加生成uuid

src/HttpServer/logic/uuid.go 0 → 100644
... ... @@ -0,0 +1,16 @@
  1 +package logic
  2 +
  3 +import (
  4 + "common/logger"
  5 + "github.com/segmentio"
  6 +)
  7 +
  8 +func Test() {
  9 +
  10 + for i:=0;i<100;i++ {
  11 +
  12 + id := ksuid.New()
  13 + logger.Info("Test........id=%v",id)
  14 + }
  15 +
  16 +}
... ...
src/HttpServer/main/main.go
... ... @@ -52,7 +52,8 @@ func main() {
52 52 logger.Info("err init redis err=%v", err)
53 53 return
54 54 }
55   -
  55 + //測試
  56 + logic.Test()
56 57 go logic.StartHttpServe()
57 58 go logic.StartHttpTicker()
58 59 time.Sleep(time.Duration(2) * time.Second)
... ...
src/github.com/segmentio/.circleci/config.yml 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +version: 2
  2 +jobs:
  3 + build:
  4 + working_directory: /go/src/github.com/segmentio/ksuid
  5 + docker:
  6 + - image: circleci/golang
  7 + steps:
  8 + - checkout
  9 + - setup_remote_docker: { reusable: true, docker_layer_caching: true }
  10 + - run: go get -v -t ./...
  11 + - run: go vet ./...
  12 + - run: go test -v -race ./...
... ...
src/github.com/segmentio/.gitignore 0 → 100644
... ... @@ -0,0 +1,31 @@
  1 +# Compiled Object files, Static and Dynamic libs (Shared Objects)
  2 +*.o
  3 +*.a
  4 +*.so
  5 +
  6 +# Folders
  7 +_obj
  8 +_test
  9 +
  10 +# Architecture specific extensions/prefixes
  11 +*.[568vq]
  12 +[568vq].out
  13 +
  14 +*.cgo1.go
  15 +*.cgo2.c
  16 +_cgo_defun.c
  17 +_cgo_gotypes.go
  18 +_cgo_export.*
  19 +
  20 +_testmain.go
  21 +
  22 +*.exe
  23 +*.test
  24 +*.prof
  25 +/ksuid
  26 +
  27 +# Emacs
  28 +*~
  29 +
  30 +# govendor
  31 +/vendor/*/
... ...
src/github.com/segmentio/LICENSE.md 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +MIT License
  2 +
  3 +Copyright (c) 2017 Segment.io
  4 +
  5 +Permission is hereby granted, free of charge, to any person obtaining a copy
  6 +of this software and associated documentation files (the "Software"), to deal
  7 +in the Software without restriction, including without limitation the rights
  8 +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9 +copies of the Software, and to permit persons to whom the Software is
  10 +furnished to do so, subject to the following conditions:
  11 +
  12 +The above copyright notice and this permission notice shall be included in all
  13 +copies or substantial portions of the Software.
  14 +
  15 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16 +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17 +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18 +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19 +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20 +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  21 +SOFTWARE.
... ...
src/github.com/segmentio/README.md 0 → 100644
... ... @@ -0,0 +1,146 @@
  1 +# ksuid [![Go Report Card](https://goreportcard.com/badge/github.com/segmentio/ksuid)](https://goreportcard.com/report/github.com/segmentio/ksuid) [![GoDoc](https://godoc.org/github.com/segmentio/ksuid?status.svg)](https://godoc.org/github.com/segmentio/ksuid) [![Circle CI](https://circleci.com/gh/segmentio/ksuid.svg?style=shield)](https://circleci.com/gh/segmentio/ksuid.svg?style=shield)
  2 +
  3 +ksuid is a Go library that can generate and parse KSUIDs.
  4 +
  5 +# Install
  6 +```bash
  7 +go get -u github.com/segmentio/ksuid
  8 +```
  9 +
  10 +# What is a KSUID?
  11 +
  12 +KSUID is for K-Sortable Unique IDentifier. It's a way to generate globally
  13 +unique IDs similar to RFC 4122 UUIDs, but contain a time component so they
  14 +can be "roughly" sorted by time of creation. The remainder of the KSUID is
  15 +randomly generated bytes.
  16 +
  17 +# Why use KSUIDs?
  18 +
  19 +Distributed systems often require unique IDs. There are numerous solutions
  20 +out there for doing this, so why KSUID?
  21 +
  22 +## 1. Sortable by Timestamp
  23 +
  24 +Unlike the more common choice of UUIDv4, KSUIDs contain a timestamp component
  25 +that allows them to be roughly sorted by generation time. This is obviously not
  26 +a strong guarantee as it depends on wall clocks, but is still incredibly useful
  27 +in practice.
  28 +
  29 +## 2. No Coordination Required
  30 +
  31 +Snowflake IDs[1] and derivatives require coordination, which significantly
  32 +increases the complexity of implementation and creates operations overhead.
  33 +While RFC 4122 UUIDv1s do have a time component, there aren't enough bytes of
  34 +randomness to provide strong protections against duplicate ID generation.
  35 +
  36 +KSUIDs use 128-bits of pseudorandom data, which provides a 64-times larger
  37 +number space than the 122-bits in the well-accepted RFC 4122 UUIDv4 standard.
  38 +The additional timestamp component drives down the extremely rare chance of
  39 +duplication to the point of near physical infeasibility, even assuming extreme
  40 +clock skew (> 24-hours) that would cause other severe anomalies.
  41 +
  42 +1. https://blog.twitter.com/2010/announcing-snowflake
  43 +
  44 +## 3. Lexographically Sortable, Portable Representations
  45 +
  46 +The binary and string representations are lexicographically sortable, which
  47 +allows them to be dropped into systems which do not natively support KSUIDs
  48 +and retain their k-sortable characteristics.
  49 +
  50 +The string representation is that it is base62-encoded, so that they can "fit"
  51 +anywhere alphanumeric strings are accepted.
  52 +
  53 +# How do they work?
  54 +
  55 +KSUIDs are 20-bytes: a 32-bit unsigned integer UTC timestamp and a 128-bit
  56 +randomly generated payload. The timestamp uses big-endian encoding, to allow
  57 +lexicographic sorting. The timestamp epoch is adjusted to March 5th, 2014,
  58 +providing over 100 years of useful life starting at UNIX epoch + 14e8. The
  59 +payload uses a cryptographically-strong pseudorandom number generator.
  60 +
  61 +The string representation is fixed at 27-characters encoded using a base62
  62 +encoding that also sorts lexicographically.
  63 +
  64 +# Command Line Tool
  65 +
  66 +This package comes with a simple command-line tool `ksuid`. This tool can
  67 +generate KSUIDs as well as inspect the internal components for debugging
  68 +purposes.
  69 +
  70 +## Usage examples
  71 +
  72 +### Generate 4 KSUID
  73 +
  74 +```sh
  75 +$ ./ksuid -n 4
  76 +0ujsszwN8NRY24YaXiTIE2VWDTS
  77 +0ujsswThIGTUYm2K8FjOOfXtY1K
  78 +0ujssxh0cECutqzMgbtXSGnjorm
  79 +0ujsszgFvbiEr7CDgE3z8MAUPFt
  80 +```
  81 +
  82 +### Inspect the components of a KSUID
  83 +
  84 +Using the inspect formatting on just 1 ksuid:
  85 +
  86 +```sh
  87 +$ ./ksuid -f inspect $(./ksuid)
  88 +
  89 +REPRESENTATION:
  90 +
  91 + String: 0ujtsYcgvSTl8PAuAdqWYSMnLOv
  92 + Raw: 0669F7EFB5A1CD34B5F99D1154FB6853345C9735
  93 +
  94 +COMPONENTS:
  95 +
  96 + Time: 2017-10-09 21:00:47 -0700 PDT
  97 + Timestamp: 107608047
  98 + Payload: B5A1CD34B5F99D1154FB6853345C9735
  99 +```
  100 +
  101 +Using the template formatting on 4 ksuid:
  102 +
  103 +```sh
  104 +$ ./ksuid -f template -t '{{ .Time }}: {{ .Payload }}' $(./ksuid -n 4)
  105 +2017-10-09 21:05:37 -0700 PDT: 304102BC687E087CC3A811F21D113CCF
  106 +2017-10-09 21:05:37 -0700 PDT: EAF0B240A9BFA55E079D887120D962F0
  107 +2017-10-09 21:05:37 -0700 PDT: DF0761769909ABB0C7BB9D66F79FC041
  108 +2017-10-09 21:05:37 -0700 PDT: 1A8F0E3D0BDEB84A5FAD702876F46543
  109 +```
  110 +
  111 +### Generate detailed versions of new KSUID
  112 +
  113 +Generate a new KSUID with the corresponding time using the time formatting:
  114 +
  115 +```sh
  116 +$ go run cmd/ksuid/main.go -f time -v
  117 +0uk0ava2lavfJwMceJOOEFXEDxl: 2017-10-09 21:56:00 -0700 PDT
  118 +```
  119 +
  120 +Generate 4 new KSUID with details using template formatting:
  121 +
  122 +```sh
  123 +$ ./ksuid -f template -t '{ "timestamp": "{{ .Timestamp }}", "payload": "{{ .Payload }}", "ksuid": "{{.String}}"}' -n 4
  124 +{ "timestamp": "107611700", "payload": "9850EEEC191BF4FF26F99315CE43B0C8", "ksuid": "0uk1Hbc9dQ9pxyTqJ93IUrfhdGq"}
  125 +{ "timestamp": "107611700", "payload": "CC55072555316F45B8CA2D2979D3ED0A", "ksuid": "0uk1HdCJ6hUZKDgcxhpJwUl5ZEI"}
  126 +{ "timestamp": "107611700", "payload": "BA1C205D6177F0992D15EE606AE32238", "ksuid": "0uk1HcdvF0p8C20KtTfdRSB9XIm"}
  127 +{ "timestamp": "107611700", "payload": "67517BA309EA62AE7991B27BB6F2FCAC", "ksuid": "0uk1Ha7hGJ1Q9Xbnkt0yZgNwg3g"}
  128 +```
  129 +
  130 +Display the detailed version of a new KSUID:
  131 +
  132 +```sh
  133 +$ ./ksuid -f inspect
  134 +
  135 +REPRESENTATION:
  136 +
  137 + String: 0ujzPyRiIAffKhBux4PvQdDqMHY
  138 + Raw: 066A029C73FC1AA3B2446246D6E89FCD909E8FE8
  139 +
  140 +COMPONENTS:
  141 +
  142 + Time: 2017-10-09 21:46:20 -0700 PDT
  143 + Timestamp: 107610780
  144 + Payload: 73FC1AA3B2446246D6E89FCD909E8FE8
  145 +
  146 +```
... ...
src/github.com/segmentio/base62.go 0 → 100644
... ... @@ -0,0 +1,211 @@
  1 +package ksuid
  2 +
  3 +import "errors"
  4 +
  5 +const (
  6 + // lexographic ordering (based on Unicode table) is 0-9A-Za-z
  7 + base62Characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
  8 + zeroString = "000000000000000000000000000"
  9 + offsetUppercase = 10
  10 + offsetLowercase = 36
  11 +)
  12 +
  13 +var (
  14 + errShortBuffer = errors.New("the output buffer is too small to hold to decoded value")
  15 +)
  16 +
  17 +// Converts a base 62 byte into the number value that it represents.
  18 +func base62Value(digit byte) byte {
  19 + switch {
  20 + case digit >= '0' && digit <= '9':
  21 + return digit - '0'
  22 + case digit >= 'A' && digit <= 'Z':
  23 + return offsetUppercase + (digit - 'A')
  24 + default:
  25 + return offsetLowercase + (digit - 'a')
  26 + }
  27 +}
  28 +
  29 +// This function encodes the base 62 representation of the src KSUID in binary
  30 +// form into dst.
  31 +//
  32 +// In order to support a couple of optimizations the function assumes that src
  33 +// is 20 bytes long and dst is 27 bytes long.
  34 +//
  35 +// Any unused bytes in dst will be set to the padding '0' byte.
  36 +func fastEncodeBase62(dst []byte, src []byte) {
  37 + const srcBase = 4294967296
  38 + const dstBase = 62
  39 +
  40 + // Split src into 5 4-byte words, this is where most of the efficiency comes
  41 + // from because this is a O(N^2) algorithm, and we make N = N / 4 by working
  42 + // on 32 bits at a time.
  43 + parts := [5]uint32{
  44 + /*
  45 + These is an inlined version of:
  46 +
  47 + binary.BigEndian.Uint32(src[0:4]),
  48 + binary.BigEndian.Uint32(src[4:8]),
  49 + binary.BigEndian.Uint32(src[8:12]),
  50 + binary.BigEndian.Uint32(src[12:16]),
  51 + binary.BigEndian.Uint32(src[16:20]),
  52 +
  53 + For some reason it gave better performance, may be caused by the
  54 + bound check that the Uint32 function does.
  55 + */
  56 + uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3]),
  57 + uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7]),
  58 + uint32(src[8])<<24 | uint32(src[9])<<16 | uint32(src[10])<<8 | uint32(src[11]),
  59 + uint32(src[12])<<24 | uint32(src[13])<<16 | uint32(src[14])<<8 | uint32(src[15]),
  60 + uint32(src[16])<<24 | uint32(src[17])<<16 | uint32(src[18])<<8 | uint32(src[19]),
  61 + }
  62 +
  63 + n := len(dst)
  64 + bp := parts[:]
  65 + bq := [5]uint32{}
  66 +
  67 + for len(bp) != 0 {
  68 + quotient := bq[:0]
  69 + remainder := uint64(0)
  70 +
  71 + for _, c := range bp {
  72 + value := uint64(c) + uint64(remainder)*srcBase
  73 + digit := value / dstBase
  74 + remainder = value % dstBase
  75 +
  76 + if len(quotient) != 0 || digit != 0 {
  77 + quotient = append(quotient, uint32(digit))
  78 + }
  79 + }
  80 +
  81 + // Writes at the end of the destination buffer because we computed the
  82 + // lowest bits first.
  83 + n--
  84 + dst[n] = base62Characters[remainder]
  85 + bp = quotient
  86 + }
  87 +
  88 + // Add padding at the head of the destination buffer for all bytes that were
  89 + // not set.
  90 + copy(dst[:n], zeroString)
  91 +}
  92 +
  93 +// This function appends the base 62 representation of the KSUID in src to dst,
  94 +// and returns the extended byte slice.
  95 +// The result is left-padded with '0' bytes to always append 27 bytes to the
  96 +// destination buffer.
  97 +func fastAppendEncodeBase62(dst []byte, src []byte) []byte {
  98 + dst = reserve(dst, stringEncodedLength)
  99 + n := len(dst)
  100 + fastEncodeBase62(dst[n:n+stringEncodedLength], src)
  101 + return dst[:n+stringEncodedLength]
  102 +}
  103 +
  104 +// This function decodes the base 62 representation of the src KSUID to the
  105 +// binary form into dst.
  106 +//
  107 +// In order to support a couple of optimizations the function assumes that src
  108 +// is 27 bytes long and dst is 20 bytes long.
  109 +//
  110 +// Any unused bytes in dst will be set to zero.
  111 +func fastDecodeBase62(dst []byte, src []byte) error {
  112 + const srcBase = 62
  113 + const dstBase = 4294967296
  114 +
  115 + // This line helps BCE (Bounds Check Elimination).
  116 + // It may be safely removed.
  117 + _ = src[26]
  118 +
  119 + parts := [27]byte{
  120 + base62Value(src[0]),
  121 + base62Value(src[1]),
  122 + base62Value(src[2]),
  123 + base62Value(src[3]),
  124 + base62Value(src[4]),
  125 + base62Value(src[5]),
  126 + base62Value(src[6]),
  127 + base62Value(src[7]),
  128 + base62Value(src[8]),
  129 + base62Value(src[9]),
  130 +
  131 + base62Value(src[10]),
  132 + base62Value(src[11]),
  133 + base62Value(src[12]),
  134 + base62Value(src[13]),
  135 + base62Value(src[14]),
  136 + base62Value(src[15]),
  137 + base62Value(src[16]),
  138 + base62Value(src[17]),
  139 + base62Value(src[18]),
  140 + base62Value(src[19]),
  141 +
  142 + base62Value(src[20]),
  143 + base62Value(src[21]),
  144 + base62Value(src[22]),
  145 + base62Value(src[23]),
  146 + base62Value(src[24]),
  147 + base62Value(src[25]),
  148 + base62Value(src[26]),
  149 + }
  150 +
  151 + n := len(dst)
  152 + bp := parts[:]
  153 + bq := [stringEncodedLength]byte{}
  154 +
  155 + for len(bp) > 0 {
  156 + quotient := bq[:0]
  157 + remainder := uint64(0)
  158 +
  159 + for _, c := range bp {
  160 + value := uint64(c) + uint64(remainder)*srcBase
  161 + digit := value / dstBase
  162 + remainder = value % dstBase
  163 +
  164 + if len(quotient) != 0 || digit != 0 {
  165 + quotient = append(quotient, byte(digit))
  166 + }
  167 + }
  168 +
  169 + if n < 4 {
  170 + return errShortBuffer
  171 + }
  172 +
  173 + dst[n-4] = byte(remainder >> 24)
  174 + dst[n-3] = byte(remainder >> 16)
  175 + dst[n-2] = byte(remainder >> 8)
  176 + dst[n-1] = byte(remainder)
  177 + n -= 4
  178 + bp = quotient
  179 + }
  180 +
  181 + var zero [20]byte
  182 + copy(dst[:n], zero[:])
  183 + return nil
  184 +}
  185 +
  186 +// This function appends the base 62 decoded version of src into dst.
  187 +func fastAppendDecodeBase62(dst []byte, src []byte) []byte {
  188 + dst = reserve(dst, byteLength)
  189 + n := len(dst)
  190 + fastDecodeBase62(dst[n:n+byteLength], src)
  191 + return dst[:n+byteLength]
  192 +}
  193 +
  194 +// Ensures that at least nbytes are available in the remaining capacity of the
  195 +// destination slice, if not, a new copy is made and returned by the function.
  196 +func reserve(dst []byte, nbytes int) []byte {
  197 + c := cap(dst)
  198 + n := len(dst)
  199 +
  200 + if avail := c - n; avail < nbytes {
  201 + c *= 2
  202 + if (c - n) < nbytes {
  203 + c = n + nbytes
  204 + }
  205 + b := make([]byte, n, c)
  206 + copy(b, dst)
  207 + dst = b
  208 + }
  209 +
  210 + return dst
  211 +}
... ...
src/github.com/segmentio/base62_test.go 0 → 100644
... ... @@ -0,0 +1,245 @@
  1 +package ksuid
  2 +
  3 +import (
  4 + "bytes"
  5 + "sort"
  6 + "strings"
  7 + "testing"
  8 +)
  9 +
  10 +func TestBase10ToBase62AndBack(t *testing.T) {
  11 + number := []byte{1, 2, 3, 4}
  12 + encoded := base2base(number, 10, 62)
  13 + decoded := base2base(encoded, 62, 10)
  14 +
  15 + if bytes.Compare(number, decoded) != 0 {
  16 + t.Fatal(number, " != ", decoded)
  17 + }
  18 +}
  19 +
  20 +func TestBase256ToBase62AndBack(t *testing.T) {
  21 + number := []byte{255, 254, 253, 251}
  22 + encoded := base2base(number, 256, 62)
  23 + decoded := base2base(encoded, 62, 256)
  24 +
  25 + if bytes.Compare(number, decoded) != 0 {
  26 + t.Fatal(number, " != ", decoded)
  27 + }
  28 +}
  29 +
  30 +func TestEncodeAndDecodeBase62(t *testing.T) {
  31 + helloWorld := []byte("hello world")
  32 + encoded := encodeBase62(helloWorld)
  33 + decoded := decodeBase62(encoded)
  34 +
  35 + if len(encoded) < len(helloWorld) {
  36 + t.Fatal("length of encoded base62 string", encoded, "should be >= than raw bytes!")
  37 +
  38 + }
  39 +
  40 + if bytes.Compare(helloWorld, decoded) != 0 {
  41 + t.Fatal(decoded, " != ", helloWorld)
  42 + }
  43 +}
  44 +
  45 +func TestLexographicOrdering(t *testing.T) {
  46 + unsortedStrings := make([]string, 256)
  47 + for i := 0; i < 256; i++ {
  48 + s := string(encodeBase62([]byte{0, byte(i)}))
  49 + unsortedStrings[i] = strings.Repeat("0", 2-len(s)) + s
  50 + }
  51 +
  52 + if !sort.StringsAreSorted(unsortedStrings) {
  53 + sortedStrings := make([]string, len(unsortedStrings))
  54 + for i, s := range unsortedStrings {
  55 + sortedStrings[i] = s
  56 + }
  57 + sort.Strings(sortedStrings)
  58 +
  59 + t.Fatal("base62 encoder does not produce lexographically sorted output.",
  60 + "expected:", sortedStrings,
  61 + "actual:", unsortedStrings)
  62 + }
  63 +}
  64 +
  65 +func TestBase62Value(t *testing.T) {
  66 + s := base62Characters
  67 +
  68 + for i := range s {
  69 + v := int(base62Value(s[i]))
  70 +
  71 + if v != i {
  72 + t.Error("bad value:")
  73 + t.Log("<<<", i)
  74 + t.Log(">>>", v)
  75 + }
  76 + }
  77 +}
  78 +
  79 +func TestFastAppendEncodeBase62(t *testing.T) {
  80 + for i := 0; i != 1000; i++ {
  81 + id := New()
  82 +
  83 + b0 := id[:]
  84 + b1 := appendEncodeBase62(nil, b0)
  85 + b2 := fastAppendEncodeBase62(nil, b0)
  86 +
  87 + s1 := string(leftpad(b1, '0', stringEncodedLength))
  88 + s2 := string(b2)
  89 +
  90 + if s1 != s2 {
  91 + t.Error("bad base62 representation of", id)
  92 + t.Log("<<<", s1, len(s1))
  93 + t.Log(">>>", s2, len(s2))
  94 + }
  95 + }
  96 +}
  97 +
  98 +func TestFastAppendDecodeBase62(t *testing.T) {
  99 + for i := 0; i != 1000; i++ {
  100 + id := New()
  101 + b0 := leftpad(encodeBase62(id[:]), '0', stringEncodedLength)
  102 +
  103 + b1 := appendDecodeBase62(nil, []byte(string(b0))) // because it modifies the input buffer
  104 + b2 := fastAppendDecodeBase62(nil, b0)
  105 +
  106 + if !bytes.Equal(leftpad(b1, 0, byteLength), b2) {
  107 + t.Error("bad binary representation of", string(b0))
  108 + t.Log("<<<", b1)
  109 + t.Log(">>>", b2)
  110 + }
  111 + }
  112 +}
  113 +
  114 +func BenchmarkAppendEncodeBase62(b *testing.B) {
  115 + a := [stringEncodedLength]byte{}
  116 + id := New()
  117 +
  118 + for i := 0; i != b.N; i++ {
  119 + appendEncodeBase62(a[:0], id[:])
  120 + }
  121 +}
  122 +
  123 +func BenchmarkAppendFastEncodeBase62(b *testing.B) {
  124 + a := [stringEncodedLength]byte{}
  125 + id := New()
  126 +
  127 + for i := 0; i != b.N; i++ {
  128 + fastAppendEncodeBase62(a[:0], id[:])
  129 + }
  130 +}
  131 +
  132 +func BenchmarkAppendDecodeBase62(b *testing.B) {
  133 + a := [byteLength]byte{}
  134 + id := []byte(New().String())
  135 +
  136 + for i := 0; i != b.N; i++ {
  137 + b := [stringEncodedLength]byte{}
  138 + copy(b[:], id)
  139 + appendDecodeBase62(a[:0], b[:])
  140 + }
  141 +}
  142 +
  143 +func BenchmarkAppendFastDecodeBase62(b *testing.B) {
  144 + a := [byteLength]byte{}
  145 + id := []byte(New().String())
  146 +
  147 + for i := 0; i != b.N; i++ {
  148 + fastAppendDecodeBase62(a[:0], id)
  149 + }
  150 +}
  151 +
  152 +// The functions bellow were the initial implementation of the base conversion
  153 +// algorithms, they were replaced by optimized versions later on. We keep them
  154 +// in the test files as a reference to ensure compatibility between the generic
  155 +// and optimized implementations.
  156 +
  157 +func appendBase2Base(dst []byte, src []byte, inBase int, outBase int) []byte {
  158 + off := len(dst)
  159 + bs := src[:]
  160 + bq := [stringEncodedLength]byte{}
  161 +
  162 + for len(bs) > 0 {
  163 + length := len(bs)
  164 + quotient := bq[:0]
  165 + remainder := 0
  166 +
  167 + for i := 0; i != length; i++ {
  168 + acc := int(bs[i]) + remainder*inBase
  169 + d := acc/outBase | 0
  170 + remainder = acc % outBase
  171 +
  172 + if len(quotient) > 0 || d > 0 {
  173 + quotient = append(quotient, byte(d))
  174 + }
  175 + }
  176 +
  177 + // Appends in reverse order, the byte slice gets reversed before it's
  178 + // returned by the function.
  179 + dst = append(dst, byte(remainder))
  180 + bs = quotient
  181 + }
  182 +
  183 + reverse(dst[off:])
  184 + return dst
  185 +}
  186 +
  187 +func base2base(src []byte, inBase int, outBase int) []byte {
  188 + return appendBase2Base(nil, src, inBase, outBase)
  189 +}
  190 +
  191 +func appendEncodeBase62(dst []byte, src []byte) []byte {
  192 + off := len(dst)
  193 + dst = appendBase2Base(dst, src, 256, 62)
  194 + for i, c := range dst[off:] {
  195 + dst[off+i] = base62Characters[c]
  196 + }
  197 + return dst
  198 +}
  199 +
  200 +func encodeBase62(in []byte) []byte {
  201 + return appendEncodeBase62(nil, in)
  202 +}
  203 +
  204 +func appendDecodeBase62(dst []byte, src []byte) []byte {
  205 + // Kind of intrusive, we modify the input buffer... it's OK here, it saves
  206 + // a memory allocation in Parse.
  207 + for i, b := range src {
  208 + // O(1)... technically. Has better real-world perf than a map
  209 + src[i] = byte(strings.IndexByte(base62Characters, b))
  210 + }
  211 + return appendBase2Base(dst, src, 62, 256)
  212 +}
  213 +
  214 +func decodeBase62(src []byte) []byte {
  215 + return appendDecodeBase62(
  216 + make([]byte, 0, len(src)*2),
  217 + append(make([]byte, 0, len(src)), src...),
  218 + )
  219 +}
  220 +
  221 +func reverse(b []byte) {
  222 + i := 0
  223 + j := len(b) - 1
  224 +
  225 + for i < j {
  226 + b[i], b[j] = b[j], b[i]
  227 + i++
  228 + j--
  229 + }
  230 +}
  231 +
  232 +func leftpad(b []byte, c byte, n int) []byte {
  233 + if n -= len(b); n > 0 {
  234 + for i := 0; i != n; i++ {
  235 + b = append(b, c)
  236 + }
  237 +
  238 + copy(b[n:], b)
  239 +
  240 + for i := 0; i != n; i++ {
  241 + b[i] = c
  242 + }
  243 + }
  244 + return b
  245 +}
... ...
src/github.com/segmentio/cmd/ksuid/main.go 0 → 100644
... ... @@ -0,0 +1,142 @@
  1 +package main
  2 +
  3 +import (
  4 + "bytes"
  5 + "encoding/hex"
  6 + "flag"
  7 + "fmt"
  8 + "io"
  9 + "os"
  10 + "strings"
  11 + "text/template"
  12 + "time"
  13 +
  14 + "github.com/segmentio/ksuid"
  15 +)
  16 +
  17 +var (
  18 + count int
  19 + format string
  20 + tpltxt string
  21 + verbose bool
  22 +)
  23 +
  24 +func init() {
  25 + flag.IntVar(&count, "n", 1, "Number of KSUIDs to generate when called with no other arguments.")
  26 + flag.StringVar(&format, "f", "string", "One of string, inspect, time, timestamp, payload, raw, or template.")
  27 + flag.StringVar(&tpltxt, "t", "", "The Go template used to format the output.")
  28 + flag.BoolVar(&verbose, "v", false, "Turn on verbose mode.")
  29 +}
  30 +
  31 +func main() {
  32 + flag.Parse()
  33 + args := flag.Args()
  34 +
  35 + var print func(ksuid.KSUID)
  36 + switch format {
  37 + case "string":
  38 + print = printString
  39 + case "inspect":
  40 + print = printInspect
  41 + case "time":
  42 + print = printTime
  43 + case "timestamp":
  44 + print = printTimestamp
  45 + case "payload":
  46 + print = printPayload
  47 + case "raw":
  48 + print = printRaw
  49 + case "template":
  50 + print = printTemplate
  51 + default:
  52 + fmt.Println("Bad formatting function:", format)
  53 + os.Exit(1)
  54 + }
  55 +
  56 + if len(args) == 0 {
  57 + for i := 0; i < count; i++ {
  58 + args = append(args, ksuid.New().String())
  59 + }
  60 + }
  61 +
  62 + var ids []ksuid.KSUID
  63 + for _, arg := range args {
  64 + id, err := ksuid.Parse(arg)
  65 + if err != nil {
  66 + fmt.Printf("Error when parsing %q: %s\n\n", arg, err)
  67 + flag.PrintDefaults()
  68 + os.Exit(1)
  69 + }
  70 + ids = append(ids, id)
  71 + }
  72 +
  73 + for _, id := range ids {
  74 + if verbose {
  75 + fmt.Printf("%s: ", id)
  76 + }
  77 + print(id)
  78 + }
  79 +}
  80 +
  81 +func printString(id ksuid.KSUID) {
  82 + fmt.Println(id.String())
  83 +}
  84 +
  85 +func printInspect(id ksuid.KSUID) {
  86 + const inspectFormat = `
  87 +REPRESENTATION:
  88 +
  89 + String: %v
  90 + Raw: %v
  91 +
  92 +COMPONENTS:
  93 +
  94 + Time: %v
  95 + Timestamp: %v
  96 + Payload: %v
  97 +
  98 +`
  99 + fmt.Printf(inspectFormat,
  100 + id.String(),
  101 + strings.ToUpper(hex.EncodeToString(id.Bytes())),
  102 + id.Time(),
  103 + id.Timestamp(),
  104 + strings.ToUpper(hex.EncodeToString(id.Payload())),
  105 + )
  106 +}
  107 +
  108 +func printTime(id ksuid.KSUID) {
  109 + fmt.Println(id.Time())
  110 +}
  111 +
  112 +func printTimestamp(id ksuid.KSUID) {
  113 + fmt.Println(id.Timestamp())
  114 +}
  115 +
  116 +func printPayload(id ksuid.KSUID) {
  117 + os.Stdout.Write(id.Payload())
  118 +}
  119 +
  120 +func printRaw(id ksuid.KSUID) {
  121 + os.Stdout.Write(id.Bytes())
  122 +}
  123 +
  124 +func printTemplate(id ksuid.KSUID) {
  125 + b := &bytes.Buffer{}
  126 + t := template.Must(template.New("").Parse(tpltxt))
  127 + t.Execute(b, struct {
  128 + String string
  129 + Raw string
  130 + Time time.Time
  131 + Timestamp uint32
  132 + Payload string
  133 + }{
  134 + String: id.String(),
  135 + Raw: strings.ToUpper(hex.EncodeToString(id.Bytes())),
  136 + Time: id.Time(),
  137 + Timestamp: id.Timestamp(),
  138 + Payload: strings.ToUpper(hex.EncodeToString(id.Payload())),
  139 + })
  140 + b.WriteByte('\n')
  141 + io.Copy(os.Stdout, b)
  142 +}
... ...
src/github.com/segmentio/ksuid.go 0 → 100644
... ... @@ -0,0 +1,352 @@
  1 +package ksuid
  2 +
  3 +import (
  4 + "bytes"
  5 + "crypto/rand"
  6 + "database/sql/driver"
  7 + "encoding/binary"
  8 + "fmt"
  9 + "io"
  10 + "math"
  11 + "sync"
  12 + "time"
  13 +)
  14 +
  15 +const (
  16 + // KSUID's epoch starts more recently so that the 32-bit number space gives a
  17 + // significantly higher useful lifetime of around 136 years from March 2017.
  18 + // This number (14e8) was picked to be easy to remember.
  19 + epochStamp int64 = 1400000000
  20 +
  21 + // Timestamp is a uint32
  22 + timestampLengthInBytes = 4
  23 +
  24 + // Payload is 16-bytes
  25 + payloadLengthInBytes = 16
  26 +
  27 + // KSUIDs are 20 bytes when binary encoded
  28 + byteLength = timestampLengthInBytes + payloadLengthInBytes
  29 +
  30 + // The length of a KSUID when string (base62) encoded
  31 + stringEncodedLength = 27
  32 +
  33 + // A string-encoded minimum value for a KSUID
  34 + minStringEncoded = "000000000000000000000000000"
  35 +
  36 + // A string-encoded maximum value for a KSUID
  37 + maxStringEncoded = "aWgEPTl1tmebfsQzFP4bxwgy80V"
  38 +)
  39 +
  40 +// KSUIDs are 20 bytes:
  41 +// 00-03 byte: uint32 BE UTC timestamp with custom epoch
  42 +// 04-19 byte: random "payload"
  43 +type KSUID [byteLength]byte
  44 +
  45 +var (
  46 + rander = rand.Reader
  47 + randMutex = sync.Mutex{}
  48 + randBuffer = [payloadLengthInBytes]byte{}
  49 +
  50 + errSize = fmt.Errorf("Valid KSUIDs are %v bytes", byteLength)
  51 + errStrSize = fmt.Errorf("Valid encoded KSUIDs are %v characters", stringEncodedLength)
  52 + errStrValue = fmt.Errorf("Valid encoded KSUIDs are bounded by %s and %s", minStringEncoded, maxStringEncoded)
  53 + errPayloadSize = fmt.Errorf("Valid KSUID payloads are %v bytes", payloadLengthInBytes)
  54 +
  55 + // Represents a completely empty (invalid) KSUID
  56 + Nil KSUID
  57 + // Represents the highest value a KSUID can have
  58 + Max = KSUID{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}
  59 +)
  60 +
  61 +// Append appends the string representation of i to b, returning a slice to a
  62 +// potentially larger memory area.
  63 +func (i KSUID) Append(b []byte) []byte {
  64 + return fastAppendEncodeBase62(b, i[:])
  65 +}
  66 +
  67 +// The timestamp portion of the ID as a Time object
  68 +func (i KSUID) Time() time.Time {
  69 + return correctedUTCTimestampToTime(i.Timestamp())
  70 +}
  71 +
  72 +// The timestamp portion of the ID as a bare integer which is uncorrected
  73 +// for KSUID's special epoch.
  74 +func (i KSUID) Timestamp() uint32 {
  75 + return binary.BigEndian.Uint32(i[:timestampLengthInBytes])
  76 +}
  77 +
  78 +// The 16-byte random payload without the timestamp
  79 +func (i KSUID) Payload() []byte {
  80 + return i[timestampLengthInBytes:]
  81 +}
  82 +
  83 +// String-encoded representation that can be passed through Parse()
  84 +func (i KSUID) String() string {
  85 + return string(i.Append(make([]byte, 0, stringEncodedLength)))
  86 +}
  87 +
  88 +// Raw byte representation of KSUID
  89 +func (i KSUID) Bytes() []byte {
  90 + // Safe because this is by-value
  91 + return i[:]
  92 +}
  93 +
  94 +// IsNil returns true if this is a "nil" KSUID
  95 +func (i KSUID) IsNil() bool {
  96 + return i == Nil
  97 +}
  98 +
  99 +// Get satisfies the flag.Getter interface, making it possible to use KSUIDs as
  100 +// part of of the command line options of a program.
  101 +func (i KSUID) Get() interface{} {
  102 + return i
  103 +}
  104 +
  105 +// Set satisfies the flag.Value interface, making it possible to use KSUIDs as
  106 +// part of of the command line options of a program.
  107 +func (i *KSUID) Set(s string) error {
  108 + return i.UnmarshalText([]byte(s))
  109 +}
  110 +
  111 +func (i KSUID) MarshalText() ([]byte, error) {
  112 + return []byte(i.String()), nil
  113 +}
  114 +
  115 +func (i KSUID) MarshalBinary() ([]byte, error) {
  116 + return i.Bytes(), nil
  117 +}
  118 +
  119 +func (i *KSUID) UnmarshalText(b []byte) error {
  120 + id, err := Parse(string(b))
  121 + if err != nil {
  122 + return err
  123 + }
  124 + *i = id
  125 + return nil
  126 +}
  127 +
  128 +func (i *KSUID) UnmarshalBinary(b []byte) error {
  129 + id, err := FromBytes(b)
  130 + if err != nil {
  131 + return err
  132 + }
  133 + *i = id
  134 + return nil
  135 +}
  136 +
  137 +// Value converts the KSUID into a SQL driver value which can be used to
  138 +// directly use the KSUID as parameter to a SQL query.
  139 +func (i KSUID) Value() (driver.Value, error) {
  140 + if i.IsNil() {
  141 + return nil, nil
  142 + }
  143 + return i.String(), nil
  144 +}
  145 +
  146 +// Scan implements the sql.Scanner interface. It supports converting from
  147 +// string, []byte, or nil into a KSUID value. Attempting to convert from
  148 +// another type will return an error.
  149 +func (i *KSUID) Scan(src interface{}) error {
  150 + switch v := src.(type) {
  151 + case nil:
  152 + return i.scan(nil)
  153 + case []byte:
  154 + return i.scan(v)
  155 + case string:
  156 + return i.scan([]byte(v))
  157 + default:
  158 + return fmt.Errorf("Scan: unable to scan type %T into KSUID", v)
  159 + }
  160 +}
  161 +
  162 +func (i *KSUID) scan(b []byte) error {
  163 + switch len(b) {
  164 + case 0:
  165 + *i = Nil
  166 + return nil
  167 + case byteLength:
  168 + return i.UnmarshalBinary(b)
  169 + case stringEncodedLength:
  170 + return i.UnmarshalText(b)
  171 + default:
  172 + return errSize
  173 + }
  174 +}
  175 +
  176 +// Parse decodes a string-encoded representation of a KSUID object
  177 +func Parse(s string) (KSUID, error) {
  178 + if len(s) != stringEncodedLength {
  179 + return Nil, errStrSize
  180 + }
  181 +
  182 + src := [stringEncodedLength]byte{}
  183 + dst := [byteLength]byte{}
  184 +
  185 + copy(src[:], s[:])
  186 +
  187 + if err := fastDecodeBase62(dst[:], src[:]); err != nil {
  188 + return Nil, errStrValue
  189 + }
  190 +
  191 + return FromBytes(dst[:])
  192 +}
  193 +
  194 +func timeToCorrectedUTCTimestamp(t time.Time) uint32 {
  195 + return uint32(t.Unix() - epochStamp)
  196 +}
  197 +
  198 +func correctedUTCTimestampToTime(ts uint32) time.Time {
  199 + return time.Unix(int64(ts)+epochStamp, 0)
  200 +}
  201 +
  202 +// Generates a new KSUID. In the strange case that random bytes
  203 +// can't be read, it will panic.
  204 +func New() KSUID {
  205 + ksuid, err := NewRandom()
  206 + if err != nil {
  207 + panic(fmt.Sprintf("Couldn't generate KSUID, inconceivable! error: %v", err))
  208 + }
  209 + return ksuid
  210 +}
  211 +
  212 +// Generates a new KSUID
  213 +func NewRandom() (ksuid KSUID, err error) {
  214 + return NewRandomWithTime(time.Now())
  215 +}
  216 +
  217 +func NewRandomWithTime(t time.Time) (ksuid KSUID, err error) {
  218 + // Go's default random number generators are not safe for concurrent use by
  219 + // multiple goroutines, the use of the rander and randBuffer are explicitly
  220 + // synchronized here.
  221 + randMutex.Lock()
  222 +
  223 + _, err = io.ReadAtLeast(rander, randBuffer[:], len(randBuffer))
  224 + copy(ksuid[timestampLengthInBytes:], randBuffer[:])
  225 +
  226 + randMutex.Unlock()
  227 +
  228 + if err != nil {
  229 + ksuid = Nil // don't leak random bytes on error
  230 + return
  231 + }
  232 +
  233 + ts := timeToCorrectedUTCTimestamp(t)
  234 + binary.BigEndian.PutUint32(ksuid[:timestampLengthInBytes], ts)
  235 + return
  236 +}
  237 +
  238 +// Constructs a KSUID from constituent parts
  239 +func FromParts(t time.Time, payload []byte) (KSUID, error) {
  240 + if len(payload) != payloadLengthInBytes {
  241 + return Nil, errPayloadSize
  242 + }
  243 +
  244 + var ksuid KSUID
  245 +
  246 + ts := timeToCorrectedUTCTimestamp(t)
  247 + binary.BigEndian.PutUint32(ksuid[:timestampLengthInBytes], ts)
  248 +
  249 + copy(ksuid[timestampLengthInBytes:], payload)
  250 +
  251 + return ksuid, nil
  252 +}
  253 +
  254 +// Constructs a KSUID from a 20-byte binary representation
  255 +func FromBytes(b []byte) (KSUID, error) {
  256 + var ksuid KSUID
  257 +
  258 + if len(b) != byteLength {
  259 + return Nil, errSize
  260 + }
  261 +
  262 + copy(ksuid[:], b)
  263 + return ksuid, nil
  264 +}
  265 +
  266 +// Sets the global source of random bytes for KSUID generation. This
  267 +// should probably only be set once globally. While this is technically
  268 +// thread-safe as in it won't cause corruption, there's no guarantee
  269 +// on ordering.
  270 +func SetRand(r io.Reader) {
  271 + if r == nil {
  272 + rander = rand.Reader
  273 + return
  274 + }
  275 + rander = r
  276 +}
  277 +
  278 +// Implements comparison for KSUID type
  279 +func Compare(a, b KSUID) int {
  280 + return bytes.Compare(a[:], b[:])
  281 +}
  282 +
  283 +// Sorts the given slice of KSUIDs
  284 +func Sort(ids []KSUID) {
  285 + quickSort(ids, 0, len(ids)-1)
  286 +}
  287 +
  288 +// IsSorted checks whether a slice of KSUIDs is sorted
  289 +func IsSorted(ids []KSUID) bool {
  290 + if len(ids) != 0 {
  291 + min := ids[0]
  292 + for _, id := range ids[1:] {
  293 + if bytes.Compare(min[:], id[:]) > 0 {
  294 + return false
  295 + }
  296 + min = id
  297 + }
  298 + }
  299 + return true
  300 +}
  301 +
  302 +func quickSort(a []KSUID, lo int, hi int) {
  303 + if lo < hi {
  304 + pivot := a[hi]
  305 + i := lo - 1
  306 +
  307 + for j, n := lo, hi; j != n; j++ {
  308 + if bytes.Compare(a[j][:], pivot[:]) < 0 {
  309 + i++
  310 + a[i], a[j] = a[j], a[i]
  311 + }
  312 + }
  313 +
  314 + i++
  315 + if bytes.Compare(a[hi][:], a[i][:]) < 0 {
  316 + a[i], a[hi] = a[hi], a[i]
  317 + }
  318 +
  319 + quickSort(a, lo, i-1)
  320 + quickSort(a, i+1, hi)
  321 + }
  322 +}
  323 +
  324 +// Next returns the next KSUID after id.
  325 +func (id KSUID) Next() KSUID {
  326 + zero := makeUint128(0, 0)
  327 +
  328 + t := id.Timestamp()
  329 + u := uint128Payload(id)
  330 + v := add128(u, makeUint128(0, 1))
  331 +
  332 + if v == zero { // overflow
  333 + t++
  334 + }
  335 +
  336 + return v.ksuid(t)
  337 +}
  338 +
  339 +// Prev returns the previoud KSUID before id.
  340 +func (id KSUID) Prev() KSUID {
  341 + max := makeUint128(math.MaxUint64, math.MaxUint64)
  342 +
  343 + t := id.Timestamp()
  344 + u := uint128Payload(id)
  345 + v := sub128(u, makeUint128(0, 1))
  346 +
  347 + if v == max { // overflow
  348 + t--
  349 + }
  350 +
  351 + return v.ksuid(t)
  352 +}
... ...
src/github.com/segmentio/ksuid_test.go 0 → 100644
... ... @@ -0,0 +1,389 @@
  1 +package ksuid
  2 +
  3 +import (
  4 + "bytes"
  5 + "encoding/json"
  6 + "flag"
  7 + "fmt"
  8 + "sort"
  9 + "strings"
  10 + "testing"
  11 + "time"
  12 +)
  13 +
  14 +func TestConstructionTimestamp(t *testing.T) {
  15 + x := New()
  16 + nowTime := time.Now().Round(1 * time.Minute)
  17 + xTime := x.Time().Round(1 * time.Minute)
  18 +
  19 + if xTime != nowTime {
  20 + t.Fatal(xTime, "!=", nowTime)
  21 + }
  22 +}
  23 +
  24 +func TestNil(t *testing.T) {
  25 + if !Nil.IsNil() {
  26 + t.Fatal("Nil should be Nil!")
  27 + }
  28 +
  29 + x, _ := FromBytes(make([]byte, byteLength))
  30 + if !x.IsNil() {
  31 + t.Fatal("Zero-byte array should be Nil!")
  32 + }
  33 +}
  34 +
  35 +func TestEncoding(t *testing.T) {
  36 + x, _ := FromBytes(make([]byte, byteLength))
  37 + if !x.IsNil() {
  38 + t.Fatal("Zero-byte array should be Nil!")
  39 + }
  40 +
  41 + encoded := x.String()
  42 + expected := strings.Repeat("0", stringEncodedLength)
  43 +
  44 + if encoded != expected {
  45 + t.Fatal("expected", expected, "encoded", encoded)
  46 + }
  47 +}
  48 +
  49 +func TestPadding(t *testing.T) {
  50 + b := make([]byte, byteLength)
  51 + for i := 0; i < byteLength; i++ {
  52 + b[i] = 255
  53 + }
  54 +
  55 + x, _ := FromBytes(b)
  56 + xEncoded := x.String()
  57 + nilEncoded := Nil.String()
  58 +
  59 + if len(xEncoded) != len(nilEncoded) {
  60 + t.Fatal("Encoding should produce equal-length strings for zero and max case")
  61 + }
  62 +}
  63 +
  64 +func TestParse(t *testing.T) {
  65 + _, err := Parse("123")
  66 + if err != errStrSize {
  67 + t.Fatal("Expected Parsing a 3-char string to return an error")
  68 + }
  69 +
  70 + parsed, err := Parse(strings.Repeat("0", stringEncodedLength))
  71 + if err != nil {
  72 + t.Fatal("Unexpected error", err)
  73 + }
  74 +
  75 + if Compare(parsed, Nil) != 0 {
  76 + t.Fatal("Parsing all-zeroes string should equal Nil value",
  77 + "expected:", Nil,
  78 + "actual:", parsed)
  79 + }
  80 +
  81 + maxBytes := make([]byte, byteLength)
  82 + for i := 0; i < byteLength; i++ {
  83 + maxBytes[i] = 255
  84 + }
  85 + maxBytesKSUID, err := FromBytes(maxBytes)
  86 + if err != nil {
  87 + t.Fatal("Unexpected error", err)
  88 + }
  89 +
  90 + maxParseKSUID, err := Parse(maxStringEncoded)
  91 + if err != nil {
  92 + t.Fatal("Unexpected error", err)
  93 + }
  94 +
  95 + if Compare(maxBytesKSUID, maxParseKSUID) != 0 {
  96 + t.Fatal("String decoder broke for max string")
  97 + }
  98 +}
  99 +
  100 +func TestIssue25(t *testing.T) {
  101 + // https://github.com/segmentio/ksuid/issues/25
  102 + for _, s := range []string{
  103 + "aaaaaaaaaaaaaaaaaaaaaaaaaaa",
  104 + "aWgEPTl1tmebfsQzFP4bxwgy80!",
  105 + } {
  106 + _, err := Parse(s)
  107 + if err != errStrValue {
  108 + t.Error("invalid KSUID representations cannot be successfully parsed, got err =", err)
  109 + }
  110 + }
  111 +}
  112 +
  113 +func TestEncodeAndDecode(t *testing.T) {
  114 + x := New()
  115 + builtFromEncodedString, err := Parse(x.String())
  116 + if err != nil {
  117 + t.Fatal("Unexpected error", err)
  118 + }
  119 +
  120 + if Compare(x, builtFromEncodedString) != 0 {
  121 + t.Fatal("Parse(X).String() != X")
  122 + }
  123 +}
  124 +
  125 +func TestMarshalText(t *testing.T) {
  126 + var id1 = New()
  127 + var id2 KSUID
  128 +
  129 + if err := id2.UnmarshalText([]byte(id1.String())); err != nil {
  130 + t.Fatal(err)
  131 + }
  132 +
  133 + if id1 != id2 {
  134 + t.Fatal(id1, "!=", id2)
  135 + }
  136 +
  137 + if b, err := id2.MarshalText(); err != nil {
  138 + t.Fatal(err)
  139 + } else if s := string(b); s != id1.String() {
  140 + t.Fatal(s)
  141 + }
  142 +}
  143 +
  144 +func TestMarshalBinary(t *testing.T) {
  145 + var id1 = New()
  146 + var id2 KSUID
  147 +
  148 + if err := id2.UnmarshalBinary(id1.Bytes()); err != nil {
  149 + t.Fatal(err)
  150 + }
  151 +
  152 + if id1 != id2 {
  153 + t.Fatal(id1, "!=", id2)
  154 + }
  155 +
  156 + if b, err := id2.MarshalBinary(); err != nil {
  157 + t.Fatal(err)
  158 + } else if bytes.Compare(b, id1.Bytes()) != 0 {
  159 + t.Fatal("bad binary form:", id2)
  160 + }
  161 +}
  162 +
  163 +func TestMashalJSON(t *testing.T) {
  164 + var id1 = New()
  165 + var id2 KSUID
  166 +
  167 + if b, err := json.Marshal(id1); err != nil {
  168 + t.Fatal(err)
  169 + } else if err := json.Unmarshal(b, &id2); err != nil {
  170 + t.Fatal(err)
  171 + } else if id1 != id2 {
  172 + t.Error(id1, "!=", id2)
  173 + }
  174 +}
  175 +
  176 +func TestFlag(t *testing.T) {
  177 + var id1 = New()
  178 + var id2 KSUID
  179 +
  180 + fset := flag.NewFlagSet("test", flag.ContinueOnError)
  181 + fset.Var(&id2, "id", "the KSUID")
  182 +
  183 + if err := fset.Parse([]string{"-id", id1.String()}); err != nil {
  184 + t.Fatal(err)
  185 + }
  186 +
  187 + if id1 != id2 {
  188 + t.Error(id1, "!=", id2)
  189 + }
  190 +}
  191 +
  192 +func TestSqlValuer(t *testing.T) {
  193 + id, _ := Parse(maxStringEncoded)
  194 +
  195 + if v, err := id.Value(); err != nil {
  196 + t.Error(err)
  197 + } else if s, ok := v.(string); !ok {
  198 + t.Error("not a string value")
  199 + } else if s != maxStringEncoded {
  200 + t.Error("bad string value::", s)
  201 + }
  202 +}
  203 +
  204 +func TestSqlValuerNilValue(t *testing.T) {
  205 + if v, err := Nil.Value(); err != nil {
  206 + t.Error(err)
  207 + } else if v != nil {
  208 + t.Errorf("bad nil value: %v", v)
  209 + }
  210 +}
  211 +
  212 +func TestSqlScanner(t *testing.T) {
  213 + id1 := New()
  214 + id2 := New()
  215 +
  216 + tests := []struct {
  217 + ksuid KSUID
  218 + value interface{}
  219 + }{
  220 + {Nil, nil},
  221 + {id1, id1.String()},
  222 + {id2, id2.Bytes()},
  223 + }
  224 +
  225 + for _, test := range tests {
  226 + t.Run(fmt.Sprintf("%T", test.value), func(t *testing.T) {
  227 + var id KSUID
  228 +
  229 + if err := id.Scan(test.value); err != nil {
  230 + t.Error(err)
  231 + }
  232 +
  233 + if id != test.ksuid {
  234 + t.Error("bad KSUID:")
  235 + t.Logf("expected %v", test.ksuid)
  236 + t.Logf("found %v", id)
  237 + }
  238 + })
  239 + }
  240 +}
  241 +
  242 +func TestAppend(t *testing.T) {
  243 + for _, repr := range []string{"0pN1Own7255s7jwpwy495bAZeEa", "aWgEPTl1tmebfsQzFP4bxwgy80V"} {
  244 + k, _ := Parse(repr)
  245 + a := make([]byte, 0, stringEncodedLength)
  246 +
  247 + a = append(a, "?: "...)
  248 + a = k.Append(a)
  249 +
  250 + if s := string(a); s != "?: "+repr {
  251 + t.Error(s)
  252 + }
  253 + }
  254 +}
  255 +
  256 +func TestSort(t *testing.T) {
  257 + ids1 := [11]KSUID{}
  258 + ids2 := [11]KSUID{}
  259 +
  260 + for i := range ids1 {
  261 + ids1[i] = New()
  262 + }
  263 +
  264 + ids2 = ids1
  265 + sort.Slice(ids2[:], func(i, j int) bool {
  266 + return Compare(ids2[i], ids2[j]) < 0
  267 + })
  268 +
  269 + Sort(ids1[:])
  270 +
  271 + if !IsSorted(ids1[:]) {
  272 + t.Error("not sorted")
  273 + }
  274 +
  275 + if ids1 != ids2 {
  276 + t.Error("bad order:")
  277 + t.Log(ids1)
  278 + t.Log(ids2)
  279 + }
  280 +}
  281 +
  282 +func TestPrevNext(t *testing.T) {
  283 + tests := []struct {
  284 + id KSUID
  285 + prev KSUID
  286 + next KSUID
  287 + }{
  288 + {
  289 + id: Nil,
  290 + prev: Max,
  291 + next: KSUID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
  292 + },
  293 + {
  294 + id: Max,
  295 + prev: KSUID{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe},
  296 + next: Nil,
  297 + },
  298 + }
  299 +
  300 + for _, test := range tests {
  301 + t.Run(test.id.String(), func(t *testing.T) {
  302 + testPrevNext(t, test.id, test.prev, test.next)
  303 + })
  304 + }
  305 +}
  306 +
  307 +func TestGetTimestamp(t *testing.T) {
  308 + nowTime := time.Now()
  309 + x, _ := NewRandomWithTime(nowTime)
  310 + xTime := int64(x.Timestamp())
  311 + unix := nowTime.Unix()
  312 + if xTime != unix - epochStamp {
  313 + t.Fatal(xTime, "!=", unix)
  314 + }
  315 +}
  316 +
  317 +func testPrevNext(t *testing.T, id, prev, next KSUID) {
  318 + id1 := id.Prev()
  319 + id2 := id.Next()
  320 +
  321 + if id1 != prev {
  322 + t.Error("previous id of the nil KSUID is wrong:", id1, "!=", prev)
  323 + }
  324 +
  325 + if id2 != next {
  326 + t.Error("next id of the nil KSUID is wrong:", id2, "!=", next)
  327 + }
  328 +}
  329 +
  330 +func BenchmarkAppend(b *testing.B) {
  331 + a := make([]byte, 0, stringEncodedLength)
  332 + k := New()
  333 +
  334 + for i := 0; i != b.N; i++ {
  335 + k.Append(a)
  336 + }
  337 +}
  338 +
  339 +func BenchmarkString(b *testing.B) {
  340 + k := New()
  341 +
  342 + for i := 0; i != b.N; i++ {
  343 + _ = k.String()
  344 + }
  345 +}
  346 +
  347 +func BenchmarkParse(b *testing.B) {
  348 + for i := 0; i != b.N; i++ {
  349 + Parse(maxStringEncoded)
  350 + }
  351 +}
  352 +
  353 +func BenchmarkCompare(b *testing.B) {
  354 + k1 := New()
  355 + k2 := New()
  356 +
  357 + for i := 0; i != b.N; i++ {
  358 + Compare(k1, k2)
  359 + }
  360 +}
  361 +
  362 +func BenchmarkSort(b *testing.B) {
  363 + ids1 := [101]KSUID{}
  364 + ids2 := [101]KSUID{}
  365 +
  366 + for i := range ids1 {
  367 + ids1[i] = New()
  368 + }
  369 +
  370 + for i := 0; i != b.N; i++ {
  371 + ids2 = ids1
  372 + Sort(ids2[:])
  373 + }
  374 +}
  375 +
  376 +func BenchmarkNew(b *testing.B) {
  377 + b.Run("with crypto rand", func(b *testing.B) {
  378 + SetRand(nil)
  379 + for i := 0; i != b.N; i++ {
  380 + New()
  381 + }
  382 + })
  383 + b.Run("with math rand", func(b *testing.B) {
  384 + SetRand(FastRander)
  385 + for i := 0; i != b.N; i++ {
  386 + New()
  387 + }
  388 + })
  389 +}
... ...
src/github.com/segmentio/rand.go 0 → 100644
... ... @@ -0,0 +1,55 @@
  1 +package ksuid
  2 +
  3 +import (
  4 + cryptoRand "crypto/rand"
  5 + "encoding/binary"
  6 + "io"
  7 + "math/rand"
  8 +)
  9 +
  10 +// FastRander is an io.Reader that uses math/rand and is optimized for
  11 +// generating 16 bytes KSUID payloads. It is intended to be used as a
  12 +// performance improvements for programs that have no need for
  13 +// cryptographically secure KSUIDs and are generating a lot of them.
  14 +var FastRander = newRBG()
  15 +
  16 +func newRBG() io.Reader {
  17 + r, err := newRandomBitsGenerator()
  18 + if err != nil {
  19 + panic(err)
  20 + }
  21 + return r
  22 +}
  23 +
  24 +func newRandomBitsGenerator() (r io.Reader, err error) {
  25 + var seed int64
  26 +
  27 + if seed, err = readCryptoRandomSeed(); err != nil {
  28 + return
  29 + }
  30 +
  31 + r = &randSourceReader{source: rand.NewSource(seed).(rand.Source64)}
  32 + return
  33 +}
  34 +
  35 +func readCryptoRandomSeed() (seed int64, err error) {
  36 + var b [8]byte
  37 +
  38 + if _, err = io.ReadFull(cryptoRand.Reader, b[:]); err != nil {
  39 + return
  40 + }
  41 +
  42 + seed = int64(binary.LittleEndian.Uint64(b[:]))
  43 + return
  44 +}
  45 +
  46 +type randSourceReader struct {
  47 + source rand.Source64
  48 +}
  49 +
  50 +func (r *randSourceReader) Read(b []byte) (int, error) {
  51 + // optimized for generating 16 bytes payloads
  52 + binary.LittleEndian.PutUint64(b[:8], r.source.Uint64())
  53 + binary.LittleEndian.PutUint64(b[8:], r.source.Uint64())
  54 + return 16, nil
  55 +}
... ...
src/github.com/segmentio/sequence.go 0 → 100644
... ... @@ -0,0 +1,55 @@
  1 +package ksuid
  2 +
  3 +import (
  4 + "encoding/binary"
  5 + "errors"
  6 + "math"
  7 +)
  8 +
  9 +// Sequence is a KSUID generator which produces a sequence of ordered KSUIDs
  10 +// from a seed.
  11 +//
  12 +// Up to 65536 KSUIDs can be generated by for a single seed.
  13 +//
  14 +// A typical usage of a Sequence looks like this:
  15 +//
  16 +// seq := ksuid.Sequence{
  17 +// Seed: ksuid.New(),
  18 +// }
  19 +// id, err := seq.Next()
  20 +//
  21 +// Sequence values are not safe to use concurrently from multiple goroutines.
  22 +type Sequence struct {
  23 + // The seed is used as base for the KSUID generator, all generated KSUIDs
  24 + // share the same leading 18 bytes of the seed.
  25 + Seed KSUID
  26 + count uint32 // uint32 for overflow, only 2 bytes are used
  27 +}
  28 +
  29 +// Next produces the next KSUID in the sequence, or returns an error if the
  30 +// sequence has been exhausted.
  31 +func (seq *Sequence) Next() (KSUID, error) {
  32 + id := seq.Seed // copy
  33 + count := seq.count
  34 + if count > math.MaxUint16 {
  35 + return Nil, errors.New("too many IDs were generated")
  36 + }
  37 + seq.count++
  38 + return withSequenceNumber(id, uint16(count)), nil
  39 +}
  40 +
  41 +// Bounds returns the inclusive min and max bounds of the KSUIDs that may be
  42 +// generated by the sequence. If all ids have been generated already then the
  43 +// returned min value is equal to the max.
  44 +func (seq *Sequence) Bounds() (min KSUID, max KSUID) {
  45 + count := seq.count
  46 + if count > math.MaxUint16 {
  47 + count = math.MaxUint16
  48 + }
  49 + return withSequenceNumber(seq.Seed, uint16(count)), withSequenceNumber(seq.Seed, math.MaxUint16)
  50 +}
  51 +
  52 +func withSequenceNumber(id KSUID, n uint16) KSUID {
  53 + binary.BigEndian.PutUint16(id[len(id)-2:], n)
  54 + return id
  55 +}
... ...
src/github.com/segmentio/sequence_test.go 0 → 100644
... ... @@ -0,0 +1,33 @@
  1 +package ksuid
  2 +
  3 +import (
  4 + "encoding/binary"
  5 + "math"
  6 + "testing"
  7 +)
  8 +
  9 +func TestSequence(t *testing.T) {
  10 + seq := Sequence{Seed: New()}
  11 +
  12 + if min, max := seq.Bounds(); min == max {
  13 + t.Error("min and max of KSUID range must differ when no ids have been generated")
  14 + }
  15 +
  16 + for i := 0; i <= math.MaxUint16; i++ {
  17 + id, err := seq.Next()
  18 + if err != nil {
  19 + t.Fatal(err)
  20 + }
  21 + if j := int(binary.BigEndian.Uint16(id[len(id)-2:])); j != i {
  22 + t.Fatalf("expected %d but got %d in %s", i, j, id)
  23 + }
  24 + }
  25 +
  26 + if _, err := seq.Next(); err == nil {
  27 + t.Fatal("no error returned after exhausting the id generator")
  28 + }
  29 +
  30 + if min, max := seq.Bounds(); min != max {
  31 + t.Error("after all KSUIDs were generated the min and max must be equal")
  32 + }
  33 +}
... ...
src/github.com/segmentio/set.go 0 → 100644
... ... @@ -0,0 +1,343 @@
  1 +package ksuid
  2 +
  3 +import (
  4 + "bytes"
  5 + "encoding/binary"
  6 +)
  7 +
  8 +// CompressedSet is an immutable data type which stores a set of KSUIDs.
  9 +type CompressedSet []byte
  10 +
  11 +// Iter returns an iterator that produces all KSUIDs in the set.
  12 +func (set CompressedSet) Iter() CompressedSetIter {
  13 + return CompressedSetIter{
  14 + content: []byte(set),
  15 + }
  16 +}
  17 +
  18 +// String satisfies the fmt.Stringer interface, returns a human-readable string
  19 +// representation of the set.
  20 +func (set CompressedSet) String() string {
  21 + b := bytes.Buffer{}
  22 + b.WriteByte('[')
  23 + set.writeTo(&b)
  24 + b.WriteByte(']')
  25 + return b.String()
  26 +}
  27 +
  28 +// String satisfies the fmt.GoStringer interface, returns a Go representation of
  29 +// the set.
  30 +func (set CompressedSet) GoString() string {
  31 + b := bytes.Buffer{}
  32 + b.WriteString("ksuid.CompressedSet{")
  33 + set.writeTo(&b)
  34 + b.WriteByte('}')
  35 + return b.String()
  36 +}
  37 +
  38 +func (set CompressedSet) writeTo(b *bytes.Buffer) {
  39 + a := [27]byte{}
  40 +
  41 + for i, it := 0, set.Iter(); it.Next(); i++ {
  42 + if i != 0 {
  43 + b.WriteString(", ")
  44 + }
  45 + b.WriteByte('"')
  46 + it.KSUID.Append(a[:0])
  47 + b.Write(a[:])
  48 + b.WriteByte('"')
  49 + }
  50 +}
  51 +
  52 +// Compress creates and returns a compressed set of KSUIDs from the list given
  53 +// as arguments.
  54 +func Compress(ids ...KSUID) CompressedSet {
  55 + c := 1 + byteLength + (len(ids) / 5)
  56 + b := make([]byte, 0, c)
  57 + return AppendCompressed(b, ids...)
  58 +}
  59 +
  60 +// AppendCompressed uses the given byte slice as pre-allocated storage space to
  61 +// build a KSUID set.
  62 +//
  63 +// Note that the set uses a compression technique to store the KSUIDs, so the
  64 +// resuling length is not 20 x len(ids). The rule of thumb here is for the given
  65 +// byte slice to reserve the amount of memory that the application would be OK
  66 +// to waste.
  67 +func AppendCompressed(set []byte, ids ...KSUID) CompressedSet {
  68 + if len(ids) != 0 {
  69 + if !IsSorted(ids) {
  70 + Sort(ids)
  71 + }
  72 + one := makeUint128(0, 1)
  73 +
  74 + // The first KSUID is always written to the set, this is the starting
  75 + // point for all deltas.
  76 + set = append(set, byte(rawKSUID))
  77 + set = append(set, ids[0][:]...)
  78 +
  79 + timestamp := ids[0].Timestamp()
  80 + lastKSUID := ids[0]
  81 + lastValue := uint128Payload(ids[0])
  82 +
  83 + for i := 1; i != len(ids); i++ {
  84 + id := ids[i]
  85 +
  86 + if id == lastKSUID {
  87 + continue
  88 + }
  89 +
  90 + t := id.Timestamp()
  91 + v := uint128Payload(id)
  92 +
  93 + if t != timestamp {
  94 + d := t - timestamp
  95 + n := varintLength32(d)
  96 +
  97 + set = append(set, timeDelta|byte(n))
  98 + set = appendVarint32(set, d, n)
  99 + set = append(set, id[timestampLengthInBytes:]...)
  100 +
  101 + timestamp = t
  102 + } else {
  103 + d := sub128(v, lastValue)
  104 +
  105 + if d != one {
  106 + n := varintLength128(d)
  107 +
  108 + set = append(set, payloadDelta|byte(n))
  109 + set = appendVarint128(set, d, n)
  110 + } else {
  111 + l, c := rangeLength(ids[i+1:], t, id, v)
  112 + m := uint64(l + 1)
  113 + n := varintLength64(m)
  114 +
  115 + set = append(set, payloadRange|byte(n))
  116 + set = appendVarint64(set, m, n)
  117 +
  118 + i += c
  119 + id = ids[i]
  120 + v = uint128Payload(id)
  121 + }
  122 + }
  123 +
  124 + lastKSUID = id
  125 + lastValue = v
  126 + }
  127 + }
  128 + return CompressedSet(set)
  129 +}
  130 +
  131 +func rangeLength(ids []KSUID, timestamp uint32, lastKSUID KSUID, lastValue uint128) (length int, count int) {
  132 + one := makeUint128(0, 1)
  133 +
  134 + for i := range ids {
  135 + id := ids[i]
  136 +
  137 + if id == lastKSUID {
  138 + continue
  139 + }
  140 +
  141 + if id.Timestamp() != timestamp {
  142 + count = i
  143 + return
  144 + }
  145 +
  146 + v := uint128Payload(id)
  147 +
  148 + if sub128(v, lastValue) != one {
  149 + count = i
  150 + return
  151 + }
  152 +
  153 + lastKSUID = id
  154 + lastValue = v
  155 + length++
  156 + }
  157 +
  158 + count = len(ids)
  159 + return
  160 +}
  161 +
  162 +func appendVarint128(b []byte, v uint128, n int) []byte {
  163 + c := v.bytes()
  164 + return append(b, c[len(c)-n:]...)
  165 +}
  166 +
  167 +func appendVarint64(b []byte, v uint64, n int) []byte {
  168 + c := [8]byte{}
  169 + binary.BigEndian.PutUint64(c[:], v)
  170 + return append(b, c[len(c)-n:]...)
  171 +}
  172 +
  173 +func appendVarint32(b []byte, v uint32, n int) []byte {
  174 + c := [4]byte{}
  175 + binary.BigEndian.PutUint32(c[:], v)
  176 + return append(b, c[len(c)-n:]...)
  177 +}
  178 +
  179 +func varint128(b []byte) uint128 {
  180 + a := [16]byte{}
  181 + copy(a[16-len(b):], b)
  182 + return makeUint128FromPayload(a[:])
  183 +}
  184 +
  185 +func varint64(b []byte) uint64 {
  186 + a := [8]byte{}
  187 + copy(a[8-len(b):], b)
  188 + return binary.BigEndian.Uint64(a[:])
  189 +}
  190 +
  191 +func varint32(b []byte) uint32 {
  192 + a := [4]byte{}
  193 + copy(a[4-len(b):], b)
  194 + return binary.BigEndian.Uint32(a[:])
  195 +}
  196 +
  197 +func varintLength128(v uint128) int {
  198 + if v[1] != 0 {
  199 + return 8 + varintLength64(v[1])
  200 + }
  201 + return varintLength64(v[0])
  202 +}
  203 +
  204 +func varintLength64(v uint64) int {
  205 + switch {
  206 + case (v & 0xFFFFFFFFFFFFFF00) == 0:
  207 + return 1
  208 + case (v & 0xFFFFFFFFFFFF0000) == 0:
  209 + return 2
  210 + case (v & 0xFFFFFFFFFF000000) == 0:
  211 + return 3
  212 + case (v & 0xFFFFFFFF00000000) == 0:
  213 + return 4
  214 + case (v & 0xFFFFFF0000000000) == 0:
  215 + return 5
  216 + case (v & 0xFFFF000000000000) == 0:
  217 + return 6
  218 + case (v & 0xFF00000000000000) == 0:
  219 + return 7
  220 + default:
  221 + return 8
  222 + }
  223 +}
  224 +
  225 +func varintLength32(v uint32) int {
  226 + switch {
  227 + case (v & 0xFFFFFF00) == 0:
  228 + return 1
  229 + case (v & 0xFFFF0000) == 0:
  230 + return 2
  231 + case (v & 0xFF000000) == 0:
  232 + return 3
  233 + default:
  234 + return 4
  235 + }
  236 +}
  237 +
  238 +const (
  239 + rawKSUID = 0
  240 + timeDelta = (1 << 6)
  241 + payloadDelta = (1 << 7)
  242 + payloadRange = (1 << 6) | (1 << 7)
  243 +)
  244 +
  245 +// CompressedSetIter is an iterator type returned by Set.Iter to produce the
  246 +// list of KSUIDs stored in a set.
  247 +//
  248 +// Here's is how the iterator type is commonly used:
  249 +//
  250 +// for it := set.Iter(); it.Next(); {
  251 +// id := it.KSUID
  252 +// // ...
  253 +// }
  254 +//
  255 +// CompressedSetIter values are not safe to use concurrently from multiple
  256 +// goroutines.
  257 +type CompressedSetIter struct {
  258 + // KSUID is modified by calls to the Next method to hold the KSUID loaded
  259 + // by the iterator.
  260 + KSUID KSUID
  261 +
  262 + content []byte
  263 + offset int
  264 +
  265 + seqlength uint64
  266 + timestamp uint32
  267 + lastValue uint128
  268 +}
  269 +
  270 +// Next moves the iterator forward, returning true if there a KSUID was found,
  271 +// or false if the iterator as reached the end of the set it was created from.
  272 +func (it *CompressedSetIter) Next() bool {
  273 + if it.seqlength != 0 {
  274 + value := incr128(it.lastValue)
  275 + it.KSUID = value.ksuid(it.timestamp)
  276 + it.seqlength--
  277 + it.lastValue = value
  278 + return true
  279 + }
  280 +
  281 + if it.offset == len(it.content) {
  282 + return false
  283 + }
  284 +
  285 + b := it.content[it.offset]
  286 + it.offset++
  287 +
  288 + const mask = rawKSUID | timeDelta | payloadDelta | payloadRange
  289 + tag := int(b) & mask
  290 + cnt := int(b) & ^mask
  291 +
  292 + switch tag {
  293 + case rawKSUID:
  294 + off0 := it.offset
  295 + off1 := off0 + byteLength
  296 +
  297 + copy(it.KSUID[:], it.content[off0:off1])
  298 +
  299 + it.offset = off1
  300 + it.timestamp = it.KSUID.Timestamp()
  301 + it.lastValue = uint128Payload(it.KSUID)
  302 +
  303 + case timeDelta:
  304 + off0 := it.offset
  305 + off1 := off0 + cnt
  306 + off2 := off1 + payloadLengthInBytes
  307 +
  308 + it.timestamp += varint32(it.content[off0:off1])
  309 +
  310 + binary.BigEndian.PutUint32(it.KSUID[:timestampLengthInBytes], it.timestamp)
  311 + copy(it.KSUID[timestampLengthInBytes:], it.content[off1:off2])
  312 +
  313 + it.offset = off2
  314 + it.lastValue = uint128Payload(it.KSUID)
  315 +
  316 + case payloadDelta:
  317 + off0 := it.offset
  318 + off1 := off0 + cnt
  319 +
  320 + delta := varint128(it.content[off0:off1])
  321 + value := add128(it.lastValue, delta)
  322 +
  323 + it.KSUID = value.ksuid(it.timestamp)
  324 + it.offset = off1
  325 + it.lastValue = value
  326 +
  327 + case payloadRange:
  328 + off0 := it.offset
  329 + off1 := off0 + cnt
  330 +
  331 + value := incr128(it.lastValue)
  332 + it.KSUID = value.ksuid(it.timestamp)
  333 + it.seqlength = varint64(it.content[off0:off1])
  334 + it.offset = off1
  335 + it.seqlength--
  336 + it.lastValue = value
  337 +
  338 + default:
  339 + panic("KSUID set iterator is reading malformed data")
  340 + }
  341 +
  342 + return true
  343 +}
... ...
src/github.com/segmentio/set_test.go 0 → 100644
... ... @@ -0,0 +1,332 @@
  1 +package ksuid
  2 +
  3 +import (
  4 + "testing"
  5 + "time"
  6 +)
  7 +
  8 +func TestCompressedSet(t *testing.T) {
  9 + tests := []struct {
  10 + scenario string
  11 + function func(*testing.T)
  12 + }{
  13 + {
  14 + scenario: "String",
  15 + function: testCompressedSetString,
  16 + },
  17 + {
  18 + scenario: "GoString",
  19 + function: testCompressedSetGoString,
  20 + },
  21 + {
  22 + scenario: "sparse",
  23 + function: testCompressedSetSparse,
  24 + },
  25 + {
  26 + scenario: "packed",
  27 + function: testCompressedSetPacked,
  28 + },
  29 + {
  30 + scenario: "mixed",
  31 + function: testCompressedSetMixed,
  32 + },
  33 + {
  34 + scenario: "iterating over a nil compressed set returns no ids",
  35 + function: testCompressedSetNil,
  36 + },
  37 + {
  38 + scenario: "concatenating multiple compressed sets is supported",
  39 + function: testCompressedSetConcat,
  40 + },
  41 + {
  42 + scenario: "duplicate ids are appear only once in the compressed set",
  43 + function: testCompressedSetDuplicates,
  44 + },
  45 + {
  46 + scenario: "building a compressed set with a single id repeated multiple times produces the id only once",
  47 + function: testCompressedSetSingle,
  48 + },
  49 + {
  50 + scenario: "iterating over a compressed sequence returns the full sequence",
  51 + function: testCompressedSetSequence,
  52 + },
  53 + }
  54 +
  55 + for _, test := range tests {
  56 + t.Run(test.scenario, test.function)
  57 + }
  58 +}
  59 +
  60 +func testCompressedSetString(t *testing.T) {
  61 + id1, _ := Parse("0uHjRkQoL2JKAQIULPdqqb5fOkk")
  62 + id2, _ := Parse("0uHjRvkOG5CbtoXW5oCEp3L2xBu")
  63 + id3, _ := Parse("0uHjSJ4Pe5606kT2XWixK6dirlo")
  64 +
  65 + set := Compress(id1, id2, id3)
  66 +
  67 + if s := set.String(); s != `["0uHjRkQoL2JKAQIULPdqqb5fOkk", "0uHjRvkOG5CbtoXW5oCEp3L2xBu", "0uHjSJ4Pe5606kT2XWixK6dirlo"]` {
  68 + t.Error(s)
  69 + }
  70 +}
  71 +
  72 +func testCompressedSetGoString(t *testing.T) {
  73 + id1, _ := Parse("0uHjRkQoL2JKAQIULPdqqb5fOkk")
  74 + id2, _ := Parse("0uHjRvkOG5CbtoXW5oCEp3L2xBu")
  75 + id3, _ := Parse("0uHjSJ4Pe5606kT2XWixK6dirlo")
  76 +
  77 + set := Compress(id1, id2, id3)
  78 +
  79 + if s := set.GoString(); s != `ksuid.CompressedSet{"0uHjRkQoL2JKAQIULPdqqb5fOkk", "0uHjRvkOG5CbtoXW5oCEp3L2xBu", "0uHjSJ4Pe5606kT2XWixK6dirlo"}` {
  80 + t.Error(s)
  81 + }
  82 +}
  83 +
  84 +func testCompressedSetSparse(t *testing.T) {
  85 + now := time.Now()
  86 +
  87 + times := [100]time.Time{}
  88 + for i := range times {
  89 + times[i] = now.Add(time.Duration(i) * 2 * time.Second)
  90 + }
  91 +
  92 + ksuids := [1000]KSUID{}
  93 + for i := range ksuids {
  94 + ksuids[i], _ = NewRandomWithTime(times[i%len(times)])
  95 + }
  96 +
  97 + set := Compress(ksuids[:]...)
  98 +
  99 + for i, it := 0, set.Iter(); it.Next(); {
  100 + if i >= len(ksuids) {
  101 + t.Error("too many KSUIDs were produced by the set iterator")
  102 + break
  103 + }
  104 + if ksuids[i] != it.KSUID {
  105 + t.Errorf("bad KSUID at index %d: expected %s but found %s", i, ksuids[i], it.KSUID)
  106 + }
  107 + i++
  108 + }
  109 +
  110 + reportCompressionRatio(t, ksuids[:], set)
  111 +}
  112 +
  113 +func testCompressedSetPacked(t *testing.T) {
  114 + sequences := [10]Sequence{}
  115 + for i := range sequences {
  116 + sequences[i] = Sequence{Seed: New()}
  117 + }
  118 +
  119 + ksuids := [1000]KSUID{}
  120 + for i := range ksuids {
  121 + ksuids[i], _ = sequences[i%len(sequences)].Next()
  122 + }
  123 +
  124 + set := Compress(ksuids[:]...)
  125 +
  126 + for i, it := 0, set.Iter(); it.Next(); {
  127 + if i >= len(ksuids) {
  128 + t.Error("too many KSUIDs were produced by the set iterator")
  129 + break
  130 + }
  131 + if ksuids[i] != it.KSUID {
  132 + t.Errorf("bad KSUID at index %d: expected %s but found %s", i, ksuids[i], it.KSUID)
  133 + }
  134 + i++
  135 + }
  136 +
  137 + reportCompressionRatio(t, ksuids[:], set)
  138 +}
  139 +
  140 +func testCompressedSetMixed(t *testing.T) {
  141 + now := time.Now()
  142 +
  143 + times := [20]time.Time{}
  144 + for i := range times {
  145 + times[i] = now.Add(time.Duration(i) * 2 * time.Second)
  146 + }
  147 +
  148 + sequences := [200]Sequence{}
  149 + for i := range sequences {
  150 + seed, _ := NewRandomWithTime(times[i%len(times)])
  151 + sequences[i] = Sequence{Seed: seed}
  152 + }
  153 +
  154 + ksuids := [1000]KSUID{}
  155 + for i := range ksuids {
  156 + ksuids[i], _ = sequences[i%len(sequences)].Next()
  157 + }
  158 +
  159 + set := Compress(ksuids[:]...)
  160 +
  161 + for i, it := 0, set.Iter(); it.Next(); {
  162 + if i >= len(ksuids) {
  163 + t.Error("too many KSUIDs were produced by the set iterator")
  164 + break
  165 + }
  166 + if ksuids[i] != it.KSUID {
  167 + t.Errorf("bad KSUID at index %d: expected %s but found %s", i, ksuids[i], it.KSUID)
  168 + }
  169 + i++
  170 + }
  171 +
  172 + reportCompressionRatio(t, ksuids[:], set)
  173 +}
  174 +
  175 +func testCompressedSetDuplicates(t *testing.T) {
  176 + sequence := Sequence{Seed: New()}
  177 +
  178 + ksuids := [1000]KSUID{}
  179 + for i := range ksuids[:10] {
  180 + ksuids[i], _ = sequence.Next() // exercise dedupe on the id range code path
  181 + }
  182 + for i := range ksuids[10:] {
  183 + ksuids[i+10] = New()
  184 + }
  185 + for i := 1; i < len(ksuids); i += 4 {
  186 + ksuids[i] = ksuids[i-1] // generate many dupes
  187 + }
  188 +
  189 + miss := make(map[KSUID]struct{})
  190 + uniq := make(map[KSUID]struct{})
  191 +
  192 + for _, id := range ksuids {
  193 + miss[id] = struct{}{}
  194 + }
  195 +
  196 + set := Compress(ksuids[:]...)
  197 +
  198 + for it := set.Iter(); it.Next(); {
  199 + if _, dupe := uniq[it.KSUID]; dupe {
  200 + t.Errorf("duplicate id found in compressed set: %s", it.KSUID)
  201 + }
  202 + uniq[it.KSUID] = struct{}{}
  203 + delete(miss, it.KSUID)
  204 + }
  205 +
  206 + if len(miss) != 0 {
  207 + t.Error("some ids were not found in the compressed set:")
  208 + for id := range miss {
  209 + t.Log(id)
  210 + }
  211 + }
  212 +}
  213 +
  214 +func testCompressedSetSingle(t *testing.T) {
  215 + id := New()
  216 +
  217 + set := Compress(
  218 + id, id, id, id, id, id, id, id, id, id,
  219 + id, id, id, id, id, id, id, id, id, id,
  220 + id, id, id, id, id, id, id, id, id, id,
  221 + id, id, id, id, id, id, id, id, id, id,
  222 + )
  223 +
  224 + n := 0
  225 +
  226 + for it := set.Iter(); it.Next(); {
  227 + if n != 0 {
  228 + t.Errorf("too many ids found in the compressed set: %s", it.KSUID)
  229 + } else if id != it.KSUID {
  230 + t.Errorf("invalid id found in the compressed set: %s != %s", it.KSUID, id)
  231 + }
  232 + n++
  233 + }
  234 +
  235 + if n == 0 {
  236 + t.Error("no ids were produced by the compressed set")
  237 + }
  238 +}
  239 +
  240 +func testCompressedSetSequence(t *testing.T) {
  241 + seq := Sequence{Seed: New()}
  242 +
  243 + ids := make([]KSUID, 5)
  244 +
  245 + for i := 0; i < 5; i++ {
  246 + ids[i], _ = seq.Next()
  247 + }
  248 +
  249 + iter := Compress(ids...).Iter()
  250 +
  251 + index := 0
  252 + for iter.Next() {
  253 + if iter.KSUID != ids[index] {
  254 + t.Errorf("mismatched id at index %d: %s != %s", index, iter.KSUID, ids[index])
  255 + }
  256 + index++
  257 + }
  258 +
  259 + if index != 5 {
  260 + t.Errorf("Expected 5 ids, got %d", index)
  261 + }
  262 +}
  263 +
  264 +func testCompressedSetNil(t *testing.T) {
  265 + set := CompressedSet(nil)
  266 +
  267 + for it := set.Iter(); it.Next(); {
  268 + t.Errorf("too many ids returned by the iterator of a nil compressed set: %s", it.KSUID)
  269 + }
  270 +}
  271 +
  272 +func testCompressedSetConcat(t *testing.T) {
  273 + ksuids := [100]KSUID{}
  274 +
  275 + for i := range ksuids {
  276 + ksuids[i] = New()
  277 + }
  278 +
  279 + set := CompressedSet(nil)
  280 + set = AppendCompressed(set, ksuids[:42]...)
  281 + set = AppendCompressed(set, ksuids[42:64]...)
  282 + set = AppendCompressed(set, ksuids[64:]...)
  283 +
  284 + for i, it := 0, set.Iter(); it.Next(); i++ {
  285 + if ksuids[i] != it.KSUID {
  286 + t.Errorf("invalid ID at index %d: %s != %s", i, ksuids[i], it.KSUID)
  287 + }
  288 + }
  289 +}
  290 +
  291 +func reportCompressionRatio(t *testing.T, ksuids []KSUID, set CompressedSet) {
  292 + len1 := byteLength * len(ksuids)
  293 + len2 := len(set)
  294 + t.Logf("original %d B, compressed %d B (%.4g%%)", len1, len2, 100*(1-(float64(len2)/float64(len1))))
  295 +}
  296 +
  297 +func BenchmarkCompressedSet(b *testing.B) {
  298 + ksuids1 := [1000]KSUID{}
  299 + ksuids2 := [1000]KSUID{}
  300 +
  301 + for i := range ksuids1 {
  302 + ksuids1[i] = New()
  303 + }
  304 +
  305 + ksuids2 = ksuids1
  306 + buf := make([]byte, 0, 1024)
  307 + set := Compress(ksuids2[:]...)
  308 +
  309 + b.Run("write", func(b *testing.B) {
  310 + n := 0
  311 + for i := 0; i != b.N; i++ {
  312 + ksuids2 = ksuids1
  313 + buf = AppendCompressed(buf[:0], ksuids2[:]...)
  314 + n = len(buf)
  315 + }
  316 + b.SetBytes(int64(n + len(ksuids2)))
  317 + })
  318 +
  319 + b.Run("read", func(b *testing.B) {
  320 + n := 0
  321 + for i := 0; i != b.N; i++ {
  322 + n = 0
  323 + for it := set.Iter(); true; {
  324 + if !it.Next() {
  325 + n++
  326 + break
  327 + }
  328 + }
  329 + }
  330 + b.SetBytes(int64((n * byteLength) + len(set)))
  331 + })
  332 +}
... ...
src/github.com/segmentio/uint128.go 0 → 100644
... ... @@ -0,0 +1,141 @@
  1 +package ksuid
  2 +
  3 +import "fmt"
  4 +
  5 +// uint128 represents an unsigned 128 bits little endian integer.
  6 +type uint128 [2]uint64
  7 +
  8 +func uint128Payload(ksuid KSUID) uint128 {
  9 + return makeUint128FromPayload(ksuid[timestampLengthInBytes:])
  10 +}
  11 +
  12 +func makeUint128(high uint64, low uint64) uint128 {
  13 + return uint128{low, high}
  14 +}
  15 +
  16 +func makeUint128FromPayload(payload []byte) uint128 {
  17 + return uint128{
  18 + // low
  19 + uint64(payload[8])<<56 |
  20 + uint64(payload[9])<<48 |
  21 + uint64(payload[10])<<40 |
  22 + uint64(payload[11])<<32 |
  23 + uint64(payload[12])<<24 |
  24 + uint64(payload[13])<<16 |
  25 + uint64(payload[14])<<8 |
  26 + uint64(payload[15]),
  27 + // high
  28 + uint64(payload[0])<<56 |
  29 + uint64(payload[1])<<48 |
  30 + uint64(payload[2])<<40 |
  31 + uint64(payload[3])<<32 |
  32 + uint64(payload[4])<<24 |
  33 + uint64(payload[5])<<16 |
  34 + uint64(payload[6])<<8 |
  35 + uint64(payload[7]),
  36 + }
  37 +}
  38 +
  39 +func (v uint128) ksuid(timestamp uint32) KSUID {
  40 + return KSUID{
  41 + // time
  42 + byte(timestamp >> 24),
  43 + byte(timestamp >> 16),
  44 + byte(timestamp >> 8),
  45 + byte(timestamp),
  46 +
  47 + // high
  48 + byte(v[1] >> 56),
  49 + byte(v[1] >> 48),
  50 + byte(v[1] >> 40),
  51 + byte(v[1] >> 32),
  52 + byte(v[1] >> 24),
  53 + byte(v[1] >> 16),
  54 + byte(v[1] >> 8),
  55 + byte(v[1]),
  56 +
  57 + // low
  58 + byte(v[0] >> 56),
  59 + byte(v[0] >> 48),
  60 + byte(v[0] >> 40),
  61 + byte(v[0] >> 32),
  62 + byte(v[0] >> 24),
  63 + byte(v[0] >> 16),
  64 + byte(v[0] >> 8),
  65 + byte(v[0]),
  66 + }
  67 +}
  68 +
  69 +func (v uint128) bytes() [16]byte {
  70 + return [16]byte{
  71 + // high
  72 + byte(v[1] >> 56),
  73 + byte(v[1] >> 48),
  74 + byte(v[1] >> 40),
  75 + byte(v[1] >> 32),
  76 + byte(v[1] >> 24),
  77 + byte(v[1] >> 16),
  78 + byte(v[1] >> 8),
  79 + byte(v[1]),
  80 +
  81 + // low
  82 + byte(v[0] >> 56),
  83 + byte(v[0] >> 48),
  84 + byte(v[0] >> 40),
  85 + byte(v[0] >> 32),
  86 + byte(v[0] >> 24),
  87 + byte(v[0] >> 16),
  88 + byte(v[0] >> 8),
  89 + byte(v[0]),
  90 + }
  91 +}
  92 +
  93 +func (v uint128) String() string {
  94 + return fmt.Sprintf("0x%016X%016X", v[0], v[1])
  95 +}
  96 +
  97 +const wordBitSize = 64
  98 +
  99 +func cmp128(x, y uint128) int {
  100 + if x[1] < y[1] {
  101 + return -1
  102 + }
  103 + if x[1] > y[1] {
  104 + return 1
  105 + }
  106 + if x[0] < y[0] {
  107 + return -1
  108 + }
  109 + if x[0] > y[0] {
  110 + return 1
  111 + }
  112 + return 0
  113 +}
  114 +
  115 +func add128(x, y uint128) (z uint128) {
  116 + x0 := x[0]
  117 + y0 := y[0]
  118 + z0 := x0 + y0
  119 + z[0] = z0
  120 +
  121 + c := (x0&y0 | (x0|y0)&^z0) >> (wordBitSize - 1)
  122 +
  123 + z[1] = x[1] + y[1] + c
  124 + return
  125 +}
  126 +
  127 +func sub128(x, y uint128) (z uint128) {
  128 + x0 := x[0]
  129 + y0 := y[0]
  130 + z0 := x0 - y0
  131 + z[0] = z0
  132 +
  133 + c := (y0&^x0 | (y0|^x0)&z0) >> (wordBitSize - 1)
  134 +
  135 + z[1] = x[1] - y[1] - c
  136 + return
  137 +}
  138 +
  139 +func incr128(x uint128) uint128 {
  140 + return add128(x, uint128{1, 0})
  141 +}
... ...
src/github.com/segmentio/uint128_test.go 0 → 100644
... ... @@ -0,0 +1,169 @@
  1 +package ksuid
  2 +
  3 +import (
  4 + "fmt"
  5 + "testing"
  6 +)
  7 +
  8 +func TestCmp128(t *testing.T) {
  9 + tests := []struct {
  10 + x uint128
  11 + y uint128
  12 + k int
  13 + }{
  14 + {
  15 + x: makeUint128(0, 0),
  16 + y: makeUint128(0, 0),
  17 + k: 0,
  18 + },
  19 + {
  20 + x: makeUint128(0, 1),
  21 + y: makeUint128(0, 0),
  22 + k: +1,
  23 + },
  24 + {
  25 + x: makeUint128(0, 0),
  26 + y: makeUint128(0, 1),
  27 + k: -1,
  28 + },
  29 + {
  30 + x: makeUint128(1, 0),
  31 + y: makeUint128(0, 1),
  32 + k: +1,
  33 + },
  34 + {
  35 + x: makeUint128(0, 1),
  36 + y: makeUint128(1, 0),
  37 + k: -1,
  38 + },
  39 + }
  40 +
  41 + for _, test := range tests {
  42 + t.Run(fmt.Sprintf("cmp128(%s,%s)", test.x, test.y), func(t *testing.T) {
  43 + if k := cmp128(test.x, test.y); k != test.k {
  44 + t.Error(k, "!=", test.k)
  45 + }
  46 + })
  47 + }
  48 +}
  49 +
  50 +func TestAdd128(t *testing.T) {
  51 + tests := []struct {
  52 + x uint128
  53 + y uint128
  54 + z uint128
  55 + }{
  56 + {
  57 + x: makeUint128(0, 0),
  58 + y: makeUint128(0, 0),
  59 + z: makeUint128(0, 0),
  60 + },
  61 + {
  62 + x: makeUint128(0, 1),
  63 + y: makeUint128(0, 0),
  64 + z: makeUint128(0, 1),
  65 + },
  66 + {
  67 + x: makeUint128(0, 0),
  68 + y: makeUint128(0, 1),
  69 + z: makeUint128(0, 1),
  70 + },
  71 + {
  72 + x: makeUint128(1, 0),
  73 + y: makeUint128(0, 1),
  74 + z: makeUint128(1, 1),
  75 + },
  76 + {
  77 + x: makeUint128(0, 1),
  78 + y: makeUint128(1, 0),
  79 + z: makeUint128(1, 1),
  80 + },
  81 + {
  82 + x: makeUint128(0, 0xFFFFFFFFFFFFFFFF),
  83 + y: makeUint128(0, 1),
  84 + z: makeUint128(1, 0),
  85 + },
  86 + }
  87 +
  88 + for _, test := range tests {
  89 + t.Run(fmt.Sprintf("add128(%s,%s)", test.x, test.y), func(t *testing.T) {
  90 + if z := add128(test.x, test.y); z != test.z {
  91 + t.Error(z, "!=", test.z)
  92 + }
  93 + })
  94 + }
  95 +}
  96 +
  97 +func TestSub128(t *testing.T) {
  98 + tests := []struct {
  99 + x uint128
  100 + y uint128
  101 + z uint128
  102 + }{
  103 + {
  104 + x: makeUint128(0, 0),
  105 + y: makeUint128(0, 0),
  106 + z: makeUint128(0, 0),
  107 + },
  108 + {
  109 + x: makeUint128(0, 1),
  110 + y: makeUint128(0, 0),
  111 + z: makeUint128(0, 1),
  112 + },
  113 + {
  114 + x: makeUint128(0, 0),
  115 + y: makeUint128(0, 1),
  116 + z: makeUint128(0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF),
  117 + },
  118 + {
  119 + x: makeUint128(1, 0),
  120 + y: makeUint128(0, 1),
  121 + z: makeUint128(0, 0xFFFFFFFFFFFFFFFF),
  122 + },
  123 + {
  124 + x: makeUint128(0, 1),
  125 + y: makeUint128(1, 0),
  126 + z: makeUint128(0xFFFFFFFFFFFFFFFF, 1),
  127 + },
  128 + {
  129 + x: makeUint128(0, 0xFFFFFFFFFFFFFFFF),
  130 + y: makeUint128(0, 1),
  131 + z: makeUint128(0, 0xFFFFFFFFFFFFFFFE),
  132 + },
  133 + }
  134 +
  135 + for _, test := range tests {
  136 + t.Run(fmt.Sprintf("sub128(%s,%s)", test.x, test.y), func(t *testing.T) {
  137 + if z := sub128(test.x, test.y); z != test.z {
  138 + t.Error(z, "!=", test.z)
  139 + }
  140 + })
  141 + }
  142 +}
  143 +
  144 +func BenchmarkCmp128(b *testing.B) {
  145 + x := makeUint128(0, 0)
  146 + y := makeUint128(0, 0)
  147 +
  148 + for i := 0; i != b.N; i++ {
  149 + cmp128(x, y)
  150 + }
  151 +}
  152 +
  153 +func BenchmarkAdd128(b *testing.B) {
  154 + x := makeUint128(0, 0)
  155 + y := makeUint128(0, 0)
  156 +
  157 + for i := 0; i != b.N; i++ {
  158 + add128(x, y)
  159 + }
  160 +}
  161 +
  162 +func BenchmarkSub128(b *testing.B) {
  163 + x := makeUint128(0, 0)
  164 + y := makeUint128(0, 0)
  165 +
  166 + for i := 0; i != b.N; i++ {
  167 + sub128(x, y)
  168 + }
  169 +}
... ...