mirror of
https://github.com/brazilofmux/tinymux
synced 2026-08-13 00:23:11 -04:00
The parking condition on #1654 was "a second consumer"; the tally reached three, all documented divergences: searchcost mail non-wizard self @mail/stats was free eval_comtitle comsys comtitles always evaluated under the module money_name_* mail the charge-refusal text could not be composed ## Shape A new engine-registered class rather than an Initialize signature change: mux_IGameConfig::GetGameConfig(GAME_CONFIG *) CID_GameConfig No existing IID moves, so there is no ABI break in either direction: an old module never asks, and a new module against an old engine gets CLASSNOTAVAILABLE, soft-fails to nullptr, and keeps its prior behaviour. Two design rules baked in rather than documented and hoped for: * Queried per CALL, never cached at Initialize. A boot-time snapshot is #1613's bug -- @admin reports Set. while the module keeps stale values. @admin search_cost=50 now takes effect on the next @mail/stats. * Versioned by cbSize: caller zeroes the struct and sets cbSize; the engine fills what fits. The struct can grow without a new interface, and zero must stay a safe default for every future field. ## Both consumers, verified as a mortal The gap was wizard-invisible -- payfor() exempts wizards and every harness runs as God -- so verification used muxscript -p with a @pcreate'd mortal: search_cost 5, rich engine 150->145 module 150->145 IDENTICAL search_cost 99999, poor both: "Finding mail stats costs 99999 Pennies.", money untouched eval_comtitle default [ec] 3 Wizard says ... both sides eval_comtitle 0 [ec] [strlen(abc)] Wizard says both sides The eval_comtitle rows close the divergence documented at channel_speaker_name since #1640/#1647. Conformance and smoke are unchanged by construction (wizard runs), and were run anyway. make test: Smoke 1561 x3, conformance PASSED, handoff 13. TESTEXIT=0. Refs #1613, #1614, #1631, #1640, #1647. Closes #1654. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
366 lines
12 KiB
C++
366 lines
12 KiB
C++
/*! \file comsys_mod.h
|
|
* \brief Comsys Module — Channel system as a loadable module
|
|
*
|
|
* This module implements the MUX channel system (@channel, addcom,
|
|
* delcom, etc.) as a dynamically loaded module rather than compiled
|
|
* into the netmux binary.
|
|
*/
|
|
|
|
#ifndef COMSYS_MOD_H
|
|
#define COMSYS_MOD_H
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
#ifndef NOTHING
|
|
#define NOTHING (-1)
|
|
#endif
|
|
|
|
const MUX_CID CID_ComsysMod = UINT64_C(0x00000002C5A2F193);
|
|
|
|
// Channel type flags (mirrored from comsys.h for module use).
|
|
//
|
|
#define CHANNEL_PLAYER_JOIN 0x00000001
|
|
#define CHANNEL_PLAYER_TRANSMIT 0x00000002
|
|
#define CHANNEL_PLAYER_RECEIVE 0x00000004
|
|
#define CHANNEL_OBJECT_JOIN 0x00000010
|
|
#define CHANNEL_OBJECT_TRANSMIT 0x00000020
|
|
#define CHANNEL_OBJECT_RECEIVE 0x00000040
|
|
#define CHANNEL_LOUD 0x00000100
|
|
#define CHANNEL_PUBLIC 0x00000200
|
|
#define CHANNEL_SPOOF 0x00000400
|
|
|
|
#define CHANNEL_DEFAULT (CHANNEL_PLAYER_JOIN | CHANNEL_PLAYER_TRANSMIT \
|
|
| CHANNEL_PLAYER_RECEIVE | CHANNEL_OBJECT_JOIN \
|
|
| CHANNEL_OBJECT_TRANSMIT | CHANNEL_OBJECT_RECEIVE)
|
|
|
|
#define MAX_CHANNEL_LEN 50
|
|
#define MAX_HEADER_LEN 100
|
|
#define MAX_TITLE_LEN 200
|
|
#define MAX_ALIAS_LEN 15
|
|
#define MAX_ALIASES_PER_PLAYER 100
|
|
|
|
// Command switch keys (mirrored from externs.h).
|
|
//
|
|
#define CEMIT_NOHEADER 1
|
|
#define CLIST_FULL 1
|
|
#define CLIST_HEADERS 2
|
|
#define COMTITLE_ON 1
|
|
#define COMTITLE_OFF 2
|
|
#define COMTITLE_GAG 3
|
|
#define COMTITLE_UNGAG 4
|
|
|
|
// @cset switch keys (mirrored from externs.h).
|
|
//
|
|
#define CSET_PUBLIC 0
|
|
#define CSET_PRIVATE 1
|
|
#define CSET_LOUD 2
|
|
#define CSET_QUIET 3
|
|
#define CSET_LIST 4
|
|
#define CSET_OBJECT 5
|
|
#define CSET_SPOOF 6
|
|
#define CSET_NOSPOOF 7
|
|
#define CSET_HEADER 8
|
|
#define CSET_LOG 9
|
|
#define CSET_LOG_TIME 10
|
|
|
|
// @cboot switch keys.
|
|
//
|
|
#define CBOOT_QUIET 1
|
|
|
|
// @editchannel flag keys.
|
|
//
|
|
#define EDIT_CHANNEL_CCHOWN 0
|
|
#define EDIT_CHANNEL_CCHARGE 1
|
|
#define EDIT_CHANNEL_CPFLAGS 2
|
|
#define EDIT_CHANNEL_COFLAGS 3
|
|
|
|
#define MAX_COST 32767
|
|
#define MOD_LBUF_SIZE 8000
|
|
// Mirrored from comsys.h / flags.h for module-side policy (#1194/#1195).
|
|
//
|
|
#define MAX_USERS_PER_CHANNEL 1000000
|
|
#define MOD_FLAG_WORD1 0
|
|
#define MOD_FLAG_WORD2 1
|
|
#define MOD_FLAG_DARK 0x00000040u
|
|
#define MOD_FLAG_GAGGED 0x00040000u
|
|
#define MOD_POW_GUEST 0x02000000u
|
|
|
|
// Per-player alias entry — pairs an alias string with its channel name.
|
|
//
|
|
struct com_alias
|
|
{
|
|
std::string alias;
|
|
std::string channel;
|
|
};
|
|
|
|
// Per-user channel subscription record.
|
|
//
|
|
struct comuser
|
|
{
|
|
dbref who;
|
|
bool bUserIsOn;
|
|
std::string title;
|
|
bool ComTitleStatus;
|
|
bool bGagJoinLeave;
|
|
bool bConnected;
|
|
|
|
comuser() : who(NOTHING), bUserIsOn(false),
|
|
ComTitleStatus(true), bGagJoinLeave(false), bConnected(false) {}
|
|
};
|
|
|
|
// Channel definition.
|
|
//
|
|
struct channel
|
|
{
|
|
UTF8 name[MAX_CHANNEL_LEN + 1];
|
|
UTF8 header[MAX_HEADER_LEN + 1];
|
|
int type;
|
|
int temp1;
|
|
int temp2;
|
|
int charge;
|
|
dbref charge_who;
|
|
int amount_col;
|
|
dbref chan_obj;
|
|
std::map<dbref, comuser> users;
|
|
int num_messages;
|
|
|
|
channel() : type(0), temp1(0), temp2(0), charge(0),
|
|
charge_who(NOTHING), amount_col(0), chan_obj(NOTHING),
|
|
num_messages(0)
|
|
{
|
|
memset(name, 0, sizeof(name));
|
|
memset(header, 0, sizeof(header));
|
|
}
|
|
};
|
|
|
|
// Per-player alias tracking.
|
|
//
|
|
struct comsys_t
|
|
{
|
|
dbref who;
|
|
std::vector<com_alias> aliases;
|
|
|
|
comsys_t() : who(NOTHING) {}
|
|
};
|
|
|
|
class CComsysMod : public mux_IComsysControl, mux_IServerEventsSink
|
|
{
|
|
private:
|
|
mux_ILog *m_pILog;
|
|
mux_IServerEventsControl *m_pIServerEventsControl;
|
|
mux_INotify *m_pINotify;
|
|
mux_IObjectInfo *m_pIObjectInfo;
|
|
mux_IAttributeAccess *m_pIAttributeAccess;
|
|
mux_IEvaluator *m_pIEvaluator;
|
|
mux_IPermissions *m_pIPermissions;
|
|
// Game-policy config (#1654). May be nullptr against an engine older
|
|
// than CID_GameConfig; every use must default sensibly.
|
|
mux_IGameConfig *m_pIGameConfig;
|
|
|
|
// Storage interface — routes SQLite access through the engine.
|
|
//
|
|
mux_IComsysStorage *m_pIStorage;
|
|
|
|
// Channel data — owned by the module.
|
|
//
|
|
std::map<std::vector<UTF8>, std::unique_ptr<channel>> m_channels;
|
|
std::unordered_map<dbref, comsys_t> m_comsys;
|
|
|
|
// Softcode gen-sync (#1191): bumped on every write-through mutation.
|
|
//
|
|
int m_revision;
|
|
|
|
// Internal helpers.
|
|
//
|
|
void bump_revision(void);
|
|
bool LoadChannels(void);
|
|
bool LoadChannelUsers(void);
|
|
bool LoadPlayerChannels(void);
|
|
|
|
struct channel *select_channel(const UTF8 *name);
|
|
struct comuser *select_user(struct channel *ch, dbref player);
|
|
comsys_t &get_comsys(dbref who);
|
|
const UTF8 *get_channel_from_alias(dbref player, const UTF8 *alias);
|
|
void do_comconnectchannel(dbref player, const UTF8 *channel,
|
|
const std::string &alias);
|
|
void do_comdisconnectchannel(dbref player, const UTF8 *channel);
|
|
void do_comconnectraw_notify(dbref player, const UTF8 *chan);
|
|
void do_comdisconnectraw_notify(dbref player, const UTF8 *chan);
|
|
|
|
bool test_transmit_access(dbref player, struct channel *ch);
|
|
bool test_receive_access(dbref player, struct channel *ch);
|
|
void do_processcom(dbref player, const UTF8 *channel, UTF8 *msg);
|
|
|
|
// Speaker prefix — "<header> <comtitle> <name>", or "<header> <comtitle>"
|
|
// on a spoof channel. The engine's BuildChannelMessage builds two
|
|
// variants because the listener, not the speaker, decides whether
|
|
// comtitles are shown; this builds the same pair (#1640).
|
|
//
|
|
// pNoComtitle is left empty on a spoof channel, where the engine has no
|
|
// second variant at all and every listener sees the comtitle.
|
|
//
|
|
void BuildSpeakerPrefix(struct channel *ch, const struct comuser *user,
|
|
UTF8 *pNormal, size_t nNormal, UTF8 *pNoComtitle, size_t nNoComtitle);
|
|
|
|
void SendChannelMessage(dbref executor, struct channel *ch,
|
|
const UTF8 *msg, const UTF8 *msgNoComtitle, bool bJoinLeaveMsg);
|
|
|
|
// @clist/full — the wide listing with Header/Access/Users/Msgs. Split
|
|
// out because it shares nothing with the default listing but the loop
|
|
// (#1640); the engine splits it the same way (do_listchannels).
|
|
//
|
|
MUX_RESULT ChanListFull(dbref executor, const UTF8 *pPattern);
|
|
// Recall-buffer write, mirroring the engine's HISTORY_n logging (#1564).
|
|
void RecordChannelHistory(dbref executor, struct channel *ch,
|
|
const UTF8 *msg, bool bJoinLeaveMsg);
|
|
|
|
// MOGRIFY`NOBUFFER (#1572): true when the channel object's hook says this
|
|
// line must not reach the recall buffer.
|
|
// Evaluate MOGRIFY`<suffix> on the channel object (#1572). Returns
|
|
// true when the attribute existed and evaluation produced a result;
|
|
// `out` holds it. Mirrors the engine's call_mogrifier.
|
|
//
|
|
bool mogrify_eval(struct channel *ch, dbref executor,
|
|
const UTF8 *suffix, const UTF8 *args[], int nargs,
|
|
UTF8 *out, size_t outsz);
|
|
|
|
// Comtitle + name, as the engine's BuildChannelMessage renders it
|
|
// (#1640). Caller supplies the buffer; see the definition for the
|
|
// eval_comtitle caveat.
|
|
//
|
|
void channel_speaker_name(const struct comuser *user, bool bSpoof,
|
|
UTF8 *speaker, size_t speakersz);
|
|
|
|
// xlate() approximation shared by the boolean hooks.
|
|
//
|
|
static bool mogrify_truthy(const UTF8 *result);
|
|
|
|
bool nobuffer_by_mogrify(dbref executor, struct channel *ch,
|
|
const UTF8 *msg);
|
|
void do_joinchannel(dbref player, struct channel *ch);
|
|
void do_leavechannel(dbref player, struct channel *ch);
|
|
void do_comwho(dbref player, struct channel *ch);
|
|
|
|
// #1630: every storage write below can be refused -- foreign keys,
|
|
// constraint violations, a closed database, a full disk -- and
|
|
// discarding the result lets stored state diverge from memory with
|
|
// nothing to notice. That is the shape of #1564, #1585, #1620 and
|
|
// #1587. Checking cannot make a refused write land; it converts an
|
|
// invisible divergence into a log grep.
|
|
//
|
|
void log_storage_failure(MUX_RESULT mr, const char *fmt, ...)
|
|
#if defined(__GNUC__)
|
|
__attribute__((format(printf, 3, 4)))
|
|
#endif
|
|
;
|
|
|
|
void sqlite_wt_channel_user(const UTF8 *channel_name,
|
|
const comuser &user);
|
|
void sqlite_wt_channel(struct channel *ch);
|
|
void sqlite_wt_player_channel(dbref who, const UTF8 *alias,
|
|
const UTF8 *channel_name);
|
|
void sqlite_wt_delete_player_channel(dbref who, const UTF8 *alias);
|
|
void sqlite_wt_delete_channel_user(const UTF8 *channel_name, dbref who);
|
|
|
|
bool test_join_access(dbref player, struct channel *ch);
|
|
void do_delcomchannel(dbref player, const UTF8 *channel, bool bQuiet);
|
|
void do_comlast(dbref player, struct channel *ch, int arg);
|
|
void sort_com_aliases(comsys_t &c);
|
|
|
|
// Policy helpers (#1194 / #1195) — flag/money/eval via COM.
|
|
//
|
|
bool flag_set(dbref obj, int word, unsigned int bit);
|
|
bool is_wizard(dbref obj);
|
|
bool is_gagged(dbref obj);
|
|
bool is_hidden(dbref obj);
|
|
bool is_guest(dbref obj);
|
|
bool undead_connected(dbref obj);
|
|
bool pay_channel_charge(dbref player, struct channel *ch);
|
|
bool blocked_by_mogrify(dbref player, struct channel *ch,
|
|
const UTF8 *arg2);
|
|
|
|
public:
|
|
// mux_IUnknown
|
|
//
|
|
MUX_RESULT QueryInterface(MUX_IID iid, void **ppv) override;
|
|
uint32_t AddRef(void) override;
|
|
uint32_t Release(void) override;
|
|
|
|
// mux_IComsysControl
|
|
//
|
|
MUX_RESULT Initialize(mux_IComsysStorage *pStorage) override;
|
|
MUX_RESULT PlayerConnect(dbref player) override;
|
|
MUX_RESULT PlayerDisconnect(dbref player) override;
|
|
MUX_RESULT PlayerNuke(dbref player) override;
|
|
MUX_RESULT AddAlias(dbref executor, const UTF8 *pAlias,
|
|
const UTF8 *pChannel) override;
|
|
MUX_RESULT DelAlias(dbref executor, const UTF8 *pAlias) override;
|
|
MUX_RESULT ClearAliases(dbref executor) override;
|
|
MUX_RESULT CreateChannel(dbref executor, const UTF8 *pName) override;
|
|
MUX_RESULT DestroyChannel(dbref executor, const UTF8 *pName) override;
|
|
MUX_RESULT AllCom(dbref executor, const UTF8 *pAction) override;
|
|
MUX_RESULT ComList(dbref executor, const UTF8 *pPattern) override;
|
|
MUX_RESULT ComTitle(dbref executor, const UTF8 *pAlias,
|
|
const UTF8 *pTitle, int key) override;
|
|
MUX_RESULT ChanList(dbref executor, const UTF8 *pPattern,
|
|
int key) override;
|
|
MUX_RESULT ChanWho(dbref executor, const UTF8 *pArg) override;
|
|
MUX_RESULT CEmit(dbref executor, const UTF8 *pChannel,
|
|
const UTF8 *pText, int key) override;
|
|
MUX_RESULT CSet(dbref executor, const UTF8 *pChannel,
|
|
const UTF8 *pValue, int key) override;
|
|
MUX_RESULT EditChannel(dbref executor, const UTF8 *pChannel,
|
|
const UTF8 *pValue, int flag) override;
|
|
MUX_RESULT CBoot(dbref executor, const UTF8 *pChannel,
|
|
const UTF8 *pVictim, int key) override;
|
|
MUX_RESULT ProcessCommand(dbref executor, const UTF8 *pCmd,
|
|
bool *pbHandled) override;
|
|
MUX_RESULT GetRevision(int *pRev) override;
|
|
|
|
// mux_IServerEventsSink
|
|
//
|
|
void startup(void) override;
|
|
void presync_database(void) override;
|
|
void presync_database_sigsegv(void) override;
|
|
void dump_database(int dump_type) override;
|
|
void dump_complete_signal(void) override;
|
|
void shutdown(void) override;
|
|
void dbck(void) override;
|
|
void connect(dbref player, int isnew, int num) override;
|
|
void disconnect(dbref player, int num) override;
|
|
void data_create(dbref object) override;
|
|
void data_clone(dbref clone, dbref source) override;
|
|
void data_free(dbref object) override;
|
|
|
|
CComsysMod(void);
|
|
MUX_RESULT FinalConstruct(void);
|
|
virtual ~CComsysMod();
|
|
|
|
private:
|
|
std::atomic<uint32_t> m_cRef;
|
|
};
|
|
|
|
class CComsysModFactory : public mux_IClassFactory
|
|
{
|
|
public:
|
|
MUX_RESULT QueryInterface(MUX_IID iid, void **ppv) override;
|
|
uint32_t AddRef(void) override;
|
|
uint32_t Release(void) override;
|
|
|
|
MUX_RESULT CreateInstance(mux_IUnknown *pUnknownOuter, MUX_IID iid, void **ppv) override;
|
|
MUX_RESULT LockServer(bool bLock) override;
|
|
|
|
CComsysModFactory(void);
|
|
virtual ~CComsysModFactory();
|
|
|
|
private:
|
|
std::atomic<uint32_t> m_cRef;
|
|
};
|
|
|
|
#endif // COMSYS_MOD_H
|