Files
fejkdata/checksum.go
T

97 lines
2.5 KiB
Go

package fejkdata
import "fmt"
// luhnCheck returns the Luhn check digit (0-9) over the digits of s; non-digit
// runes are skipped. Doubling runs from the rightmost digit, so the result is
// correct whatever the payload length.
func luhnCheck(s string) int {
sum, double := 0, true
for i := len(s) - 1; i >= 0; i-- {
c := s[i]
if c < '0' || c > '9' {
continue
}
d := int(c - '0')
if double {
if d *= 2; d > 9 {
d -= 9
}
}
double = !double
sum += d
}
return (10 - sum%10) % 10
}
// mod11Check returns the weighted mod-11 check character over the digits of s
// (weights 2..7 cycling from the right). A would-be value of 10 emits 'X', as in
// ISBN-10 / ISO 7064; non-digits are skipped.
func mod11Check(s string) string {
sum, w := 0, 2
for i := len(s) - 1; i >= 0; i-- {
c := s[i]
if c < '0' || c > '9' {
continue
}
sum += int(c-'0') * w
if w++; w > 7 {
w = 2
}
}
if chk := (11 - sum%11) % 11; chk != 10 {
return string(rune('0' + chk))
}
return "X"
}
// eanCheck returns the EAN-13 / UPC-A / ISBN-13 / GTIN check digit over the
// digits of s: weights 3 and 1 alternating from the rightmost digit, mod 10.
func eanCheck(s string) string {
sum, w := 0, 3
for i := len(s) - 1; i >= 0; i-- {
c := s[i]
if c < '0' || c > '9' {
continue
}
sum += int(c-'0') * w
w = 4 - w // 3 <-> 1
}
return string(rune('0' + (10-sum%10)%10))
}
// ibanLen maps a supported country code to the full IBAN length. The check digits
// sit between the country code and the BBAN, so — unlike luhn/ean — iban can't be
// a left-to-right derivation; it generates the whole value instead.
var ibanLen = map[string]int{"BE": 16, "DE": 22, "DK": 18, "ES": 24, "FI": 18, "NO": 15, "SE": 24}
func ibanArg(_ map[string]node, a []string) error {
if _, ok := ibanLen[a[0]]; !ok {
return fmt.Errorf("iban(%q): unsupported country code", a[0])
}
return nil
}
// iban generates a structurally valid IBAN for cc: a numeric BBAN of the right
// length, then mod-97 check digits. Real bank/branch structure isn't modelled —
// the result passes length and checksum validation, which is what fake data needs.
func iban(r rng, cc string) string {
bban := make([]byte, ibanLen[cc]-4)
for i := range bban {
bban[i] = byte('0' + r.IntN(10))
}
rem := 0
feed := func(d int) { rem = (rem*10 + d) % 97 }
for _, c := range bban {
feed(int(c - '0'))
}
for i := 0; i < len(cc); i++ { // letters A-Z -> 10..35, fed as two digits
v := int(cc[i]-'A') + 10
feed(v / 10)
feed(v % 10)
}
feed(0)
feed(0)
return fmt.Sprintf("%s%02d%s", cc, 98-rem, bban)
}