1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-10-10 19:32:10 +00:00
This commit is contained in:
DS 2025-09-30 20:09:27 +03:00 committed by GitHub
commit 2fbcaf5bb3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
50 changed files with 1545 additions and 94 deletions

View file

@ -75,6 +75,7 @@ set(client_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/texturesource.cpp
${CMAKE_CURRENT_SOURCE_DIR}/imagesource.cpp
${CMAKE_CURRENT_SOURCE_DIR}/wieldmesh.cpp
${CMAKE_CURRENT_SOURCE_DIR}/mod_vfs.cpp
${CMAKE_CURRENT_SOURCE_DIR}/shadows/dynamicshadows.cpp
${CMAKE_CURRENT_SOURCE_DIR}/shadows/dynamicshadowsrender.cpp
${CMAKE_CURRENT_SOURCE_DIR}/shadows/shadowsshadercallbacks.cpp

View file

@ -10,6 +10,7 @@
#include <json/json.h>
#include "client.h"
#include "client/fontengine.h"
#include "client/mod_vfs.h"
#include "network/clientopcodes.h"
#include "network/connection.h"
#include "network/networkpacket.h"
@ -54,6 +55,8 @@
#include "content/mod_configuration.h"
#include "mapnode.h"
#include "item_visuals_manager.h"
#include "script/sscsm/sscsm_controller.h"
#include "script/sscsm/sscsm_events.h"
extern gui::IGUIEnvironment* guienv;
@ -136,6 +139,62 @@ Client::Client(
m_cache_save_interval = g_settings->getU16("server_map_save_interval");
m_mesh_grid = { g_settings->getU16("client_mesh_chunk") };
m_sscsm_controller = SSCSMController::create();
{
auto event1 = std::make_unique<SSCSMEventUpdateVFSFiles>();
ModVFS tmp_mod_vfs;
// FIXME: only read files that are relevant to sscsm, and compute sha2 digests
tmp_mod_vfs.scanModIntoMemory("*client_builtin*", getBuiltinLuaPath());
for (auto &p : tmp_mod_vfs.m_vfs) {
event1->files.emplace_back(p.first, std::move(p.second));
}
m_sscsm_controller->runEvent(this, std::move(event1));
// load client builtin immediately
auto event2 = std::make_unique<SSCSMEventLoadMods>();
event2->mods.emplace_back("*client_builtin*", "*client_builtin*:init.lua");
m_sscsm_controller->runEvent(this, std::move(event2));
}
{
//FIXME: network packets
//FIXME: check that *client_builtin* is not overridden
std::string enable_sscsm = g_settings->get("enable_sscsm");
if (enable_sscsm == "singleplayer") { //FIXME: enum
auto event1 = std::make_unique<SSCSMEventUpdateVFSFiles>();
// some simple test code
event1->files.emplace_back("sscsm_test0:init.lua",
R"=+=(
print("sscsm_test0: loading")
--print(dump(_G))
--print(debug.traceback())
do
local pos = vector.zero()
local function print_nodes()
print(string.format("node at %s: %s", pos, dump(core.get_node_or_nil(pos))))
pos = pos:offset(1, 0, 0)
core.after(1, print_nodes)
end
core.after(0, print_nodes)
end
)=+=");
m_sscsm_controller->runEvent(this, std::move(event1));
auto event2 = std::make_unique<SSCSMEventLoadMods>();
event2->mods.emplace_back("sscsm_test0", "sscsm_test0:init.lua");
m_sscsm_controller->runEvent(this, std::move(event2));
}
}
}
void Client::migrateModStorage()
@ -183,12 +242,14 @@ void Client::loadMods()
return;
}
m_mod_vfs = std::make_unique<ModVFS>();
m_script = new ClientScripting(this);
m_env.setScript(m_script);
m_script->setEnv(&m_env);
// Load builtin
scanModIntoMemory(BUILTIN_MOD_NAME, getBuiltinLuaPath());
m_mod_vfs->scanModIntoMemory(BUILTIN_MOD_NAME, getBuiltinLuaPath());
m_script->loadModFromMemory(BUILTIN_MOD_NAME);
m_script->checkSetByBuiltin();
@ -227,7 +288,7 @@ void Client::loadMods()
// Load "mod" scripts
for (const ModSpec &mod : m_mods) {
mod.checkAndLog();
scanModIntoMemory(mod.name, mod.path);
m_mod_vfs->scanModIntoMemory(mod.name, mod.path);
}
// Run them
@ -249,35 +310,6 @@ void Client::loadMods()
m_script->on_minimap_ready(m_minimap.get());
}
void Client::scanModSubfolder(const std::string &mod_name, const std::string &mod_path,
std::string mod_subpath)
{
std::string full_path = mod_path + DIR_DELIM + mod_subpath;
std::vector<fs::DirListNode> mod = fs::GetDirListing(full_path);
for (const fs::DirListNode &j : mod) {
if (j.name[0] == '.')
continue;
if (j.dir) {
scanModSubfolder(mod_name, mod_path, mod_subpath + j.name + DIR_DELIM);
continue;
}
std::replace(mod_subpath.begin(), mod_subpath.end(), DIR_DELIM_CHAR, '/');
std::string real_path = full_path + j.name;
std::string vfs_path = mod_name + ":" + mod_subpath + j.name;
infostream << "Client::scanModSubfolder(): Loading \"" << real_path
<< "\" as \"" << vfs_path << "\"." << std::endl;
std::string contents;
if (!fs::ReadFile(real_path, contents, true)) {
continue;
}
m_mod_vfs.emplace(vfs_path, contents);
}
}
const std::string &Client::getBuiltinLuaPath()
{
static const std::string builtin_dir = porting::path_share + DIR_DELIM + "builtin";
@ -523,6 +555,12 @@ void Client::step(float dtime)
*/
LocalPlayer *player = m_env.getLocalPlayer();
{
auto event = std::make_unique<SSCSMEventOnStep>();
event->dtime = dtime;
m_sscsm_controller->runEvent(this, std::move(event));
}
// Step environment (also handles player controls)
m_env.step(dtime);
m_sound->step(dtime);
@ -2034,23 +2072,6 @@ scene::IAnimatedMesh* Client::getMesh(const std::string &filename, bool cache)
return mesh;
}
const std::string* Client::getModFile(std::string filename)
{
// strip dir delimiter from beginning of path
auto pos = filename.find_first_of(':');
if (pos == std::string::npos)
return nullptr;
pos++;
auto pos2 = filename.find_first_not_of('/', pos);
if (pos2 > pos)
filename.erase(pos, pos2 - pos);
StringMap::const_iterator it = m_mod_vfs.find(filename);
if (it == m_mod_vfs.end())
return nullptr;
return &it->second;
}
/*
* Mod channels
*/

