libos: Add basic IPC message framework

This commit is contained in:
apio 2023-08-14 18:14:35 +02:00
parent 3d90d7f98e
commit 9125561cab
Signed by: apio
GPG Key ID: B8A7D06E42258954
3 changed files with 197 additions and 0 deletions

View File

@ -16,6 +16,7 @@ set(SOURCES
src/Security.cpp
src/LocalServer.cpp
src/LocalClient.cpp
src/IPC.cpp
)
add_library(os ${SOURCES})

157
libos/include/os/IPC.h Normal file
View File

@ -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 <os/LocalClient.h>
#include <os/LocalServer.h>
#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<void> Whether the operation succeded.
*/
extern Result<void> 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<void> Whether the operation succeded.
*/
extern Result<void> 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<void> Whether the operation succeded.
*/
template <typename Client, typename T> Result<void> 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<void> Whether the operation succeded.
*/
template <typename Client> Result<void> 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<ResponseType> An error, or the response.
*/
template <typename ResponseType, typename T>
Result<ResponseType> 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<void> Whether the operation succeded.
*/
Result<void> 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<void> Whether the operation succeded.
*/
Result<void> check_for_messages(os::LocalServer::Client& server,
decltype(handle_ipc_server_event) handler = handle_ipc_server_event);
}
}

39
libos/src/IPC.cpp Normal file
View File

@ -0,0 +1,39 @@
/**
* @file IPC.cpp
* @author apio (cloudapio.eu)
* @brief Inter-process communication primitives.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <os/IPC.h>
namespace os::IPC
{
Result<void> 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<void> 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);
}
}