From f79c2341229def4471bbaf125b8ef19bf49c2b44 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Tue, 27 Dec 2016 09:53:03 -0700 Subject: Implement sd_id128 --- sd_id128/sd_id128.go | 222 +++++++++++++++++++++++++++++++++++++++++++++++++++ sd_id128/util.go | 45 +++++++++++ 2 files changed, 267 insertions(+) create mode 100644 sd_id128/sd_id128.go create mode 100644 sd_id128/util.go 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 +} -- cgit v1.2.3