mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-15 07:06:11 -04:00
198 lines
5.4 KiB
C++
198 lines
5.4 KiB
C++
// Copyright 2025 Google LLC
|
|
//
|
|
// 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
|
|
//
|
|
// https://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.
|
|
|
|
#include "internal/encoding/base85.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <optional>
|
|
#include <string>
|
|
|
|
namespace nearby {
|
|
namespace encoding {
|
|
namespace {
|
|
|
|
constexpr int kBase85CharsNumber = 85;
|
|
constexpr uint8_t kBase85Zero = 'z';
|
|
constexpr unsigned char kBase85EncodeChars[] = {
|
|
'!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/',
|
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>',
|
|
'?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
|
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\',
|
|
']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
|
|
'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u'};
|
|
|
|
constexpr uint8_t kBase85LastEncodeChar =
|
|
kBase85EncodeChars[kBase85CharsNumber - 1];
|
|
|
|
constexpr uint8_t kBase85DecodeCharsCount[] = {0, 0, 1, 2, 3};
|
|
|
|
static uint8_t kBase85DecodeChars[256];
|
|
|
|
void InitBase85DecodeChars() {
|
|
if (kBase85DecodeChars[kBase85LastEncodeChar]) {
|
|
return;
|
|
}
|
|
|
|
std::fill(kBase85DecodeChars, kBase85DecodeChars + 256, 0xff);
|
|
|
|
for (uint8_t i = 0; i < kBase85CharsNumber; ++i) {
|
|
kBase85DecodeChars[kBase85EncodeChars[i]] = i;
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
std::string Base85Encode(const std::string& input, bool padding) {
|
|
size_t result_size =
|
|
5 * (input.size() >> 2) + (((input.size() % 4) * 5 + 3) >> 2);
|
|
if (padding) {
|
|
result_size = 5 * (input.size() >> 2) + ((input.size() % 4 != 0) ? 5 : 0);
|
|
}
|
|
|
|
std::string result;
|
|
result.resize(result_size);
|
|
char* result_buffer = result.data();
|
|
char* result_buffer_end = result.data() + result.size();
|
|
|
|
size_t bytes = input.size();
|
|
const unsigned char* data =
|
|
reinterpret_cast<const unsigned char*>(input.data());
|
|
while (bytes) {
|
|
int count;
|
|
uint32_t value = 0;
|
|
for (count = 24; count >= 0; count -= 8) {
|
|
uint32_t ch = *data++;
|
|
value |= ch << count;
|
|
if (--bytes == 0) break;
|
|
}
|
|
|
|
if (value == 0) {
|
|
// special case, use 'z' to encode it
|
|
result_buffer[0] = kBase85Zero;
|
|
++result_buffer;
|
|
if (count < 0 || padding) {
|
|
result_buffer_end -= 4;
|
|
} else {
|
|
count = (24 - count) >> 3;
|
|
result_buffer_end -= (((count % 4) * 5 + 3) >> 2) - 1;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
int changed_count = 0;
|
|
for (count = 4; count >= 0; count--) {
|
|
int val = value % 85;
|
|
value /= 85;
|
|
if (result_buffer + count < result_buffer_end) {
|
|
result_buffer[count] = kBase85EncodeChars[val];
|
|
++changed_count;
|
|
}
|
|
}
|
|
|
|
result_buffer += changed_count;
|
|
}
|
|
|
|
return std::string(result.data(), result_buffer - result.data());
|
|
}
|
|
|
|
std::optional<std::string> Base85Decode(const std::string& input) {
|
|
InitBase85DecodeChars();
|
|
|
|
size_t length = 0;
|
|
size_t pos = 0;
|
|
for (; pos < input.size();) {
|
|
if (input[pos] == kBase85Zero) {
|
|
// special zero case
|
|
length += 4;
|
|
++pos;
|
|
continue;
|
|
}
|
|
if (pos + 5 > input.size()) {
|
|
size_t remain = input.size() - pos;
|
|
if (remain == 1) {
|
|
return std::nullopt;
|
|
}
|
|
length += kBase85DecodeCharsCount[remain];
|
|
break;
|
|
}
|
|
pos += 5;
|
|
length += 4;
|
|
}
|
|
|
|
std::string result;
|
|
result.resize(length);
|
|
|
|
const char* input_buffer = input.data();
|
|
const char* input_buffer_end = input_buffer + input.size();
|
|
char* result_buffer = result.data();
|
|
char* result_buffer_end = result_buffer + result.size();
|
|
|
|
while (length) {
|
|
int count = 4;
|
|
int decoded_char;
|
|
uint8_t input_char;
|
|
uint32_t value = 0;
|
|
bool skip_the_group = false;
|
|
do {
|
|
if (input_buffer < input_buffer_end) {
|
|
input_char = *input_buffer++;
|
|
// handle special case 'z'
|
|
if (count == 4 && input_char == kBase85Zero) {
|
|
for (int i = 0; i < count; ++i) {
|
|
*result_buffer++ = 0x00;
|
|
}
|
|
length -= 4;
|
|
skip_the_group = true;
|
|
break;
|
|
}
|
|
|
|
decoded_char = kBase85DecodeChars[input_char];
|
|
if (decoded_char == 0xff) return std::nullopt;
|
|
} else {
|
|
decoded_char = kBase85LastEncodeChar;
|
|
}
|
|
value = value * 85 + decoded_char;
|
|
} while (--count);
|
|
if (skip_the_group) {
|
|
continue;
|
|
}
|
|
if (input_buffer < input_buffer_end) {
|
|
input_char = *input_buffer++;
|
|
decoded_char = kBase85DecodeChars[input_char];
|
|
if (decoded_char == 0xff) return std::nullopt;
|
|
} else {
|
|
decoded_char = kBase85LastEncodeChar;
|
|
}
|
|
/* Detect overflow. */
|
|
if (0xffffffff / 85 < value || 0xffffffff - decoded_char < (value *= 85))
|
|
return std::nullopt;
|
|
value += decoded_char;
|
|
|
|
count = (length < 4) ? length : 4;
|
|
length -= count;
|
|
do {
|
|
value = (value << 8) | (value >> 24);
|
|
if (result_buffer < result_buffer_end) {
|
|
*result_buffer++ = value;
|
|
}
|
|
} while (--count);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
} // namespace encoding
|
|
} // namespace nearby
|