3 * Copyright (c) 2020 Project CHIP Authors
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
20 * This file implements converting an array of bytes into a Base41 String and
23 * The encoding chosen is: treat every 2 bytes of input data as a little-endian
24 * uint16_t, then div and mod that into 3 base41 characters, with the least-significant
25 * encoding bits in the first character of the resulting string. If an odd
26 * number of bytes is encoded, 2 characters are used to encode the last byte.
36 static const char codes[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
37 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
38 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', '*', '+', '-', '.' };
39 static const int kBase41ChunkLen = 3;
40 static const int kBytesChunkLen = 2;
41 static const int kRadix = sizeof(codes) / sizeof(codes[0]);
43 std::string base41Encode(const uint8_t * buf, size_t buf_len)
47 // eat little-endian uint16_ts from the byte array
48 // encode as 3 base41 characters
50 while (buf_len >= kBytesChunkLen)
53 for (int i = kBytesChunkLen - 1; i >= 0; i--)
58 buf_len -= kBytesChunkLen;
59 buf += kBytesChunkLen;
61 // This code needs to correctly convey to the decoder
62 // the length of data encoded, so normally emits 3 chars for
64 // But there's a special case possible where the last
65 // two bytes would fit in exactly 2 chars.
66 // The following conditions must be met:
67 // 1. this must be the last value
68 // 2. the value doesn't fit in a single byte, if we encode a
69 // small value at the end of the encoded string with a
70 // shortened chunk, the decoder will think only one byte
72 // 3. the value can be encoded in 2 base41 chars
74 int encodeLen = kBase41ChunkLen;
75 if (buf_len == 0 && // the very last value, i.e. not an odd byte
76 (value > UINT8_MAX) && // the value wouldn't fit in one byte
77 (value < kRadix * kRadix)) // the value can be encoded with 2 base41 chars
81 for (int _ = 0; _ < encodeLen; _++)
83 result += codes[value % kRadix];
88 // handle leftover bytes, if any
92 static_assert(sizeof(value) * CHAR_BIT >= kBytesChunkLen * 8, "value might overflow");
94 for (size_t i = buf_len; i > 0; i--)
100 // need to indicate there are leftover bytes, so append at least one encoding char
101 result += codes[value % kRadix];
104 // if there's still value, encode with more chars
107 result += codes[value % kRadix];
114 static inline CHIP_ERROR decodeChar(char c, uint8_t & value)
116 static const int kBogus = 255;
117 // map of base41 charater to numeric value
118 // subtract 32 from the charater, then index into this array, if possible
119 const uint8_t decodes[] = {
180 if (c < ' ' || c > 'Z')
182 return CHIP_ERROR_INVALID_INTEGER_VALUE;
184 uint8_t v = decodes[c - ' '];
187 return CHIP_ERROR_INVALID_INTEGER_VALUE;
193 CHIP_ERROR base41Decode(std::string base41, std::vector<uint8_t> & result)
197 for (size_t i = 0; base41.length() - i >= kBase41ChunkLen; i += kBase41ChunkLen)
201 for (size_t iv = i + kBase41ChunkLen; iv > i; iv--)
204 CHIP_ERROR err = decodeChar(base41[iv - 1], v);
206 if (err != CHIP_NO_ERROR)
211 value = static_cast<uint16_t>(value * kRadix + v);
214 result.push_back(static_cast<uint8_t>(value % 256));
215 // Cast is safe, because we divided a uint16_t by 256 to get here,
216 // so what's left has to fit inside uint8_t.
217 result.push_back(static_cast<uint8_t>(value / 256));
220 if (base41.length() % kBase41ChunkLen != 0) // only 1 or 2 chars left
222 size_t tail = (base41.length() % kBase41ChunkLen);
223 size_t i = base41.length() - tail;
226 for (size_t iv = base41.length(); iv > i; iv--)
229 CHIP_ERROR err = decodeChar(base41[iv - 1], v);
231 if (err != CHIP_NO_ERROR)
236 value = static_cast<uint16_t>(value * kRadix + v);
238 result.push_back(static_cast<uint8_t>(value % 256));
242 // Cast is safe, because we divided a uint16_t by 256 to get here,
243 // so what's left has to fit inside uint8_t.
244 result.push_back(static_cast<uint8_t>(value));
247 return CHIP_NO_ERROR;