Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2021 Project CHIP Authors
4 : *
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
8 : *
9 : * http://www.apache.org/licenses/LICENSE-2.0
10 : *
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.
16 : */
17 :
18 : /**
19 : * @file
20 : * This file implements converting an array of bytes into a Base38 String.
21 : *
22 : * The encoding chosen is: treat every 3 bytes of input data as a little-endian
23 : * uint32_t, then div and mod that into 5 base38 characters, with the least-significant
24 : * encoding bits in the first character of the resulting string. If a number of bytes
25 : * is used that is not multiple of 3, then last 2 bytes are encoded to 4 base38 characters
26 : * or last 1 byte is encoded to 2 base38 characters. Algoritm considers worst case size
27 : * of bytes chunks and does not introduce code length optimization.
28 : *
29 : */
30 :
31 : #include "Base38Encode.h"
32 :
33 : #include <climits>
34 : #include <cstring>
35 :
36 : namespace {
37 :
38 : static const uint8_t kMaxBytesSingleChunkLen = 3;
39 :
40 : } // unnamed namespace
41 :
42 : namespace chip {
43 :
44 56 : CHIP_ERROR base38Encode(ByteSpan in_buf, MutableCharSpan & out_buf)
45 : {
46 56 : CHIP_ERROR err = CHIP_NO_ERROR;
47 56 : const uint8_t * in_buf_ptr = in_buf.data();
48 56 : size_t in_buf_len = in_buf.size();
49 56 : size_t out_idx = 0;
50 :
51 263 : while (in_buf_len > 0)
52 : {
53 209 : uint32_t value = 0;
54 : static_assert((sizeof(value) * CHAR_BIT) >= (static_cast<size_t>(kMaxBytesSingleChunkLen) * 8),
55 : "Type for value is too small for conversions");
56 :
57 209 : size_t bytesInChunk = (in_buf_len >= kMaxBytesSingleChunkLen) ? kMaxBytesSingleChunkLen : in_buf_len;
58 :
59 780 : for (size_t byte_idx = 0; byte_idx < bytesInChunk; byte_idx++)
60 : {
61 571 : value += static_cast<uint32_t>(in_buf_ptr[byte_idx] << (8 * byte_idx));
62 : }
63 209 : in_buf_len -= bytesInChunk;
64 209 : in_buf_ptr += bytesInChunk;
65 :
66 : // Without code length optimization there is constant characters number needed for specific chunk size.
67 209 : const uint8_t base38CharactersNeeded = kBase38CharactersNeededInNBytesChunk[bytesInChunk - 1];
68 :
69 209 : if ((out_idx + base38CharactersNeeded) >= out_buf.size())
70 : {
71 2 : err = CHIP_ERROR_BUFFER_TOO_SMALL;
72 2 : break;
73 : }
74 :
75 1177 : for (uint8_t character = 0; character < base38CharactersNeeded; character++)
76 : {
77 970 : out_buf.data()[out_idx++] = kCodes[value % kRadix];
78 970 : value /= kRadix;
79 : }
80 : }
81 :
82 56 : if (out_idx < out_buf.size())
83 : {
84 54 : out_buf.data()[out_idx] = '\0';
85 : // Reduce output span size to be the size of written data and to not include null-terminator.
86 54 : out_buf.reduce_size(out_idx);
87 : }
88 : else
89 : {
90 : // out_buf size is zero.
91 2 : err = CHIP_ERROR_BUFFER_TOO_SMALL;
92 : }
93 :
94 56 : return err;
95 : }
96 :
97 36 : size_t base38EncodedLength(size_t num_bytes)
98 : {
99 : // Each group of 3 bytes converts to 5 chars, and each remaining byte converts to 2 chars.
100 : // Add one for the null terminator.
101 36 : return (num_bytes / 3) * 5 + (num_bytes % 3) * 2 + 1;
102 : }
103 :
104 : } // namespace chip
|