Complete
Some checks failed
CI / build-linux-and-wasm (x64-linux) (push) Has been cancelled

This commit is contained in:
2026-01-22 21:45:17 +05:30
parent 3ad1e3a2fb
commit 6acc2cb45d
14 changed files with 690 additions and 342 deletions

View File

@ -46,6 +46,8 @@ CheckOptions:
# Public struct members (like a Rust struct) -> x, y, width # Public struct members (like a Rust struct) -> x, y, width
- key: readability-identifier-naming.PublicMemberCase - key: readability-identifier-naming.PublicMemberCase
value: lower_case value: lower_case
- key: readability-identifier-naming.StructMemberIgnoredRegexp
value: ^_[a-z0-9_]*$
# Private/Protected class members -> m_parser, m_count # Private/Protected class members -> m_parser, m_count
- key: readability-identifier-naming.PrivateMemberCase - key: readability-identifier-naming.PrivateMemberCase

View File

@ -17,7 +17,7 @@ set(SRC_FILES
"imp/cpp/StreamReader.cpp" "imp/cpp/StreamReader.cpp"
"imp/cpp/StreamWriter.cpp" "imp/cpp/StreamWriter.cpp"
#"imp/cpp/Http/Common.cpp" "imp/cpp/Http/Common.cpp"
#"imp/cpp/Http/Client.cpp" #"imp/cpp/Http/Client.cpp"
#"imp/cpp/Http/Server.cpp" #"imp/cpp/Http/Server.cpp"
) )

View File

