nodeid.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. package protocol
  2. import (
  3. "bytes"
  4. "crypto/sha256"
  5. "encoding/base32"
  6. "errors"
  7. "fmt"
  8. "regexp"
  9. "strings"
  10. "github.com/calmh/syncthing/luhn"
  11. )
  12. type NodeID [32]byte
  13. var LocalNodeID = NodeID{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}
  14. // NewNodeID generates a new node ID from the raw bytes of a certificate
  15. func NewNodeID(rawCert []byte) NodeID {
  16. var n NodeID
  17. hf := sha256.New()
  18. hf.Write(rawCert)
  19. hf.Sum(n[:0])
  20. return n
  21. }
  22. func NodeIDFromString(s string) (NodeID, error) {
  23. var n NodeID
  24. err := n.UnmarshalText([]byte(s))
  25. return n, err
  26. }
  27. func NodeIDFromBytes(bs []byte) NodeID {
  28. var n NodeID
  29. if len(bs) != len(n) {
  30. panic("incorrect length of byte slice representing node ID")
  31. }
  32. copy(n[:], bs)
  33. return n
  34. }
  35. // String returns the canonical string representation of the node ID
  36. func (n NodeID) String() string {
  37. id := base32.StdEncoding.EncodeToString(n[:])
  38. id = strings.Trim(id, "=")
  39. id, err := luhnify(id)
  40. if err != nil {
  41. // Should never happen
  42. panic(err)
  43. }
  44. id = chunkify(id)
  45. return id
  46. }
  47. func (n NodeID) GoString() string {
  48. return n.String()
  49. }
  50. func (n NodeID) Compare(other NodeID) int {
  51. return bytes.Compare(n[:], other[:])
  52. }
  53. func (n NodeID) Equals(other NodeID) bool {
  54. return bytes.Compare(n[:], other[:]) == 0
  55. }
  56. func (n *NodeID) MarshalText() ([]byte, error) {
  57. return []byte(n.String()), nil
  58. }
  59. func (n *NodeID) UnmarshalText(bs []byte) error {
  60. id := string(bs)
  61. id = strings.Trim(id, "=")
  62. id = strings.ToUpper(id)
  63. id = untypeoify(id)
  64. id = unchunkify(id)
  65. var err error
  66. switch len(id) {
  67. case 56:
  68. // New style, with check digits
  69. id, err = unluhnify(id)
  70. if err != nil {
  71. return err
  72. }
  73. fallthrough
  74. case 52:
  75. // Old style, no check digits
  76. dec, err := base32.StdEncoding.DecodeString(id + "====")
  77. if err != nil {
  78. return err
  79. }
  80. copy(n[:], dec)
  81. return nil
  82. default:
  83. return errors.New("node ID invalid: incorrect length")
  84. }
  85. }
  86. func luhnify(s string) (string, error) {
  87. if len(s) != 52 {
  88. panic("unsupported string length")
  89. }
  90. res := make([]string, 0, 4)
  91. for i := 0; i < 4; i++ {
  92. p := s[i*13 : (i+1)*13]
  93. l, err := luhn.Base32.Generate(p)
  94. if err != nil {
  95. return "", err
  96. }
  97. res = append(res, fmt.Sprintf("%s%c", p, l))
  98. }
  99. return res[0] + res[1] + res[2] + res[3], nil
  100. }
  101. func unluhnify(s string) (string, error) {
  102. if len(s) != 56 {
  103. return "", fmt.Errorf("unsupported string length %d", len(s))
  104. }
  105. res := make([]string, 0, 4)
  106. for i := 0; i < 4; i++ {
  107. p := s[i*14 : (i+1)*14-1]
  108. l, err := luhn.Base32.Generate(p)
  109. if err != nil {
  110. return "", err
  111. }
  112. if g := fmt.Sprintf("%s%c", p, l); g != s[i*14:(i+1)*14] {
  113. return "", errors.New("check digit incorrect")
  114. }
  115. res = append(res, p)
  116. }
  117. return res[0] + res[1] + res[2] + res[3], nil
  118. }
  119. func chunkify(s string) string {
  120. s = regexp.MustCompile("(.{7})").ReplaceAllString(s, "$1-")
  121. s = strings.Trim(s, "-")
  122. return s
  123. }
  124. func unchunkify(s string) string {
  125. s = strings.Replace(s, "-", "", -1)
  126. s = strings.Replace(s, " ", "", -1)
  127. return s
  128. }
  129. func untypeoify(s string) string {
  130. s = strings.Replace(s, "0", "O", -1)
  131. s = strings.Replace(s, "1", "I", -1)
  132. s = strings.Replace(s, "8", "B", -1)
  133. return s
  134. }