mirror of
https://github.com/syncthing/syncthing.git
synced 2024-11-16 18:41:59 -07:00
9682bbfbda
These functions were very naive and slow. We haven't done much about them because they pretty much don't matter at all for Syncthing performance. They are however called very often in the discovery server and these optimizations have a huge effect on the CPU load on the public discovery servers. The code isn't exactly obvious, but we have good test coverage on all these functions. benchmark old ns/op new ns/op delta BenchmarkLuhnify-8 12458 1045 -91.61% BenchmarkUnluhnify-8 12598 1074 -91.47% BenchmarkChunkify-8 10792 104 -99.04% benchmark old allocs new allocs delta BenchmarkLuhnify-8 18 1 -94.44% BenchmarkUnluhnify-8 18 1 -94.44% BenchmarkChunkify-8 44 2 -95.45% benchmark old bytes new bytes delta BenchmarkLuhnify-8 1278 64 -94.99% BenchmarkUnluhnify-8 1278 64 -94.99% BenchmarkChunkify-8 42552 128 -99.70% GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4346
229 lines
4.7 KiB
Go
229 lines
4.7 KiB
Go
// Copyright (C) 2014 The Protocol Authors.
|
|
|
|
package protocol
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base32"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/calmh/luhn"
|
|
"github.com/syncthing/syncthing/lib/sha256"
|
|
)
|
|
|
|
const DeviceIDLength = 32
|
|
|
|
type DeviceID [DeviceIDLength]byte
|
|
type ShortID uint64
|
|
|
|
var (
|
|
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}
|
|
EmptyDeviceID = DeviceID{ /* all zeroes */ }
|
|
)
|
|
|
|
// NewDeviceID generates a new device ID from the raw bytes of a certificate
|
|
func NewDeviceID(rawCert []byte) DeviceID {
|
|
var n DeviceID
|
|
hf := sha256.New()
|
|
hf.Write(rawCert)
|
|
hf.Sum(n[:0])
|
|
return n
|
|
}
|
|
|
|
func DeviceIDFromString(s string) (DeviceID, error) {
|
|
var n DeviceID
|
|
err := n.UnmarshalText([]byte(s))
|
|
return n, err
|
|
}
|
|
|
|
func DeviceIDFromBytes(bs []byte) DeviceID {
|
|
var n DeviceID
|
|
if len(bs) != len(n) {
|
|
panic("incorrect length of byte slice representing device ID")
|
|
}
|
|
copy(n[:], bs)
|
|
return n
|
|
}
|
|
|
|
// String returns the canonical string representation of the device ID
|
|
func (n DeviceID) String() string {
|
|
if n == EmptyDeviceID {
|
|
return ""
|
|
}
|
|
id := base32.StdEncoding.EncodeToString(n[:])
|
|
id = strings.Trim(id, "=")
|
|
id, err := luhnify(id)
|
|
if err != nil {
|
|
// Should never happen
|
|
panic(err)
|
|
}
|
|
id = chunkify(id)
|
|
return id
|
|
}
|
|
|
|
func (n DeviceID) GoString() string {
|
|
return n.String()
|
|
}
|
|
|
|
func (n DeviceID) Compare(other DeviceID) int {
|
|
return bytes.Compare(n[:], other[:])
|
|
}
|
|
|
|
func (n DeviceID) Equals(other DeviceID) bool {
|
|
return bytes.Equal(n[:], other[:])
|
|
}
|
|
|
|
// Short returns an integer representing bits 0-63 of the device ID.
|
|
func (n DeviceID) Short() ShortID {
|
|
return ShortID(binary.BigEndian.Uint64(n[:]))
|
|
}
|
|
|
|
func (n *DeviceID) MarshalText() ([]byte, error) {
|
|
return []byte(n.String()), nil
|
|
}
|
|
|
|
func (s ShortID) String() string {
|
|
if s == 0 {
|
|
return ""
|
|
}
|
|
var bs [8]byte
|
|
binary.BigEndian.PutUint64(bs[:], uint64(s))
|
|
return base32.StdEncoding.EncodeToString(bs[:])[:7]
|
|
}
|
|
|
|
func (n *DeviceID) 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 0:
|
|
*n = EmptyDeviceID
|
|
return nil
|
|
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 fmt.Errorf("%q: device ID invalid: incorrect length", bs)
|
|
}
|
|
}
|
|
|
|
func (n *DeviceID) ProtoSize() int {
|
|
// Used by protobuf marshaller.
|
|
return DeviceIDLength
|
|
}
|
|
|
|
func (n *DeviceID) MarshalTo(bs []byte) (int, error) {
|
|
// Used by protobuf marshaller.
|
|
if len(bs) < DeviceIDLength {
|
|
return 0, errors.New("destination too short")
|
|
}
|
|
copy(bs, (*n)[:])
|
|
return DeviceIDLength, nil
|
|
}
|
|
|
|
func (n *DeviceID) Unmarshal(bs []byte) error {
|
|
// Used by protobuf marshaller.
|
|
if len(bs) < DeviceIDLength {
|
|
return fmt.Errorf("%q: not enough data", bs)
|
|
}
|
|
copy((*n)[:], bs)
|
|
return nil
|
|
}
|
|
|
|
func luhnify(s string) (string, error) {
|
|
if len(s) != 52 {
|
|
panic("unsupported string length")
|
|
}
|
|
|
|
res := make([]byte, 4*(13+1))
|
|
for i := 0; i < 4; i++ {
|
|
p := s[i*13 : (i+1)*13]
|
|
copy(res[i*(13+1):], p)
|
|
l, err := luhn.Base32.Generate(p)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
res[(i+1)*(13)+i] = byte(l)
|
|
}
|
|
return string(res), nil
|
|
}
|
|
|
|
func unluhnify(s string) (string, error) {
|
|
if len(s) != 56 {
|
|
return "", fmt.Errorf("%q: unsupported string length %d", s, len(s))
|
|
}
|
|
|
|
res := make([]byte, 52)
|
|
for i := 0; i < 4; i++ {
|
|
p := s[i*(13+1) : (i+1)*(13+1)-1]
|
|
copy(res[i*13:], p)
|
|
l, err := luhn.Base32.Generate(p)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if s[(i+1)*14-1] != byte(l) {
|
|
return "", fmt.Errorf("%q: check digit incorrect", s)
|
|
}
|
|
}
|
|
return string(res), nil
|
|
}
|
|
|
|
func chunkify(s string) string {
|
|
chunks := len(s) / 7
|
|
res := make([]byte, chunks*(7+1)-1)
|
|
for i := 0; i < chunks; i++ {
|
|
if i > 0 {
|
|
res[i*(7+1)-1] = '-'
|
|
}
|
|
copy(res[i*(7+1):], s[i*7:(i+1)*7])
|
|
}
|
|
return string(res)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// DeviceIDs is a sortable slice of DeviceID
|
|
type DeviceIDs []DeviceID
|
|
|
|
func (l DeviceIDs) Len() int {
|
|
return len(l)
|
|
}
|
|
|
|
func (l DeviceIDs) Less(a, b int) bool {
|
|
return l[a].Compare(l[b]) == -1
|
|
}
|
|
|
|
func (l DeviceIDs) Swap(a, b int) {
|
|
l[a], l[b] = l[b], l[a]
|
|
}
|