codec.go raw

   1  // Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru>
   2  //
   3  // Permission is hereby granted, free of charge, to any person obtaining
   4  // a copy of this software and associated documentation files (the
   5  // "Software"), to deal in the Software without restriction, including
   6  // without limitation the rights to use, copy, modify, merge, publish,
   7  // distribute, sublicense, and/or sell copies of the Software, and to
   8  // permit persons to whom the Software is furnished to do so, subject to
   9  // the following conditions:
  10  //
  11  // The above copyright notice and this permission notice shall be
  12  // included in all copies or substantial portions of the Software.
  13  //
  14  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  15  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  16  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  17  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  18  // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  19  // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  20  // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  21  
  22  package uuid
  23  
  24  import (
  25  	"errors"
  26  	"fmt"
  27  )
  28  
  29  // FromBytes returns a UUID generated from the raw byte slice input.
  30  // It will return an error if the slice isn't 16 bytes long.
  31  func FromBytes(input []byte) (UUID, error) {
  32  	u := UUID{}
  33  	err := u.UnmarshalBinary(input)
  34  	return u, err
  35  }
  36  
  37  // FromBytesOrNil returns a UUID generated from the raw byte slice input.
  38  // Same behavior as FromBytes(), but returns uuid.Nil instead of an error.
  39  func FromBytesOrNil(input []byte) UUID {
  40  	uuid, err := FromBytes(input)
  41  	if err != nil {
  42  		return Nil
  43  	}
  44  	return uuid
  45  }
  46  
  47  var errInvalidFormat = errors.New("uuid: invalid UUID format")
  48  
  49  func fromHexChar(c byte) byte {
  50  	switch {
  51  	case '0' <= c && c <= '9':
  52  		return c - '0'
  53  	case 'a' <= c && c <= 'f':
  54  		return c - 'a' + 10
  55  	case 'A' <= c && c <= 'F':
  56  		return c - 'A' + 10
  57  	}
  58  	return 255
  59  }
  60  
  61  // Parse parses the UUID stored in the string text. Parsing and supported
  62  // formats are the same as UnmarshalText.
  63  func (u *UUID) Parse(s string) error {
  64  	switch len(s) {
  65  	case 32: // hash
  66  	case 36: // canonical
  67  	case 34, 38:
  68  		if s[0] != '{' || s[len(s)-1] != '}' {
  69  			return fmt.Errorf("uuid: incorrect UUID format in string %q", s)
  70  		}
  71  		s = s[1 : len(s)-1]
  72  	case 41, 45:
  73  		if s[:9] != "urn:uuid:" {
  74  			return fmt.Errorf("uuid: incorrect UUID format in string %q", s[:9])
  75  		}
  76  		s = s[9:]
  77  	default:
  78  		return fmt.Errorf("uuid: incorrect UUID length %d in string %q", len(s), s)
  79  	}
  80  	// canonical
  81  	if len(s) == 36 {
  82  		if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
  83  			return fmt.Errorf("uuid: incorrect UUID format in string %q", s)
  84  		}
  85  		for i, x := range [16]byte{
  86  			0, 2, 4, 6,
  87  			9, 11,
  88  			14, 16,
  89  			19, 21,
  90  			24, 26, 28, 30, 32, 34,
  91  		} {
  92  			v1 := fromHexChar(s[x])
  93  			v2 := fromHexChar(s[x+1])
  94  			if v1|v2 == 255 {
  95  				return errInvalidFormat
  96  			}
  97  			u[i] = (v1 << 4) | v2
  98  		}
  99  		return nil
 100  	}
 101  	// hash like
 102  	for i := 0; i < 32; i += 2 {
 103  		v1 := fromHexChar(s[i])
 104  		v2 := fromHexChar(s[i+1])
 105  		if v1|v2 == 255 {
 106  			return errInvalidFormat
 107  		}
 108  		u[i/2] = (v1 << 4) | v2
 109  	}
 110  	return nil
 111  }
 112  
 113  // FromString returns a UUID parsed from the input string.
 114  // Input is expected in a form accepted by UnmarshalText.
 115  func FromString(text string) (UUID, error) {
 116  	var u UUID
 117  	err := u.Parse(text)
 118  	return u, err
 119  }
 120  
 121  // FromStringOrNil returns a UUID parsed from the input string.
 122  // Same behavior as FromString(), but returns uuid.Nil instead of an error.
 123  func FromStringOrNil(input string) UUID {
 124  	uuid, err := FromString(input)
 125  	if err != nil {
 126  		return Nil
 127  	}
 128  	return uuid
 129  }
 130  
 131  // MarshalText implements the encoding.TextMarshaler interface.
 132  // The encoding is the same as returned by the String() method.
 133  func (u UUID) MarshalText() ([]byte, error) {
 134  	var buf [36]byte
 135  	encodeCanonical(buf[:], u)
 136  	return buf[:], nil
 137  }
 138  
 139  // UnmarshalText implements the encoding.TextUnmarshaler interface.
 140  // Following formats are supported:
 141  //
 142  //	"6ba7b810-9dad-11d1-80b4-00c04fd430c8",
 143  //	"{6ba7b810-9dad-11d1-80b4-00c04fd430c8}",
 144  //	"urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8"
 145  //	"6ba7b8109dad11d180b400c04fd430c8"
 146  //	"{6ba7b8109dad11d180b400c04fd430c8}",
 147  //	"urn:uuid:6ba7b8109dad11d180b400c04fd430c8"
 148  //
 149  // ABNF for supported UUID text representation follows:
 150  //
 151  //	URN := 'urn'
 152  //	UUID-NID := 'uuid'
 153  //
 154  //	hexdig := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' |
 155  //	          'a' | 'b' | 'c' | 'd' | 'e' | 'f' |
 156  //	          'A' | 'B' | 'C' | 'D' | 'E' | 'F'
 157  //
 158  //	hexoct := hexdig hexdig
 159  //	2hexoct := hexoct hexoct
 160  //	4hexoct := 2hexoct 2hexoct
 161  //	6hexoct := 4hexoct 2hexoct
 162  //	12hexoct := 6hexoct 6hexoct
 163  //
 164  //	hashlike := 12hexoct
 165  //	canonical := 4hexoct '-' 2hexoct '-' 2hexoct '-' 6hexoct
 166  //
 167  //	plain := canonical | hashlike
 168  //	uuid := canonical | hashlike | braced | urn
 169  //
 170  //	braced := '{' plain '}' | '{' hashlike  '}'
 171  //	urn := URN ':' UUID-NID ':' plain
 172  func (u *UUID) UnmarshalText(b []byte) error {
 173  	switch len(b) {
 174  	case 32: // hash
 175  	case 36: // canonical
 176  	case 34, 38:
 177  		if b[0] != '{' || b[len(b)-1] != '}' {
 178  			return fmt.Errorf("uuid: incorrect UUID format in string %q", b)
 179  		}
 180  		b = b[1 : len(b)-1]
 181  	case 41, 45:
 182  		if string(b[:9]) != "urn:uuid:" {
 183  			return fmt.Errorf("uuid: incorrect UUID format in string %q", b[:9])
 184  		}
 185  		b = b[9:]
 186  	default:
 187  		return fmt.Errorf("uuid: incorrect UUID length %d in string %q", len(b), b)
 188  	}
 189  	if len(b) == 36 {
 190  		if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' {
 191  			return fmt.Errorf("uuid: incorrect UUID format in string %q", b)
 192  		}
 193  		for i, x := range [16]byte{
 194  			0, 2, 4, 6,
 195  			9, 11,
 196  			14, 16,
 197  			19, 21,
 198  			24, 26, 28, 30, 32, 34,
 199  		} {
 200  			v1 := fromHexChar(b[x])
 201  			v2 := fromHexChar(b[x+1])
 202  			if v1|v2 == 255 {
 203  				return errInvalidFormat
 204  			}
 205  			u[i] = (v1 << 4) | v2
 206  		}
 207  		return nil
 208  	}
 209  	for i := 0; i < 32; i += 2 {
 210  		v1 := fromHexChar(b[i])
 211  		v2 := fromHexChar(b[i+1])
 212  		if v1|v2 == 255 {
 213  			return errInvalidFormat
 214  		}
 215  		u[i/2] = (v1 << 4) | v2
 216  	}
 217  	return nil
 218  }
 219  
 220  // MarshalBinary implements the encoding.BinaryMarshaler interface.
 221  func (u UUID) MarshalBinary() ([]byte, error) {
 222  	return u.Bytes(), nil
 223  }
 224  
 225  // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
 226  // It will return an error if the slice isn't 16 bytes long.
 227  func (u *UUID) UnmarshalBinary(data []byte) error {
 228  	if len(data) != Size {
 229  		return fmt.Errorf("uuid: UUID must be exactly 16 bytes long, got %d bytes", len(data))
 230  	}
 231  	copy(u[:], data)
 232  
 233  	return nil
 234  }
 235