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  }