/** * @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); } }