summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEuAndreh <eu@euandre.org>2024-09-12 16:13:11 -0300
committerEuAndreh <eu@euandre.org>2024-09-13 05:19:32 -0300
commit1e62da79d8c61cee0a13df4e16a349ca9bae383c (patch)
tree43d975c5c2407c2513c317eed4ed26343a24f517
parentInitial empty commit (diff)
downloaduuid-1e62da79d8c61cee0a13df4e16a349ca9bae383c.tar.gz
uuid-1e62da79d8c61cee0a13df4e16a349ca9bae383c.tar.xz
Initial version of v4 UUID
Diffstat (limited to '')
-rw-r--r--.gitignore4
-rw-r--r--Makefile137
-rw-r--r--src/guuid.go103
-rw-r--r--tests/guuid.go247
-rw-r--r--tests/libbuild.go11
-rw-r--r--tests/main.go7
6 files changed, 509 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1b60c9b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+/src/version.go
+/src/*.a
+/tests/*.a
+/tests/*.bin
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..9caae43
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,137 @@
+.POSIX:
+DATE = 1970-01-01
+VERSION = 0.1.0
+NAME = guuid
+NAME_UC = $(NAME)
+LANGUAGES = en
+## Installation prefix. Defaults to "/usr".
+PREFIX = /usr
+BINDIR = $(PREFIX)/bin
+LIBDIR = $(PREFIX)/lib
+GOLIBDIR = $(LIBDIR)/go
+INCLUDEDIR = $(PREFIX)/include
+SRCDIR = $(PREFIX)/src/$(NAME)
+SHAREDIR = $(PREFIX)/share
+LOCALEDIR = $(SHAREDIR)/locale
+MANDIR = $(SHAREDIR)/man
+EXEC = ./
+## Where to store the installation. Empty by default.
+DESTDIR =
+LDLIBS =
+GOCFLAGS = -I $(GOLIBDIR)
+GOLDFLAGS = -L $(GOLIBDIR)
+
+
+
+.SUFFIXES:
+.SUFFIXES: .go .a .bin .bin-check
+
+
+
+objects = \
+ src/$(NAME).a \
+ tests/$(NAME).a \
+ tests/main.a \
+ tests/libbuild.a \
+
+sources = \
+ src/$(NAME).go \
+ src/version.go \
+
+
+derived-assets = \
+ src/version.go \
+ $(objects) \
+ tests/main.bin \
+ tests/libbuild.bin \
+
+side-assets = \
+
+
+
+## Default target. Builds all artifacts required for testing
+## and installation.
+all: $(derived-assets)
+
+
+$(objects): Makefile
+
+src/$(NAME).a: src/$(NAME).go src/version.go
+ go tool compile $(GOCFLAGS) -o $@ -p $(*F) -I $(@D) $*.go src/version.go
+
+tests/main.a: tests/main.go tests/$(NAME).a
+tests/main.a:
+ go tool compile $(GOCFLAGS) -o $@ -p $(*F) -I $(@D) $*.go
+
+tests/libbuild.a: tests/libbuild.go src/$(NAME).a
+ go tool compile $(GOCFLAGS) -o $@ -p main -I src $*.go
+
+tests/$(NAME).a: tests/$(NAME).go src/$(NAME).go src/version.go
+ go tool compile $(GOCFLAGS) -o $@ -p $(*F) $*.go src/$(*F).go src/version.go
+
+tests/libbuild.bin: tests/libbuild.a
+ go tool link $(GOLDFLAGS) -o $@ -L src $*.a
+
+tests/main.bin: tests/main.a
+ go tool link $(GOLDFLAGS) -o $@ -L $(@D) --extldflags '$(LDLIBS)' $*.a
+
+src/version.go: Makefile
+ echo 'package $(NAME); var version = "$(VERSION)"' > $@
+
+
+
+tests.bin-check = \
+ tests/main.bin-check \
+ tests/libbuild.bin-check \
+
+tests/main.bin-check: tests/main.bin
+tests/libbuild.bin-check: tests/libbuild.bin
+$(tests.bin-check):
+ $(EXEC)$*.bin
+
+check-unit: $(tests.bin-check)
+
+
+integration-tests = \
+
+.PRECIOUS: $(integration-tests)
+$(integration-tests): $(NAME).bin
+$(integration-tests): ALWAYS
+ sh $@
+
+check-integration: $(integration-tests)
+
+
+## Run all tests. Each test suite is isolated, so that a parallel
+## build can run tests at the same time. The required artifacts
+## are created if missing.
+check: check-unit check-integration
+
+
+
+## Remove *all* derived artifacts produced during the build.
+## A dedicated test asserts that this is always true.
+clean:
+ rm -rf $(derived-assets) $(side-assets)
+
+
+## Installs into $(DESTDIR)$(PREFIX). Its dependency target
+## ensures that all installable artifacts are crafted beforehand.
+install: all
+ mkdir -p \
+ '$(DESTDIR)$(GOLIBDIR)' \
+ '$(DESTDIR)$(SRCDIR)' \
+
+ cp src/$(NAME).a '$(DESTDIR)$(GOLIBDIR)'
+ cp $(sources) '$(DESTDIR)$(SRCDIR)'
+
+## Uninstalls from $(DESTDIR)$(PREFIX). This is a perfect mirror
+## of the "install" target, and removes *all* that was installed.
+## A dedicated test asserts that this is always true.
+uninstall:
+ rm -rf \
+ '$(DESTDIR)$(GOLIBDIR)'/$(NAME).a \
+ '$(DESTDIR)$(SRCDIR)' \
+
+
+ALWAYS:
diff --git a/src/guuid.go b/src/guuid.go
new file mode 100644
index 0000000..9e3e2ba
--- /dev/null
+++ b/src/guuid.go
@@ -0,0 +1,103 @@
+package guuid
+
+import (
+ "crypto/rand"
+ "encoding/hex"
+ "errors"
+ "io"
+ "strings"
+)
+
+
+
+const (
+ uuidByteCount = 16
+ uuidDashCount = 4
+ uuidEncodedLength = (uuidByteCount * 2) + uuidDashCount
+)
+
+
+var (
+ dashIndexes = []int { 8, 13, 18, 23 }
+ emptyUUID = UUID {}
+)
+
+var (
+ BadLengthError = errors.New("guuid: str isn't of the correct length")
+ BadDashCountError = errors.New("guuid: Bad count of dashes in string")
+ BadDashPositionError = errors.New("guuid: Bad char in string")
+)
+
+
+
+type UUID [uuidByteCount]byte
+
+
+
+func NewFrom(r io.Reader) (UUID, error) {
+ var uuid UUID
+ _, err := io.ReadFull(r, uuid[:])
+ if err != nil {
+ return emptyUUID, err
+ }
+ uuid[6] = (uuid[6] & 0x0f) | 0x40 // v4
+ uuid[8] = (uuid[8] & 0x3f) | 0x80 // variant 10
+ return uuid, nil
+}
+
+func New() UUID {
+ uuid, err := NewFrom(rand.Reader)
+ if err != nil {
+ panic(err)
+ }
+ return uuid
+}
+
+func (uuid UUID) String() string {
+ dst := [uuidEncodedLength]byte {
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ '-',
+ 0, 0, 0, 0,
+ '-',
+ 0, 0, 0, 0,
+ '-',
+ 0, 0, 0, 0,
+ '-',
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ }
+
+ hex.Encode(dst[ 0:8], uuid[0:4])
+ hex.Encode(dst[ 9:13], uuid[4:6])
+ hex.Encode(dst[14:18], uuid[6:8])
+ hex.Encode(dst[19:23], uuid[8:10])
+ hex.Encode(dst[24:36], uuid[10:])
+
+ return string(dst[:])
+}
+
+func FromString(str string) (UUID, error) {
+ if len(str) != uuidEncodedLength {
+ return emptyUUID, BadLengthError
+ }
+
+ if strings.Count(str, "-") != uuidDashCount {
+ return emptyUUID, BadDashCountError
+ }
+
+ for _, idx := range dashIndexes {
+ if str[idx] != '-' {
+ return emptyUUID, BadDashPositionError
+ }
+ }
+
+ hexstr := strings.Join(strings.Split(str, "-"), "")
+ data, err := hex.DecodeString(hexstr)
+ if err != nil {
+ return emptyUUID, err
+ }
+
+ return [uuidByteCount]byte(data), nil
+}
diff --git a/tests/guuid.go b/tests/guuid.go
new file mode 100644
index 0000000..6470fca
--- /dev/null
+++ b/tests/guuid.go
@@ -0,0 +1,247 @@
+package guuid
+
+import (
+ "bytes"
+ "encoding/hex"
+ "fmt"
+ "os"
+ "reflect"
+ "strings"
+)
+
+
+
+func showColour() bool {
+ return os.Getenv("NO_COLOUR") == ""
+}
+
+func testStart(name string) {
+ fmt.Fprintf(os.Stderr, "%s:\n", name)
+}
+
+func testing(message string, body func()) {
+ if showColour() {
+ fmt.Fprintf(
+ os.Stderr,
+ "\033[0;33mtesting\033[0m: %s... ",
+ message,
+ )
+ body()
+ fmt.Fprintf(os.Stderr, "\033[0;32mOK\033[0m.\n")
+ } else {
+ fmt.Fprintf(os.Stderr, "testing: %s... ", message)
+ body()
+ fmt.Fprintf(os.Stderr, "OK.\n")
+ }
+}
+
+func assertEq(given any, expected any) {
+ if !reflect.DeepEqual(given, expected) {
+ if showColour() {
+ fmt.Fprintf(os.Stderr, "\033[0;31mERR\033[0m.\n")
+ } else {
+ fmt.Fprintf(os.Stderr, "ERR.\n")
+ }
+ fmt.Fprintf(os.Stderr, "given != expected\n")
+ fmt.Fprintf(os.Stderr, "given: %#v\n", given)
+ fmt.Fprintf(os.Stderr, "expected: %#v\n", expected)
+ os.Exit(1)
+ }
+}
+
+
+func test_NewFrom() {
+ testStart("NewFrom()")
+
+ testing("we get the same UUID from the same input", func() {
+ const s = "abcdefghijklmnop"
+
+ r1 := strings.NewReader(s)
+ uuid1, err := NewFrom(r1)
+ assertEq(err, nil)
+
+ r2 := strings.NewReader(s)
+ uuid2, err := NewFrom(r2)
+ assertEq(err, nil)
+
+ assertEq(uuid1, uuid2)
+ })
+
+ testing("we the bytes in UUID are what the reader gives us", func() {
+ input := []byte{
+ 0x00,
+ 0x01,
+ 0x02,
+ 0x03,
+ 0x04,
+ 0x05,
+ 0x06,
+ 0x07,
+ 0x08,
+ 0x09,
+ 0x0a,
+ 0x0b,
+ 0x0c,
+ 0x0d,
+ 0x0e,
+ 0x0f,
+ }
+
+ expected := UUID{
+ 0x00,
+ 0x01,
+ 0x02,
+ 0x03,
+ 0x04,
+ 0x05,
+ 0x06 + 0x40,
+ 0x07,
+ 0x08 + 0x80,
+ 0x09,
+ 0x0a,
+ 0x0b,
+ 0x0c,
+ 0x0d,
+ 0x0e,
+ 0x0f,
+ }
+
+ r := bytes.NewReader(input)
+ given, err := NewFrom(r)
+ assertEq(err, nil)
+ assertEq(given, expected)
+ })
+
+ testing("v4 and variant markers", func() {
+ input := []byte{
+ 0x11,
+ 0x11,
+ 0x11,
+ 0x11,
+ 0x11,
+ 0x11,
+ 0x11,
+ 0x11,
+ 0x71,
+ 0x11,
+ 0x11,
+ 0x11,
+ 0x11,
+ 0x11,
+ 0x11,
+ 0x11,
+ }
+
+ expected := UUID{
+ 0x11,
+ 0x11,
+ 0x11,
+ 0x11,
+ 0x11,
+ 0x11,
+ 0x41, // not 0x11
+ 0x11,
+ 0xb1, // not 0x11
+ 0x11,
+ 0x11,
+ 0x11,
+ 0x11,
+ 0x11,
+ 0x11,
+ 0x11,
+ }
+
+ r := bytes.NewReader(input)
+ given, err := NewFrom(r)
+ assertEq(err, nil)
+ assertEq(given, expected)
+ })
+}
+
+func test_New() {
+ testStart("New()")
+
+ testing("we can generate UUID values: ", func() {
+ var uuid UUID = New()
+ assertEq(len(uuid), 16)
+ })
+}
+
+func test_String() {
+ testStart("UUID.String()")
+
+ testing("simple example values", func() {
+ uuids := []UUID {
+ UUID {
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ },
+ UUID {
+ 0, 1, 2, 3, 4, 5, 6, 7,
+ 8, 9, 10, 11, 12, 13, 14, 15,
+ },
+ UUID {
+ 222, 222, 222, 222, 222, 222, 222, 222,
+ 222, 222, 222, 222, 222, 222, 222, 222,
+ },
+ UUID {
+ 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255,
+ },
+ }
+
+ strs := []string {
+ "00000000-0000-0000-0000-000000000000",
+ "00010203-0405-0607-0809-0a0b0c0d0e0f",
+ "dededede-dede-dede-dede-dededededede",
+ "ffffffff-ffff-ffff-ffff-ffffffffffff",
+ }
+
+ for i := range uuids {
+ s1 := uuids[i].String()
+ s2 := fmt.Sprintf("%v", uuids[i])
+ assertEq(s1, strs[i])
+ assertEq(s2, strs[i])
+ }
+ })
+}
+
+func test_FromString() {
+ testStart("FromString()")
+
+ testing("UUID -> string -> UUID round trip", func() {
+ for i := 0; i < 100; i++ {
+ uuid0 := New()
+ uuid1, err := FromString(uuid0.String())
+ assertEq(err, nil)
+ assertEq(uuid0, uuid1)
+ }
+ })
+
+ testing("errors we detect", func() {
+ var err error
+
+ _, err = FromString("")
+ assertEq(err, BadLengthError)
+
+ _, err = FromString("---000000000000000000000000000000000")
+ assertEq(err, BadDashCountError)
+
+ _, err = FromString("----00000000000000000000000000000000")
+ assertEq(err, BadDashPositionError)
+
+ _, err = FromString("00000000-0000-0000-0000-00000000000g")
+ assertEq(err, hex.InvalidByteError('g'))
+
+ _, err = FromString("00000000-0000-0000-0000-000000000000")
+ assertEq(err, nil)
+ })
+}
+
+
+func MainTest() {
+ test_NewFrom()
+ test_New()
+ test_String()
+ test_FromString()
+}
diff --git a/tests/libbuild.go b/tests/libbuild.go
new file mode 100644
index 0000000..2c02686
--- /dev/null
+++ b/tests/libbuild.go
@@ -0,0 +1,11 @@
+package main
+
+import (
+ "fmt"
+
+ "guuid"
+)
+
+func main() {
+ fmt.Println(guuid.New())
+}
diff --git a/tests/main.go b/tests/main.go
new file mode 100644
index 0000000..b803727
--- /dev/null
+++ b/tests/main.go
@@ -0,0 +1,7 @@
+package main
+
+import "guuid"
+
+func main() {
+ guuid.MainTest()
+}