View file

@ -45,6 +45,8 @@ class NodeDefManager;
class ParticleManager;
class RenderingEngine;
class SingleMediaDownloader;
class ClientScripting;
class SSCSMController;
struct ChatMessage;
struct ClientDynamicInfo;
struct ClientEvent;
@ -53,6 +55,7 @@ struct MapNode;
struct PlayerControl;
struct PointedThing;
struct ItemVisualsManager;
struct ModVFS;
namespace scene {
class IAnimatedMesh;
@ -100,8 +103,6 @@ private:
std::map<u16, u32> m_packets;
};
class ClientScripting;
class Client : public con::PeerHandler, public InventoryManager, public IGameDef
{
public:
@ -127,14 +128,6 @@ public:
~Client();
DISABLE_CLASS_COPY(Client);
// Load local mods into memory
void scanModSubfolder(const std::string &mod_name, const std::string &mod_path,
std::string mod_subpath);
inline void scanModIntoMemory(const std::string &mod_name, const std::string &mod_path)
{
scanModSubfolder(mod_name, mod_path, "");
}
/*
request all threads managed by client to be stopped
*/
@ -385,7 +378,7 @@ public:
bool checkLocalPrivilege(const std::string &priv)
{ return checkPrivilege(priv); }
virtual scene::IAnimatedMesh* getMesh(const std::string &filename, bool cache = false);
const std::string* getModFile(std::string filename);
ModVFS *getModVFS() { return m_mod_vfs.get(); }
ModStorageDatabase *getModStorageDatabase() override { return m_mod_storage_database; }
ItemVisualsManager *getItemVisualsManager() { return m_item_visuals_manager; }
@ -586,7 +579,10 @@ private:
ModStorageDatabase *m_mod_storage_database = nullptr;
float m_mod_storage_save_timer = 10.0f;
std::vector<ModSpec> m_mods;
StringMap m_mod_vfs;
std::unique_ptr<ModVFS> m_mod_vfs;
// SSCSM
std::unique_ptr<SSCSMController> m_sscsm_controller;
bool m_shutdown = false;

56
src/client/mod_vfs.cpp Normal file
View file

@ -0,0 +1,56 @@
// SPDX-FileCopyrightText: 2025 Luanti authors
//
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "mod_vfs.h"
#include "filesys.h"
#include "log.h"
#include <algorithm>
void ModVFS::scanModSubfolder(const std::string &mod_name, const std::string &mod_path,
std::string mod_subpath)
{
std::string full_path = mod_path + DIR_DELIM + mod_subpath;
std::vector<fs::DirListNode> mod = fs::GetDirListing(full_path);
for (const fs::DirListNode &j : mod) {
if (j.name[0] == '.')
continue;
if (j.dir) {
scanModSubfolder(mod_name, mod_path, mod_subpath + j.name + DIR_DELIM);
continue;
}
std::replace(mod_subpath.begin(), mod_subpath.end(), DIR_DELIM_CHAR, '/');
std::string real_path = full_path + j.name;
std::string vfs_path = mod_name + ":" + mod_subpath + j.name;
infostream << "ModVFS::scanModSubfolder(): Loading \"" << real_path
<< "\" as \"" << vfs_path << "\"." << std::endl;
std::string contents;
if (!fs::ReadFile(real_path, contents)) {
errorstream << "ModVFS::scanModSubfolder(): Can't read file \""
<< real_path << "\"." << std::endl;
continue;
}
m_vfs.emplace(vfs_path, contents);
}
}
const std::string *ModVFS::getModFile(std::string filename)
{
// strip dir delimiter from beginning of path
auto pos = filename.find_first_of(':');
if (pos == std::string::npos)
return nullptr;
++pos;
auto pos2 = filename.find_first_not_of('/', pos);
if (pos2 > pos)
filename.erase(pos, pos2 - pos);
auto it = m_vfs.find(filename);
if (it == m_vfs.end())
return nullptr;
return &it->second;
}

23
src/client/mod_vfs.h Normal file
View file

@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: 2025 Luanti authors
//
// SPDX-License-Identifier: LGPL-2.1-or-later
#pragma once
#include <string>
#include <unordered_map>
struct ModVFS
{
void scanModSubfolder(const std::string &mod_name, const std::string &mod_path,
std::string mod_subpath);
inline void scanModIntoMemory(const std::string &mod_name, const std::string &mod_path)
{
scanModSubfolder(mod_name, mod_path, "");
}
const std::string *getModFile(std::string filename);
std::unordered_map<std::string, std::string> m_vfs;
};

View file

@ -105,3 +105,7 @@
// The intent is to ensure that the rendering doesn't turn terribly blurry
// when filtering is enabled.
#define TEXTURE_FILTER_MIN_SIZE 192U
// Resolution of clocks that SSCSM has access to, in us.
// Used as countermeasure against side-channel attacks.
#define SSCSM_CLOCK_RESOLUTION_US 20

View file

@ -121,6 +121,7 @@ void set_default_settings()
settings->setDefault("curl_verify_cert", "true");
settings->setDefault("enable_remote_media_server", "true");
settings->setDefault("enable_client_modding", "false");
settings->setDefault("enable_sscsm", "nowhere");
settings->setDefault("max_out_chat_queue_size", "20");
settings->setDefault("pause_on_lost_focus", "false");
settings->setDefault("enable_split_login_register", "true");

View file

@ -87,6 +87,11 @@ public:
ModError(const std::string &s): BaseException(s) {}
};
class MisbehavedSSCSMException : public BaseException {
public:
MisbehavedSSCSMException(const std::string &s): BaseException(s) {}
};
/*
Some "old-style" interrupts:

View file

@ -1,6 +1,7 @@
add_subdirectory(common)
add_subdirectory(cpp_api)
add_subdirectory(lua_api)
add_subdirectory(sscsm)
# Used by server and client
file(GLOB common_SCRIPT_HDRS "${CMAKE_CURRENT_SOURCE_DIR}/*.h")
@ -20,9 +21,11 @@ set(client_SCRIPT_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/scripting_mainmenu.cpp
${CMAKE_CURRENT_SOURCE_DIR}/scripting_client.cpp
${CMAKE_CURRENT_SOURCE_DIR}/scripting_pause_menu.cpp
${CMAKE_CURRENT_SOURCE_DIR}/scripting_sscsm.cpp
${client_SCRIPT_COMMON_SRCS}
${client_SCRIPT_CPP_API_SRCS}
${client_SCRIPT_LUA_API_SRCS}
${client_SCRIPT_SSCSM_SRCS}
PARENT_SCOPE)

View file

@ -22,5 +22,6 @@ set(client_SCRIPT_CPP_API_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/s_client_common.cpp
${CMAKE_CURRENT_SOURCE_DIR}/s_mainmenu.cpp
${CMAKE_CURRENT_SOURCE_DIR}/s_pause_menu.cpp
${CMAKE_CURRENT_SOURCE_DIR}/s_sscsm.cpp
PARENT_SCOPE)

View file

@ -16,6 +16,8 @@
#include "server.h"
#if CHECK_CLIENT_BUILD()
#include "client/client.h"
#include "client/mod_vfs.h"
#include "sscsm/sscsm_environment.h"
#endif
#if BUILD_WITH_TRACY
@ -74,7 +76,7 @@ ScriptApiBase::ScriptApiBase(ScriptingType type):
lua_atpanic(m_luastack, &luaPanic);
if (m_type == ScriptingType::Client)
if (m_type == ScriptingType::Client || m_type == ScriptingType::SSCSM)
clientOpenLibs(m_luastack);
else
luaL_openlibs(m_luastack);
@ -143,7 +145,8 @@ ScriptApiBase::ScriptApiBase(ScriptingType type):
// Finally, put the table into the global environment:
lua_setglobal(m_luastack, "core");
if (m_type == ScriptingType::Client)
if (m_type == ScriptingType::Client
|| m_type == ScriptingType::SSCSM)
lua_pushstring(m_luastack, "/");
else
lua_pushstring(m_luastack, DIR_DELIM);
@ -210,7 +213,8 @@ void ScriptApiBase::checkSetByBuiltin()
if (getType() == ScriptingType::Server ||
(getType() == ScriptingType::Async && m_gamedef) ||
getType() == ScriptingType::Emerge ||
getType() == ScriptingType::Client) {
getType() == ScriptingType::Client ||
getType() == ScriptingType::SSCSM) {
CHECK(CUSTOM_RIDX_READ_NODE, "read_node");
CHECK(CUSTOM_RIDX_PUSH_NODE, "push_node");
}
@ -265,16 +269,18 @@ void ScriptApiBase::loadScript(const std::string &script_path)
}
#if CHECK_CLIENT_BUILD()
void ScriptApiBase::loadModFromMemory(const std::string &mod_name)
void ScriptApiBase::loadModFromMemory(const std::string &mod_name, std::string init_path)
{
ModNameStorer mod_name_storer(getStack(), mod_name);
sanity_check(m_type == ScriptingType::Client);
sanity_check(m_type == ScriptingType::Client
|| m_type == ScriptingType::SSCSM);
const std::string init_filename = mod_name + ":init.lua";
const std::string chunk_name = "@" + init_filename;
if (init_path.empty())
init_path = mod_name + ":init.lua";
const std::string chunk_name = "@" + init_path;
const std::string *contents = getClient()->getModFile(init_filename);
const std::string *contents = getModVFS()->getModFile(init_path);
if (!contents)
throw ModError("Mod \"" + mod_name + "\" lacks init.lua");
@ -540,8 +546,18 @@ Server* ScriptApiBase::getServer()
}
#if CHECK_CLIENT_BUILD()
Client* ScriptApiBase::getClient()
Client *ScriptApiBase::getClient()
{
return dynamic_cast<Client *>(m_gamedef);
}
ModVFS *ScriptApiBase::getModVFS()
{
if (m_type == ScriptingType::Client)
return getClient()->getModVFS();
else if (m_type == ScriptingType::SSCSM)
return getSSCSMEnv()->getModVFS();
else
return nullptr;
}
#endif

View file

@ -43,11 +43,12 @@ extern "C" {
enum class ScriptingType: u8 {
Async, // either mainmenu (client) or ingame (server)
Client,
Client, // CPCSM
MainMenu,
Server,
Emerge,
PauseMenu,
SSCSM,
};
class Server;
@ -58,8 +59,10 @@ class EmergeThread;
class IGameDef;
class Environment;
class GUIEngine;
class SSCSMEnvironment;
class ServerActiveObject;
struct PlayerHPChangeReason;
struct ModVFS;
class ScriptApiBase : protected LuaHelper {
public:
@ -77,7 +80,7 @@ public:
void loadScript(const std::string &script_path);
#if CHECK_CLIENT_BUILD()
void loadModFromMemory(const std::string &mod_name);
void loadModFromMemory(const std::string &mod_name, std::string init_path = "");
#endif
void runCallbacksRaw(int nargs,
@ -90,9 +93,10 @@ public:
ScriptingType getType() { return m_type; }
IGameDef *getGameDef() { return m_gamedef; }
Server* getServer();
Server *getServer();
#if CHECK_CLIENT_BUILD()
Client* getClient();
Client *getClient();
ModVFS *getModVFS();
#endif
// IMPORTANT: These cannot be used for any security-related uses, they exist
@ -158,6 +162,9 @@ protected:
#if CHECK_CLIENT_BUILD()
GUIEngine* getGuiEngine() { return m_guiengine; }
void setGuiEngine(GUIEngine* guiengine) { m_guiengine = guiengine; }
SSCSMEnvironment *getSSCSMEnv() { return m_sscsm_environment; }
void setSSCSMEnv(SSCSMEnvironment *env) { m_sscsm_environment = env; }
#endif
EmergeThread* getEmergeThread() { return m_emerge; }
@ -178,14 +185,15 @@ protected:
private:
static int luaPanic(lua_State *L);
lua_State *m_luastack = nullptr;
lua_State *m_luastack = nullptr;
IGameDef *m_gamedef = nullptr;
Environment *m_environment = nullptr;
IGameDef *m_gamedef = nullptr;
Environment *m_environment = nullptr;
#if CHECK_CLIENT_BUILD()
GUIEngine *m_guiengine = nullptr;
GUIEngine *m_guiengine = nullptr;
SSCSMEnvironment *m_sscsm_environment = nullptr;
#endif
EmergeThread *m_emerge = nullptr;
EmergeThread *m_emerge = nullptr;
ScriptingType m_type;
ScriptingType m_type;
};

View file

@ -9,8 +9,10 @@
#include "server.h"
#if CHECK_CLIENT_BUILD()
#include "client/client.h"
#include "client/mod_vfs.h"
#endif
#include "settings.h"
#include "constants.h"
#include <cerrno>
#include <string>
@ -378,6 +380,140 @@ void ScriptApiSecurity::initializeSecurityClient()
setLuaEnv(L, thread);
}
void ScriptApiSecurity::initializeSecuritySSCSM()
{
static const char *whitelist[] = {
"assert",
"core",
"collectgarbage",
"DIR_DELIM",
"error",
"getfenv",
"ipairs",
"next",
"pairs",
"pcall",
"rawequal",
"rawget",
"rawset",
"select",
"setfenv",
"getmetatable",
"setmetatable",
"tonumber",
"tostring",
"type",
"unpack",
"_VERSION",
"xpcall",
// Completely safe libraries
"coroutine",
"table",
"math",
"bit",
};
static const char *os_whitelist[] = {
"difftime",
"time"
};
static const char *debug_whitelist[] = {
"getinfo", // used by client builtin and unset before mods load
"traceback"
};
static const char *string_whitelist[] = { // all but string.dump
"byte",
"char",
"dump",
"find",
"format",
"gmatch",
"gsub",
"len",
"lower",
"match",
"rep",
"reverse",
"sub",
"upper"
};
#if USE_LUAJIT
static const char *jit_whitelist[] = {
"arch",
"flush",
"off",
"on",
"opt",
"os",
"status",
"version",
"version_num",
};
#endif
m_secure = true;
lua_State *L = getStack();
int thread = getThread(L);
// create an empty environment
createEmptyEnv(L);
// Copy safe base functions
lua_getglobal(L, "_G");
lua_getfield(L, -2, "_G");
copy_safe(L, whitelist, sizeof(whitelist));
// And replace unsafe ones
SECURE_API(g, dofile);
SECURE_API(g, load);
SECURE_API(g, loadfile);
SECURE_API(g, loadstring);
SECURE_API(g, require);
lua_pop(L, 2);
// Copy safe OS functions
lua_getglobal(L, "os");
lua_newtable(L);
copy_safe(L, os_whitelist, sizeof(os_whitelist));
// And replace unsafe ones
SECURE_API(os, clock);
lua_setfield(L, -3, "os");
lua_pop(L, 1); // Pop old OS
// Copy safe debug functions
lua_getglobal(L, "debug");
lua_newtable(L);
copy_safe(L, debug_whitelist, sizeof(debug_whitelist));
lua_setfield(L, -3, "debug");
lua_pop(L, 1); // Pop old debug
// Copy safe string functions
lua_getglobal(L, "string");
lua_newtable(L);
copy_safe(L, string_whitelist, sizeof(string_whitelist));
lua_setfield(L, -3, "string");
lua_pop(L, 1); // Pop old string
#if USE_LUAJIT
// Copy safe jit functions, if they exist
lua_getglobal(L, "jit");
lua_newtable(L);
copy_safe(L, jit_whitelist, sizeof(jit_whitelist));
lua_setfield(L, -3, "jit");
lua_pop(L, 1); // Pop old jit
#endif
// Set the environment to the one we created earlier
setLuaEnv(L, thread);
}
#endif
int ScriptApiSecurity::getThread(lua_State *L)
@ -775,10 +911,11 @@ int ScriptApiSecurity::sl_g_loadfile(lua_State *L)
#if CHECK_CLIENT_BUILD()
ScriptApiBase *script = ModApiBase::getScriptApiBase(L);
// Client implementation
if (script->getType() == ScriptingType::Client) {
// SSCSM & CPCSM implementation
if (script->getType() == ScriptingType::Client
|| script->getType() == ScriptingType::SSCSM) {
std::string path = readParam<std::string>(L, 1);
const std::string *contents = script->getClient()->getModFile(path);
const std::string *contents = script->getModVFS()->getModFile(path);
if (!contents) {
std::string error_msg = "Couldn't find script called: " + path;
lua_pushnil(L);
@ -963,3 +1100,12 @@ int ScriptApiSecurity::sl_os_setlocale(lua_State *L)
lua_call(L, cat ? 2 : 1, 1);
return 1;
}
int ScriptApiSecurity::sl_os_clock(lua_State *L)
{
auto t = clock();
t = t - t % (SSCSM_CLOCK_RESOLUTION_US * CLOCKS_PER_SEC / 1'000'000);
lua_pushnumber(L, static_cast<lua_Number>(t) / static_cast<lua_Number>(CLOCKS_PER_SEC));
return 1;
}

View file

@ -31,8 +31,10 @@ public:
void initializeSecurity();
#if CHECK_CLIENT_BUILD()
void initializeSecurityClient();
void initializeSecuritySSCSM();
#else
inline void initializeSecurityClient() { assert(0); }
void initializeSecurityClient() { assert(0); }
void initializeSecuritySSCSM() { assert(0); }
#endif
// Checks if the Lua state has been secured
@ -115,4 +117,5 @@ private:
static int sl_os_rename(lua_State *L);
static int sl_os_remove(lua_State *L);
static int sl_os_setlocale(lua_State *L);
static int sl_os_clock(lua_State *L);
};

View file

@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: 2025 Luanti authors
//
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "s_sscsm.h"
#include "s_internal.h"
#include "script/sscsm/sscsm_environment.h"
void ScriptApiSSCSM::load_mods(const std::vector<std::pair<std::string, std::string>> &mods)
{
infostream << "Loading SSCSMs:" << std::endl;
for (const auto &m : mods) {
infostream << "Loading SSCSM " << m.first << std::endl;
loadModFromMemory(m.first, m.second);
}
}
void ScriptApiSSCSM::environment_step(float dtime)
{
SCRIPTAPI_PRECHECKHEADER
// Get core.registered_globalsteps
lua_getglobal(L, "core");
lua_getfield(L, -1, "registered_globalsteps");
// Call callbacks
lua_pushnumber(L, dtime);
runCallbacks(1, RUN_CALLBACKS_MODE_FIRST);
}

View file

@ -0,0 +1,15 @@
// SPDX-FileCopyrightText: 2025 Luanti authors
//
// SPDX-License-Identifier: LGPL-2.1-or-later
#pragma once
#include "cpp_api/s_base.h"
class ScriptApiSSCSM : virtual public ScriptApiBase
{
public:
void load_mods(const std::vector<std::pair<std::string, std::string>> &mods);
void environment_step(float dtime);
};

View file

@ -43,4 +43,5 @@ set(client_SCRIPT_LUA_API_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/l_particles_local.cpp
${CMAKE_CURRENT_SOURCE_DIR}/l_pause_menu.cpp
${CMAKE_CURRENT_SOURCE_DIR}/l_storage.cpp
${CMAKE_CURRENT_SOURCE_DIR}/l_sscsm.cpp
PARENT_SCOPE)

View file

@ -58,6 +58,11 @@ GUIEngine *ModApiBase::getGuiEngine(lua_State *L)
{
return getScriptApiBase(L)->getGuiEngine();
}
SSCSMEnvironment *ModApiBase::getSSCSMEnv(lua_State *L)
{
return getScriptApiBase(L)->getSSCSMEnv();
}
#endif
EmergeThread *ModApiBase::getEmergeThread(lua_State *L)

View file

@ -23,6 +23,7 @@ class EmergeThread;
class ScriptApiBase;
class Server;
class Environment;
class SSCSMEnvironment;
class ServerInventoryManager;
class ModApiBase : protected LuaHelper {
@ -33,6 +34,7 @@ public:
#if CHECK_CLIENT_BUILD()
static Client* getClient(lua_State *L);
static GUIEngine* getGuiEngine(lua_State *L);
static SSCSMEnvironment *getSSCSMEnv(lua_State *L);
#endif // !SERVER
static EmergeThread* getEmergeThread(lua_State *L);

View file

@ -59,7 +59,7 @@ int ModApiClient::l_get_current_modname(lua_State *L)
int ModApiClient::l_get_modpath(lua_State *L)
{
std::string modname = readParam<std::string>(L, 1);
// Client mods use a virtual filesystem, see Client::scanModSubfolder()
// Client mods use a virtual filesystem, see ModVFS::scanModSubfolder()
std::string path = modname + ":";
lua_pushstring(L, path.c_str());
return 1;
@ -284,7 +284,20 @@ int ModApiClient::l_get_privilege_list(lua_State *L)
// get_builtin_path()
int ModApiClient::l_get_builtin_path(lua_State *L)
{
lua_pushstring(L, BUILTIN_MOD_NAME ":");
std::string modname;
if (getScriptApiBase(L)->getType() == ScriptingType::Client) {
modname = BUILTIN_MOD_NAME;
} else if (getScriptApiBase(L)->getType() == ScriptingType::SSCSM) {
// get_builtin_path() is only called in builtin, so this is fine
modname = ScriptApiBase::getCurrentModNameInsecure(L);
if (modname != "*client_builtin*" && modname != "*server_builtin*")
modname = "";
}
if (modname.empty())
return 0;
lua_pushstring(L, (modname + ":").c_str());
return 1;
}
@ -322,3 +335,11 @@ void ModApiClient::Initialize(lua_State *L, int top)
API_FCT(get_language);
API_FCT(get_csm_restrictions);
}
void ModApiClient::InitializeSSCSM(lua_State *L, int top)
{
API_FCT(get_current_modname);
API_FCT(get_modpath);
API_FCT(print);
API_FCT(get_builtin_path);
}

View file

@ -71,4 +71,5 @@ private:
public:
static void Initialize(lua_State *L, int top);
static void InitializeSSCSM(lua_State *L, int top);
};

View file

@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2025 Luanti authors
//
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "l_sscsm.h"
#include "common/c_content.h"
#include "common/c_converter.h"
#include "l_internal.h"
#include "script/sscsm/sscsm_environment.h"
#include "script/sscsm/sscsm_requests.h"
// get_node_or_nil(pos)
// pos = {x=num, y=num, z=num}
int ModApiSSCSM::l_get_node_or_nil(lua_State *L)
{
// pos
v3s16 pos = read_v3s16(L, 1);
// Do it
auto request = SSCSMRequestGetNode{};
request.pos = pos;
auto answer = getSSCSMEnv(L)->doRequest(std::move(request));
if (answer.is_pos_ok) {
// Return node
pushnode(L, answer.node);
} else {
lua_pushnil(L);
}
return 1;
}
void ModApiSSCSM::Initialize(lua_State *L, int top)
{
API_FCT(get_node_or_nil);
}

View file

@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: 2025 Luanti authors
//
// SPDX-License-Identifier: LGPL-2.1-or-later
#pragma once
#include "lua_api/l_base.h"
class ModApiSSCSM : public ModApiBase
{
private:
// get_node_or_nil(pos)
static int l_get_node_or_nil(lua_State *L);
public:
static void Initialize(lua_State *L, int top);
};

View file

@ -30,6 +30,7 @@
#include "util/png.h"
#include "player.h"
#include "daynightratio.h"
#include "constants.h"
#include <cstdio>
// only available in zstd 1.3.5+
@ -82,6 +83,16 @@ int ModApiUtil::l_get_us_time(lua_State *L)
return 1;
}
// get_us_time() for SSCSM
int ModApiUtil::l_get_us_time_sscsm(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
auto t = porting::getTimeUs();
t = t - t % SSCSM_CLOCK_RESOLUTION_US;
lua_pushnumber(L, t);
return 1;
}
// Maximum depth of a JSON object:
// Reading and writing should not overflow the Lua, C, or jsoncpp stacks.
constexpr static u16 MAX_JSON_DEPTH = 1024;
@ -798,6 +809,37 @@ void ModApiUtil::InitializeClient(lua_State *L, int top)
lua_setfield(L, top, "settings");
}
void ModApiUtil::InitializeSSCSM(lua_State *L, int top)
{
API_FCT(log);
registerFunction(L, "get_us_time", l_get_us_time_sscsm, top);
API_FCT(parse_json);
API_FCT(write_json);
API_FCT(is_yes);
API_FCT(compress);
API_FCT(decompress);
API_FCT(encode_base64);
API_FCT(decode_base64);
API_FCT(get_version);
API_FCT(sha1);
API_FCT(sha256);
API_FCT(colorspec_to_colorstring);
API_FCT(colorspec_to_bytes);
API_FCT(colorspec_to_table);
API_FCT(time_to_day_night_ratio);
API_FCT(get_last_run_mod);
API_FCT(set_last_run_mod);
API_FCT(urlencode);
}
void ModApiUtil::InitializeAsync(lua_State *L, int top)
{
API_FCT(log);

View file

@ -28,6 +28,9 @@ private:
// get us precision time
static int l_get_us_time(lua_State *L);
// get_us_time() for SSCSM. less precise
static int l_get_us_time_sscsm(lua_State *L);
// parse_json(str[, nullvalue])
static int l_parse_json(lua_State *L);
@ -134,4 +137,5 @@ public:
static void Initialize(lua_State *L, int top);
static void InitializeAsync(lua_State *L, int top);
static void InitializeClient(lua_State *L, int top);
static void InitializeSSCSM(lua_State *L, int top);
};

View file

@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2025 Luanti authors
//
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "scripting_sscsm.h"
#include "cpp_api/s_internal.h"
#include "lua_api/l_sscsm.h"
#include "lua_api/l_util.h"
#include "lua_api/l_client.h"
SSCSMScripting::SSCSMScripting(SSCSMEnvironment *env) :
ScriptApiBase(ScriptingType::SSCSM)
{
setSSCSMEnv(env);
SCRIPTAPI_PRECHECKHEADER
initializeSecuritySSCSM();
lua_getglobal(L, "core");
int top = lua_gettop(L);
// Initialize our lua_api modules
initializeModApi(L, top);
lua_pop(L, 1);
// Push builtin initialization type
lua_pushstring(L, "sscsm");
lua_setglobal(L, "INIT");
}
void SSCSMScripting::initializeModApi(lua_State *L, int top)
{
ModApiUtil::InitializeSSCSM(L, top);
ModApiClient::InitializeSSCSM(L, top);
ModApiSSCSM::Initialize(L, top);
}

View file

@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: 2025 Luanti authors
//
// SPDX-License-Identifier: LGPL-2.1-or-later
#pragma once
#include "cpp_api/s_base.h"
#include "cpp_api/s_sscsm.h"
#include "cpp_api/s_security.h"
class SSCSMScripting :
virtual public ScriptApiBase,
public ScriptApiSSCSM,
public ScriptApiSecurity
{
public:
SSCSMScripting(SSCSMEnvironment *env);
protected:
bool checkPathInternal(const std::string &abs_path, bool write_required,
bool *write_allowed) { return false; };
private:
void initializeModApi(lua_State *L, int top);
};

View file

@ -0,0 +1,7 @@
file(GLOB client_SCRIPT_SSCSM_HDRS "${CMAKE_CURRENT_SOURCE_DIR}/*.h")
set(client_SCRIPT_SSCSM_SRCS
${client_SCRIPT_SSCSM_HDRS}
${CMAKE_CURRENT_SOURCE_DIR}/sscsm_controller.cpp
${CMAKE_CURRENT_SOURCE_DIR}/sscsm_environment.cpp
PARENT_SCOPE)

View file

@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: 2024 Luanti authors
//
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "sscsm_controller.h"
#include "sscsm_environment.h"
#include "sscsm_requests.h"
#include "sscsm_events.h"
#include "sscsm_stupid_channel.h"
std::unique_ptr<SSCSMController> SSCSMController::create()
{
auto channel = std::make_shared<StupidChannel>();
auto thread = std::make_unique<SSCSMEnvironment>(channel);
thread->start();
// Wait for thread to finish initializing.
auto req0 = deserializeSSCSMRequest(channel->recvB());
FATAL_ERROR_IF(!dynamic_cast<SSCSMRequestPollNextEvent *>(req0.get()),
"First request must be pollEvent.");
return std::make_unique<SSCSMController>(std::move(thread), channel);
}
SSCSMController::SSCSMController(std::unique_ptr<SSCSMEnvironment> thread,
std::shared_ptr<StupidChannel> channel) :
m_thread(std::move(thread)), m_channel(std::move(channel))
{
}
SSCSMController::~SSCSMController()
{
// send tear-down
auto answer = SSCSMRequestPollNextEvent::Answer{};
answer.next_event = std::make_unique<SSCSMEventTearDown>();
m_channel->sendB(serializeSSCSMAnswer(std::move(answer)));
// wait for death
m_thread->stop();
m_thread->wait();
}
SerializedSSCSMAnswer SSCSMController::handleRequest(Client *client, ISSCSMRequest *req)
{
return req->exec(client);
}
void SSCSMController::runEvent(Client *client, std::unique_ptr<ISSCSMEvent> event)
{
auto answer0 = SSCSMRequestPollNextEvent::Answer{};
answer0.next_event = std::move(event);
auto answer = serializeSSCSMAnswer(std::move(answer0));
while (true) {
auto request = deserializeSSCSMRequest(m_channel->exchangeB(std::move(answer)));
if (dynamic_cast<SSCSMRequestPollNextEvent *>(request.get()) != nullptr) {
break;
}
answer = handleRequest(client, request.get());
}
}

View file

@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: 2024 Luanti authors
//
// SPDX-License-Identifier: LGPL-2.1-or-later
#pragma once
#include <memory>
#include "irrlichttypes.h"
#include "sscsm_irequest.h"
#include "sscsm_ievent.h"
#include "util/basic_macros.h"
class SSCSMEnvironment;
class StupidChannel;
/**
* The purpose of this class is to:
* * Be the RAII owner of the SSCSM process.
* * Send events to SSCSM process, and process requests. (`runEvent`)
* * Hide details (e.g. that it is a separate process, or that it has to do IPC calls).
*
* See also SSCSMEnvironment for other side.
*/
class SSCSMController
{
std::unique_ptr<SSCSMEnvironment> m_thread;
std::shared_ptr<StupidChannel> m_channel;
SerializedSSCSMAnswer handleRequest(Client *client, ISSCSMRequest *req);
public:
static std::unique_ptr<SSCSMController> create();
SSCSMController(std::unique_ptr<SSCSMEnvironment> thread,
std::shared_ptr<StupidChannel> channel);
~SSCSMController();
DISABLE_CLASS_COPY(SSCSMController);
// Handles requests until the next event is polled
void runEvent(Client *client, std::unique_ptr<ISSCSMEvent> event);
};

