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 46 : CHIP_ERROR base38Encode(ByteSpan in_buf, MutableCharSpan & out_buf) 45 : { 46 46 : CHIP_ERROR err = CHIP_NO_ERROR; 47 46 : const uint8_t * in_buf_ptr = in_buf.data(); 48 46 : size_t in_buf_len = in_buf.size(); 49 46 : size_t out_idx = 0; 50 : 51 213 : while (in_buf_len > 0) 52 : { 53 169 : uint32_t value = 0; 54 : static_assert((sizeof(value) * CHAR_BIT) >= (kMaxBytesSingleChunkLen * 8), "Type for value is too small for conversions"); 55 : 56 169 : size_t bytesInChunk = (in_buf_len >= kMaxBytesSingleChunkLen) ? kMaxBytesSingleChunkLen : in_buf_len; 57 : 58 630 : for (size_t byte_idx = 0; byte_idx < bytesInChunk; byte_idx++) 59 : { 60 461 : value += static_cast<uint32_t>(in_buf_ptr[byte_idx] << (8 * byte_idx)); 61 : } 62 169 : in_buf_len -= bytesInChunk; 63 169 : in_buf_ptr += bytesInChunk; 64 : 65 : // Without code length optimization there is constant characters number needed for specific chunk size. 66 169 : const uint8_t base38CharactersNeeded = kBase38CharactersNeededInNBytesChunk[bytesInChunk - 1]; 67 : 68 169 : if ((out_idx + base38CharactersNeeded) >= out_buf.size()) 69 : { 70 2 : err = CHIP_ERROR_BUFFER_TOO_SMALL; 71 2 : break; 72 : } 73 : 74 947 : for (uint8_t character = 0; character < base38CharactersNeeded; character++) 75 : { 76 780 : out_buf.data()[out_idx++] = kCodes[value % kRadix]; 77 780 : value /= kRadix; 78 : } 79 : } 80 : 81 46 : if (out_idx < out_buf.size()) 82 : { 83 44 : 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 44 : 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 46 : return err; 94 : } 95 : 96 26 : 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 26 : return (num_bytes / 3) * 5 + (num_bytes % 3) * 2 + 1; 101 : } 102 : 103 : } // namespace chip