blob: 5e663d23bec5eda801bac7ea03312be88f522189 [file] [log] [blame]
/*
*
* 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