summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@sbcglobal.net>2016-12-27 09:53:03 -0700
committerLuke Shumaker <lukeshu@sbcglobal.net>2016-12-27 09:53:03 -0700
commitf79c2341229def4471bbaf125b8ef19bf49c2b44 (patch)
tree8df13412df058fc8da0501465fa5b3dad9a3df43
parent7dfa7331fa66c4d1f3342441190c3dd3cdc18b99 (diff)
Implement sd_id128
-rw-r--r--sd_id128/sd_id128.go222
-rw-r--r--sd_id128/util.go45
2 files changed, 267 insertions, 0 deletions
diff --git a/sd_id128/sd_id128.go b/sd_id128/sd_id128.go
new file mode 100644
index 0000000..063a4d4
--- /dev/null
+++ b/sd_id128/sd_id128.go
@@ -0,0 +1,222 @@
+// Copyright 2016 Luke Shumaker
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package sd_id128 provides functions for dealing with 128-bit IDs.
+package sd_id128
+
+import (
+ "crypto/rand"
+ "errors"
+ "os"
+ "strings"
+)
+
+var (
+ ErrInvalid = errors.New("Invalid ID")
+ ErrNone = errors.New("No Invocation ID set")
+)
+
+type ID128 [16]byte
+
+// String formats the ID as a hexadecimal number.
+func (id ID128) String() string {
+ var s [32]byte
+ for n := range id {
+ s[n*2] = hexchar(id[n] >> 4)
+ s[n*2+1] = hexchar(id[n] & 0xF)
+ }
+ return string(s[:])
+}
+
+// UUID formats the ID as a UUID string.
+func (id ID128) UUID() string {
+ var s [36]byte
+ k := 0
+ for n := range id {
+ switch n {
+ case 4, 6, 8, 10:
+ s[k] = '-'
+ k++
+ }
+ s[k] = hexchar(id[n] >> 4)
+ k++
+ s[k] = hexchar(id[n] & 0xF)
+ k++
+ }
+ return string(s[:])
+}
+
+// Parse parses a 128-bit ID represented either as a hexadecimal
+// number, or as a UUID string (that is, a hexadecimal number with
+// hyphens inserted at positions 8, 12, 18, and 23).
+func Parse(s string) (ID128, error) {
+ ret := ID128{}
+ isGuid := false
+
+ i := 0
+ n := 0
+ for n < 16 {
+ if s[i] == '-' {
+ if i == 8 {
+ isGuid = true
+ } else if i == 13 || i == 18 || i == 23 {
+ if !isGuid {
+ return ID128{}, ErrInvalid
+ }
+ } else {
+ return ID128{}, ErrInvalid
+ }
+ i++
+ } else {
+ if i+2 >= len(s) {
+ return ID128{}, ErrInvalid
+ }
+
+ a, err := unhexchar(s[i])
+ i++
+ if err != nil {
+ return ID128{}, err
+ }
+
+ b, err := unhexchar(s[i])
+ i++
+ if err != nil {
+ return ID128{}, err
+ }
+
+ ret[n] = (a << 4) | b
+ n++
+ }
+ }
+
+ if !((i == 36 && isGuid) || (i == 32 && !isGuid)) {
+ return ID128{}, ErrInvalid
+ }
+ if i != len(s) {
+ return ID128{}, ErrInvalid
+ }
+
+ return ret, nil
+}
+
+// ParsePlain is like Parse, but only accepts hexadecimal numbers, not
+// UUID-formatted strings.
+func ParsePlain(s string) (ID128, error) {
+ if len(s) != 32 {
+ return ID128{}, ErrInvalid
+ }
+ return Parse(s)
+}
+
+// ParseUUID is like Parse, but only accepts UUID-formatted strings,
+// not hexadecimal numbers.
+func ParseUUID(s string) (ID128, error) {
+ if len(s) != 36 {
+ return ID128{}, ErrInvalid
+ }
+ return Parse(s)
+}
+
+// GetRandomID generates a random v4 UUID (122 random bits).
+func GetRandomID() (ID128, error) {
+ var id ID128
+ _, err := rand.Read(id[:])
+ if err != nil {
+ return ID128{}, err
+ }
+
+ // Turn this in to a valid v4 UUID, to be nice.
+
+ // Set UUID version to 4 --- truly random generation
+ id[6] = (id[6] & 0x0F) | 0x40
+
+ // Set the UUID variant to DCE
+ id[8] = (id[8] & 0x3F) | 0x80
+
+ return id, nil
+}
+
+var cachedMachineID ID128
+
+// GetMachineID returns a unique identifier for this machine.
+//
+// This is read as a plain hexadecimal number (with an optional
+// trailing newline) from "/etc/machine-id".
+func GetMachineID() (ID128, error) {
+ if cachedMachineID == (ID128{}) {
+ str, err := readfile("/etc/machine-id")
+ if err != nil {
+ return ID128{}, err
+ }
+ id, err := ParsePlain(strings.TrimSuffix(str, "\n"))
+ if err != nil {
+ return ID128{}, err
+ }
+ if id == (ID128{}) {
+ return ID128{}, ErrInvalid
+ }
+ cachedMachineID = id
+ }
+ return cachedMachineID, nil
+}
+
+var cachedBootID ID128
+
+// GetBootID returns a unique identifier for this boot.
+//
+// This is read as a UUID (with trailing newline) from
+// "/proc/sys/kernel/random/boot_id".
+func GetBootID() (ID128, error) {
+ if cachedBootID == (ID128{}) {
+ str, err := readfile("/proc/sys/kernel/random/boot_id")
+ if err != nil {
+ return ID128{}, err
+ }
+ id, err := ParseUUID(strings.TrimSuffix(str, "\n"))
+ if err != nil {
+ return ID128{}, err
+ }
+ cachedBootID = id
+ }
+ return cachedBootID, nil
+}
+
+var cachedInvocationID ID128
+
+// GetInvocationID returns a unique identifier for this invocation of
+// the currently executed service.
+//
+// This is read from the "INVOCATION_ID" environment variable, which
+// is expected to be set by the service manager.
+//
+// The error returned is ErrNone if the service manager did not set
+// INVOCATION_ID, or ErrInvalid if the value of INVOCATION_ID could
+// not be parsed.
+func GetInvocationID() (ID128, error) {
+ if cachedInvocationID == (ID128{}) {
+ // BUG(lukeshu): GetInvocationID should distrust the
+ // environment in certain situations, similarly to GNU
+ // libc secure_getenv.
+ str, have := os.LookupEnv("INVOCATION_ID")
+ if !have {
+ return ID128{}, ErrNone
+ }
+ t, err := Parse(str)
+ if err != nil {
+ return ID128{}, err
+ }
+ cachedInvocationID = t
+ }
+ return cachedInvocationID, nil
+}
diff --git a/sd_id128/util.go b/sd_id128/util.go
new file mode 100644
index 0000000..ae50008
--- /dev/null
+++ b/sd_id128/util.go
@@ -0,0 +1,45 @@
+// Copyright 2016 Luke Shumaker
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package sd_id128
+
+import (
+ "io/ioutil"
+ "os"
+)
+
+func hexchar(x byte) byte {
+ return "0123456789abcdef"[x&15]
+}
+
+func unhexchar(c byte) (byte, error) {
+ if c >= '0' && c <= '9' {
+ return c - '0', nil
+ } else if c >= 'a' && c <= 'f' {
+ return c - 'a' + 10, nil
+ } else if c >= 'A' && c <= 'F' {
+ return c - 'A' + 10, nil
+ }
+ return 0, ErrInvalid
+}
+
+func readfile(filename string) (string, error) {
+ file, err := os.Open(filename)
+ if err != nil {
+ return "", err
+ }
+ defer file.Close()
+ b, err := ioutil.ReadAll(file)
+ return string(b), err
+}