| /* |
| * |
| * Copyright (c) 2021 Project CHIP Authors |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /** |
| * @file |
| * This file implements converting an array of bytes into a Base38 String. |
| * |
| * The encoding chosen is: treat every 3 bytes of input data as a little-endian |
| * uint32_t, then div and mod that into 5 base38 characters, with the least-significant |
| * encoding bits in the first character of the resulting string. If a number of bytes |
| * is used that is not multiple of 3, then last 2 bytes are encoded to 4 base38 characters |
| * or last 1 byte is encoded to 2 base38 characters. Algoritm considers worst case size |
| * of bytes chunks and does not introduce code length optimization. |
| * |
| */ |
| |
| #include "Base38Encode.h" |
| |
| #include <climits> |
| #include <cstring> |
| |
| namespace { |
| |
| static const uint8_t kMaxBytesSingleChunkLen = 3; |
| |
| } // unnamed namespace |
| |
| namespace chip { |
| |
| CHIP_ERROR base38Encode(ByteSpan in_buf, MutableCharSpan & out_buf) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| const uint8_t * in_buf_ptr = in_buf.data(); |
| size_t in_buf_len = in_buf.size(); |
| size_t out_idx = 0; |
| |
| while (in_buf_len > 0) |
| { |
| uint32_t value = 0; |
| static_assert((sizeof(value) * CHAR_BIT) >= (kMaxBytesSingleChunkLen * 8), "Type for value is too small for conversions"); |
| |
| size_t bytesInChunk = (in_buf_len >= kMaxBytesSingleChunkLen) ? kMaxBytesSingleChunkLen : in_buf_len; |
| |
| for (size_t byte_idx = 0; byte_idx < bytesInChunk; byte_idx++) |
| { |
| value += static_cast<uint32_t>(in_buf_ptr[byte_idx] << (8 * byte_idx)); |
| } |
| in_buf_len -= bytesInChunk; |
| in_buf_ptr += bytesInChunk; |
| |
| // Without code length optimization there is constant characters number needed for specific chunk size. |
| const uint8_t base38CharactersNeeded = kBase38CharactersNeededInNBytesChunk[bytesInChunk - 1]; |
| |
| if ((out_idx + base38CharactersNeeded) >= out_buf.size()) |
| { |
| err = CHIP_ERROR_BUFFER_TOO_SMALL; |
| break; |
| } |
| |
| for (uint8_t character = 0; character < base38CharactersNeeded; character++) |
| { |
| out_buf.data()[out_idx++] = kCodes[value % kRadix]; |
| value /= kRadix; |
| } |
| } |
| |
| if (out_idx < out_buf.size()) |
| { |
| out_buf.data()[out_idx] = '\0'; |
| // Reduce output span size to be the size of written data and to not include null-terminator. |
| out_buf.reduce_size(out_idx); |
| } |
| else |
| { |
| // out_buf size is zero. |
| err = CHIP_ERROR_BUFFER_TOO_SMALL; |
| } |
| |
| return err; |
| } |
| |
| size_t base38EncodedLength(size_t num_bytes) |
| { |
| // Each group of 3 bytes converts to 5 chars, and each remaining byte converts to 2 chars. |
| // Add one for the null terminator. |
| return (num_bytes / 3) * 5 + (num_bytes % 3) * 2 + 1; |
| } |
| |
| } // namespace chip |