// 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 }