From a2fd838f5d4f3bd517ad45693ec5bf6c898dc22f Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 14 Aug 2023 18:14:35 +0200 Subject: [PATCH] libos: Add basic IPC message framework --- libos/CMakeLists.txt | 1 + libos/include/os/IPC.h | 157 +++++++++++++++++++++++++++++++++++++++++ libos/src/IPC.cpp | 39 ++++++++++ 3 files changed, 197 insertions(+) create mode 100644 libos/include/os/IPC.h create mode 100644 libos/src/IPC.cpp diff --git a/libos/CMakeLists.txt b/libos/CMakeLists.txt index 3bee1165..cab087a8 100644 --- a/libos/CMakeLists.txt +++ b/libos/CMakeLists.txt @@ -16,6 +16,7 @@ set(SOURCES src/Security.cpp src/LocalServer.cpp src/LocalClient.cpp + src/IPC.cpp ) add_library(os ${SOURCES}) diff --git a/libos/include/os/IPC.h b/libos/include/os/IPC.h new file mode 100644 index 00000000..7e937bb0 --- /dev/null +++ b/libos/include/os/IPC.h @@ -0,0 +1,157 @@ +/** + * @file IPC.h + * @author apio (cloudapio.eu) + * @brief Inter-process communication primitives. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#pragma once +#include +#include + +#define IPC_ENUM_SERVER(name) __##name##_SERVER_ERROR = 0 +#define IPC_ENUM_CLIENT(name) __##name##_CLIENT_ERROR = 0 + +/** + * @brief Called to handle IPC events (client-side). + * + * @param conn The connection object being used. + * @param id The ID of the message. + * @return Result Whether the operation succeded. + */ +extern Result handle_ipc_client_event(os::LocalClient& conn, u8 id); + +/** + * @brief Called to handle IPC events (server-side). + * + * @param conn The connection object being used. + * @param id The ID of the message. + * @return Result Whether the operation succeded. + */ +extern Result handle_ipc_server_event(os::LocalServer::Client& conn, u8 id); + +namespace os +{ + namespace IPC + { + static constexpr usize IPC_STRING_LENGTH = 256; + +#define IPC_STRING(name) char name[os::IPC::IPC_STRING_LENGTH]; +#define COPY_IPC_STRING(name) \ + TRY(String::from_string_view(StringView::from_fixed_size_cstring(name, os::IPC::IPC_STRING_LENGTH))) +#define SET_IPC_STRING(name, value) strlcpy(name, value, os::IPC::IPC_STRING_LENGTH) + + /** + * @brief Sends an IPC message without waiting for a reply. + * + * @tparam Client The type of the client interface being used to communicate. + * @tparam T The type of the message. + * @param client The connection object being used to communicate. + * @param message The IPC message. + * @return Result Whether the operation succeded. + */ + template Result send_async(Client& client, const T& message) + { + u8 id = T::id; + TRY(client.send_typed(id)); + TRY(client.send_typed(message)); + return {}; + } + + /** + * @brief Sends an error result to the IPC connection, indicating that an operation could not be performed. + * + * @tparam Client The type of the client interface being used to communicate. + * @param client The connection object being used to communicate. + * @param error The error code. + * @return Result Whether the operation succeded. + */ + template Result send_error(Client& client, int error) + { + u8 id = 0; + TRY(client.send_typed(id)); + TRY(client.send_typed(error)); + return {}; + } + + /** + * @brief Sends an IPC message and waits for a reply (client-only). + * + * @tparam ResponseType The type of the response. + * @tparam T The type of the message. + * @param client The connection object being used to communicate. + * @param message The IPC message. + * @param handler The function used to handle messages that do not match the reply. + * @return Result An error, or the response. + */ + template + Result send_sync(os::LocalClient& client, const T& message, + decltype(handle_ipc_client_event) handler = handle_ipc_client_event) + { + u8 id = T::id; + TRY(client.send_typed(id)); + TRY(client.send_typed(message)); + + // We allow receiving 5 messages of different types, but if those have passed and we still don't have a + // reply, fail with ENOMSG. + int max_other_messages = 5; + + while (max_other_messages) + { + u8 response_id; + auto rc = client.recv_typed(response_id); + if (rc.has_error() && rc.error() == EAGAIN) continue; + + if (response_id == 0) // Error result + { + while (1) + { + int code; + rc = client.recv_typed(code); + if (rc.has_error() && rc.error() == EAGAIN) continue; + return err(code); + } + } + + if (response_id != ResponseType::id) + { + TRY(handler(client, response_id)); + max_other_messages--; + continue; + } + + while (1) + { + ResponseType response; + rc = client.recv_typed(response); + if (rc.has_error() && rc.error() == EAGAIN) continue; + return response; + } + } + + return err(ENOMSG); + } + + /** + * @brief Check for new IPC messages on a connection and handle them appropriately. + * + * @param client The client connection. + * @param handler The function used to handle messages. + * @return Result Whether the operation succeded. + */ + Result check_for_messages(os::LocalClient& client, + decltype(handle_ipc_client_event) handler = handle_ipc_client_event); + + /** + * @brief Check for new IPC messages on a connection and handle them appropriately. + * + * @param server The server connection. + * @param handler The function used to handle messages. + * @return Result Whether the operation succeded. + */ + Result check_for_messages(os::LocalServer::Client& server, + decltype(handle_ipc_server_event) handler = handle_ipc_server_event); + } +} diff --git a/libos/src/IPC.cpp b/libos/src/IPC.cpp new file mode 100644 index 00000000..fbe6b164 --- /dev/null +++ b/libos/src/IPC.cpp @@ -0,0 +1,39 @@ +/** + * @file IPC.cpp + * @author apio (cloudapio.eu) + * @brief Inter-process communication primitives. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#include + +namespace os::IPC +{ + Result check_for_messages(os::LocalClient& client, decltype(handle_ipc_client_event) handler) + { + u8 id; + auto rc = client.recv_typed(id); + if (rc.has_error()) + { + if (rc.error() == EAGAIN) return {}; // No messages, and the caller does not want us to block. + return rc.release_error(); + } + + return handler(client, id); + } + + Result check_for_messages(os::LocalServer::Client& client, decltype(handle_ipc_server_event) handler) + { + u8 id; + auto rc = client.recv_typed(id); + if (rc.has_error()) + { + if (rc.error() == EAGAIN) return {}; // No messages, and the caller does not want us to block. + return rc.release_error(); + } + + return handler(client, id); + } +}