github.com/xmidt-org/webpa-common@v1.11.9/device/id.go (about) 1 package device 2 3 import ( 4 "fmt" 5 "net/http" 6 "regexp" 7 "strings" 8 "unicode" 9 ) 10 11 // ID represents a normalized identifer for a device. 12 type ID string 13 14 // Bytes is a convenience function to obtain the []byte representation of an ID. 15 func (id ID) Bytes() []byte { 16 return []byte(id) 17 } 18 19 const ( 20 hexDigits = "0123456789abcdefABCDEF" 21 macDelimiters = ":-.," 22 macPrefix = "mac" 23 macLength = 12 24 ) 25 26 var ( 27 invalidID = ID("") 28 29 // idPattern is the precompiled regular expression that all device identifiers must match. 30 // Matching is partial, as everything after the service is ignored. 31 idPattern = regexp.MustCompile( 32 `^(?P<prefix>(?i)mac|uuid|dns|serial):(?P<id>[^/]+)(?P<service>/[^/]+)?`, 33 ) 34 ) 35 36 // IntToMAC accepts a 64-bit integer and formats that as a device MAC address identifier 37 // The returned ID will be of the form mac:XXXXXXXXXXXX, where X is a hexadecimal digit using 38 // lowercased letters. 39 func IntToMAC(value uint64) ID { 40 return ID(fmt.Sprintf("mac:%012x", value&0x0000FFFFFFFFFFFF)) 41 } 42 43 // ParseID parses a raw device name into a canonicalized identifier. 44 func ParseID(deviceName string) (ID, error) { 45 match := idPattern.FindStringSubmatch(deviceName) 46 if match == nil { 47 return invalidID, ErrorInvalidDeviceName 48 } 49 50 var ( 51 prefix = strings.ToLower(match[1]) 52 idPart = match[2] 53 ) 54 55 if prefix == macPrefix { 56 var invalidCharacter rune = -1 57 idPart = strings.Map( 58 func(r rune) rune { 59 switch { 60 case strings.ContainsRune(hexDigits, r): 61 return unicode.ToLower(r) 62 case strings.ContainsRune(macDelimiters, r): 63 return -1 64 default: 65 invalidCharacter = r 66 return -1 67 } 68 }, 69 idPart, 70 ) 71 72 if invalidCharacter != -1 || len(idPart) != macLength { 73 return invalidID, ErrorInvalidDeviceName 74 } 75 } 76 77 return ID(fmt.Sprintf("%s:%s", prefix, idPart)), nil 78 } 79 80 // IDHashParser is a parsing function that examines an HTTP request to produce 81 // a []byte key for consistent hashing. The returned function examines the 82 // given request header and invokes ParseID on the value. 83 // 84 // If deviceNameHeader is the empty string, DefaultDeviceNameHeader is used. 85 func IDHashParser(request *http.Request) ([]byte, error) { 86 deviceName := request.Header.Get(DeviceNameHeader) 87 if len(deviceName) == 0 { 88 return nil, ErrorMissingDeviceNameHeader 89 } 90 91 id, err := ParseID(deviceName) 92 if err != nil { 93 return nil, err 94 } 95 96 return id.Bytes(), nil 97 }