File indexing completed on 2024-04-28 05:50:08
0001 /* 0002 * SPDX-License-Identifier: GPL-3.0-or-later 0003 * SPDX-FileCopyrightText: 2019-2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com> 0004 */ 0005 #include "base32.h" 0006 #include "../logging_p.h" 0007 0008 KEYSMITH_LOGGER(logger, ".base32") 0009 0010 static const QChar alphaMinLowerCase(QLatin1Char('a')); 0011 static const QChar alphaMaxLowerCase(QLatin1Char('z')); 0012 static const QChar alphaMinUpperCase(QLatin1Char('A')); 0013 static const QChar alphaMaxUpperCase(QLatin1Char('Z')); 0014 static const QChar numMin(QLatin1Char('2')); 0015 static const QChar numMax(QLatin1Char('7')); 0016 static const QChar pad(QLatin1Char('=')); 0017 0018 static inline bool checkInputRange(const QString &encoded, int from, int until) 0019 { 0020 /* 0021 * from should be between 0 (inclusive) and size (exclusive) 0022 * until should be between from (inclusive) and size (inclusive) 0023 * total range size (until - from) should be a multiple of 8 or it is not valid base32 0024 */ 0025 int size = encoded.size(); 0026 return from >= 0 && from <= size && until >= from && until <= size && ((until - from) % 8) == 0; 0027 } 0028 0029 static std::optional<int> decode(const QChar &chr) 0030 { 0031 if (chr >= alphaMinLowerCase && chr <= alphaMaxLowerCase) { 0032 return std::optional<int>(chr.toLatin1() - alphaMinLowerCase.toLatin1()); 0033 } 0034 if (chr >= alphaMinUpperCase && chr <= alphaMaxUpperCase) { 0035 return std::optional<int>(chr.toLatin1() - alphaMinUpperCase.toLatin1()); 0036 } 0037 if (chr >= numMin && chr <= numMax) { 0038 return std::optional<int>(26 + chr.toLatin1() - numMin.toLatin1()); 0039 } 0040 0041 if (chr >= pad) { 0042 return std::optional<int>(0); 0043 } 0044 0045 return std::nullopt; 0046 } 0047 0048 static std::optional<quint64> decode(const QString &encoded, int index) 0049 { 0050 quint64 result = 0ULL; 0051 0052 for (int i = 0; i < 8; ++i) { 0053 const QChar inputChar = encoded[index + i]; 0054 std::optional<int> v = decode(inputChar); 0055 if (v) { 0056 result = (result << 5) | *v; 0057 } else { 0058 qCDebug(logger) << "Not a valid base32 character:" << inputChar; 0059 return std::nullopt; 0060 } 0061 } 0062 0063 return std::optional<quint64>(result); 0064 } 0065 0066 static std::optional<size_t> decode(const QString &encoded, int index, int end, int padding, size_t offset, size_t capacity, char * const output) 0067 { 0068 Q_ASSERT_X(offset <= capacity, Q_FUNC_INFO, "invalid offset into output buffer"); 0069 Q_ASSERT_X(end >= 0 && end <= encoded.size(), Q_FUNC_INFO, "end of encoded data should be valid"); 0070 Q_ASSERT_X(padding >= 0 && padding <= end, Q_FUNC_INFO, "padding index should be valid"); 0071 Q_ASSERT_X(index >= 0 && index <= padding && ((end - index) % 8) == 0, Q_FUNC_INFO, "index should be valid"); 0072 0073 size_t group; 0074 0075 switch ((index + 8) - padding) 0076 { 0077 case 2: 0078 case 5: 0079 case 7: 0080 Q_ASSERT_X(false, Q_FUNC_INFO, "invalid amount of padding should have been caught by previous validation"); 0081 return std::nullopt; 0082 case 1: 0083 group = 4; 0084 break; 0085 case 3: 0086 group = 3; 0087 break; 0088 case 4: 0089 group = 2; 0090 break; 0091 case 6: 0092 group = 1; 0093 break; 0094 default: // no padding (yet) for the group at the given index: there are 8 or more bytes left 0095 group = 5; 0096 break; 0097 } 0098 0099 Q_ASSERT_X((capacity - offset) >= group, Q_FUNC_INFO, "offset/output group too big for output buffer size"); 0100 0101 std::optional<quint64> bits = decode(encoded, index); 0102 0103 Q_ASSERT_X(bits, Q_FUNC_INFO, "invalid input should have been caught by prior validation"); 0104 0105 quint64 value = *bits; 0106 for (size_t i = 0; i < group; ++i) { 0107 output[offset + i] = (char) ((value >> (32ULL - i * 8ULL)) & 0xFFULL); 0108 } 0109 0110 return std::optional<size_t>(group); 0111 } 0112 0113 static inline bool isBase32(const QChar &c) 0114 { 0115 return (c >= alphaMinLowerCase && c <= alphaMaxLowerCase) || (c >= alphaMinUpperCase && c <= alphaMaxUpperCase) || (c >= numMin && c <= numMax); 0116 } 0117 0118 static bool isPaddingValid(const QString &encoded, int paddingIndex, int amount) 0119 { 0120 static const int padMasks[7] = { 0121 0x7, // 8 - 1 padding -> 7 * 5 - 32 bits -> 3 trailing bits: mask 0x7 0122 0x0, // 8 - 2 padding -> invalid 0123 0x1, // 8 - 3 padding -> 5 * 5 - 24 bits -> 1 trailing bit : mask 0x1 0124 0xF, // 8 - 4 padding -> 4 * 5 - 16 bits -> 4 trailing bits: mask 0xF 0125 0x0, // 8 - 5 padding -> invalid 0126 0x3, // 8 - 6 padding -> 2 * 5 - 8 bits -> 2 trailing bits: mask 0x3 0127 0x0 // 8 - 7 padding -> invalid 0128 }; 0129 0130 if (amount == 0) { 0131 return true; 0132 } 0133 0134 if (amount >= 8) { 0135 return false; 0136 } 0137 0138 Q_ASSERT_X(paddingIndex >= 0, Q_FUNC_INFO, "invalid amount of padding should have been caught by previous validation"); 0139 0140 const QChar c = encoded[paddingIndex - 1]; 0141 Q_ASSERT_X(c != pad, Q_FUNC_INFO, "invalid amount of padding should have been caught by previous validation"); 0142 0143 /* 0144 * Check if the amount of padding corresponds to a known (valid) input 'group' size 0145 * by looking up the mask for the last character before padding (0 = invalid) 0146 */ 0147 int p = padMasks[amount - 1]; 0148 if (p == 0) { 0149 return false; 0150 } 0151 0152 std::optional<int> d = decode(c); 0153 Q_ASSERT_X(d, Q_FUNC_INFO, "invalid input should have been caught by prior validation"); 0154 0155 /* 0156 * check if there are no trailing bits, 0157 * i.e. the last character before padding does not encode bits that are not whitelisted by the mask 0158 */ 0159 return ((*d) & p) == 0; 0160 0161 } 0162 0163 static std::optional<int> isBase32(const QString &encoded, int from, int until) 0164 { 0165 if (!checkInputRange(encoded, from, until)) { 0166 return std::nullopt; 0167 } 0168 0169 int paddingIndex = until; 0170 for (int i = from; i < until; ++i) { 0171 const QChar at = encoded[i]; 0172 if (at == pad) { 0173 if (paddingIndex == until) { 0174 paddingIndex = i; 0175 } 0176 } else { 0177 /* 0178 * Reject input if: 0179 * - padding has 'started' but the current character is not the padding character 0180 * - the current character is not a (valid) value character 0181 */ 0182 if (paddingIndex < until || !isBase32(at)) { 0183 return std::nullopt; 0184 } 0185 } 0186 } 0187 0188 int amount = until - paddingIndex; 0189 0190 return isPaddingValid(encoded, paddingIndex, amount) ? std::optional<int>(paddingIndex) : std::nullopt; 0191 } 0192 0193 static inline size_t determineCapacity(size_t encodedBytes, size_t accountFor, size_t lastBytes) 0194 { 0195 return 5 * (encodedBytes - accountFor) / 8 + lastBytes; 0196 } 0197 0198 static size_t requiredCapacity(int paddingIndex, int from, int until) 0199 { 0200 // based on the amount of padding, determine the exact size of the encoded data 0201 int size = paddingIndex - from; 0202 switch (until - paddingIndex) { 0203 case 0: 0204 return determineCapacity(size, 0, 0); 0205 case 1: 0206 return determineCapacity(size, 7, 4); 0207 case 3: 0208 return determineCapacity(size, 5, 3); 0209 case 4: 0210 return determineCapacity(size, 4, 2); 0211 case 6: 0212 return determineCapacity(size, 2, 1); 0213 default: 0214 Q_ASSERT_X(false, Q_FUNC_INFO, "invalid input size/amount of padding should have been caught by previous validation"); 0215 return 0; 0216 } 0217 } 0218 0219 namespace base32 0220 { 0221 std::optional<size_t> validate(const QString &encoded, int from, int until) 0222 { 0223 int max = until == -1 ? encoded.size() : until; 0224 if (!checkInputRange(encoded, from, max)) { 0225 return std::nullopt; 0226 } 0227 0228 std::optional<int> padding = isBase32(encoded, from, max); 0229 return padding ? std::optional<size_t>(requiredCapacity(*padding, from, max)) : std::nullopt; 0230 } 0231 0232 std::optional<size_t> decode(const QString &encoded, char * const out, size_t outlen, int from, int until) 0233 { 0234 int max = until == -1 ? encoded.size() : until; 0235 if (!checkInputRange(encoded, from, max)) { 0236 qCDebug(logger) << "Invalid input range from:" << from << "until:" << until << "implied limit:" << max; 0237 return std::nullopt; 0238 } 0239 0240 std::optional<int> padding = isBase32(encoded, from, max); 0241 if (!padding) { 0242 qCDebug(logger) << "Unable to decode: input range is not valid base32"; 0243 return std::nullopt; 0244 } 0245 0246 size_t needed = requiredCapacity(*padding, from, max); 0247 if (outlen < needed) { 0248 qCDebug(logger) << "Unable to decode: required capacity:" << needed << "exceeds allocated output buffer size:" << outlen; 0249 return std::nullopt; 0250 } 0251 0252 int index; 0253 size_t decoded = 0; 0254 for(index = from; index < max && decoded < needed; index += 8) { 0255 std::optional<size_t> group = decode(encoded, index, max, *padding, decoded, needed, out); 0256 Q_ASSERT_X(group, Q_FUNC_INFO, "input should have been fully validated; decoding should succeed"); 0257 decoded += *group; 0258 } 0259 0260 Q_ASSERT_X(decoded == needed, Q_FUNC_INFO, "number of bytes decoded should match expected output capacity required"); 0261 Q_ASSERT_X(index == max, Q_FUNC_INFO, "number of characters decoded should match end of the input range exactly"); 0262 0263 return std::optional<size_t>(decoded); 0264 } 0265 0266 std::optional<QByteArray> decode(const QString &encoded) 0267 { 0268 std::optional<QByteArray> result = std::nullopt; 0269 std::optional<size_t> capacity = validate(encoded); 0270 0271 if (!capacity) { 0272 qCDebug(logger) << "Unable to decode input: invalid base32"; 0273 return std::nullopt; 0274 } 0275 0276 QByteArray decoded; 0277 decoded.reserve((int) *capacity); 0278 decoded.resize((int) *capacity); 0279 if (decode(encoded, decoded.data(), *capacity)) { 0280 result.emplace(decoded); 0281 } else { 0282 qCDebug(logger) << "Failed to decode base32"; 0283 } 0284 0285 return result; 0286 } 0287 }