Initial project snapshot

This commit is contained in:
2026-04-28 22:29:50 +03:00
commit 8ba0561f4f
365 changed files with 91832 additions and 0 deletions
+401
View File
@@ -0,0 +1,401 @@
#include "rdp_worker/common/json.hpp"
#include <cctype>
#include <cstdint>
#include <sstream>
#include <stdexcept>
namespace rdp_worker::common {
JsonValue::JsonValue() : value(nullptr) {}
JsonValue::JsonValue(std::nullptr_t) : value(nullptr) {}
JsonValue::JsonValue(bool input) : value(input) {}
JsonValue::JsonValue(double input) : value(input) {}
JsonValue::JsonValue(int input) : value(static_cast<double>(input)) {}
JsonValue::JsonValue(const char* input) : value(std::string(input)) {}
JsonValue::JsonValue(std::string input) : value(std::move(input)) {}
JsonValue::JsonValue(JsonArray input) : value(std::move(input)) {}
JsonValue::JsonValue(JsonObject input) : value(std::move(input)) {}
bool JsonValue::IsObject() const { return std::holds_alternative<JsonObject>(value); }
bool JsonValue::IsArray() const { return std::holds_alternative<JsonArray>(value); }
bool JsonValue::IsString() const { return std::holds_alternative<std::string>(value); }
bool JsonValue::IsBool() const { return std::holds_alternative<bool>(value); }
bool JsonValue::IsNumber() const { return std::holds_alternative<double>(value); }
const JsonObject& JsonValue::AsObject() const { return std::get<JsonObject>(value); }
const JsonArray& JsonValue::AsArray() const { return std::get<JsonArray>(value); }
const std::string& JsonValue::AsString() const { return std::get<std::string>(value); }
bool JsonValue::AsBool() const { return std::get<bool>(value); }
double JsonValue::AsNumber() const { return std::get<double>(value); }
namespace {
void AppendUtf8(std::string& output, std::uint32_t codepoint) {
if (codepoint <= 0x7F) {
output.push_back(static_cast<char>(codepoint));
} else if (codepoint <= 0x7FF) {
output.push_back(static_cast<char>(0xC0 | (codepoint >> 6)));
output.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
} else if (codepoint <= 0xFFFF) {
output.push_back(static_cast<char>(0xE0 | (codepoint >> 12)));
output.push_back(static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F)));
output.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
} else if (codepoint <= 0x10FFFF) {
output.push_back(static_cast<char>(0xF0 | (codepoint >> 18)));
output.push_back(static_cast<char>(0x80 | ((codepoint >> 12) & 0x3F)));
output.push_back(static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F)));
output.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
} else {
throw std::runtime_error("invalid JSON unicode escape");
}
}
class Parser {
public:
explicit Parser(const std::string& input) : input_(input), index_(0) {}
JsonValue Parse() {
SkipWhitespace();
JsonValue value = ParseValue();
SkipWhitespace();
if (index_ != input_.size()) {
throw std::runtime_error("unexpected trailing JSON data");
}
return value;
}
private:
JsonValue ParseValue() {
SkipWhitespace();
if (Match("null")) {
return JsonValue(nullptr);
}
if (Match("true")) {
return JsonValue(true);
}
if (Match("false")) {
return JsonValue(false);
}
if (Peek() == '"') {
return JsonValue(ParseString());
}
if (Peek() == '{') {
return JsonValue(ParseObject());
}
if (Peek() == '[') {
return JsonValue(ParseArray());
}
if (Peek() == '-' || std::isdigit(static_cast<unsigned char>(Peek()))) {
return JsonValue(ParseNumber());
}
throw std::runtime_error("unexpected JSON token");
}
JsonObject ParseObject() {
Expect('{');
JsonObject object;
SkipWhitespace();
if (Peek() == '}') {
Advance();
return object;
}
while (true) {
const std::string key = ParseString();
SkipWhitespace();
Expect(':');
object.emplace(key, ParseValue());
SkipWhitespace();
if (Peek() == '}') {
Advance();
break;
}
Expect(',');
}
return object;
}
JsonArray ParseArray() {
Expect('[');
JsonArray array;
SkipWhitespace();
if (Peek() == ']') {
Advance();
return array;
}
while (true) {
array.emplace_back(ParseValue());
SkipWhitespace();
if (Peek() == ']') {
Advance();
break;
}
Expect(',');
}
return array;
}
std::string ParseString() {
Expect('"');
std::string output;
while (index_ < input_.size()) {
const char current = input_[index_++];
if (current == '"') {
return output;
}
if (current == '\\') {
if (index_ >= input_.size()) {
throw std::runtime_error("invalid JSON escape");
}
const char escaped = input_[index_++];
switch (escaped) {
case '"':
case '\\':
case '/':
output.push_back(escaped);
break;
case 'b':
output.push_back('\b');
break;
case 'f':
output.push_back('\f');
break;
case 'n':
output.push_back('\n');
break;
case 'r':
output.push_back('\r');
break;
case 't':
output.push_back('\t');
break;
case 'u':
AppendUtf8(output, ParseUnicodeEscape());
break;
default:
throw std::runtime_error("unsupported JSON escape");
}
continue;
}
output.push_back(current);
}
throw std::runtime_error("unterminated JSON string");
}
std::uint32_t ParseUnicodeEscape() {
const std::uint32_t first = ParseHexQuad();
if (first >= 0xD800 && first <= 0xDBFF) {
if (index_ + 1 >= input_.size() || input_[index_] != '\\' || input_[index_ + 1] != 'u') {
throw std::runtime_error("invalid JSON unicode surrogate");
}
index_ += 2;
const std::uint32_t second = ParseHexQuad();
if (second < 0xDC00 || second > 0xDFFF) {
throw std::runtime_error("invalid JSON unicode surrogate");
}
return 0x10000 + (((first - 0xD800) << 10) | (second - 0xDC00));
}
if (first >= 0xDC00 && first <= 0xDFFF) {
throw std::runtime_error("invalid JSON unicode surrogate");
}
return first;
}
std::uint32_t ParseHexQuad() {
if (index_ + 4 > input_.size()) {
throw std::runtime_error("invalid JSON unicode escape");
}
std::uint32_t value = 0;
for (int i = 0; i < 4; ++i) {
const char ch = input_[index_++];
value <<= 4;
if (ch >= '0' && ch <= '9') {
value |= static_cast<std::uint32_t>(ch - '0');
} else if (ch >= 'a' && ch <= 'f') {
value |= static_cast<std::uint32_t>(ch - 'a' + 10);
} else if (ch >= 'A' && ch <= 'F') {
value |= static_cast<std::uint32_t>(ch - 'A' + 10);
} else {
throw std::runtime_error("invalid JSON unicode escape");
}
}
return value;
}
double ParseNumber() {
const std::size_t start = index_;
if (Peek() == '-') {
Advance();
}
while (std::isdigit(static_cast<unsigned char>(Peek()))) {
Advance();
}
if (Peek() == '.') {
Advance();
while (std::isdigit(static_cast<unsigned char>(Peek()))) {
Advance();
}
}
return std::stod(input_.substr(start, index_ - start));
}
void SkipWhitespace() {
while (index_ < input_.size() && std::isspace(static_cast<unsigned char>(input_[index_]))) {
++index_;
}
}
char Peek() const {
if (index_ >= input_.size()) {
return '\0';
}
return input_[index_];
}
void Advance() {
if (index_ < input_.size()) {
++index_;
}
}
void Expect(char expected) {
SkipWhitespace();
if (Peek() != expected) {
throw std::runtime_error("unexpected JSON character");
}
Advance();
}
bool Match(const char* keyword) {
const std::size_t length = std::char_traits<char>::length(keyword);
if (input_.substr(index_, length) == keyword) {
index_ += length;
return true;
}
return false;
}
const std::string& input_;
std::size_t index_;
};
std::string Escape(const std::string& value) {
std::ostringstream output;
for (const char ch : value) {
switch (ch) {
case '"':
output << "\\\"";
break;
case '\\':
output << "\\\\";
break;
case '\n':
output << "\\n";
break;
case '\r':
output << "\\r";
break;
case '\t':
output << "\\t";
break;
default:
output << ch;
break;
}
}
return output.str();
}
std::string SerializeInternal(const JsonValue& value) {
if (std::holds_alternative<std::nullptr_t>(value.value)) {
return "null";
}
if (std::holds_alternative<bool>(value.value)) {
return std::get<bool>(value.value) ? "true" : "false";
}
if (std::holds_alternative<double>(value.value)) {
std::ostringstream output;
output << std::get<double>(value.value);
return output.str();
}
if (std::holds_alternative<std::string>(value.value)) {
return "\"" + Escape(std::get<std::string>(value.value)) + "\"";
}
if (std::holds_alternative<JsonArray>(value.value)) {
std::ostringstream output;
output << "[";
const auto& array = std::get<JsonArray>(value.value);
for (std::size_t i = 0; i < array.size(); ++i) {
if (i > 0) {
output << ",";
}
output << SerializeInternal(array[i]);
}
output << "]";
return output.str();
}
std::ostringstream output;
output << "{";
const auto& object = std::get<JsonObject>(value.value);
bool first = true;
for (const auto& [key, child] : object) {
if (!first) {
output << ",";
}
first = false;
output << "\"" << Escape(key) << "\":" << SerializeInternal(child);
}
output << "}";
return output.str();
}
} // namespace
JsonValue ParseJson(const std::string& input) {
return Parser(input).Parse();
}
std::string SerializeJson(const JsonValue& value) {
return SerializeInternal(value);
}
std::optional<std::string> GetString(const JsonObject& object, const std::string& key) {
auto iterator = object.find(key);
if (iterator == object.end() || !iterator->second.IsString()) {
return std::nullopt;
}
return iterator->second.AsString();
}
std::optional<bool> GetBool(const JsonObject& object, const std::string& key) {
auto iterator = object.find(key);
if (iterator == object.end() || !iterator->second.IsBool()) {
return std::nullopt;
}
return iterator->second.AsBool();
}
std::optional<double> GetNumber(const JsonObject& object, const std::string& key) {
auto iterator = object.find(key);
if (iterator == object.end() || !iterator->second.IsNumber()) {
return std::nullopt;
}
return iterator->second.AsNumber();
}
const JsonObject* GetObject(const JsonObject& object, const std::string& key) {
auto iterator = object.find(key);
if (iterator == object.end() || !iterator->second.IsObject()) {
return nullptr;
}
return &iterator->second.AsObject();
}
const JsonArray* GetArray(const JsonObject& object, const std::string& key) {
auto iterator = object.find(key);
if (iterator == object.end() || !iterator->second.IsArray()) {
return nullptr;
}
return &iterator->second.AsArray();
}
} // namespace rdp_worker::common
+52
View File
@@ -0,0 +1,52 @@
#include "rdp_worker/common/logger.hpp"
#include <iostream>
#include "rdp_worker/common/time.hpp"
namespace rdp_worker::common {
namespace {
std::string LevelToString(LogLevel level) {
switch (level) {
case LogLevel::kDebug:
return "DEBUG";
case LogLevel::kInfo:
return "INFO";
case LogLevel::kWarn:
return "WARN";
case LogLevel::kError:
return "ERROR";
}
return "INFO";
}
} // namespace
Logger::Logger(std::string service_name) : service_name_(std::move(service_name)) {}
void Logger::Debug(const std::string& message) {
Write(LogLevel::kDebug, message);
}
void Logger::Info(const std::string& message) {
Write(LogLevel::kInfo, message);
}
void Logger::Warn(const std::string& message) {
Write(LogLevel::kWarn, message);
}
void Logger::Error(const std::string& message) {
Write(LogLevel::kError, message);
}
void Logger::Write(LogLevel level, const std::string& message) {
std::lock_guard<std::mutex> lock(mutex_);
std::cout << "{\"ts\":\"" << ToRfc3339(NowUtc()) << "\",\"service\":\"" << service_name_
<< "\",\"level\":\"" << LevelToString(level) << "\",\"message\":\"" << message
<< "\"}" << std::endl;
}
} // namespace rdp_worker::common
+42
View File
@@ -0,0 +1,42 @@
#include "rdp_worker/common/time.hpp"
#include <ctime>
#include <iomanip>
#include <sstream>
#include <stdexcept>
namespace rdp_worker::common {
Clock::time_point NowUtc() {
return Clock::now();
}
std::string ToRfc3339(Clock::time_point time_point) {
const std::time_t raw = Clock::to_time_t(time_point);
std::tm utc_tm{};
#if defined(_WIN32)
gmtime_s(&utc_tm, &raw);
#else
gmtime_r(&raw, &utc_tm);
#endif
std::ostringstream output;
output << std::put_time(&utc_tm, "%Y-%m-%dT%H:%M:%SZ");
return output.str();
}
Clock::time_point ParseRfc3339(const std::string& value) {
std::tm utc_tm{};
std::istringstream input(value);
input >> std::get_time(&utc_tm, "%Y-%m-%dT%H:%M:%SZ");
if (input.fail()) {
throw std::runtime_error("failed to parse RFC3339 timestamp: " + value);
}
#if defined(_WIN32)
const std::time_t raw = _mkgmtime(&utc_tm);
#else
const std::time_t raw = timegm(&utc_tm);
#endif
return Clock::from_time_t(raw);
}
} // namespace rdp_worker::common