New node ID:s contain four Luhn check digits and are grouped differently. Code uses NodeID type instead of string, so it's formatted homogenously everywhere.
137 lines
2.7 KiB
Go
137 lines
2.7 KiB
Go
package protocol
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"encoding/base32"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/calmh/syncthing/luhn"
|
|
)
|
|
|
|
type NodeID [32]byte
|
|
|
|
// NewNodeID generates a new node ID from the raw bytes of a certificate
|
|
func NewNodeID(rawCert []byte) NodeID {
|
|
var n NodeID
|
|
hf := sha256.New()
|
|
hf.Write(rawCert)
|
|
hf.Sum(n[:0])
|
|
return n
|
|
}
|
|
|
|
func NodeIDFromString(s string) (NodeID, error) {
|
|
var n NodeID
|
|
err := n.UnmarshalText([]byte(s))
|
|
return n, err
|
|
}
|
|
|
|
// String returns the canonical string representation of the node ID
|
|
func (n NodeID) String() string {
|
|
id := base32.StdEncoding.EncodeToString(n[:])
|
|
id = strings.Trim(id, "=")
|
|
id = luhnify(id)
|
|
id = chunkify(id)
|
|
return id
|
|
}
|
|
|
|
func (n NodeID) GoString() string {
|
|
return n.String()
|
|
}
|
|
|
|
func (n NodeID) Compare(other NodeID) int {
|
|
return bytes.Compare(n[:], other[:])
|
|
}
|
|
|
|
func (n NodeID) Equals(other NodeID) bool {
|
|
return bytes.Compare(n[:], other[:]) == 0
|
|
}
|
|
|
|
func (n *NodeID) MarshalText() ([]byte, error) {
|
|
return []byte(n.String()), nil
|
|
}
|
|
|
|
func (n *NodeID) UnmarshalText(bs []byte) error {
|
|
id := string(bs)
|
|
id = strings.Trim(id, "=")
|
|
id = strings.ToUpper(id)
|
|
id = untypeoify(id)
|
|
id = unchunkify(id)
|
|
|
|
var err error
|
|
switch len(id) {
|
|
case 56:
|
|
// New style, with check digits
|
|
id, err = unluhnify(id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fallthrough
|
|
case 52:
|
|
// Old style, no check digits
|
|
dec, err := base32.StdEncoding.DecodeString(id + "====")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
copy(n[:], dec)
|
|
return nil
|
|
default:
|
|
return errors.New("node ID invalid: incorrect length")
|
|
}
|
|
}
|
|
|
|
func luhnify(s string) string {
|
|
if len(s) != 52 {
|
|
panic("unsupported string length")
|
|
}
|
|
|
|
res := make([]string, 0, 4)
|
|
for i := 0; i < 4; i++ {
|
|
p := s[i*13 : (i+1)*13]
|
|
l := luhn.Base32Trimmed.Generate(p)
|
|
res = append(res, fmt.Sprintf("%s%c", p, l))
|
|
}
|
|
return res[0] + res[1] + res[2] + res[3]
|
|
}
|
|
|
|
func unluhnify(s string) (string, error) {
|
|
if len(s) != 56 {
|
|
return "", fmt.Errorf("unsupported string length %d", len(s))
|
|
}
|
|
|
|
res := make([]string, 0, 4)
|
|
for i := 0; i < 4; i++ {
|
|
p := s[i*14 : (i+1)*14-1]
|
|
l := luhn.Base32Trimmed.Generate(p)
|
|
if g := fmt.Sprintf("%s%c", p, l); g != s[i*14:(i+1)*14] {
|
|
log.Printf("%q; %q", g, s[i*14:(i+1)*14])
|
|
return "", errors.New("check digit incorrect")
|
|
}
|
|
res = append(res, p)
|
|
}
|
|
return res[0] + res[1] + res[2] + res[3], nil
|
|
}
|
|
|
|
func chunkify(s string) string {
|
|
s = regexp.MustCompile("(.{7})").ReplaceAllString(s, "$1-")
|
|
s = strings.Trim(s, "-")
|
|
return s
|
|
}
|
|
|
|
func unchunkify(s string) string {
|
|
s = strings.Replace(s, "-", "", -1)
|
|
s = strings.Replace(s, " ", "", -1)
|
|
return s
|
|
}
|
|
|
|
func untypeoify(s string) string {
|
|
s = strings.Replace(s, "0", "O", -1)
|
|
s = strings.Replace(s, "1", "I", -1)
|
|
s = strings.Replace(s, "8", "B", -1)
|
|
return s
|
|
}
|