View file

@ -0,0 +1,73 @@
// SPDX-FileCopyrightText: 2024 Luanti authors
//
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "sscsm_environment.h"
#include "sscsm_requests.h"
#include "sscsm_events.h"
#include "sscsm_stupid_channel.h"
#include "client/mod_vfs.h"
SSCSMEnvironment::SSCSMEnvironment(std::shared_ptr<StupidChannel> channel) :
Thread("SSCSMEnvironment-thread"),
m_channel(std::move(channel)),
m_script(std::make_unique<SSCSMScripting>(this)),
m_vfs(std::make_unique<ModVFS>())
{
}
SSCSMEnvironment::~SSCSMEnvironment() = default;
void *SSCSMEnvironment::run()
{
while (true) {
auto next_event = [&]{
auto request = SSCSMRequestPollNextEvent{};
auto answer = doRequest(std::move(request));
return std::move(answer.next_event);
}();
if (dynamic_cast<SSCSMEventTearDown *>(next_event.get())) {
break;
}
try {
next_event->exec(this);
} catch (LuaError &e) {
setFatalError(std::string("Lua error: ") + e.what());
} catch (ModError &e) {
setFatalError(std::string("Mod error: ") + e.what());
}
}
return nullptr;
}
SerializedSSCSMAnswer SSCSMEnvironment::exchange(SerializedSSCSMRequest req)
{
return m_channel->exchangeA(std::move(req));
}
void SSCSMEnvironment::updateVFSFiles(std::vector<std::pair<std::string, std::string>> &&files)
{
for (auto &&p : files) {
m_vfs->m_vfs.emplace(std::move(p.first), std::move(p.second));
}
}
std::optional<std::string_view> SSCSMEnvironment::readVFSFile(const std::string &path)
{
auto it = m_vfs->m_vfs.find(path);
if (it == m_vfs->m_vfs.end())
return std::nullopt;
else
return it->second;
}
void SSCSMEnvironment::setFatalError(const std::string &reason)
{
auto request = SSCSMRequestSetFatalError{};
request.reason = reason;
doRequest(std::move(request));
}

