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
68 lines
1.5 KiB
Go
68 lines
1.5 KiB
Go
// Copyright (C) 2014 Jakob Borg
|
|
|
|
// Package luhn generates and validates Luhn mod N check digits.
|
|
package luhn
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// An alphabet is a string of N characters, representing the digits of a given
|
|
// base N.
|
|
type Alphabet string
|
|
|
|
var (
|
|
Base32 Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
|
|
)
|
|
|
|
// Generate returns a check digit for the string s, which should be composed
|
|
// of characters from the Alphabet a.
|
|
func (a Alphabet) Generate(s string) (rune, error) {
|
|
factor := 1
|
|
sum := 0
|
|
n := len(a)
|
|
|
|
for i := range s {
|
|
codepoint := strings.IndexByte(string(a), s[i])
|
|
if codepoint == -1 {
|
|
return 0, fmt.Errorf("Digit %q not valid in alphabet %q", s[i], a)
|
|
}
|
|
addend := factor * codepoint
|
|
if factor == 2 {
|
|
factor = 1
|
|
} else {
|
|
factor = 2
|
|
}
|
|
addend = (addend / n) + (addend % n)
|
|
sum += addend
|
|
}
|
|
remainder := sum % n
|
|
checkCodepoint := (n - remainder) % n
|
|
return rune(a[checkCodepoint]), nil
|
|
}
|
|
|
|
// Validate returns true if the last character of the string s is correct, for
|
|
// a string s composed of characters in the alphabet a.
|
|
func (a Alphabet) Validate(s string) bool {
|
|
t := s[:len(s)-1]
|
|
c, err := a.Generate(t)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return rune(s[len(s)-1]) == c
|
|
}
|
|
|
|
// NewAlphabet converts the given string an an Alphabet, verifying that it
|
|
// is correct.
|
|
func NewAlphabet(s string) (Alphabet, error) {
|
|
cm := make(map[byte]bool, len(s))
|
|
for i := range s {
|
|
if cm[s[i]] {
|
|
return "", fmt.Errorf("Digit %q non-unique in alphabet %q", s[i], s)
|
|
}
|
|
cm[s[i]] = true
|
|
}
|
|
return Alphabet(s), nil
|
|
}
|