@ -17,61 +17,73 @@
#include <IACore/Http/Client.hpp> #include <IACore/Http/Client.hpp>
namespace IACore { namespace IACore {
Result<UniquePtr<HttpClient>> HttpClient::Create(const String &host) { // Helper struct to access protected constructor via make_box (std::make_unique)
return MakeUniqueProtected<HttpClient>(httplib::Client(host)); struct PublicHttpClient : public HttpClient {
explicit PublicHttpClient(httplib::Client &&client)
: HttpClient(std::move(client)) {}
};
auto HttpClient::create(const String &host) -> Result<Box<HttpClient>> {
return make_box<PublicHttpClient>(httplib::Client(host));
} }
httplib::Headers BuildHeaders(Span<const HttpClient::Header> headers, static auto build_headers(Span<const HttpClient::Header> headers,
const char *defaultContentType) { const char *default_content_type)
-> httplib::Headers {
httplib::Headers out; httplib::Headers out;
bool hasContentType = false; bool has_content_type = false;
for (const auto &h : headers) { for (const auto &h : headers) {
out.emplace(h.first, h.second); out.emplace(h.first, h.second);
if (h.first == if (h.first == HttpClient::header_type_to_string(
HttpClient::HeaderTypeToString(HttpClient::EHeaderType::CONTENT_TYPE)) HttpClient::EHeaderType::CONTENT_TYPE)) {
hasContentType = true; has_content_type = true;
}
} }
if (!hasContentType && defaultContentType) if (!has_content_type && default_content_type) {
out.emplace("Content-Type", defaultContentType); out.emplace("Content-Type", default_content_type);
}
return out; return out;
} }
HttpClient::HttpClient(httplib::Client &&client) HttpClient::HttpClient(httplib::Client &&client)
: m_client(std::move(client)), : m_client(std::move(client)),
m_lastResponseCode(EResponseCode::INTERNAL_SERVER_ERROR) { m_last_response_code(EResponseCode::INTERNAL_SERVER_ERROR) {
m_client.enable_server_certificate_verification(true); m_client.enable_server_certificate_verification(true);
} }
HttpClient::~HttpClient() {} HttpClient::~HttpClient() = default;
void HttpClient::EnableCertificateVerfication() { void HttpClient::enable_certificate_verification() {
m_client.enable_server_certificate_verification(true); m_client.enable_server_certificate_verification(true);
} }
void HttpClient::DisableCertificateVerfication() { void HttpClient::disable_certificate_verification() {
m_client.enable_server_certificate_verification(false); m_client.enable_server_certificate_verification(false);
} }
String HttpClient::PreprocessResponse(const String &response) { auto HttpClient::preprocess_response(const String &response) -> String {
const auto responseBytes = const auto response_bytes = Span<const u8>{
Span<const u8>{(const u8 *)response.data(), response.size()}; reinterpret_cast<const u8 *>(response.data()), response.size()};
const auto compression = DataOps::DetectCompression(responseBytes); const auto compression = DataOps::detect_compression(response_bytes);
switch (compression) { switch (compression) {
case DataOps::CompressionType::Gzip: { case DataOps::CompressionType::Gzip: {
const auto data = DataOps::GZipInflate(responseBytes); const auto data = DataOps::gzip_inflate(response_bytes);
if (!data) if (!data) {
return response; return response;
return String((const char *)data->data(), data->size()); }
return String(reinterpret_cast<const char *>(data->data()), data->size());
} }
case DataOps::CompressionType::Zlib: { case DataOps::CompressionType::Zlib: {
const auto data = DataOps::ZlibInflate(responseBytes); const auto data = DataOps::zlib_inflate(response_bytes);
if (!data) if (!data) {
return response; return response;
return String((const char *)data->data(), data->size()); }
return String(reinterpret_cast<const char *>(data->data()), data->size());
} }
case DataOps::CompressionType::None: case DataOps::CompressionType::None:
@ -81,53 +93,58 @@ String HttpClient::PreprocessResponse(const String &response) {
return response; return response;
} }
Result<String> auto HttpClient::raw_get(const String &path, Span<const Header> headers,
const char *default_content_type) -> Result<String> {
auto http_headers = build_headers(headers, default_content_type);
HttpClient::RawGet(const String &path, Span<const Header> headers, String adjusted_path = path;
const char *defaultContentType) { if (!path.empty() && path[0] != '/') {
auto httpHeaders = BuildHeaders(headers, defaultContentType); adjusted_path = "/" + path;
auto res = m_client.Get(
(!path.empty() && path[0] != '/') ? ('/' + path).c_str() : path.c_str(),
httpHeaders);
if (res) {
m_lastResponseCode = static_cast<EResponseCode>(res->status);
if (res->status >= 200 && res->status < 300)
return PreprocessResponse(res->body);
else
return (std::format("HTTP Error {} : {}", res->status, res->body));
} }
return (std::format("Network Error: {}", httplib::to_string(res.error()))); auto res = m_client.Get(adjusted_path.c_str(), http_headers);
if (res) {
m_last_response_code = static_cast<EResponseCode>(res->status);
if (res->status >= 200 && res->status < 300) {
return preprocess_response(res->body);
}
return fail("HTTP Error {} : {}", res->status, res->body);
}
return fail("Network Error: {}", httplib::to_string(res.error()));
} }
Result<String> auto HttpClient::raw_post(const String &path, Span<const Header> headers,
const String &body, const char *default_content_type)
-> Result<String> {
auto http_headers = build_headers(headers, default_content_type);
HttpClient::RawPost(const String &path, Span<const Header> headers, String content_type = default_content_type;
const String &body, const char *defaultContentType) { if (http_headers.count("Content-Type")) {
auto httpHeaders = BuildHeaders(headers, defaultContentType); const auto t = http_headers.find("Content-Type");
content_type = t->second;
String contentType = defaultContentType; http_headers.erase(t);
if (httpHeaders.count("Content-Type")) {
const auto t = httpHeaders.find("Content-Type");
contentType = t->second;
httpHeaders.erase(t);
} }
m_client.set_keep_alive(true); m_client.set_keep_alive(true);
auto res = m_client.Post(
(!path.empty() && path[0] != '/') ? ('/' + path).c_str() : path.c_str(),
httpHeaders, body, contentType.c_str());
if (res) { String adjusted_path = path;
m_lastResponseCode = static_cast<EResponseCode>(res->status); if (!path.empty() && path[0] != '/') {
if (res->status >= 200 && res->status < 300) adjusted_path = "/" + path;
return PreprocessResponse(res->body);
else
return (std::format("HTTP Error {} : {}", res->status, res->body));
} }
return (std::format("Network Error: {}", httplib::to_string(res.error()))); auto res = m_client.Post(adjusted_path.c_str(), http_headers, body,
content_type.c_str());
if (res) {
m_last_response_code = static_cast<EResponseCode>(res->status);
if (res->status >= 200 && res->status < 300) {
return preprocess_response(res->body);
}
return fail("HTTP Error {} : {}", res->status, res->body);
}
return fail("Network Error: {}", httplib::to_string(res.error()));
} }
} // namespace IACore } // namespace IACore

View File

@ -16,7 +16,7 @@
#include <IACore/Http/Common.hpp> #include <IACore/Http/Common.hpp>
namespace IACore { namespace IACore {
String HttpCommon::UrlEncode(const String &value) { auto HttpCommon::url_encode(const String &value) -> String {
std::stringstream escaped; std::stringstream escaped;
escaped.fill('0'); escaped.fill('0');
escaped << std::hex << std::uppercase; escaped << std::hex << std::uppercase;
@ -33,16 +33,16 @@ String HttpCommon::UrlEncode(const String &value) {
return escaped.str(); return escaped.str();
} }
String HttpCommon::UrlDecode(const String &value) { auto HttpCommon::url_decode(const String &value) -> String {
String result; String result;
result.reserve(value.length()); result.reserve(value.length());
for (size_t i = 0; i < value.length(); ++i) { for (size_t i = 0; i < value.length(); ++i) {
if (value[i] == '%' && i + 2 < value.length()) { if (value[i] == '%' && i + 2 < value.length()) {
std::string hexStr = value.substr(i + 1, 2); std::string hex_str = value.substr(i + 1, 2);
char decodedChar = char decoded_char =
static_cast<char>(std::strtol(hexStr.c_str(), nullptr, 16)); static_cast<char>(std::strtol(hex_str.c_str(), nullptr, 16));
result += decodedChar; result += decoded_char;
i += 2; i += 2;
} else if (value[i] == '+') } else if (value[i] == '+')
result += ' '; result += ' ';
@ -53,7 +53,7 @@ String HttpCommon::UrlDecode(const String &value) {
return result; return result;
} }
String HttpCommon::HeaderTypeToString(EHeaderType type) { auto HttpCommon::header_type_to_string(EHeaderType type) -> String {
switch (type) { switch (type) {
case EHeaderType::ACCEPT: case EHeaderType::ACCEPT:
return "Accept"; return "Accept";
@ -111,7 +111,7 @@ String HttpCommon::HeaderTypeToString(EHeaderType type) {
return "<Unknown>"; return "<Unknown>";
} }
bool HttpCommon::IsSuccessResponseCode(EResponseCode code) { auto HttpCommon::is_success_response_code(EResponseCode code) -> bool {
return (i32)code >= 200 && (i32)code < 300; return (i32)code >= 200 && (i32)code < 300;
} }
} // namespace IACore } // namespace IACore

View File

@ -15,7 +15,159 @@
#include <IACore/Http/Server.hpp> #include <IACore/Http/Server.hpp>
namespace IACore namespace IACore {
{ // =============================================================================
// Request Implementation
// =============================================================================
} auto HttpServer::Request::get_header(const String &key) const -> String {
if (auto it = headers.find(key); it != headers.end()) {
return it->second;
}
return "";
}
auto HttpServer::Request::get_param(const String &key) const -> String {
if (auto it = params.find(key); it != params.end()) {
return it->second;
}
return "";
}
auto HttpServer::Request::get_path_param(const String &key) const -> String {
if (auto it = path_params.find(key); it != path_params.end()) {
return it->second;
}
return "";
}
auto HttpServer::Request::has_header(const String &key) const -> bool {
return headers.contains(key);
}
auto HttpServer::Request::has_param(const String &key) const -> bool {
return params.contains(key);
}
auto HttpServer::Request::has_path_param(const String &key) const -> bool {
return path_params.contains(key);
}
// =============================================================================
// Response Implementation
// =============================================================================
void HttpServer::Response::set_content(const String &content,
const String &type) {
body = content;
content_type = type;
}
void HttpServer::Response::set_status(EResponseCode status_code) {
code = status_code;
}
void HttpServer::Response::add_header(const String &key, const String &value) {
headers[key] = value;
}
// =============================================================================
// HttpServer Implementation
// =============================================================================
struct PublicHttpServer : public HttpServer {
PublicHttpServer() = default;
};
HttpServer::HttpServer() = default;
HttpServer::~HttpServer() { stop(); }
auto HttpServer::create() -> Result<Box<HttpServer>> {
return make_box<PublicHttpServer>();
}
auto HttpServer::listen(const String &host, u32 port) -> Result<void> {
if (!m_server.listen(host.c_str(), static_cast<int>(port))) {
return fail("Failed to start HTTP server on {}:{}", host, port);
}
return {};
}
void HttpServer::stop() {
if (m_server.is_running()) {
m_server.stop();
}
}
auto HttpServer::is_running() const -> bool { return m_server.is_running(); }
void HttpServer::register_handler(const String &method, const String &pattern,
Handler handler) {
auto wrapper = [handler = std::move(handler)](const httplib::Request &req,
httplib::Response &res) {
Request ia_req;
ia_req.path = req.path;
ia_req.method = req.method;
ia_req.body = req.body;
// Convert headers
for (const auto &item : req.headers) {
ia_req.headers[item.first] = item.second;
}
// Convert params
for (const auto &item : req.params) {
ia_req.params[item.first] = item.second;
}
// Convert path params
for (const auto &item : req.path_params) {
ia_req.path_params[item.first] = item.second;
}
Response ia_res;
handler(ia_req, ia_res);
res.status = static_cast<int>(ia_res.code);
res.set_content(ia_res.body, ia_res.content_type.c_str());
for (const auto &item : ia_res.headers) {
res.set_header(item.first.c_str(), item.second.c_str());
}
};
if (method == "GET") {
m_server.Get(pattern.c_str(), wrapper);
} else if (method == "POST") {
m_server.Post(pattern.c_str(), wrapper);
} else if (method == "PUT") {
m_server.Put(pattern.c_str(), wrapper);
} else if (method == "DELETE") {
m_server.Delete(pattern.c_str(), wrapper);
} else if (method == "OPTIONS") {
m_server.Options(pattern.c_str(), wrapper);
}
}
void HttpServer::get(const String &pattern, Handler handler) {
register_handler("GET", pattern, std::move(handler));
}
void HttpServer::post(const String &pattern, Handler handler) {
register_handler("POST", pattern, std::move(handler));
}
void HttpServer::put(const String &pattern, Handler handler) {
register_handler("PUT", pattern, std::move(handler));
}
void HttpServer::del(const String &pattern, Handler handler) {
register_handler("DELETE", pattern, std::move(handler));
}
void HttpServer::options(const String &pattern, Handler handler) {
register_handler("OPTIONS", pattern, std::move(handler));
}
} // namespace IACore

View File

@ -114,13 +114,17 @@ auto IpcNode::connect(const char *connection_string) -> Result<void> {
u8 *moni_ptr = m_shared_memory + layout->moni_data_offset; u8 *moni_ptr = m_shared_memory + layout->moni_data_offset;
u8 *mino_ptr = m_shared_memory + layout->mino_data_offset; u8 *mino_ptr = m_shared_memory + layout->mino_data_offset;
m_moni = make_box<RingBufferView>( IA_TRY(m_moni,
&layout->moni_control, RingBufferView::create(
Span<u8>(moni_ptr, static_cast<usize>(layout->moni_data_size)), false); &layout->moni_control,
Span<u8>(moni_ptr, static_cast<usize>(layout->moni_data_size)),
false));
m_mino = make_box<RingBufferView>( IA_TRY(m_mino,
&layout->mino_control, RingBufferView::create(
Span<u8>(mino_ptr, static_cast<usize>(layout->mino_data_size)), false); &layout->mino_control,
Span<u8>(mino_ptr, static_cast<usize>(layout->mino_data_size)),
false));
#if IA_PLATFORM_WINDOWS #if IA_PLATFORM_WINDOWS
u_long mode = 1; u_long mode = 1;
@ -135,14 +139,14 @@ auto IpcNode::connect(const char *connection_string) -> Result<void> {
} }
void IpcNode::update() { void IpcNode::update() {
if (!m_moni) { if (!m_moni.is_valid()) {
return; return;
} }
IpcPacketHeader header; IpcPacketHeader header;
// Process all available messages from Manager // Process all available messages from Manager
while (m_moni->pop( while (m_moni.pop(
header, Span<u8>(m_receive_buffer.data(), m_receive_buffer.size()))) { header, Span<u8>(m_receive_buffer.data(), m_receive_buffer.size()))) {
on_packet(header.id, {m_receive_buffer.data(), header.payload_size}); on_packet(header.id, {m_receive_buffer.data(), header.payload_size});
} }
@ -166,10 +170,11 @@ void IpcNode::send_signal(u8 signal) {
} }
} }
void IpcNode::send_packet(u16 packet_id, Span<const u8> payload) { auto IpcNode::send_packet(u16 packet_id, Span<const u8> payload)
if (m_mino) { -> Result<void> {
m_mino->push(packet_id, payload); if (!m_mino.is_valid())
} return fail("invalid MINO");
return m_mino.push(packet_id, payload);
} }
// ============================================================================= // =============================================================================
@ -183,18 +188,19 @@ void IpcManager::NodeSession::send_signal(u8 signal) {
} }
} }
void IpcManager::NodeSession::send_packet(u16 packet_id, auto IpcManager::NodeSession::send_packet(u16 packet_id, Span<const u8> payload)
Span<const u8> payload) { -> Result<void> {
// Protect the RingBuffer write cursor from concurrent threads // Protect the RingBuffer write cursor from concurrent threads
std::scoped_lock lock(send_mutex); std::scoped_lock lock(send_mutex);
if (moni) { if (!moni.is_valid())
moni->push(packet_id, payload); return fail("invalid MONI");
} return moni.push(packet_id, payload);
} }
IpcManager::IpcManager() { IpcManager::IpcManager() {
// SocketOps is smart enough to track multiple inits ensure(SocketOps::is_initialized(),
SocketOps::initialize(); "SocketOps must be initialized before using IpcManager");
m_receive_buffer.resize(UINT16_MAX + 1); m_receive_buffer.resize(UINT16_MAX + 1);
} }
@ -214,9 +220,6 @@ IpcManager::~IpcManager() {
SocketOps::close(session->listener_socket); SocketOps::close(session->listener_socket);
} }
m_pending_sessions.clear(); m_pending_sessions.clear();
// SocketOps is smart enough to track multiple terminates
SocketOps::terminate();
} }
void IpcManager::update() { void IpcManager::update() {
@ -270,7 +273,7 @@ void IpcManager::update() {
IpcPacketHeader header; IpcPacketHeader header;
while (node->mino->pop( while (node->mino.pop(
header, Span<u8>(m_receive_buffer.data(), m_receive_buffer.size()))) { header, Span<u8>(m_receive_buffer.data(), m_receive_buffer.size()))) {
on_packet(node_id, header.id, on_packet(node_id, header.id,
{m_receive_buffer.data(), header.payload_size}); {m_receive_buffer.data(), header.payload_size});
@ -345,17 +348,19 @@ auto IpcManager::spawn_node(const Path &executable_path, u32 shared_memory_size)
layout->mino_data_offset = header_size + half_size; layout->mino_data_offset = header_size + half_size;
layout->mino_data_size = half_size; layout->mino_data_size = half_size;
session->moni = make_box<RingBufferView>( IA_TRY(session->moni,
&layout->moni_control, RingBufferView::create(
Span<u8>(session->mapped_ptr + layout->moni_data_offset, &layout->moni_control,
static_cast<usize>(layout->moni_data_size)), Span<u8>(session->mapped_ptr + layout->moni_data_offset,
true); static_cast<usize>(layout->moni_data_size)),
true));
session->mino = make_box<RingBufferView>( IA_TRY(session->mino,
&layout->mino_control, RingBufferView::create(
Span<u8>(session->mapped_ptr + layout->mino_data_offset, &layout->mino_control,
static_cast<usize>(layout->mino_data_size)), Span<u8>(session->mapped_ptr + layout->mino_data_offset,
true); static_cast<usize>(layout->mino_data_size)),
true));
IpcConnectionDescriptor desc; IpcConnectionDescriptor desc;
desc.socket_path = sock_path; desc.socket_path = sock_path;
@ -446,12 +451,11 @@ void IpcManager::send_signal(NativeProcessID node, u8 signal) {
it_node->second->send_signal(signal); it_node->second->send_signal(signal);
} }
void IpcManager::send_packet(NativeProcessID node, u16 packet_id, auto IpcManager::send_packet(NativeProcessID node, u16 packet_id,
Span<const u8> payload) { Span<const u8> payload) -> Result<void> {
const auto it_node = m_active_session_map.find(node); const auto it_node = m_active_session_map.find(node);
if (it_node == m_active_session_map.end()) { if (it_node == m_active_session_map.end())
return; return fail("no such node");
} return it_node->second->send_packet(packet_id, payload);
it_node->second->send_packet(packet_id, payload);
} }
} // namespace IACore } // namespace IACore

View File

@ -53,8 +53,11 @@ public:
}; };
public: public:
RingBufferView(Span<u8> buffer, bool is_owner); static auto default_instance() -> RingBufferView;
RingBufferView(ControlBlock *control_block, Span<u8> buffer, bool is_owner);
static auto create(Span<u8> buffer, bool is_owner) -> Result<RingBufferView>;
static auto create(ControlBlock *control_block, Span<u8> buffer,
bool is_owner) -> Result<RingBufferView>;
// Returns: // Returns:
// - Ok(nullopt) if empty // - Ok(nullopt) if empty
@ -70,6 +73,12 @@ public:
auto get_control_block() -> ControlBlock *; auto get_control_block() -> ControlBlock *;
[[nodiscard]] auto is_valid() const -> bool;
protected:
RingBufferView(Span<u8> buffer, bool is_owner);
RingBufferView(ControlBlock *control_block, Span<u8> buffer, bool is_owner);
private: private:
u8 *m_data_ptr{}; u8 *m_data_ptr{};
u32 m_capacity{}; u32 m_capacity{};
@ -84,10 +93,40 @@ private:
// Implementation // Implementation
// ============================================================================= // =============================================================================
inline RingBufferView::RingBufferView(Span<u8> buffer, bool is_owner) { auto RingBufferView::default_instance() -> RingBufferView {
ensure(buffer.size() > sizeof(ControlBlock), return RingBufferView(nullptr, {}, false);
"Buffer too small for ControlBlock"); }
inline auto RingBufferView::create(Span<u8> buffer, bool is_owner)
-> Result<RingBufferView> {
if (buffer.size() <= sizeof(ControlBlock)) {
return fail("Buffer too small for ControlBlock");
}
if (!is_owner) {
auto *cb = reinterpret_cast<ControlBlock *>(buffer.data());
u32 capacity = static_cast<u32>(buffer.size()) - sizeof(ControlBlock);
if (cb->consumer.capacity != capacity) {
return fail("Capacity mismatch");
}
}
return RingBufferView(buffer, is_owner);
}
inline auto RingBufferView::create(ControlBlock *control_block, Span<u8> buffer,
bool is_owner) -> Result<RingBufferView> {
if (control_block == nullptr) {
return fail("ControlBlock is null");
}
if (buffer.empty()) {
return fail("Buffer is empty");
}
return RingBufferView(control_block, buffer, is_owner);
}
inline RingBufferView::RingBufferView(Span<u8> buffer, bool is_owner) {
m_control_block = reinterpret_cast<ControlBlock *>(buffer.data()); m_control_block = reinterpret_cast<ControlBlock *>(buffer.data());
m_data_ptr = buffer.data() + sizeof(ControlBlock); m_data_ptr = buffer.data() + sizeof(ControlBlock);
@ -97,17 +136,11 @@ inline RingBufferView::RingBufferView(Span<u8> buffer, bool is_owner) {
m_control_block->consumer.capacity = m_capacity; m_control_block->consumer.capacity = m_capacity;
m_control_block->producer.write_offset.store(0, std::memory_order_release); m_control_block->producer.write_offset.store(0, std::memory_order_release);
m_control_block->consumer.read_offset.store(0, std::memory_order_release); m_control_block->consumer.read_offset.store(0, std::memory_order_release);
} else {
ensure(m_control_block->consumer.capacity == m_capacity,
"Capacity mismatch");
} }
} }
inline RingBufferView::RingBufferView(ControlBlock *control_block, inline RingBufferView::RingBufferView(ControlBlock *control_block,
Span<u8> buffer, bool is_owner) { Span<u8> buffer, bool is_owner) {
ensure(control_block != nullptr, "ControlBlock is null");
ensure(!buffer.empty(), "Buffer is empty");
m_control_block = control_block; m_control_block = control_block;
m_data_ptr = buffer.data(); m_data_ptr = buffer.data();
m_capacity = static_cast<u32>(buffer.size()); m_capacity = static_cast<u32>(buffer.size());
@ -154,8 +187,9 @@ inline auto RingBufferView::pop(PacketHeader &out_header, Span<u8> out_buffer)
inline auto RingBufferView::push(u16 packet_id, Span<const u8> data) inline auto RingBufferView::push(u16 packet_id, Span<const u8> data)
-> Result<void> { -> Result<void> {
ensure(data.size() <= std::numeric_limits<u16>::max(), if (data.size() > std::numeric_limits<u16>::max()) {
"Data size exceeds u16 limit"); return fail("Data size exceeds u16 limit");
}
const u32 total_size = sizeof(PacketHeader) + static_cast<u32>(data.size()); const u32 total_size = sizeof(PacketHeader) + static_cast<u32>(data.size());
@ -228,4 +262,8 @@ inline auto RingBufferView::read_wrapped(u32 offset, void *out_data, u32 size)
} }
} }
[[nodiscard]] auto RingBufferView::is_valid() const -> bool {
return m_control_block && m_data_ptr && m_capacity;
}
} // namespace IACore } // namespace IACore

View File

@ -17,26 +17,63 @@
#include <IACore/PCH.hpp> #include <IACore/PCH.hpp>
#if IA_PLATFORM_WINDOWS #if !IA_PLATFORM_WINDOWS
#include <errhandlingapi.h>
#include <libloaderapi.h>
#else
#include <dlfcn.h> #include <dlfcn.h>
#endif #endif
// Ensure 'ia' namespace alias exists for IA_TRY macro usage
namespace ia = IACore;
namespace IACore { namespace IACore {
class DynamicLib { class DynamicLib {
public: public:
DynamicLib() : m_handle(nullptr) {} // Factory method to load a dynamic library
[[nodiscard]] static auto load(const String &search_path, const String &name)
-> Result<DynamicLib> {
namespace fs = std::filesystem;
auto full_path = fs::path(search_path) / name;
DynamicLib(DynamicLib &&other) NOEXCEPT : m_handle(other.m_handle) { if (!full_path.has_extension()) {
#if IA_PLATFORM_WINDOWS
full_path += ".dll";
#elif IA_PLATFORM_APPLE
full_path += ".dylib";
#else
full_path += ".so";
#endif
}
DynamicLib lib;
#if IA_PLATFORM_WINDOWS
const HMODULE h = LoadLibraryA(full_path.string().c_str());
if (!h) {
return fail(get_windows_error());
}
lib.m_handle = static_cast<void *>(h);
#else
// RTLD_LAZY: Resolve symbols only as code executes (Standard for plugins)
void *h = dlopen(full_path.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!h) {
const char *err = dlerror();
return fail(err ? err : "Unknown dlopen error");
}
lib.m_handle = h;
#endif
return lib;
}
DynamicLib() = default;
DynamicLib(DynamicLib &&other) noexcept : m_handle(other.m_handle) {
other.m_handle = nullptr; other.m_handle = nullptr;
} }
DynamicLib &operator=(DynamicLib &&other) NOEXCEPT { auto operator=(DynamicLib &&other) noexcept -> DynamicLib & {
if (this != &other) { if (this != &other) {
Unload(); // Free current if exists unload();
m_handle = other.m_handle; m_handle = other.m_handle;
other.m_handle = nullptr; other.m_handle = nullptr;
} }
@ -44,88 +81,46 @@ public:
} }
DynamicLib(const DynamicLib &) = delete; DynamicLib(const DynamicLib &) = delete;
DynamicLib &operator=(const DynamicLib &) = delete; auto operator=(const DynamicLib &) -> DynamicLib & = delete;
~DynamicLib() { Unload(); } ~DynamicLib() { unload(); }
// Automatically detects extension (.dll/.so) if not provided [[nodiscard]] auto get_symbol(const String &name) const -> Result<void *> {
NO_DISCARD("Check for load errors") if (!m_handle) {
return fail("Library not loaded");
static tl::Result<DynamicLib> Load(const String &searchPath,
const String &name) {
namespace fs = std::filesystem;
fs::path fullPath = fs::path(searchPath) / name;
if (!fullPath.has_extension()) {
#if IA_PLATFORM_WINDOWS
fullPath += ".dll";
#elif IA_PLATFORM_MAC
fullPath += ".dylib";
#else
fullPath += ".so";
#endif
} }
DynamicLib lib;
#if IA_PLATFORM_WINDOWS
HMODULE h = LoadLibraryA(fullPath.string().c_str());
if (!h) {
return (GetWindowsError());
}
lib.m_handle = CAST(h, void *);
#else
// RTLD_LAZY: Resolve symbols only as code executes (Standard for plugins)
void *h = dlopen(fullPath.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!h) {
// dlerror returns a string describing the last error
const char *err = dlerror();
return (String(err ? err : "Unknown dlopen error"));
}
lib.m_handle = h;
#endif
return std::move(lib);
}
NO_DISCARD("Check if symbol exists")
tl::Result<void *> GetSymbol(const String &name) const {
if (!m_handle)
return (String("Library not loaded"));
void *sym = nullptr; void *sym = nullptr;
#if IA_PLATFORM_WINDOWS #if IA_PLATFORM_WINDOWS
sym = CAST(GetProcAddress(CAST(m_handle, HMODULE), name.c_str()), void *); sym = static_cast<void *>(
if (!sym) GetProcAddress(static_cast<HMODULE>(m_handle), name.c_str()));
return (GetWindowsError()); if (!sym) {
return fail(get_windows_error());
}
#else #else
// Clear any previous error // Clear any previous error
dlerror(); dlerror();
sym = dlsym(m_handle, name.c_str()); sym = dlsym(m_handle, name.c_str());
const char *err = dlerror(); if (const char *err = dlerror()) {
if (err) return fail(err);
return (String(err)); }
#endif #endif
return sym; return sym;
} }
// Template helper for casting
template <typename FuncT> template <typename FuncT>
tl::Result<FuncT> GetFunction(const String &name) const { [[nodiscard]] auto get_function(const String &name) const -> Result<FuncT> {
auto res = GetSymbol(name); void *sym = nullptr;
if (!res) IA_TRY(sym, get_symbol(name));
return (res.error()); return reinterpret_cast<FuncT>(sym);
return REINTERPRET(*res, FuncT);
} }
void Unload() { void unload() {
if (m_handle) { if (m_handle) {
#if IA_PLATFORM_WINDOWS #if IA_PLATFORM_WINDOWS
FreeLibrary(CAST(m_handle, HMODULE)); FreeLibrary(static_cast<HMODULE>(m_handle));
#else #else
dlclose(m_handle); dlclose(m_handle);
#endif #endif
@ -133,28 +128,30 @@ public:
} }
} }
bool IsLoaded() const { return m_handle != nullptr; } [[nodiscard]] auto is_loaded() const -> bool { return m_handle != nullptr; }
private: private:
void *m_handle; void *m_handle = nullptr;
#if IA_PLATFORM_WINDOWS #if IA_PLATFORM_WINDOWS
static String GetWindowsError() { static auto get_windows_error() -> String {
DWORD errorID = ::GetLastError(); const DWORD error_id = ::GetLastError();
if (errorID == 0) if (error_id == 0) {
return String(); return String();
}
LPSTR messageBuffer = nullptr; LPSTR message_buffer = nullptr;
size_t size = FormatMessageA( const usize size = FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS, FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, errorID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), nullptr, error_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR)&messageBuffer, 0, NULL); reinterpret_cast<LPSTR>(&message_buffer), 0, nullptr);
String message(messageBuffer, size); String message(message_buffer, size);
LocalFree(messageBuffer); LocalFree(message_buffer);
return String("Win32 Error: ") + message; return "Win32 Error: " + message;
} }
#endif #endif
}; };
} // namespace IACore } // namespace IACore