View file

@ -0,0 +1,59 @@
// SPDX-FileCopyrightText: 2024 Luanti authors
//
// SPDX-License-Identifier: LGPL-2.1-or-later
#pragma once
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include "client/client.h"
#include "threading/thread.h"
#include "sscsm_controller.h"
#include "sscsm_irequest.h"
#include "../scripting_sscsm.h"
/** The thread that runs SSCSM code.
*
* Meant to be replaced by a sandboxed process.
*
* RAII-owns and abstracts away resources to communicate to the main process / thread.
*
* See also SSCSMController for other side.
*/
class SSCSMEnvironment : public Thread
{
std::shared_ptr<StupidChannel> m_channel;
std::unique_ptr<SSCSMScripting> m_script;
// the virtual file system.
// paths look like this:
// *client_builtin*:subdir/foo.lua
// *server_builtin*:subdir/foo.lua
// modname:subdir/foo.lua
std::unique_ptr<ModVFS> m_vfs;
void *run() override;
SerializedSSCSMAnswer exchange(SerializedSSCSMRequest req);
public:
SSCSMEnvironment(std::shared_ptr<StupidChannel> channel);
~SSCSMEnvironment() override;
SSCSMScripting *getScript() { return m_script.get(); }
ModVFS *getModVFS() { return m_vfs.get(); }
void updateVFSFiles(std::vector<std::pair<std::string, std::string>> &&files);
std::optional<std::string_view> readVFSFile(const std::string &path);
void setFatalError(const std::string &reason);
template <typename RQ>
typename RQ::Answer doRequest(RQ &&rq)
{
return deserializeSSCSMAnswer<typename RQ::Answer>(
exchange(serializeSSCSMRequest(std::forward<RQ>(rq)))
);
}
};

