This commit is contained in:
parent
7098ac75cb
commit
df91762271
29 changed files with 3079 additions and 838 deletions
|
|
@ -359,4 +359,66 @@ std::string ReferenceFromToken(std::string_view token) {
|
|||
return out;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// ISO 7064 mod-97-10 over an alphanumeric string, the same arithmetic that
|
||||
// checks an IBAN: letters become two digits (A=10 … Z=35), everything is read
|
||||
// as one long decimal number, and the remainder mod 97 is taken. Folded
|
||||
// incrementally so no big-integer type is needed — the running value never
|
||||
// exceeds 97*100+35, which fits an int comfortably.
|
||||
//
|
||||
// Returns nullopt on any character that is not [0-9A-Z], because silently
|
||||
// skipping one would make two different references check out identically.
|
||||
std::optional<int> Mod97(std::string_view s) {
|
||||
int rem = 0;
|
||||
for (const char c : s) {
|
||||
if (c >= '0' && c <= '9') {
|
||||
rem = (rem * 10 + (c - '0')) % 97;
|
||||
} else if (c >= 'A' && c <= 'Z') {
|
||||
const int v = c - 'A' + 10;
|
||||
rem = (rem * 100 + v) % 97;
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
return rem;
|
||||
}
|
||||
|
||||
// The body an RF reference carries: the CC- reference with its hyphen dropped,
|
||||
// because ISO 11649 permits only alphanumerics. "CC-2B6457" -> "CC2B6457".
|
||||
std::string ReferenceBody(std::string_view token) {
|
||||
const std::string human = ReferenceFromToken(token);
|
||||
std::string body;
|
||||
body.reserve(human.size());
|
||||
for (const char c : human) {
|
||||
if (c != '-') body += c;
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string CreditorReferenceFromToken(std::string_view token) {
|
||||
const std::string body = ReferenceBody(token);
|
||||
// The check digits are computed over the body followed by "RF00" — the
|
||||
// standard's rearrangement, prefix and placeholder moved to the end.
|
||||
const std::optional<int> rem = Mod97(body + "RF00");
|
||||
if (!rem) return {}; // unreachable for our own token alphabet
|
||||
const int check = 98 - *rem;
|
||||
return std::format("RF{:02}{}", check, body);
|
||||
}
|
||||
|
||||
bool IsValidCreditorReference(std::string_view s) {
|
||||
// "RF" + 2 check digits + 1..21 body characters.
|
||||
if (s.size() < 5 || s.size() > 25) return false;
|
||||
if (s[0] != 'R' || s[1] != 'F') return false;
|
||||
if (s[2] < '0' || s[2] > '9' || s[3] < '0' || s[3] > '9') return false;
|
||||
// Rearranged the same way the generator does it, then the whole thing must
|
||||
// leave a remainder of exactly 1 — that is what mod-97-10 verification is.
|
||||
std::string rearranged(s.substr(4));
|
||||
rearranged += s.substr(0, 4);
|
||||
const std::optional<int> rem = Mod97(rearranged);
|
||||
return rem && *rem == 1;
|
||||
}
|
||||
|
||||
} // namespace Catcrafts::Server
|
||||
|
|
|
|||
Loading…
Reference in a new issue