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 53 : CHIP_ERROR base38Encode(ByteSpan in_buf, MutableCharSpan & out_buf)
45 : {
46 53 : CHIP_ERROR err = CHIP_NO_ERROR;
47 53 : const uint8_t * in_buf_ptr = in_buf.data();
48 53 : size_t in_buf_len = in_buf.size();
49 53 : size_t out_idx = 0;
50 :
51 248 : while (in_buf_len > 0)
52 : {
53 197 : uint32_t value = 0;
54 : static_assert((sizeof(value) * CHAR_BIT) >= (kMaxBytesSingleChunkLen * 8), "Type for value is too small for conversions");
55 :
56 197 : size_t bytesInChunk = (in_buf_len >= kMaxBytesSingleChunkLen) ? kMaxBytesSingleChunkLen : in_buf_len;
57 :
58 735 : for (size_t byte_idx = 0; byte_idx < bytesInChunk; byte_idx++)
59 : {
60 538 : value += static_cast<uint32_t>(in_buf_ptr[byte_idx] << (8 * byte_idx));
61 : }
62 197 : in_buf_len -= bytesInChunk;
63 197 : in_buf_ptr += bytesInChunk;
64 :
65 : // Without code length optimization there is constant characters number needed for specific chunk size.
66 197 : const uint8_t base38CharactersNeeded = kBase38CharactersNeededInNBytesChunk[bytesInChunk - 1];
67 :
68 197 : if ((out_idx + base38CharactersNeeded) >= out_buf.size())
69 : {
70 2 : err = CHIP_ERROR_BUFFER_TOO_SMALL;
71 2 : break;
72 : }
73 :
74 1108 : for (uint8_t character = 0; character < base38CharactersNeeded; character++)
75 : {
76 913 : out_buf.data()[out_idx++] = kCodes[value % kRadix];
77 913 : value /= kRadix;
78 : }
79 : }
80 :
81 53 : if (out_idx < out_buf.size())
82 : {
83 51 : out_buf.data()[out_idx] = '\0';
84 : // Reduce output span size to be the size of written data and to not include null-terminator.
85 51 : out_buf.reduce_size(out_idx);
86 : }
87 : else
88 : {
89 : // out_buf size is zero.
90 2 : err = CHIP_ERROR_BUFFER_TOO_SMALL;
91 : }
92 :
93 53 : return err;
94 : }
95 :
96 33 : size_t base38EncodedLength(size_t num_bytes)
97 : {
98 : // Each group of 3 bytes converts to 5 chars, and each remaining byte converts to 2 chars.
99 : // Add one for the null terminator.
100 33 : return (num_bytes / 3) * 5 + (num_bytes % 3) * 2 + 1;
101 : }
102 :
103 : } // namespace chip
|