View File

@ -16,88 +16,83 @@
#pragma once #pragma once
#include <IACore/PCH.hpp> #include <IACore/PCH.hpp>
#include <cstdlib>
namespace IACore namespace IACore {
{ class Environment {
class Environment public:
{ static auto find(const String &name) -> Option<String> {
public:
static Optional<String> Find(const String &name)
{
#if IA_PLATFORM_WINDOWS #if IA_PLATFORM_WINDOWS
DWORD bufferSize = GetEnvironmentVariableA(name.c_str(), nullptr, 0); const DWORD buffer_size = GetEnvironmentVariableA(name.c_str(), nullptr, 0);
if (bufferSize == 0) if (buffer_size == 0) {
{ return std::nullopt;
// DWORD 0 means failed }
return std::nullopt;
}
// 2. Allocate (bufferSize includes the null terminator request) String result;
String result; result.resize(buffer_size);
result.resize(bufferSize);
// 3. Fetch const DWORD actual_size =
// Returns num chars written excluding null terminator GetEnvironmentVariableA(name.c_str(), result.data(), buffer_size);
DWORD actualSize = GetEnvironmentVariableA(name.c_str(), result.data(), bufferSize);
if (actualSize == 0 || actualSize > bufferSize) if (actual_size == 0 || actual_size > buffer_size) {
{ return std::nullopt;
return std::nullopt; }
}
// Resize down to exclude the null terminator and any slack // Resize down to exclude the null terminator and any slack
result.resize(actualSize); result.resize(actual_size);
return result; return result;
#else #else
// getenv returns a pointer to the environment area const char *val = std::getenv(name.c_str());
const char *val = std::getenv(name.c_str()); if (val == nullptr) {
if (val == nullptr) return std::nullopt;
{ }
return std::nullopt; return String(val);
}
return String(val);
#endif #endif
} }
static String Get(const String &name, const String &defaultValue = "") static auto get(const String &name, const String &default_value = "")
{ -> String {
return Find(name).value_or(defaultValue); return find(name).value_or(default_value);
} }
static bool Set(const String &name, const String &value) static auto set(const String &name, const String &value) -> Result<void> {
{ if (name.empty()) {
if (name.empty()) return fail("Environment variable name cannot be empty");
return FALSE; }
#if IA_PLATFORM_WINDOWS #if IA_PLATFORM_WINDOWS
return SetEnvironmentVariableA(name.c_str(), value.c_str()) != 0; if (SetEnvironmentVariableA(name.c_str(), value.c_str()) == 0) {
return fail("Failed to set environment variable: {}", name);
}
#else #else
// Returns 0 on success, -1 on error if (setenv(name.c_str(), value.c_str(), 1) != 0) {
return setenv(name.c_str(), value.c_str(), 1) == 0; return fail("Failed to set environment variable: {}", name);
}
#endif #endif
} return {};
}
static bool Unset(const String &name) static auto unset(const String &name) -> Result<void> {
{ if (name.empty()) {
if (name.empty()) return fail("Environment variable name cannot be empty");
return FALSE; }
#if IA_PLATFORM_WINDOWS #if IA_PLATFORM_WINDOWS
return SetEnvironmentVariableA(name.c_str(), nullptr) != 0; if (SetEnvironmentVariableA(name.c_str(), nullptr) == 0) {
return fail("Failed to unset environment variable: {}", name);
}
#else #else
return unsetenv(name.c_str()) == 0; if (unsetenv(name.c_str()) != 0) {
return fail("Failed to unset environment variable: {}", name);
}
#endif #endif
} return {};
}
static bool Exists(const String &name) static auto exists(const String &name) -> bool {
{ return find(name).has_value();
#if IA_PLATFORM_WINDOWS }
return GetEnvironmentVariableA(name.c_str(), nullptr, 0) > 0; };
#else
return std::getenv(name.c_str()) != nullptr;
#endif
}
};
} // namespace IACore } // namespace IACore

View File

@ -16,73 +16,83 @@
#pragma once #pragma once
#include <IACore/Http/Common.hpp> #include <IACore/Http/Common.hpp>
#include <IACore/JSON.hpp>
namespace IACore { namespace IACore {
class HttpClient : public HttpCommon { class HttpClient : public HttpCommon {
public: public:
static Result<UniquePtr<HttpClient>> Create(const String &host); static auto create(const String &host) -> Result<Box<HttpClient>>;
~HttpClient(); ~HttpClient();
public: HttpClient(HttpClient &&) = default;
Result<String> HttpClient(const HttpClient &) = delete;
RawGet(const String &path, Span<const Header> headers, auto operator=(HttpClient &&) -> HttpClient & = default;
const char *defaultContentType = "application/x-www-form-urlencoded"); auto operator=(const HttpClient &) -> HttpClient & = delete;
Result<String>
RawPost(const String &path, Span<const Header> headers, const String &body,
const char *defaultContentType = "application/x-www-form-urlencoded");
template <typename _response_type>
Result<_response_type> JsonGet(const String &path,
Span<const Header> headers);
template <typename _payload_type, typename _response_type>
Result<_response_type> JsonPost(const String &path,
Span<const Header> headers,
const _payload_type &body);
// Certificate verfication is enabled by default
void EnableCertificateVerfication();
void DisableCertificateVerfication();
public: public:
EResponseCode LastResponseCode() { return m_lastResponseCode; } auto raw_get(const String &path, Span<const Header> headers,
const char *default_content_type =
"application/x-www-form-urlencoded") -> Result<String>;
auto raw_post(
const String &path, Span<const Header> headers, const String &body,
const char *default_content_type = "application/x-www-form-urlencoded")
-> Result<String>;
template <typename ResponseType>
auto json_get(const String &path, Span<const Header> headers)
-> Result<ResponseType>;
template <typename PayloadType, typename ResponseType>
auto json_post(const String &path, Span<const Header> headers,
const PayloadType &body) -> Result<ResponseType>;
// Certificate verification is enabled by default
void enable_certificate_verification();
void disable_certificate_verification();
public:
auto last_response_code() -> EResponseCode { return m_last_response_code; }
private: private:
httplib::Client m_client; httplib::Client m_client;
EResponseCode m_lastResponseCode; EResponseCode m_last_response_code;
private: private:
String PreprocessResponse(const String &response); auto preprocess_response(const String &response) -> String;
protected: protected:
HttpClient(httplib::Client &&client); explicit HttpClient(httplib::Client &&client);
}; };
template <typename _response_type> template <typename ResponseType>
Result<_response_type> HttpClient::JsonGet(const String &path, auto HttpClient::json_get(const String &path, Span<const Header> headers)
Span<const Header> headers) { -> Result<ResponseType> {
const auto rawResponse = RawGet(path, headers, "application/json"); String raw_response;
if (!rawResponse) IA_TRY(raw_response, raw_get(path, headers, "application/json"));
return (rawResponse.error());
if (LastResponseCode() != EResponseCode::OK) if (last_response_code() != EResponseCode::OK) {
return ( return fail("Server responded with code {}",
std::format("Server responded with code {}", (i32)LastResponseCode())); static_cast<i32>(last_response_code()));
return JSON::ParseToStruct<_response_type>(*rawResponse); }
return Json::parse_to_struct<ResponseType>(raw_response);
} }
template <typename _payload_type, typename _response_type> template <typename PayloadType, typename ResponseType>
Result<_response_type> HttpClient::JsonPost(const String &path, auto HttpClient::json_post(const String &path, Span<const Header> headers,
Span<const Header> headers, const PayloadType &body) -> Result<ResponseType> {
const _payload_type &body) { String encoded_body;
const auto encodedBody = IA_TRY(JSON::EncodeStruct(body)); IA_TRY(encoded_body, Json::encode_struct(body));
const auto rawResponse =
RawPost(path, headers, encodedBody, "application/json"); String raw_response;
if (!rawResponse) IA_TRY(raw_response,
return (rawResponse.error()); raw_post(path, headers, encoded_body, "application/json"));
if (LastResponseCode() != EResponseCode::OK)
return ( if (last_response_code() != EResponseCode::OK) {
std::format("Server responded with code {}", (i32)LastResponseCode())); return fail("Server responded with code {}",
return JSON::ParseToStruct<_response_type>(*rawResponse); static_cast<i32>(last_response_code()));
}
return Json::parse_to_struct<ResponseType>(raw_response);
} }
} // namespace IACore } // namespace IACore

View File

@ -125,29 +125,31 @@ public:
NETWORK_AUTHENTICATION_REQUIRED = 511 NETWORK_AUTHENTICATION_REQUIRED = 511
}; };
using Header = KeyValuePair<String, String>; using Header = Pair<String, String>;
static String UrlEncode(const String &value); static auto url_encode(const String &value) -> String;
static String UrlDecode(const String &value); static auto url_decode(const String &value) -> String;
static String HeaderTypeToString(EHeaderType type); static auto header_type_to_string(EHeaderType type) -> String;
static inline Header CreateHeader(EHeaderType key, const String &value); static inline auto create_header(EHeaderType key, const String &value)
static inline Header CreateHeader(const String &key, const String &value); -> Header;
static inline auto create_header(const String &key, const String &value)
-> Header;
static bool IsSuccessResponseCode(EResponseCode code); static auto is_success_response_code(EResponseCode code) -> bool;
protected: protected:
HttpCommon() = default; HttpCommon() = default;
}; };
HttpCommon::Header HttpCommon::CreateHeader(EHeaderType key, auto HttpCommon::create_header(EHeaderType key, const String &value)
const String &value) { -> HttpCommon::Header {
return std::make_pair(HeaderTypeToString(key), value); return std::make_pair(header_type_to_string(key), value);
} }
HttpCommon::Header HttpCommon::CreateHeader(const String &key, auto HttpCommon::create_header(const String &key, const String &value)
const String &value) { -> HttpCommon::Header {
return std::make_pair(key, value); return std::make_pair(key, value);
} }
} // namespace IACore } // namespace IACore

