diff --git a/.clang-tidy b/.clang-tidy index 85ea101..7e48697 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -46,6 +46,8 @@ CheckOptions: # Public struct members (like a Rust struct) -> x, y, width - key: readability-identifier-naming.PublicMemberCase value: lower_case + - key: readability-identifier-naming.StructMemberIgnoredRegexp + value: ^_[a-z0-9_]*$ # Private/Protected class members -> m_parser, m_count - key: readability-identifier-naming.PrivateMemberCase diff --git a/Src/IACore/CMakeLists.txt b/Src/IACore/CMakeLists.txt index 279aa27..085a18e 100644 --- a/Src/IACore/CMakeLists.txt +++ b/Src/IACore/CMakeLists.txt @@ -17,7 +17,7 @@ set(SRC_FILES "imp/cpp/StreamReader.cpp" "imp/cpp/StreamWriter.cpp" - #"imp/cpp/Http/Common.cpp" + "imp/cpp/Http/Common.cpp" #"imp/cpp/Http/Client.cpp" #"imp/cpp/Http/Server.cpp" ) diff --git a/Src/IACore/imp/cpp/Http/Client.cpp b/Src/IACore/imp/cpp/Http/Client.cpp index 9dce4ee..eb73f4c 100644 --- a/Src/IACore/imp/cpp/Http/Client.cpp +++ b/Src/IACore/imp/cpp/Http/Client.cpp @@ -17,61 +17,73 @@ #include namespace IACore { -Result> HttpClient::Create(const String &host) { - return MakeUniqueProtected(httplib::Client(host)); +// Helper struct to access protected constructor via make_box (std::make_unique) +struct PublicHttpClient : public HttpClient { + explicit PublicHttpClient(httplib::Client &&client) + : HttpClient(std::move(client)) {} +}; + +auto HttpClient::create(const String &host) -> Result> { + return make_box(httplib::Client(host)); } -httplib::Headers BuildHeaders(Span headers, - const char *defaultContentType) { +static auto build_headers(Span headers, + const char *default_content_type) + -> httplib::Headers { httplib::Headers out; - bool hasContentType = false; + bool has_content_type = false; for (const auto &h : headers) { out.emplace(h.first, h.second); - if (h.first == - HttpClient::HeaderTypeToString(HttpClient::EHeaderType::CONTENT_TYPE)) - hasContentType = true; + if (h.first == HttpClient::header_type_to_string( + HttpClient::EHeaderType::CONTENT_TYPE)) { + has_content_type = true; + } } - if (!hasContentType && defaultContentType) - out.emplace("Content-Type", defaultContentType); + if (!has_content_type && default_content_type) { + out.emplace("Content-Type", default_content_type); + } return out; } HttpClient::HttpClient(httplib::Client &&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); } -HttpClient::~HttpClient() {} +HttpClient::~HttpClient() = default; -void HttpClient::EnableCertificateVerfication() { +void HttpClient::enable_certificate_verification() { m_client.enable_server_certificate_verification(true); } -void HttpClient::DisableCertificateVerfication() { +void HttpClient::disable_certificate_verification() { m_client.enable_server_certificate_verification(false); } -String HttpClient::PreprocessResponse(const String &response) { - const auto responseBytes = - Span{(const u8 *)response.data(), response.size()}; - const auto compression = DataOps::DetectCompression(responseBytes); +auto HttpClient::preprocess_response(const String &response) -> String { + const auto response_bytes = Span{ + reinterpret_cast(response.data()), response.size()}; + const auto compression = DataOps::detect_compression(response_bytes); + switch (compression) { case DataOps::CompressionType::Gzip: { - const auto data = DataOps::GZipInflate(responseBytes); - if (!data) + const auto data = DataOps::gzip_inflate(response_bytes); + if (!data) { return response; - return String((const char *)data->data(), data->size()); + } + return String(reinterpret_cast(data->data()), data->size()); } case DataOps::CompressionType::Zlib: { - const auto data = DataOps::ZlibInflate(responseBytes); - if (!data) + const auto data = DataOps::zlib_inflate(response_bytes); + if (!data) { return response; - return String((const char *)data->data(), data->size()); + } + return String(reinterpret_cast(data->data()), data->size()); } case DataOps::CompressionType::None: @@ -81,53 +93,58 @@ String HttpClient::PreprocessResponse(const String &response) { return response; } -Result +auto HttpClient::raw_get(const String &path, Span headers, + const char *default_content_type) -> Result { + auto http_headers = build_headers(headers, default_content_type); -HttpClient::RawGet(const String &path, Span headers, - const char *defaultContentType) { - auto httpHeaders = BuildHeaders(headers, defaultContentType); - - auto res = m_client.Get( - (!path.empty() && path[0] != '/') ? ('/' + path).c_str() : path.c_str(), - httpHeaders); - - if (res) { - m_lastResponseCode = static_cast(res->status); - if (res->status >= 200 && res->status < 300) - return PreprocessResponse(res->body); - else - return (std::format("HTTP Error {} : {}", res->status, res->body)); + String adjusted_path = path; + if (!path.empty() && path[0] != '/') { + adjusted_path = "/" + path; } - 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(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 +auto HttpClient::raw_post(const String &path, Span headers, + const String &body, const char *default_content_type) + -> Result { + auto http_headers = build_headers(headers, default_content_type); -HttpClient::RawPost(const String &path, Span headers, - const String &body, const char *defaultContentType) { - auto httpHeaders = BuildHeaders(headers, defaultContentType); - - String contentType = defaultContentType; - if (httpHeaders.count("Content-Type")) { - const auto t = httpHeaders.find("Content-Type"); - contentType = t->second; - httpHeaders.erase(t); + String content_type = default_content_type; + if (http_headers.count("Content-Type")) { + const auto t = http_headers.find("Content-Type"); + content_type = t->second; + http_headers.erase(t); } 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) { - m_lastResponseCode = static_cast(res->status); - if (res->status >= 200 && res->status < 300) - return PreprocessResponse(res->body); - else - return (std::format("HTTP Error {} : {}", res->status, res->body)); + String adjusted_path = path; + if (!path.empty() && path[0] != '/') { + adjusted_path = "/" + path; } - 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(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 \ No newline at end of file diff --git a/Src/IACore/imp/cpp/Http/Common.cpp b/Src/IACore/imp/cpp/Http/Common.cpp index e69ec09..adb1f14 100644 --- a/Src/IACore/imp/cpp/Http/Common.cpp +++ b/Src/IACore/imp/cpp/Http/Common.cpp @@ -16,7 +16,7 @@ #include namespace IACore { -String HttpCommon::UrlEncode(const String &value) { +auto HttpCommon::url_encode(const String &value) -> String { std::stringstream escaped; escaped.fill('0'); escaped << std::hex << std::uppercase; @@ -33,16 +33,16 @@ String HttpCommon::UrlEncode(const String &value) { return escaped.str(); } -String HttpCommon::UrlDecode(const String &value) { +auto HttpCommon::url_decode(const String &value) -> String { String result; result.reserve(value.length()); for (size_t i = 0; i < value.length(); ++i) { if (value[i] == '%' && i + 2 < value.length()) { - std::string hexStr = value.substr(i + 1, 2); - char decodedChar = - static_cast(std::strtol(hexStr.c_str(), nullptr, 16)); - result += decodedChar; + std::string hex_str = value.substr(i + 1, 2); + char decoded_char = + static_cast(std::strtol(hex_str.c_str(), nullptr, 16)); + result += decoded_char; i += 2; } else if (value[i] == '+') result += ' '; @@ -53,7 +53,7 @@ String HttpCommon::UrlDecode(const String &value) { return result; } -String HttpCommon::HeaderTypeToString(EHeaderType type) { +auto HttpCommon::header_type_to_string(EHeaderType type) -> String { switch (type) { case EHeaderType::ACCEPT: return "Accept"; @@ -111,7 +111,7 @@ String HttpCommon::HeaderTypeToString(EHeaderType type) { return ""; } -bool HttpCommon::IsSuccessResponseCode(EResponseCode code) { +auto HttpCommon::is_success_response_code(EResponseCode code) -> bool { return (i32)code >= 200 && (i32)code < 300; } } // namespace IACore \ No newline at end of file diff --git a/Src/IACore/imp/cpp/Http/Server.cpp b/Src/IACore/imp/cpp/Http/Server.cpp index 30f6ffe..b1bdc76 100644 --- a/Src/IACore/imp/cpp/Http/Server.cpp +++ b/Src/IACore/imp/cpp/Http/Server.cpp @@ -15,7 +15,159 @@ #include -namespace IACore -{ +namespace IACore { +// ============================================================================= +// Request Implementation +// ============================================================================= -} \ No newline at end of file +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> { + return make_box(); +} + +auto HttpServer::listen(const String &host, u32 port) -> Result { + if (!m_server.listen(host.c_str(), static_cast(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(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 \ No newline at end of file diff --git a/Src/IACore/imp/cpp/IPC.cpp b/Src/IACore/imp/cpp/IPC.cpp index 21af9e3..804ddda 100644 --- a/Src/IACore/imp/cpp/IPC.cpp +++ b/Src/IACore/imp/cpp/IPC.cpp @@ -114,13 +114,17 @@ auto IpcNode::connect(const char *connection_string) -> Result { u8 *moni_ptr = m_shared_memory + layout->moni_data_offset; u8 *mino_ptr = m_shared_memory + layout->mino_data_offset; - m_moni = make_box( - &layout->moni_control, - Span(moni_ptr, static_cast(layout->moni_data_size)), false); + IA_TRY(m_moni, + RingBufferView::create( + &layout->moni_control, + Span(moni_ptr, static_cast(layout->moni_data_size)), + false)); - m_mino = make_box( - &layout->mino_control, - Span(mino_ptr, static_cast(layout->mino_data_size)), false); + IA_TRY(m_mino, + RingBufferView::create( + &layout->mino_control, + Span(mino_ptr, static_cast(layout->mino_data_size)), + false)); #if IA_PLATFORM_WINDOWS u_long mode = 1; @@ -135,14 +139,14 @@ auto IpcNode::connect(const char *connection_string) -> Result { } void IpcNode::update() { - if (!m_moni) { + if (!m_moni.is_valid()) { return; } IpcPacketHeader header; // Process all available messages from Manager - while (m_moni->pop( + while (m_moni.pop( header, Span(m_receive_buffer.data(), m_receive_buffer.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 payload) { - if (m_mino) { - m_mino->push(packet_id, payload); - } +auto IpcNode::send_packet(u16 packet_id, Span payload) + -> Result { + 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, - Span payload) { +auto IpcManager::NodeSession::send_packet(u16 packet_id, Span payload) + -> Result { // Protect the RingBuffer write cursor from concurrent threads std::scoped_lock lock(send_mutex); - if (moni) { - moni->push(packet_id, payload); - } + if (!moni.is_valid()) + return fail("invalid MONI"); + return moni.push(packet_id, payload); } IpcManager::IpcManager() { - // SocketOps is smart enough to track multiple inits - SocketOps::initialize(); + ensure(SocketOps::is_initialized(), + "SocketOps must be initialized before using IpcManager"); + m_receive_buffer.resize(UINT16_MAX + 1); } @@ -214,9 +220,6 @@ IpcManager::~IpcManager() { SocketOps::close(session->listener_socket); } m_pending_sessions.clear(); - - // SocketOps is smart enough to track multiple terminates - SocketOps::terminate(); } void IpcManager::update() { @@ -270,7 +273,7 @@ void IpcManager::update() { IpcPacketHeader header; - while (node->mino->pop( + while (node->mino.pop( header, Span(m_receive_buffer.data(), m_receive_buffer.size()))) { on_packet(node_id, header.id, {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_size = half_size; - session->moni = make_box( - &layout->moni_control, - Span(session->mapped_ptr + layout->moni_data_offset, - static_cast(layout->moni_data_size)), - true); + IA_TRY(session->moni, + RingBufferView::create( + &layout->moni_control, + Span(session->mapped_ptr + layout->moni_data_offset, + static_cast(layout->moni_data_size)), + true)); - session->mino = make_box( - &layout->mino_control, - Span(session->mapped_ptr + layout->mino_data_offset, - static_cast(layout->mino_data_size)), - true); + IA_TRY(session->mino, + RingBufferView::create( + &layout->mino_control, + Span(session->mapped_ptr + layout->mino_data_offset, + static_cast(layout->mino_data_size)), + true)); IpcConnectionDescriptor desc; desc.socket_path = sock_path; @@ -446,12 +451,11 @@ void IpcManager::send_signal(NativeProcessID node, u8 signal) { it_node->second->send_signal(signal); } -void IpcManager::send_packet(NativeProcessID node, u16 packet_id, - Span payload) { +auto IpcManager::send_packet(NativeProcessID node, u16 packet_id, + Span payload) -> Result { const auto it_node = m_active_session_map.find(node); - if (it_node == m_active_session_map.end()) { - return; - } - it_node->second->send_packet(packet_id, payload); + if (it_node == m_active_session_map.end()) + return fail("no such node"); + return it_node->second->send_packet(packet_id, payload); } } // namespace IACore \ No newline at end of file diff --git a/Src/IACore/inc/IACore/ADT/RingBuffer.hpp b/Src/IACore/inc/IACore/ADT/RingBuffer.hpp index 3e3571e..2bdfb6e 100644 --- a/Src/IACore/inc/IACore/ADT/RingBuffer.hpp +++ b/Src/IACore/inc/IACore/ADT/RingBuffer.hpp @@ -53,8 +53,11 @@ public: }; public: - RingBufferView(Span buffer, bool is_owner); - RingBufferView(ControlBlock *control_block, Span buffer, bool is_owner); + static auto default_instance() -> RingBufferView; + + static auto create(Span buffer, bool is_owner) -> Result; + static auto create(ControlBlock *control_block, Span buffer, + bool is_owner) -> Result; // Returns: // - Ok(nullopt) if empty @@ -70,6 +73,12 @@ public: auto get_control_block() -> ControlBlock *; + [[nodiscard]] auto is_valid() const -> bool; + +protected: + RingBufferView(Span buffer, bool is_owner); + RingBufferView(ControlBlock *control_block, Span buffer, bool is_owner); + private: u8 *m_data_ptr{}; u32 m_capacity{}; @@ -84,10 +93,40 @@ private: // Implementation // ============================================================================= -inline RingBufferView::RingBufferView(Span buffer, bool is_owner) { - ensure(buffer.size() > sizeof(ControlBlock), - "Buffer too small for ControlBlock"); +auto RingBufferView::default_instance() -> RingBufferView { + return RingBufferView(nullptr, {}, false); +} +inline auto RingBufferView::create(Span buffer, bool is_owner) + -> Result { + if (buffer.size() <= sizeof(ControlBlock)) { + return fail("Buffer too small for ControlBlock"); + } + + if (!is_owner) { + auto *cb = reinterpret_cast(buffer.data()); + u32 capacity = static_cast(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 buffer, + bool is_owner) -> Result { + 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 buffer, bool is_owner) { m_control_block = reinterpret_cast(buffer.data()); m_data_ptr = buffer.data() + sizeof(ControlBlock); @@ -97,17 +136,11 @@ inline RingBufferView::RingBufferView(Span buffer, bool is_owner) { m_control_block->consumer.capacity = m_capacity; m_control_block->producer.write_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, Span buffer, bool is_owner) { - ensure(control_block != nullptr, "ControlBlock is null"); - ensure(!buffer.empty(), "Buffer is empty"); - m_control_block = control_block; m_data_ptr = buffer.data(); m_capacity = static_cast(buffer.size()); @@ -154,8 +187,9 @@ inline auto RingBufferView::pop(PacketHeader &out_header, Span out_buffer) inline auto RingBufferView::push(u16 packet_id, Span data) -> Result { - ensure(data.size() <= std::numeric_limits::max(), - "Data size exceeds u16 limit"); + if (data.size() > std::numeric_limits::max()) { + return fail("Data size exceeds u16 limit"); + } const u32 total_size = sizeof(PacketHeader) + static_cast(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 \ No newline at end of file diff --git a/Src/IACore/inc/IACore/DynamicLib.hpp b/Src/IACore/inc/IACore/DynamicLib.hpp index 1c48e59..4754a82 100644 --- a/Src/IACore/inc/IACore/DynamicLib.hpp +++ b/Src/IACore/inc/IACore/DynamicLib.hpp @@ -17,26 +17,63 @@ #include -#if IA_PLATFORM_WINDOWS -#include -#include -#else +#if !IA_PLATFORM_WINDOWS #include #endif +// Ensure 'ia' namespace alias exists for IA_TRY macro usage +namespace ia = IACore; + namespace IACore { class DynamicLib { public: - DynamicLib() : m_handle(nullptr) {} + // Factory method to load a dynamic library + [[nodiscard]] static auto load(const String &search_path, const String &name) + -> Result { + 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(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; } - DynamicLib &operator=(DynamicLib &&other) NOEXCEPT { + auto operator=(DynamicLib &&other) noexcept -> DynamicLib & { if (this != &other) { - Unload(); // Free current if exists + unload(); m_handle = other.m_handle; other.m_handle = nullptr; } @@ -44,88 +81,46 @@ public: } 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 - NO_DISCARD("Check for load errors") - - static tl::Result 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 + [[nodiscard]] auto get_symbol(const String &name) const -> Result { + if (!m_handle) { + return fail("Library not loaded"); } - 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 GetSymbol(const String &name) const { - if (!m_handle) - return (String("Library not loaded")); - void *sym = nullptr; #if IA_PLATFORM_WINDOWS - sym = CAST(GetProcAddress(CAST(m_handle, HMODULE), name.c_str()), void *); - if (!sym) - return (GetWindowsError()); + sym = static_cast( + GetProcAddress(static_cast(m_handle), name.c_str())); + if (!sym) { + return fail(get_windows_error()); + } #else // Clear any previous error dlerror(); sym = dlsym(m_handle, name.c_str()); - const char *err = dlerror(); - if (err) - return (String(err)); + if (const char *err = dlerror()) { + return fail(err); + } #endif return sym; } - // Template helper for casting template - tl::Result GetFunction(const String &name) const { - auto res = GetSymbol(name); - if (!res) - return (res.error()); - return REINTERPRET(*res, FuncT); + [[nodiscard]] auto get_function(const String &name) const -> Result { + void *sym = nullptr; + IA_TRY(sym, get_symbol(name)); + return reinterpret_cast(sym); } - void Unload() { + void unload() { if (m_handle) { #if IA_PLATFORM_WINDOWS - FreeLibrary(CAST(m_handle, HMODULE)); + FreeLibrary(static_cast(m_handle)); #else dlclose(m_handle); #endif @@ -133,28 +128,30 @@ public: } } - bool IsLoaded() const { return m_handle != nullptr; } + [[nodiscard]] auto is_loaded() const -> bool { return m_handle != nullptr; } private: - void *m_handle; + void *m_handle = nullptr; #if IA_PLATFORM_WINDOWS - static String GetWindowsError() { - DWORD errorID = ::GetLastError(); - if (errorID == 0) + static auto get_windows_error() -> String { + const DWORD error_id = ::GetLastError(); + if (error_id == 0) { return String(); + } - LPSTR messageBuffer = nullptr; - size_t size = FormatMessageA( + LPSTR message_buffer = nullptr; + const usize size = FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, errorID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPSTR)&messageBuffer, 0, NULL); + nullptr, error_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&message_buffer), 0, nullptr); - String message(messageBuffer, size); - LocalFree(messageBuffer); - return String("Win32 Error: ") + message; + String message(message_buffer, size); + LocalFree(message_buffer); + return "Win32 Error: " + message; } #endif }; + } // namespace IACore \ No newline at end of file diff --git a/Src/IACore/inc/IACore/Environment.hpp b/Src/IACore/inc/IACore/Environment.hpp index 8676836..0fffbf5 100644 --- a/Src/IACore/inc/IACore/Environment.hpp +++ b/Src/IACore/inc/IACore/Environment.hpp @@ -16,88 +16,83 @@ #pragma once #include +#include -namespace IACore -{ - class Environment - { - public: - static Optional Find(const String &name) - { +namespace IACore { +class Environment { +public: + static auto find(const String &name) -> Option { #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) - { - // DWORD 0 means failed - return std::nullopt; - } + if (buffer_size == 0) { + return std::nullopt; + } - // 2. Allocate (bufferSize includes the null terminator request) - String result; - result.resize(bufferSize); + String result; + result.resize(buffer_size); - // 3. Fetch - // Returns num chars written excluding null terminator - DWORD actualSize = GetEnvironmentVariableA(name.c_str(), result.data(), bufferSize); + const DWORD actual_size = + GetEnvironmentVariableA(name.c_str(), result.data(), buffer_size); - if (actualSize == 0 || actualSize > bufferSize) - { - return std::nullopt; - } + if (actual_size == 0 || actual_size > buffer_size) { + return std::nullopt; + } - // Resize down to exclude the null terminator and any slack - result.resize(actualSize); - return result; + // Resize down to exclude the null terminator and any slack + result.resize(actual_size); + return result; #else - // getenv returns a pointer to the environment area - const char *val = std::getenv(name.c_str()); - if (val == nullptr) - { - return std::nullopt; - } - return String(val); + const char *val = std::getenv(name.c_str()); + if (val == nullptr) { + return std::nullopt; + } + return String(val); #endif - } + } - static String Get(const String &name, const String &defaultValue = "") - { - return Find(name).value_or(defaultValue); - } + static auto get(const String &name, const String &default_value = "") + -> String { + return find(name).value_or(default_value); + } - static bool Set(const String &name, const String &value) - { - if (name.empty()) - return FALSE; + static auto set(const String &name, const String &value) -> Result { + if (name.empty()) { + return fail("Environment variable name cannot be empty"); + } #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 - // Returns 0 on success, -1 on error - return setenv(name.c_str(), value.c_str(), 1) == 0; + if (setenv(name.c_str(), value.c_str(), 1) != 0) { + return fail("Failed to set environment variable: {}", name); + } #endif - } + return {}; + } - static bool Unset(const String &name) - { - if (name.empty()) - return FALSE; + static auto unset(const String &name) -> Result { + if (name.empty()) { + return fail("Environment variable name cannot be empty"); + } #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 - return unsetenv(name.c_str()) == 0; + if (unsetenv(name.c_str()) != 0) { + return fail("Failed to unset environment variable: {}", name); + } #endif - } + return {}; + } - static bool Exists(const String &name) - { -#if IA_PLATFORM_WINDOWS - return GetEnvironmentVariableA(name.c_str(), nullptr, 0) > 0; -#else - return std::getenv(name.c_str()) != nullptr; -#endif - } - }; + static auto exists(const String &name) -> bool { + return find(name).has_value(); + } +}; } // namespace IACore \ No newline at end of file diff --git a/Src/IACore/inc/IACore/Http/Client.hpp b/Src/IACore/inc/IACore/Http/Client.hpp index 3e177da..dbf9e10 100644 --- a/Src/IACore/inc/IACore/Http/Client.hpp +++ b/Src/IACore/inc/IACore/Http/Client.hpp @@ -16,73 +16,83 @@ #pragma once #include +#include namespace IACore { class HttpClient : public HttpCommon { public: - static Result> Create(const String &host); + static auto create(const String &host) -> Result>; ~HttpClient(); -public: - Result - RawGet(const String &path, Span headers, - const char *defaultContentType = "application/x-www-form-urlencoded"); - Result - RawPost(const String &path, Span headers, const String &body, - const char *defaultContentType = "application/x-www-form-urlencoded"); - - template - Result<_response_type> JsonGet(const String &path, - Span headers); - - template - Result<_response_type> JsonPost(const String &path, - Span headers, - const _payload_type &body); - - // Certificate verfication is enabled by default - void EnableCertificateVerfication(); - void DisableCertificateVerfication(); + HttpClient(HttpClient &&) = default; + HttpClient(const HttpClient &) = delete; + auto operator=(HttpClient &&) -> HttpClient & = default; + auto operator=(const HttpClient &) -> HttpClient & = delete; public: - EResponseCode LastResponseCode() { return m_lastResponseCode; } + auto raw_get(const String &path, Span headers, + const char *default_content_type = + "application/x-www-form-urlencoded") -> Result; + + auto raw_post( + const String &path, Span headers, const String &body, + const char *default_content_type = "application/x-www-form-urlencoded") + -> Result; + + template + auto json_get(const String &path, Span headers) + -> Result; + + template + auto json_post(const String &path, Span headers, + const PayloadType &body) -> Result; + + // 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: httplib::Client m_client; - EResponseCode m_lastResponseCode; + EResponseCode m_last_response_code; private: - String PreprocessResponse(const String &response); + auto preprocess_response(const String &response) -> String; protected: - HttpClient(httplib::Client &&client); + explicit HttpClient(httplib::Client &&client); }; -template -Result<_response_type> HttpClient::JsonGet(const String &path, - Span headers) { - const auto rawResponse = RawGet(path, headers, "application/json"); - if (!rawResponse) - return (rawResponse.error()); - if (LastResponseCode() != EResponseCode::OK) - return ( - std::format("Server responded with code {}", (i32)LastResponseCode())); - return JSON::ParseToStruct<_response_type>(*rawResponse); +template +auto HttpClient::json_get(const String &path, Span headers) + -> Result { + String raw_response; + IA_TRY(raw_response, raw_get(path, headers, "application/json")); + + if (last_response_code() != EResponseCode::OK) { + return fail("Server responded with code {}", + static_cast(last_response_code())); + } + return Json::parse_to_struct(raw_response); } -template -Result<_response_type> HttpClient::JsonPost(const String &path, - Span headers, - const _payload_type &body) { - const auto encodedBody = IA_TRY(JSON::EncodeStruct(body)); - const auto rawResponse = - RawPost(path, headers, encodedBody, "application/json"); - if (!rawResponse) - return (rawResponse.error()); - if (LastResponseCode() != EResponseCode::OK) - return ( - std::format("Server responded with code {}", (i32)LastResponseCode())); - return JSON::ParseToStruct<_response_type>(*rawResponse); +template +auto HttpClient::json_post(const String &path, Span headers, + const PayloadType &body) -> Result { + String encoded_body; + IA_TRY(encoded_body, Json::encode_struct(body)); + + String raw_response; + IA_TRY(raw_response, + raw_post(path, headers, encoded_body, "application/json")); + + if (last_response_code() != EResponseCode::OK) { + return fail("Server responded with code {}", + static_cast(last_response_code())); + } + return Json::parse_to_struct(raw_response); } } // namespace IACore \ No newline at end of file diff --git a/Src/IACore/inc/IACore/Http/Common.hpp b/Src/IACore/inc/IACore/Http/Common.hpp index 439c839..acfd168 100644 --- a/Src/IACore/inc/IACore/Http/Common.hpp +++ b/Src/IACore/inc/IACore/Http/Common.hpp @@ -125,29 +125,31 @@ public: NETWORK_AUTHENTICATION_REQUIRED = 511 }; - using Header = KeyValuePair; + using Header = Pair; - static String UrlEncode(const String &value); - static String UrlDecode(const String &value); + static auto url_encode(const String &value) -> String; + 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 Header CreateHeader(const String &key, const String &value); + static inline auto create_header(EHeaderType 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: HttpCommon() = default; }; -HttpCommon::Header HttpCommon::CreateHeader(EHeaderType key, - const String &value) { - return std::make_pair(HeaderTypeToString(key), value); +auto HttpCommon::create_header(EHeaderType key, const String &value) + -> HttpCommon::Header { + return std::make_pair(header_type_to_string(key), value); } -HttpCommon::Header HttpCommon::CreateHeader(const String &key, - const String &value) { +auto HttpCommon::create_header(const String &key, const String &value) + -> HttpCommon::Header { return std::make_pair(key, value); } } // namespace IACore \ No newline at end of file diff --git a/Src/IACore/inc/IACore/Http/Server.hpp b/Src/IACore/inc/IACore/Http/Server.hpp index 054955d..1fce779 100644 --- a/Src/IACore/inc/IACore/Http/Server.hpp +++ b/Src/IACore/inc/IACore/Http/Server.hpp @@ -16,12 +16,138 @@ #pragma once #include +#include +#include + +namespace IACore { +class HttpServer : public HttpCommon { +public: + struct Request { + String path; + String method; + String body; + HashMap headers; + HashMap params; // Query parameters + HashMap 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 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; + +public: + static auto create() -> Result>; + + ~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 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 + void json_get(const String &pattern, + std::function(const Request &)> handler); + + template + void + json_post(const String &pattern, + std::function(const PayloadType &)> handler); + +protected: + HttpServer(); + +private: + httplib::Server m_server; + + void register_handler(const String &method, const String &pattern, + Handler handler); +}; + +// ============================================================================= +// Template Implementations +// ============================================================================= + +template +void HttpServer::json_get( + const String &pattern, + std::function(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 +void HttpServer::json_post( + const String &pattern, + std::function(const PayloadType &)> handler) { + post(pattern, [handler](const Request &req, Response &res) { + auto payload = Json::parse_to_struct(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 \ No newline at end of file diff --git a/Src/IACore/inc/IACore/IPC.hpp b/Src/IACore/inc/IACore/IPC.hpp index a920e04..772a75d 100644 --- a/Src/IACore/inc/IACore/IPC.hpp +++ b/Src/IACore/inc/IACore/IPC.hpp @@ -76,7 +76,7 @@ public: void update(); void send_signal(u8 signal); - void send_packet(u16 packet_id, Span payload); + auto send_packet(u16 packet_id, Span payload) -> Result; protected: virtual void on_signal(u8 signal) = 0; @@ -88,8 +88,8 @@ private: Vec m_receive_buffer; SocketHandle m_socket{INVALID_SOCKET}; - Box m_moni; // Manager Out, Node In - Box m_mino; // Manager In, Node Out + RingBufferView m_moni; // Manager Out, Node In + RingBufferView m_mino; // Manager In, Node Out }; class IpcManager { @@ -105,13 +105,15 @@ class IpcManager { SocketHandle listener_socket{INVALID_SOCKET}; SocketHandle data_socket{INVALID_SOCKET}; - Box moni; // Manager Out, Node In - Box mino; // Manager In, Node Out + RingBufferView moni = + RingBufferView::default_instance(); // Manager Out, Node In + RingBufferView mino = + RingBufferView::default_instance(); // Manager In, Node Out bool is_ready{false}; void send_signal(u8 signal); - void send_packet(u16 packet_id, Span payload); + auto send_packet(u16 packet_id, Span payload) -> Result; }; public: @@ -131,7 +133,8 @@ public: void shutdown_node(NativeProcessID node); void send_signal(NativeProcessID node, u8 signal); - void send_packet(NativeProcessID node, u16 packet_id, Span payload); + auto send_packet(NativeProcessID node, u16 packet_id, Span payload) + -> Result; protected: virtual void on_signal(NativeProcessID node, u8 signal) = 0; diff --git a/Src/IACore/inc/IACore/SocketOps.hpp b/Src/IACore/inc/IACore/SocketOps.hpp index d8b4acd..7afbfc9 100644 --- a/Src/IACore/inc/IACore/SocketOps.hpp +++ b/Src/IACore/inc/IACore/SocketOps.hpp @@ -77,6 +77,8 @@ public: #endif } + static auto is_initialized() -> bool { return s_init_count > 0; } + static auto is_port_available_tcp(u16 port) -> bool { return is_port_available(port, SOCK_STREAM); }