30constexpr quint64 MASK_40BIT = quint64(0xF8) << 32;
31constexpr quint64 MASK_35BIT = quint64(0x7C0000000);
32constexpr quint64 MASK_25BIT = quint64(0x1F00000);
33constexpr quint64 MASK_20BIT = quint64(0xF8000);
34constexpr quint64 MASK_10BIT = quint64(0x3E0);
38constexpr char kAlphabet[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
40constexpr quint8 ALPH_POS_2 = 26;
43constexpr quint8 ASCII_2 =
static_cast<quint8
>(
'2');
45constexpr quint8 ASCII_7 =
static_cast<quint8
>(
'7');
47constexpr quint8 ASCII_A =
static_cast<quint8
>(
'A');
49constexpr quint8 ASCII_Z =
static_cast<quint8
>(
'Z');
51constexpr quint8 ASCII_a =
static_cast<quint8
>(
'a');
53constexpr quint8 ASCII_z =
static_cast<quint8
>(
'z');
55constexpr quint8 ASCII_EQ =
static_cast<quint8
>(
'=');
66auto countPadding(
const QByteArray &encodedData) ->
int {
68 for (qsizetype i = encodedData.size() - 1;
69 i >= 0 && i > encodedData.size() - 7; --i) {
70 if (encodedData.at(i) !=
'=') {
92auto tailShape(
int nPads) -> std::optional<Tail> {
111auto symbolValue(quint8 ch) -> std::optional<quint8> {
112 if ((ASCII_A <= ch && ch <= ASCII_Z) || (ASCII_a <= ch && ch <= ASCII_z)) {
114 if (ch >= ALPH_POS_2) {
116 ch -= ASCII_a - ASCII_A;
120 if (ASCII_2 <= ch && ch <= ASCII_7) {
121 return static_cast<quint8
>(ch - ASCII_2 + ALPH_POS_2);
130 if (encodedData.isEmpty() || encodedData.size() % 8 != 0) {
133 const int nPads = countPadding(encodedData);
134 const std::optional<Tail> tail = tailShape(nPads);
139 const qsizetype nQuanta = encodedData.size() / 8;
140 const qsizetype nBytes =
141 tail->bytes > 0 ? (nQuanta - 1) * 5 + tail->bytes : nQuanta * 5;
143 QByteArray data(nBytes, Qt::Uninitialized);
146 char *out = data.data();
152 const qsizetype firstPad = encodedData.size() - nPads;
154 while (i < encodedData.size()) {
156 int nQuantumBytes = 5;
158 for (
int n = 0; n < 8; ++n) {
159 const auto ch =
static_cast<quint8
>(encodedData.at(i++));
160 if (ASCII_EQ == ch) {
161 if (i - 1 < firstPad) {
165 if (i == encodedData.size()) {
167 quantum >>= tail->offset;
168 nQuantumBytes = tail->bytes;
172 const std::optional<quint8> value = symbolValue(ch);
180 const int offset = (nQuantumBytes - 1) * 8;
181 quint64 mask = quint64(0xFF) << offset;
182 for (
int n = offset; n >= 0 && o < nBytes; n -= 8) {
183 out[o++] =
static_cast<char>((quantum & mask) >> n);
192 if (data.isEmpty()) {
196 const qsizetype nBits = data.size() * 8;
197 const int rBits =
static_cast<int>(nBits % 40);
198 const qsizetype nQuanta = nBits / 40 + (rBits > 0 ? 1 : 0);
199 const qsizetype nBytes = nQuanta * 8;
200 QByteArray encodedData(nBytes, Qt::Uninitialized);
202 char *out = encodedData.data();
213 const auto byteAt = [&data](qsizetype index) -> quint64 {
214 return static_cast<quint64
>(
static_cast<quint8
>(data.at(index)));
218 while (i + 5 <= data.size()) {
220 for (n = 32; n >= 0; n -= 8) {
221 quantum |= byteAt(i++) << n;
225 for (n = 35; n >= 0; n -= 5) {
226 out[o++] = kAlphabet[(quantum & mask) >> n];
232 if (i < data.size()) {
234 for (n = rBits - 8; n >= 0; n -= 8) {
235 quantum |= byteAt(i++) << n;
261 out[o++] = kAlphabet[(quantum & mask) >> n];
267 while (o < encodedData.size()) {
276 if (encodedData.isEmpty() || encodedData.size() % 8 == 0) {
280 const int rBytes =
static_cast<int>(encodedData.size() % 8);
282 if (1 == rBytes || 3 == rBytes || 6 == rBytes) {
286 QByteArray newEncodedData(encodedData);
287 for (
int nPads = 8 - rBytes; nPads > 0; --nPads) {
288 newEncodedData.append(
'=');
291 return newEncodedData;
295 if (encodedData.isEmpty() || encodedData.size() % 8 != 0) {
299 QByteArray newEncodedData(encodedData);
300 newEncodedData.resize(encodedData.size() - countPadding(encodedData));
302 return newEncodedData;
306 if (encodedData.isEmpty()) {
310 QByteArray newEncodedData(encodedData.size(), Qt::Uninitialized);
312 char *out = newEncodedData.data();
314 for (
auto ch : encodedData) {
326 if ((
'A' <= ch && ch <=
'Z') || (
'a' <= ch && ch <=
'z') ||
327 (
'2' <= ch && ch <=
'7')) {
332 newEncodedData.resize(i);
Strict RFC 4648 base32 codec plus the input sanitizer that makes human-typed TOTP secrets decodable.
static auto sanitizeInput(const QByteArray &encodedData) -> QByteArray
Make human-entered base32 text decodable.
static auto addPadding(const QByteArray &encodedData) -> QByteArray
Append '=' padding up to a multiple of 8 characters.
static auto decode(const QByteArray &encodedData) -> QByteArray
Decode strict RFC 4648 base32 data.
static auto encode(const QByteArray &data) -> QByteArray
Encode raw bytes as padded RFC 4648 base32.
static auto removePadding(const QByteArray &encodedData) -> QByteArray
Strip trailing '=' padding.