blob: e7b24873a0b7d21e333fb6a4f3a3c8cf918b36d7 [file]
/*
*
* Copyright (c) 2026 Project CHIP Authors
* All rights reserved.
*
* 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
* Base-85 utility functions (RFC 1924 alphabet, git/Python compatible).
*/
#include "Base85.h"
#include <string.h>
namespace chip {
// RFC 1924 alphabet: 0-9 A-Z a-z !#$%&()*+-;<=>?@^_`{|}~
// Values 0-9 map to '0'-'9', 10-35 to 'A'-'Z', 36-61 to 'a'-'z',
// and values 62-84 map to the 23 punctuation characters below.
// Convert a value in the range 0..84 to its RFC 1924 base85 character.
static char Base85ValToChar(uint8_t val)
{
switch (val)
{
// clang-format off
case 62: return '!';
case 63: return '#';
case 64: return '$';
case 65: return '%';
case 66: return '&';
case 67: return '(';
case 68: return ')';
case 69: return '*';
case 70: return '+';
case 71: return '-';
case 72: return ';';
case 73: return '<';
case 74: return '=';
case 75: return '>';
case 76: return '?';
case 77: return '@';
case 78: return '^';
case 79: return '_';
case 80: return '`';
case 81: return '{';
case 82: return '|';
case 83: return '}';
case 84: return '~';
// clang-format on
default:
if (val < 10)
{
return static_cast<char>('0' + val);
}
if (10 <= val && val < 36)
{
return static_cast<char>('A' + val - 10);
}
if (36 <= val && val < 62)
{
return static_cast<char>('a' + val - 36);
}
}
return 0; // not possible
}
// Convert an RFC 1924 base85 character to a value in the range 0..84,
// or UINT8_MAX if the character is not in the alphabet.
static uint8_t Base85CharToVal(char c)
{
switch (c)
{
// clang-format off
case '!': return 62;
case '#': return 63;
case '$': return 64;
case '%': return 65;
case '&': return 66;
case '(': return 67;
case ')': return 68;
case '*': return 69;
case '+': return 70;
case '-': return 71;
case ';': return 72;
case '<': return 73;
case '=': return 74;
case '>': return 75;
case '?': return 76;
case '@': return 77;
case '^': return 78;
case '_': return 79;
case '`': return 80;
case '{': return 81;
case '|': return 82;
case '}': return 83;
case '~': return 84;
// clang-format on
default:
if (c >= '0' && c <= '9')
{
return static_cast<uint8_t>(c - '0');
}
if (c >= 'A' && c <= 'Z')
{
return static_cast<uint8_t>(c - 'A' + 10);
}
if (c >= 'a' && c <= 'z')
{
return static_cast<uint8_t>(c - 'a' + 36);
}
}
return UINT8_MAX;
}
static uint32_t ReadGroup(const uint8_t * src, size_t count)
{
uint8_t padded[4] = { 0 };
memcpy(padded, src, count);
return (static_cast<uint32_t>(padded[0]) << 24) | //
(static_cast<uint32_t>(padded[1]) << 16) | //
(static_cast<uint32_t>(padded[2]) << 8) | //
(static_cast<uint32_t>(padded[3]));
}
static void WriteGroup(uint32_t value, uint8_t * dest, size_t count)
{
uint8_t decoded[4] = {
static_cast<uint8_t>(value >> 24),
static_cast<uint8_t>(value >> 16),
static_cast<uint8_t>(value >> 8),
static_cast<uint8_t>(value),
};
memcpy(dest, decoded, count);
}
static bool DecodeGroup(const char * src, size_t count, uint32_t & outValue)
{
// "|NsC0" is the largest allowed group value (UINT32_MAX); we could
// check against this value here directly, but it is simpler to do the
// calculation in a uint64_t and validate the decoded result.
uint64_t value = 0;
for (size_t j = 0; j < count; j++)
{
uint8_t digit = Base85CharToVal(src[j]);
VerifyOrReturnValue(digit != UINT8_MAX, false);
value = value * 85 + digit;
}
for (size_t j = count; j < 5; j++)
{
value = value * 85 + 84; // implicitly pad to 5 "digits" with '~' (84)
}
VerifyOrReturnValue(value <= UINT32_MAX, false);
outValue = static_cast<uint32_t>(value);
return true;
}
static void EncodeGroup(uint32_t value, char * out, size_t count)
{
for (size_t j = 5; j-- > count;)
{
value /= 85; // truncate least significant "digits"
}
for (size_t j = count; j-- > 0;)
{
out[j] = Base85ValToChar(static_cast<uint8_t>(value % 85));
value /= 85;
}
}
CHIP_ERROR BytesToBase85(const uint8_t * src, size_t srcSize, char * dest, size_t destSize)
{
VerifyOrReturnError(src != nullptr || srcSize == 0, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(dest != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
// Base85EncodedLength(srcSize) could overflow / saturate to SIZE_MAX,
// checking via Base85DecodedLength(destSize) instead avoids this issue.
VerifyOrReturnError(Base85DecodedLength(destSize) >= srcSize, CHIP_ERROR_BUFFER_TOO_SMALL);
size_t remainder = srcSize % 4;
for (auto * end = src + (srcSize - remainder); src < end; src += 4, dest += 5)
{
EncodeGroup(ReadGroup(src, 4), dest, 5);
}
if (remainder > 0)
{
EncodeGroup(ReadGroup(src, remainder), dest, remainder + 1);
}
return CHIP_NO_ERROR;
}
CHIP_ERROR Base85ToBytes(const char * src, size_t srcSize, uint8_t * dest, size_t destSize)
{
VerifyOrReturnError(src != nullptr || srcSize == 0, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(dest != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(destSize >= Base85DecodedLength(srcSize), CHIP_ERROR_BUFFER_TOO_SMALL);
uint32_t value;
size_t remainder = srcSize % 5;
VerifyOrReturnError(remainder != 1, CHIP_ERROR_INVALID_ARGUMENT); // 1 "digit" does not encode a full byte
for (auto * end = src + (srcSize - remainder); src < end; src += 5, dest += 4)
{
VerifyOrReturnError(DecodeGroup(src, 5, value), CHIP_ERROR_INVALID_ARGUMENT);
WriteGroup(value, dest, 4);
}
if (remainder > 0) // 2..4
{
VerifyOrReturnError(DecodeGroup(src, remainder, value), CHIP_ERROR_INVALID_ARGUMENT);
char check[4];
uint32_t padding = UINT32_MAX >> (8 * (remainder - 1));
EncodeGroup(value & ~padding, check, remainder);
VerifyOrReturnError(memcmp(src, check, remainder) == 0, CHIP_ERROR_INVALID_ARGUMENT);
WriteGroup(value, dest, remainder - 1); // might clobber src
}
return CHIP_NO_ERROR;
}
} // namespace chip