View File

@ -16,12 +16,138 @@
#pragma once #pragma once
#include <IACore/Http/Common.hpp> #include <IACore/Http/Common.hpp>
#include <IACore/JSON.hpp>
#include <functional>
namespace IACore {
class HttpServer : public HttpCommon {
public:
struct Request {
String path;
String method;
String body;
HashMap<String, String> headers;
HashMap<String, String> params; // Query parameters
HashMap<String, String> path_params; // Path parameters (e.g. /users/:id)
[[nodiscard]] auto get_header(const String &key) const -> String;
[[nodiscard]] auto get_param(const String &key) const -> String;
[[nodiscard]] auto get_path_param(const String &key) const -> String;
[[nodiscard]] auto has_header(const String &key) const -> bool;
[[nodiscard]] auto has_param(const String &key) const -> bool;
[[nodiscard]] auto has_path_param(const String &key) const -> bool;
};
struct Response {
EResponseCode code = EResponseCode::OK;
String body;
HashMap<String, String> headers;
String content_type = "text/plain";
void set_content(const String &content, const String &type);
void set_status(EResponseCode status_code);
void add_header(const String &key, const String &value);
};
using Handler = std::function<void(const Request &, Response &)>;
public:
static auto create() -> Result<Box<HttpServer>>;
~HttpServer();
HttpServer(HttpServer &&) = default;
HttpServer(const HttpServer &) = delete;
auto operator=(HttpServer &&) -> HttpServer & = default;
auto operator=(const HttpServer &) -> HttpServer & = delete;
auto listen(const String &host, u32 port) -> Result<void>;
void stop();
auto is_running() const -> bool;
void get(const String &pattern, Handler handler);
void post(const String &pattern, Handler handler);
void put(const String &pattern, Handler handler);
void del(const String &pattern, Handler handler);
void options(const String &pattern, Handler handler);
template <typename ResponseType>
void json_get(const String &pattern,
std::function<Result<ResponseType>(const Request &)> handler);
template <typename PayloadType, typename ResponseType>
void
json_post(const String &pattern,
std::function<Result<ResponseType>(const PayloadType &)> handler);
protected:
HttpServer();
private:
httplib::Server m_server;
void register_handler(const String &method, const String &pattern,
Handler handler);
};
// =============================================================================
// Template Implementations
// =============================================================================
template <typename ResponseType>
void HttpServer::json_get(
const String &pattern,
std::function<Result<ResponseType>(const Request &)> handler) {
get(pattern, [handler](const Request &req, Response &res) {
auto result = handler(req);
if (!result) {
res.set_status(EResponseCode::INTERNAL_SERVER_ERROR);
res.set_content(result.error(), "text/plain");
return;
}
auto json_res = Json::encode_struct(*result);
if (!json_res) {
res.set_status(EResponseCode::INTERNAL_SERVER_ERROR);
res.set_content("Failed to encode JSON response", "text/plain");
return;
}
res.set_status(EResponseCode::OK);
res.set_content(*json_res, "application/json");
});
}
template <typename PayloadType, typename ResponseType>
void HttpServer::json_post(
const String &pattern,
std::function<Result<ResponseType>(const PayloadType &)> handler) {
post(pattern, [handler](const Request &req, Response &res) {
auto payload = Json::parse_to_struct<PayloadType>(req.body);
if (!payload) {
res.set_status(EResponseCode::BAD_REQUEST);
res.set_content("Invalid JSON Payload", "text/plain");
return;
}
auto result = handler(*payload);
if (!result) {
res.set_status(EResponseCode::INTERNAL_SERVER_ERROR);
res.set_content(result.error(), "text/plain");
return;
}
auto json_res = Json::encode_struct(*result);
if (!json_res) {
res.set_status(EResponseCode::INTERNAL_SERVER_ERROR);
res.set_content("Failed to encode JSON response", "text/plain");
return;
}
res.set_status(EResponseCode::OK);
res.set_content(*json_res, "application/json");
});
}
namespace IACore
{
class HttpServer : public HttpCommon
{
public:
HttpServer(const String &host, u32 port);
};
} // namespace IACore } // namespace IACore

View File

@ -76,7 +76,7 @@ public:
void update(); void update();
void send_signal(u8 signal); void send_signal(u8 signal);
void send_packet(u16 packet_id, Span<const u8> payload); auto send_packet(u16 packet_id, Span<const u8> payload) -> Result<void>;
protected: protected:
virtual void on_signal(u8 signal) = 0; virtual void on_signal(u8 signal) = 0;
@ -88,8 +88,8 @@ private:
Vec<u8> m_receive_buffer; Vec<u8> m_receive_buffer;
SocketHandle m_socket{INVALID_SOCKET}; SocketHandle m_socket{INVALID_SOCKET};
Box<RingBufferView> m_moni; // Manager Out, Node In RingBufferView m_moni; // Manager Out, Node In
Box<RingBufferView> m_mino; // Manager In, Node Out RingBufferView m_mino; // Manager In, Node Out
}; };
class IpcManager { class IpcManager {
@ -105,13 +105,15 @@ class IpcManager {
SocketHandle listener_socket{INVALID_SOCKET}; SocketHandle listener_socket{INVALID_SOCKET};
SocketHandle data_socket{INVALID_SOCKET}; SocketHandle data_socket{INVALID_SOCKET};
Box<RingBufferView> moni; // Manager Out, Node In RingBufferView moni =
Box<RingBufferView> mino; // Manager In, Node Out RingBufferView::default_instance(); // Manager Out, Node In
RingBufferView mino =
RingBufferView::default_instance(); // Manager In, Node Out
bool is_ready{false}; bool is_ready{false};
void send_signal(u8 signal); void send_signal(u8 signal);
void send_packet(u16 packet_id, Span<const u8> payload); auto send_packet(u16 packet_id, Span<const u8> payload) -> Result<void>;
}; };
public: public:
@ -131,7 +133,8 @@ public:
void shutdown_node(NativeProcessID node); void shutdown_node(NativeProcessID node);
void send_signal(NativeProcessID node, u8 signal); void send_signal(NativeProcessID node, u8 signal);
void send_packet(NativeProcessID node, u16 packet_id, Span<const u8> payload); auto send_packet(NativeProcessID node, u16 packet_id, Span<const u8> payload)
-> Result<void>;
protected: protected:
virtual void on_signal(NativeProcessID node, u8 signal) = 0; virtual void on_signal(NativeProcessID node, u8 signal) = 0;

View File

@ -77,6 +77,8 @@ public:
#endif #endif
} }
static auto is_initialized() -> bool { return s_init_count > 0; }
static auto is_port_available_tcp(u16 port) -> bool { static auto is_port_available_tcp(u16 port) -> bool {
return is_port_available(port, SOCK_STREAM); return is_port_available(port, SOCK_STREAM);
} }