View file

@ -0,0 +1,51 @@
// SPDX-FileCopyrightText: 2024 Luanti authors
//
// SPDX-License-Identifier: LGPL-2.1-or-later
#pragma once
#include "sscsm_ievent.h"
#include "debug.h"
#include "irrlichttypes.h"
#include "sscsm_environment.h"
struct SSCSMEventTearDown : public ISSCSMEvent
{
void exec(SSCSMEnvironment *env) override
{
FATAL_ERROR("SSCSMEventTearDown needs to be handled by SSCSMEnvironment::run()");
}
};
struct SSCSMEventUpdateVFSFiles : public ISSCSMEvent
{
// pairs are virtual path and file content
std::vector<std::pair<std::string, std::string>> files;
void exec(SSCSMEnvironment *env) override
{
env->updateVFSFiles(std::move(files));
}
};
struct SSCSMEventLoadMods : public ISSCSMEvent
{
// modnames and paths to init.lua file, in load order
std::vector<std::pair<std::string, std::string>> mods;
void exec(SSCSMEnvironment *env) override
{
env->getScript()->load_mods(mods);
}
};
struct SSCSMEventOnStep : public ISSCSMEvent
{
f32 dtime;
void exec(SSCSMEnvironment *env) override
{
env->getScript()->environment_step(dtime);
}
};

