deviceid.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. // Copyright (C) 2014 The Protocol Authors.
  2. package protocol
  3. import (
  4. "bytes"
  5. "encoding/base32"
  6. "encoding/binary"
  7. "errors"
  8. "fmt"
  9. "regexp"
  10. "strings"
  11. "github.com/syncthing/syncthing/lib/sha256"
  12. "github.com/calmh/luhn"
  13. )
  14. const DeviceIDLength = 32
  15. type DeviceID [DeviceIDLength]byte
  16. type ShortID uint64
  17. var (
  18. LocalDeviceID = DeviceID{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
  19. EmptyDeviceID = DeviceID{ /* all zeroes */ }
  20. )
  21. // NewDeviceID generates a new device ID from the raw bytes of a certificate
  22. func NewDeviceID(rawCert []byte) DeviceID {
  23. var n DeviceID
  24. hf := sha256.New()
  25. hf.Write(rawCert)
  26. hf.Sum(n[:0])
  27. return n
  28. }
  29. func DeviceIDFromString(s string) (DeviceID, error) {
  30. var n DeviceID
  31. err := n.UnmarshalText([]byte(s))
  32. return n, err
  33. }
  34. func DeviceIDFromBytes(bs []byte) DeviceID {
  35. var n DeviceID
  36. if len(bs) != len(n) {
  37. panic("incorrect length of byte slice representing device ID")
  38. }
  39. copy(n[:], bs)
  40. return n
  41. }
  42. // String returns the canonical string representation of the device ID
  43. func (n DeviceID) String() string {
  44. if n == EmptyDeviceID {
  45. return ""
  46. }
  47. id := base32.StdEncoding.EncodeToString(n[:])
  48. id = strings.Trim(id, "=")
  49. id, err := luhnify(id)
  50. if err != nil {
  51. // Should never happen
  52. panic(err)
  53. }
  54. id = chunkify(id)
  55. return id
  56. }
  57. func (n DeviceID) GoString() string {
  58. return n.String()
  59. }
  60. func (n DeviceID) Compare(other DeviceID) int {
  61. return bytes.Compare(n[:], other[:])
  62. }
  63. func (n DeviceID) Equals(other DeviceID) bool {
  64. return bytes.Equal(n[:], other[:])
  65. }
  66. // Short returns an integer representing bits 0-63 of the device ID.
  67. func (n DeviceID) Short() ShortID {
  68. return ShortID(binary.BigEndian.Uint64(n[:]))
  69. }
  70. func (n *DeviceID) MarshalText() ([]byte, error) {
  71. return []byte(n.String()), nil
  72. }
  73. func (s ShortID) String() string {
  74. if s == 0 {
  75. return ""
  76. }
  77. var bs [8]byte
  78. binary.BigEndian.PutUint64(bs[:], uint64(s))
  79. return base32.StdEncoding.EncodeToString(bs[:])[:7]
  80. }
  81. func (n *DeviceID) UnmarshalText(bs []byte) error {
  82. id := string(bs)
  83. id = strings.Trim(id, "=")
  84. id = strings.ToUpper(id)
  85. id = untypeoify(id)
  86. id = unchunkify(id)
  87. var err error
  88. switch len(id) {
  89. case 0:
  90. *n = EmptyDeviceID
  91. return nil
  92. case 56:
  93. // New style, with check digits
  94. id, err = unluhnify(id)
  95. if err != nil {
  96. return err
  97. }
  98. fallthrough
  99. case 52:
  100. // Old style, no check digits
  101. dec, err := base32.StdEncoding.DecodeString(id + "====")
  102. if err != nil {
  103. return err
  104. }
  105. copy(n[:], dec)
  106. return nil
  107. default:
  108. return errors.New("device ID invalid: incorrect length")
  109. }
  110. }
  111. func (n *DeviceID) ProtoSize() int {
  112. // Used by protobuf marshaller.
  113. return DeviceIDLength
  114. }
  115. func (n *DeviceID) MarshalTo(bs []byte) (int, error) {
  116. // Used by protobuf marshaller.
  117. if len(bs) < DeviceIDLength {
  118. return 0, errors.New("destination too short")
  119. }
  120. copy(bs, (*n)[:])
  121. return DeviceIDLength, nil
  122. }
  123. func (n *DeviceID) Unmarshal(bs []byte) error {
  124. // Used by protobuf marshaller.
  125. if len(bs) < DeviceIDLength {
  126. return errors.New("not enough data")
  127. }
  128. copy((*n)[:], bs)
  129. return nil
  130. }
  131. func luhnify(s string) (string, error) {
  132. if len(s) != 52 {
  133. panic("unsupported string length")
  134. }
  135. res := make([]string, 0, 4)
  136. for i := 0; i < 4; i++ {
  137. p := s[i*13 : (i+1)*13]
  138. l, err := luhn.Base32.Generate(p)
  139. if err != nil {
  140. return "", err
  141. }
  142. res = append(res, fmt.Sprintf("%s%c", p, l))
  143. }
  144. return res[0] + res[1] + res[2] + res[3], nil
  145. }
  146. func unluhnify(s string) (string, error) {
  147. if len(s) != 56 {
  148. return "", fmt.Errorf("unsupported string length %d", len(s))
  149. }
  150. res := make([]string, 0, 4)
  151. for i := 0; i < 4; i++ {
  152. p := s[i*14 : (i+1)*14-1]
  153. l, err := luhn.Base32.Generate(p)
  154. if err != nil {
  155. return "", err
  156. }
  157. if g := fmt.Sprintf("%s%c", p, l); g != s[i*14:(i+1)*14] {
  158. return "", errors.New("check digit incorrect")
  159. }
  160. res = append(res, p)
  161. }
  162. return res[0] + res[1] + res[2] + res[3], nil
  163. }
  164. func chunkify(s string) string {
  165. s = regexp.MustCompile("(.{7})").ReplaceAllString(s, "$1-")
  166. s = strings.Trim(s, "-")
  167. return s
  168. }
  169. func unchunkify(s string) string {
  170. s = strings.Replace(s, "-", "", -1)
  171. s = strings.Replace(s, " ", "", -1)
  172. return s
  173. }
  174. func untypeoify(s string) string {
  175. s = strings.Replace(s, "0", "O", -1)
  176. s = strings.Replace(s, "1", "I", -1)
  177. s = strings.Replace(s, "8", "B", -1)
  178. return s
  179. }
  180. // DeviceIDs is a sortable slice of DeviceID
  181. type DeviceIDs []DeviceID
  182. func (l DeviceIDs) Len() int {
  183. return len(l)
  184. }
  185. func (l DeviceIDs) Less(a, b int) bool {
  186. return l[a].Compare(l[b]) == -1
  187. }
  188. func (l DeviceIDs) Swap(a, b int) {
  189. l[a], l[b] = l[b], l[a]
  190. }