85 lines
3.4 KiB
C++
85 lines
3.4 KiB
C++
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
||
|
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||
|
|
|
||
|
|
module Crafter.Debug.Core:Protocol_impl;
|
||
|
|
import std;
|
||
|
|
import :Value;
|
||
|
|
import :Protocol;
|
||
|
|
|
||
|
|
using namespace Crafter::Debug;
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
constexpr std::string_view alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||
|
|
|
||
|
|
std::int32_t Sextet(char c) {
|
||
|
|
if (c >= 'A' && c <= 'Z') return c - 'A';
|
||
|
|
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
|
||
|
|
if (c >= '0' && c <= '9') return c - '0' + 52;
|
||
|
|
if (c == '+') return 62;
|
||
|
|
if (c == '/') return 63;
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Value Crafter::Debug::MakeReply(Value result) {
|
||
|
|
Value reply = Value::MakeObject();
|
||
|
|
reply.Set("result", std::move(result));
|
||
|
|
return reply;
|
||
|
|
}
|
||
|
|
|
||
|
|
Value Crafter::Debug::MakeErrorReply(std::string_view code, std::string_view message) {
|
||
|
|
Value error = Value::MakeObject();
|
||
|
|
error.Set("code", Value(code));
|
||
|
|
error.Set("message", Value(message));
|
||
|
|
Value reply = Value::MakeObject();
|
||
|
|
reply.Set("error", std::move(error));
|
||
|
|
return reply;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string Crafter::Debug::Base64Encode(std::span<const std::byte> bytes) {
|
||
|
|
std::string out;
|
||
|
|
out.reserve((bytes.size() + 2) / 3 * 4);
|
||
|
|
std::size_t i = 0;
|
||
|
|
for (; i + 3 <= bytes.size(); i += 3) {
|
||
|
|
const std::uint32_t chunk = (std::to_integer<std::uint32_t>(bytes[i]) << 16) | (std::to_integer<std::uint32_t>(bytes[i + 1]) << 8) | std::to_integer<std::uint32_t>(bytes[i + 2]);
|
||
|
|
out += alphabet[(chunk >> 18) & 63];
|
||
|
|
out += alphabet[(chunk >> 12) & 63];
|
||
|
|
out += alphabet[(chunk >> 6) & 63];
|
||
|
|
out += alphabet[chunk & 63];
|
||
|
|
}
|
||
|
|
const std::size_t rest = bytes.size() - i;
|
||
|
|
if (rest == 1) {
|
||
|
|
const std::uint32_t chunk = std::to_integer<std::uint32_t>(bytes[i]) << 16;
|
||
|
|
out += alphabet[(chunk >> 18) & 63];
|
||
|
|
out += alphabet[(chunk >> 12) & 63];
|
||
|
|
out += "==";
|
||
|
|
} else if (rest == 2) {
|
||
|
|
const std::uint32_t chunk = (std::to_integer<std::uint32_t>(bytes[i]) << 16) | (std::to_integer<std::uint32_t>(bytes[i + 1]) << 8);
|
||
|
|
out += alphabet[(chunk >> 18) & 63];
|
||
|
|
out += alphabet[(chunk >> 12) & 63];
|
||
|
|
out += alphabet[(chunk >> 6) & 63];
|
||
|
|
out += '=';
|
||
|
|
}
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::optional<std::vector<std::byte>> Crafter::Debug::Base64Decode(std::string_view text) {
|
||
|
|
if (text.size() % 4 != 0) return std::nullopt;
|
||
|
|
std::vector<std::byte> out;
|
||
|
|
out.reserve(text.size() / 4 * 3);
|
||
|
|
for (std::size_t i = 0; i < text.size(); i += 4) {
|
||
|
|
const bool last = i + 4 == text.size();
|
||
|
|
const std::int32_t a = Sextet(text[i]);
|
||
|
|
const std::int32_t b = Sextet(text[i + 1]);
|
||
|
|
const bool pad2 = last && text[i + 2] == '=';
|
||
|
|
const bool pad3 = last && text[i + 3] == '=';
|
||
|
|
const std::int32_t c = pad2 ? 0 : Sextet(text[i + 2]);
|
||
|
|
const std::int32_t d = pad3 ? 0 : Sextet(text[i + 3]);
|
||
|
|
if (a < 0 || b < 0 || c < 0 || d < 0 || (pad2 && !pad3)) return std::nullopt;
|
||
|
|
const std::uint32_t chunk = (static_cast<std::uint32_t>(a) << 18) | (static_cast<std::uint32_t>(b) << 12) | (static_cast<std::uint32_t>(c) << 6) | static_cast<std::uint32_t>(d);
|
||
|
|
out.push_back(static_cast<std::byte>((chunk >> 16) & 0xFF));
|
||
|
|
if (!pad2) out.push_back(static_cast<std::byte>((chunk >> 8) & 0xFF));
|
||
|
|
if (!pad3) out.push_back(static_cast<std::byte>(chunk & 0xFF));
|
||
|
|
}
|
||
|
|
return out;
|
||
|
|
}
|