View file

@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: 2024 Luanti authors
//
// SPDX-License-Identifier: LGPL-2.1-or-later
#pragma once
#include <memory>
#include <type_traits>
class SSCSMEnvironment;
// Event triggered from the main env for the SSCSM env.
struct ISSCSMEvent
{
virtual ~ISSCSMEvent() = default;
// Note: No return value (difference to ISSCSMRequest). These are not callbacks
// that you can run at arbitrary locations, because the untrusted code could
// then clobber your local variables.
virtual void exec(SSCSMEnvironment *cntrl) = 0;
};
// FIXME: actually serialize, and replace this with a string
using SerializedSSCSMEvent = std::unique_ptr<ISSCSMEvent>;
template <typename T>
inline SerializedSSCSMEvent serializeSSCSMEvent(const T &event)
{
static_assert(std::is_base_of_v<ISSCSMEvent, T>);
return std::make_unique<T>(event);
}
inline std::unique_ptr<ISSCSMEvent> deserializeSSCSMEvent(SerializedSSCSMEvent event_serialized)
{
// The actual deserialization will have to use a type tag, and then choose
// the appropriate deserializer.
return event_serialized;
}

View file

@ -0,0 +1,67 @@
// SPDX-FileCopyrightText: 2024 Luanti authors
//
// SPDX-License-Identifier: LGPL-2.1-or-later
#pragma once
#include "exceptions.h"
#include <memory>
#include <type_traits>
class SSCSMController;
class Client;
struct ISSCSMAnswer
{
virtual ~ISSCSMAnswer() = default;
};
// FIXME: actually serialize, and replace this with a string
using SerializedSSCSMAnswer = std::unique_ptr<ISSCSMAnswer>;
// Request made by the sscsm env to the main env.
struct ISSCSMRequest
{
virtual ~ISSCSMRequest() = default;
virtual SerializedSSCSMAnswer exec(Client *client) = 0;
};
// FIXME: actually serialize, and replace this with a string
using SerializedSSCSMRequest = std::unique_ptr<ISSCSMRequest>;
template <typename T>
inline SerializedSSCSMRequest serializeSSCSMRequest(const T &request)
{
static_assert(std::is_base_of_v<ISSCSMRequest, T>);
return std::make_unique<T>(request);
}
template <typename T>
inline T deserializeSSCSMAnswer(SerializedSSCSMAnswer answer_serialized)
{
static_assert(std::is_base_of_v<ISSCSMAnswer, T>);
// dynamic cast in place of actual deserialization
auto ptr = dynamic_cast<T *>(answer_serialized.get());
if (!ptr) {
throw SerializationError("deserializeSSCSMAnswer failed");
}
return std::move(*ptr);
}
template <typename T>
inline SerializedSSCSMAnswer serializeSSCSMAnswer(T &&answer)
{
static_assert(std::is_base_of_v<ISSCSMAnswer, T>);
return std::make_unique<T>(std::move(answer));
}
inline std::unique_ptr<ISSCSMRequest> deserializeSSCSMRequest(SerializedSSCSMRequest request_serialized)
{
// The actual deserialization will have to use a type tag, and then choose
// the appropriate deserializer.
return request_serialized;
}

View file

@ -0,0 +1,107 @@
// SPDX-FileCopyrightText: 2024 Luanti authors
//
// SPDX-License-Identifier: LGPL-2.1-or-later
#pragma once
#include "sscsm_irequest.h"
#include "sscsm_ievent.h"
#include "mapnode.h"
#include "map.h"
#include "client/client.h"
#include "log_internal.h"
// Poll the next event (e.g. on_globalstep)
struct SSCSMRequestPollNextEvent : public ISSCSMRequest
{
struct Answer : public ISSCSMAnswer
{
std::unique_ptr<ISSCSMEvent> next_event;
};
SerializedSSCSMAnswer exec(Client *client) override
{
FATAL_ERROR("SSCSMRequestPollNextEvent needs to be handled by SSCSMControler::runEvent()");
}
};
// Some error occured in the SSCSM env
struct SSCSMRequestSetFatalError : public ISSCSMRequest
{
struct Answer : public ISSCSMAnswer
{
};
std::string reason;
SerializedSSCSMAnswer exec(Client *client) override
{
client->setFatalError("[SSCSM] " + reason);
return serializeSSCSMAnswer(Answer{});
}
};
// print(text)
// FIXME: override global loggers to use this in sscsm process
struct SSCSMRequestPrint : public ISSCSMRequest
{
struct Answer : public ISSCSMAnswer
{
};
std::string text;
SerializedSSCSMAnswer exec(Client *client) override
{
rawstream << text << std::endl;
return serializeSSCSMAnswer(Answer{});
}
};
// core.log(level, text)
// FIXME: override global loggers to use this in sscsm process
struct SSCSMRequestLog : public ISSCSMRequest
{
struct Answer : public ISSCSMAnswer
{
};
std::string text;
LogLevel level;
SerializedSSCSMAnswer exec(Client *client) override
{
if (level >= LL_MAX) {
throw MisbehavedSSCSMException("Tried to log at non-existent level.");
} else {
g_logger.log(level, text);
}
return serializeSSCSMAnswer(Answer{});
}
};
// core.get_node(pos)
struct SSCSMRequestGetNode : public ISSCSMRequest
{
struct Answer : public ISSCSMAnswer
{
MapNode node;
bool is_pos_ok;
};
v3s16 pos;
SerializedSSCSMAnswer exec(Client *client) override
{
bool is_pos_ok = false;
MapNode node = client->getEnv().getMap().getNode(pos, &is_pos_ok);
Answer answer{};
answer.node = node;
answer.is_pos_ok = is_pos_ok;
return serializeSSCSMAnswer(std::move(answer));
}
};

View file

@ -0,0 +1,84 @@
// SPDX-FileCopyrightText: 2024 Luanti authors
//
// SPDX-License-Identifier: LGPL-2.1-or-later
#pragma once
#include <memory>
#include <mutex>
#include <condition_variable>
#include "sscsm_irequest.h"
// FIXME: replace this with an ipc channel
class StupidChannel
{
std::mutex m_mutex;
std::condition_variable m_condvar;
SerializedSSCSMRequest m_request;
SerializedSSCSMAnswer m_answer;
public:
void sendA(SerializedSSCSMRequest request)
{
{
auto lock = std::lock_guard(m_mutex);
m_request = std::move(request);
}
m_condvar.notify_one();
}
SerializedSSCSMAnswer recvA()
{
auto lock = std::unique_lock(m_mutex);
while (!m_answer) {
m_condvar.wait(lock);
}
auto answer = std::move(m_answer);
m_answer = nullptr;
return answer;
}
SerializedSSCSMAnswer exchangeA(SerializedSSCSMRequest request)
{
sendA(std::move(request));
return recvA();
}
void sendB(SerializedSSCSMAnswer answer)
{
{
auto lock = std::lock_guard(m_mutex);
m_answer = std::move(answer);
}
m_condvar.notify_one();
}
SerializedSSCSMRequest recvB()
{
auto lock = std::unique_lock(m_mutex);
while (!m_request) {
m_condvar.wait(lock);
}
auto request = std::move(m_request);
m_request = nullptr;
return request;
}
SerializedSSCSMRequest exchangeB(SerializedSSCSMAnswer answer)
{
sendB(std::move(answer));
return recvB();
}
};