#include "wrap-string.hpp" #include "wrap-vector.hpp" #include "util.hpp" #include "drivenengine.hpp" #include #include #include #include #include #include DrivenEngineReg *DrivenEngineReg::All; DrivenEngineReg::DrivenEngineReg(const char *n, DrivenEngineMaker fn) { name = n; maker = fn; next = All; All = this; } DrivenEngineInitializer DrivenEngineInitializerReg::func; DrivenEngineInitializerReg::DrivenEngineInitializerReg(DrivenEngineInitializer fn) { assert(func == nullptr); func = fn; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // // DrivenEngine private methods // ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// int DrivenEngine::find_unused_chid() { // Note: channel ID zero is special, it is never reused. for (int i = 0; i < DRV_MAX_CHAN; i++) { int id = next_unused_chid_++; if (next_unused_chid_ == DRV_MAX_CHAN) next_unused_chid_ = 1; if (channels_[id] == nullptr) return id; } assert(false); return 0; } Channel *DrivenEngine::get_chid(int chid) const { assert(unsigned(chid) < DRV_MAX_CHAN); assert(channels_[chid].get() != nullptr); return channels_[chid].get(); } static DrivenEngine *make_engine(std::string_view kind) { for (auto reg = DrivenEngineReg::All; reg != nullptr; reg=reg->next) { if (kind == reg->name) { UniqueDrivenEngine result = reg->maker(); return result.release(); } } return nullptr; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // // Class Channel // ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// Channel::Channel(DrivenEngine *de, int chid, int port, const eng::string &target, bool stop) { chid_ = chid; port_ = port; closed_ = false; target_ = target; readline_lastc_ = 0; desired_prompt_ = ""; stop_driver_ = stop; sb_in_ = eng::make_shared(); sb_out_ = eng::make_shared(); sb_drvout_ = sb_out_; } void Channel::erase_command() { int ccsize = current_prompt_.size() + current_command_.size(); if (ccsize > 0) { sb_drvout_->write_bytes(util::repeat_string("\b \b", ccsize)); current_prompt_ = ""; current_command_ = ""; } } void Channel::echo_command() { // If the prompt has changed, erase everything and start over. if (desired_prompt_ != current_prompt_) { int ccsize = current_prompt_.size() + current_command_.size(); sb_drvout_->write_bytes(util::repeat_string("\b \b", ccsize)); sb_drvout_->write_bytes(desired_prompt_); current_command_ = ""; current_prompt_ = desired_prompt_; } // Find out how much of the command matches. int match = sv::common_prefix_length(current_command_, desired_command_); // Echo backspaces to remove the non-matching part. int remove = current_command_.size() - match; if (remove > 0) { sb_drvout_->write_bytes(util::repeat_string("\b \b", remove)); current_command_ = current_command_.substr(0, match); } // Echo the new part. eng::string newpart = desired_command_.substr(current_command_.size()); if (newpart != "") { sb_drvout_->write_bytes(newpart); current_command_ = desired_command_; } } void Channel::set_prompt(const eng::string &p) { desired_prompt_ = p; } void Channel::feed_readline(std::string_view data) { int nbytes = data.size(); const char *bytes = data.data(); for (int i = 0; i < nbytes; i++) { char c = bytes[i]; if ((c == '\n') && (readline_lastc_ == '\r')) { // Ignore newline immediately after carriage return. // Otherwise, crlf produces two newlines. } else if ((c == '\r') || (c == '\n')) { echo_command(); sb_drvout_->write_bytes(eng::string(" \n")); sb_in_->write_bytes(desired_command_); sb_in_->write_uint8('\n'); desired_command_ = ""; current_prompt_ = ""; current_command_ = ""; } else if ((c == '\b') || (c == 127)) { int len = desired_command_.size(); if (len > 0) { desired_command_ = desired_command_.substr(0, len-1); } } else if (c >= 32) { int len = desired_command_.size(); if (len < READLINE_MAX) { desired_command_ = desired_command_ + c; } } readline_lastc_ = c; } } std::string_view Channel::peek_outgoing() const { return sb_drvout_->view(); } void Channel::pump_readline() { if (sb_drvout_ != sb_out_) { if (!sb_out_->empty()) { erase_command(); sb_out_->transfer_into(sb_drvout_.get()); } echo_command(); } } void Channel::sent_outgoing(int nbytes) { sb_drvout_->read_bytes(nbytes); } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // // DrivenEngine Client-Side API // ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// void DrivenEngine::listen_port(int port) { assert(listen_ports_.size() < DRV_MAX_LISTEN_PORTS); listen_ports_.push_back(port); } double DrivenEngine::get_clock() { return clock_; } SharedChannel DrivenEngine::new_outgoing_channel(const eng::string &target) { int chid = find_unused_chid(); new_outgoing_.push_back(chid); SharedChannel result = eng::make_shared(this, chid, 0, target, stop_driver_); channels_[chid] = result; return result; } SharedChannel DrivenEngine::new_incoming_channel() { if (accepted_channels_.empty()) { return nullptr; } else { SharedChannel result = std::move(accepted_channels_.back()); accepted_channels_.pop_back(); return result; } } SharedChannel DrivenEngine::get_stdio_channel() { return stdio_channel_; } util::LuaSourcePtr DrivenEngine::get_lua_source() { return std::move(lua_source_); } void DrivenEngine::rescan_lua_source() { rescan_lua_source_ = true; } void DrivenEngine::stop_driver() { stop_driver_ = true; for (int i = 0; i < DRV_MAX_CHAN; i++) { if (channels_[i] != nullptr) { channels_[i]->stop_driver_ = true; } } } DrivenEngine::DrivenEngine() { next_unused_chid_ = 1; stdio_channel_ = eng::make_shared(this, 0, 0, "", false); stdio_channel_->sb_drvout_ = eng::make_shared(); channels_[0] = stdio_channel_; rescan_lua_source_ = false; clock_ = 0.0; stop_driver_ = false; } DrivenEngine::~DrivenEngine() {} ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // // LOGFILE EVENT IDS. // // There's one event ID for each mutator, plus one for 'release'. // // There are no event IDs for getters, these aren't considered loggable events. // ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// enum DrvAction { PLAY_INITIALIZE, PLAY_CLEAR_NEW_OUTGOING, PLAY_SENT_OUTGOING, PLAY_RECV_INCOMING, PLAY_NOTIFY_CLOSE, PLAY_NOTIFY_ACCEPT, PLAY_INVOKE_EVENT_UPDATE, PLAY_SET_LUA_SOURCE, PLAY_RELEASE, }; inline static const char *action_string(DrvAction act) { switch(act) { case PLAY_INITIALIZE: return "PLAY_INITIALIZE"; case PLAY_CLEAR_NEW_OUTGOING: return "PLAY_CLEAR_NEW_OUTGOING"; case PLAY_SENT_OUTGOING: return "PLAY_SENT_OUTGOING"; case PLAY_RECV_INCOMING: return "PLAY_RECV_INCOMING"; case PLAY_NOTIFY_CLOSE: return "PLAY_NOTIFY_CLOSE"; case PLAY_NOTIFY_ACCEPT: return "PLAY_NOTIFY_ACCEPT"; case PLAY_SET_LUA_SOURCE: return "PLAY_SET_LUA_SOURCE"; case PLAY_INVOKE_EVENT_UPDATE: return "PLAY_INVOKE_EVENT_UPDATE"; case PLAY_RELEASE: return "PLAY_RELEASE"; default: return "unknown"; } } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // // RLOG and WLOG, functions to read and write binary data to logfiles. // // After doing an rlog operation, you should check the stream // for "good" to find out if there was any error. // ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// class PlayLogfile : public std::ofstream { using std::ofstream::ofstream; }; class ReplayLogfile : public std::ifstream { using std::ifstream::ifstream; }; static uint8_t rlog_uint8(EngineWrapper *w) { uint8_t result; w->rlog->read((char *)&result, 1); if (!w->rlog->good()) return 0; return result; } static uint32_t rlog_uint32(EngineWrapper *w) { uint32_t result; w->rlog->read((char *)&result, 4); if (!w->rlog->good()) return 0; return result; } static uint64_t rlog_uint64(EngineWrapper *w) { uint64_t result; w->rlog->read((char *)&result, 8); if (!w->rlog->good()) return 0; return result; } static double rlog_double(EngineWrapper *w) { double result; w->rlog->read((char *)&result, 8); if (!w->rlog->good()) return 0.0; return result; } std::string_view rlog_short_string(EngineWrapper *w) { uint32_t len = rlog_uint8(w); if (len == 255) { len = rlog_uint32(w); } assert (len <= DRV_SHORTSTRING_SIZE); if (len > 0) w->rlog->read(w->databuffer, len); if (!w->rlog->good()) return std::string_view(); return std::string_view(w->databuffer, len); } std::string rlog_string(EngineWrapper *w) { uint32_t len = rlog_uint8(w); if (len == 255) { len = rlog_uint32(w); } std::string result(len, ' '); if (len > 0) w->rlog->read(&result[0], len); if (!w->rlog->good()) return ""; return result; } static void wlog_uint8(EngineWrapper *w, uint8_t v) { w->wlog->put((char)v); } static void wlog_uint32(EngineWrapper *w, uint32_t v) { w->wlog->write((const char *)&v, 4); } static void wlog_uint64(EngineWrapper *w, uint64_t v) { w->wlog->write((const char *)&v, 8); } static void wlog_double(EngineWrapper *w, double v) { w->wlog->write((const char *)&v, 8); } static void wlog_short_string(EngineWrapper *w, std::string_view v) { assert (v.size() <= DRV_SHORTSTRING_SIZE); if (v.size() >= 255) { wlog_uint8(w, 0xFF); wlog_uint32(w, v.size()); } else { wlog_uint8(w, v.size()); } w->wlog->write(v.data(), v.size()); } static void wlog_string(EngineWrapper *w, std::string_view v) { if (v.size() >= 255) { wlog_uint8(w, 0xFF); wlog_uint32(w, v.size()); } else { wlog_uint8(w, v.size()); } w->wlog->write(v.data(), v.size()); } static void wlog_cmd_hash(EngineWrapper *w, DrvAction act, uint32_t hash) { wlog_uint8(w, act); wlog_uint32(w, hash); } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // // reset_wrapper // // Shut down a EngineWrapper, store an optional error message. // // release // // Shut down an EngineWrapper cleanly, with no error message, and // log the step if the logfile is open. // ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// static void reset_wrapper(EngineWrapper *w, const char *format, ...) { va_list argp; va_start(argp, format); memset(w->error, 0, DRV_ERRMSG_SIZE); vsnprintf(w->error, DRV_ERRMSG_SIZE, format, argp); w->error[DRV_ERRMSG_SIZE - 1] = 0; if (w->wlog != nullptr) { w->wlog->close(); delete w->wlog; w->wlog = nullptr; } if (w->rlog != nullptr) { w->rlog->close(); delete w->rlog; w->rlog = nullptr; } if (w->engine != nullptr) { delete w->engine; w->engine = nullptr; } } static void release(EngineWrapper *w) { if (w->wlog != nullptr) { wlog_cmd_hash(w, PLAY_RELEASE, eng::memhash()); } reset_wrapper(w, ""); }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // // DRIVER Methods: Getters // ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// void DrivenEngine::drv_get_listen_ports(uint32_t *nports, const uint32_t **ports) const { *nports = listen_ports_.size(); *ports = &listen_ports_[0]; } void DrivenEngine::drv_get_new_outgoing(uint32_t *nchids, const uint32_t **chids) const { *nchids = new_outgoing_.size(); *chids = &new_outgoing_[0]; } const char *DrivenEngine::drv_get_target(uint32_t chid) const { return get_chid(chid)->target_.c_str(); } bool DrivenEngine::drv_get_channel_released(uint32_t chid) const { return channels_[chid].use_count() == 1; } void DrivenEngine::drv_get_outgoing(uint32_t chid, uint32_t *len, const char **data) const { std::string_view v = get_chid(chid)->peek_outgoing(); *len = v.size(); *data = v.data(); } bool DrivenEngine::drv_get_outgoing_empty(uint32_t chid) const { std::string_view v = get_chid(chid)->peek_outgoing(); return (v.size() == 0); } double DrivenEngine::drv_get_clock() const { return clock_; } bool DrivenEngine::drv_get_rescan_lua_source() const { return rescan_lua_source_; } bool DrivenEngine::drv_get_stop_driver() const { return stop_driver_; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // // DRIVER Methods: Mutators // ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// void DrivenEngine::drv_initialize(uint32_t srcpklen, const char *srcpk, int argc, char **argv) { drv_set_lua_source(srcpklen, srcpk); event_init(argc, argv); stdio_channel_->pump_readline(); } void DrivenEngine::drv_clear_new_outgoing() { new_outgoing_.clear(); } void DrivenEngine::drv_sent_outgoing(uint32_t chid, uint32_t nbytes) { return get_chid(chid)->sent_outgoing(nbytes); } void DrivenEngine::drv_recv_incoming(uint32_t chid, uint32_t nbytes, const char *bytes) { std::string_view sbytes(bytes, nbytes); if (nbytes > 0) { Channel *ch = get_chid(chid); if (ch->sb_drvout_ != ch->sb_out_) { ch->feed_readline(sbytes); } else { ch->sb_in_->write_bytes(sbytes); } } } void DrivenEngine::drv_notify_close(uint32_t chid, uint32_t len, const char *data) { Channel *ch = get_chid(chid); ch->closed_ = true; ch->error_ = std::string(data, len); channels_[chid].reset(); } uint32_t DrivenEngine::drv_notify_accept(uint32_t port) { int chid = find_unused_chid(); channels_[chid] = eng::make_shared(this, chid, port, "", stop_driver_); accepted_channels_.push_back(channels_[chid]); return chid; } void DrivenEngine::drv_invoke_event_update(double clock) { clock_ = clock; event_update(); stdio_channel_->pump_readline(); } void DrivenEngine::drv_set_lua_source(uint32_t srcpklen, const char *srcpk) { StreamBuffer sb(srcpk, srcpklen); uint32_t nfiles = sb.read_uint32(); lua_source_.reset(new util::LuaSourceVec); lua_source_->resize(nfiles); for (uint32_t i = 0; i < nfiles; i++) { (*lua_source_)[i].first = sb.read_string(); } for (uint32_t i = 0; i < nfiles; i++) { (*lua_source_)[i].second = sb.read_string(); } rescan_lua_source_ = false; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // // C Wrappers: Getters // // These wrappers make it possible to call the drv_get routines using C // functions instead of methods. This is important if the engine is compiled // with one C++ compiler, but the driver is compiled with a different C++ // compiler. // // Some of these take parameter 'EngineWrapper', some take 'EngineWrapper', // and some come in two versions. This all depends on whether they are used // during play, during replay, or both. // ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// static void drv_get_listen_ports(EngineWrapper *w, uint32_t *nports, const uint32_t **ports) { return w->engine->drv_get_listen_ports(nports, ports); } static void drv_get_new_outgoing(EngineWrapper *w, uint32_t *nchanids, const uint32_t **chanids) { return w->engine->drv_get_new_outgoing(nchanids, chanids); } static const char *drv_get_target(EngineWrapper *w, uint32_t chid) { return w->engine->drv_get_target(chid); } static bool drv_get_channel_released(EngineWrapper *w, uint32_t chid) { return w->engine->drv_get_channel_released(chid); } static void drv_get_outgoing(EngineWrapper *w, uint32_t chid, uint32_t *len, const char **data) { return w->engine->drv_get_outgoing(chid, len, data); } static bool drv_get_outgoing_empty(EngineWrapper *w, uint32_t chid) { return w->engine->drv_get_outgoing_empty(chid); } static double drv_get_clock(EngineWrapper *w) { return w->engine->drv_get_clock(); } static bool drv_get_rescan_lua_source(EngineWrapper *w) { return w->engine->drv_get_rescan_lua_source(); } static bool drv_get_stop_driver(EngineWrapper *w) { return w->engine->drv_get_stop_driver(); } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // // C Wrappers: Mutators // // The wrapper for a mutator consists of two parts: the wrapper which is used at // 'play' time, and the wrapper which is used at 'replay' time. // ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// static void play_initialize(EngineWrapper *w, uint32_t argc, char **argv, uint32_t srcpklen, const char *srcpk, const char *logfn) { if (w->engine != nullptr) { return reset_wrapper(w, "Cannot initialize wrapper, it's already initialized."); } // Clear the error message. memset(w->error, 0, DRV_ERRMSG_SIZE); // Open the logfile, if any is specified. if ((logfn != nullptr) && (logfn[0] != 0)) { w->wlog = new PlayLogfile(logfn, std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); if (!w->wlog->good()) { return reset_wrapper(w, "Could not open replay log for writing: %s", logfn); } } else { w->wlog = nullptr; } // If we have a logfile, then log this initialization. if (w->wlog != nullptr) { wlog_cmd_hash(w, PLAY_INITIALIZE, eng::memhash()); wlog_uint32(w, argc); for (uint32_t i = 0; i < argc; i++) { wlog_string(w, argv[i]); } wlog_string(w, std::string_view(srcpk, srcpklen)); w->wlog->flush(); } // Create the engine of the appropriate type. if (argc < 1) { std::ostringstream oss; oss << "Must pass an engine type on the command line. Known types:\n"; for (auto reg = DrivenEngineReg::All; reg != nullptr; reg=reg->next) { oss << " " << reg->name << std::endl; } std::string err = oss.str(); return reset_wrapper(w, err.c_str()); } w->engine = make_engine(argv[0]); if (w->engine == nullptr) { return reset_wrapper(w, "No such driven engine type: %s", argv[0]); } // Call the engine initialization sequence. w->engine->drv_initialize(srcpklen, srcpk, argc - 1, argv + 1); } static void replay_initialize(EngineWrapper *w) { assert(w->rlog != nullptr); std::vector argvstr; uint32_t argc = rlog_uint32(w); for (uint32_t i = 0; i < argc; i++) { argvstr.push_back(rlog_string(w)); } std::string srcpk = rlog_string(w); if (!w->rlog->good()) { return reset_wrapper(w, "replay log corrupt in replay_initialize"); } // We need to convert the argument vector from an array // of C++ strings into the canonical argc, argv format. std::vector argvec; for (uint32_t i = 0; i < argc; i++) { argvec.push_back(&argvstr[i][0]); } char **argv = &argvec[0]; // Create the engine. w->engine = make_engine(argv[0]); if (w->engine == nullptr) { return reset_wrapper(w, "No such driven engine type: %s", argvstr[0]); } w->engine->drv_initialize(srcpk.size(), srcpk.c_str(), argc - 1, argv + 1); } //////////////////////// static void play_clear_new_outgoing(EngineWrapper *w) { assert(w->rlog == nullptr); if (w->wlog != nullptr) { wlog_cmd_hash(w, PLAY_CLEAR_NEW_OUTGOING, eng::memhash()); w->wlog->flush(); } w->engine->drv_clear_new_outgoing(); } static void replay_clear_new_outgoing(EngineWrapper *w) { w->engine->drv_clear_new_outgoing(); } //////////////////////// static void play_sent_outgoing(EngineWrapper *w, uint32_t chid, uint32_t nbytes) { assert(w->rlog == nullptr); if (w->wlog != nullptr) { uint32_t ndata; const char *data; w->engine->drv_get_outgoing(chid, &ndata, &data); assert(nbytes <= ndata); wlog_cmd_hash(w, PLAY_SENT_OUTGOING, eng::memhash()); wlog_uint32(w, chid); wlog_uint32(w, nbytes); wlog_uint64(w, SpookyHash::QkHash64(data, nbytes)); w->wlog->flush(); } w->engine->drv_sent_outgoing(chid, nbytes); } static void replay_sent_outgoing(EngineWrapper *w) { uint32_t chid = rlog_uint32(w); uint32_t nbytes = rlog_uint32(w); uint64_t hash = rlog_uint64(w); if (!w->rlog->good()) { return reset_wrapper(w, "replay log corrupt in replay_sent_outgoing"); } uint32_t ndata; const char *data; w->engine->drv_get_outgoing(chid, &ndata, &data); if ((nbytes > ndata) || (hash != SpookyHash::QkHash64(data, nbytes))) { return reset_wrapper(w, "nondeterministic in replay_sent_outgoing"); } if (w->replay_cb_sent_outgoing != nullptr) { w->replay_cb_sent_outgoing(w->replay_cb_vp, chid, ndata, data); } w->engine->drv_sent_outgoing(chid, nbytes); } //////////////////////// static void play_recv_incoming(EngineWrapper *w, uint32_t chid, uint32_t len, const char *data) { assert(w->rlog == nullptr); if (w->wlog != nullptr) { wlog_cmd_hash(w, PLAY_RECV_INCOMING, eng::memhash()); wlog_uint32(w, chid); wlog_short_string(w, std::string_view(data, len)); w->wlog->flush(); } w->engine->drv_recv_incoming(chid, len, data); } static void replay_recv_incoming(EngineWrapper *w) { uint32_t chid = rlog_uint32(w); std::string_view data = rlog_short_string(w); if (!w->rlog->good()) { return reset_wrapper(w, "replay log corrupt in replay_recv_incoming"); } w->engine->drv_recv_incoming(chid, data.size(), data.data()); } //////////////////////// static void play_notify_close(EngineWrapper *w, uint32_t chid, uint32_t len, const char *data) { assert(w->rlog == nullptr); if (w->wlog != nullptr) { wlog_cmd_hash(w, PLAY_NOTIFY_CLOSE, eng::memhash()); wlog_uint32(w, chid); wlog_string(w, std::string_view(data, len)); w->wlog->flush(); } w->engine->drv_notify_close(chid, len, data); } static void replay_notify_close(EngineWrapper *w) { uint32_t chid = rlog_uint32(w); std::string message = rlog_string(w); if (!w->rlog->good()) { return reset_wrapper(w, "replay log corrupt in replay_notify_close"); } w->engine->drv_notify_close(chid, message.size(), message.c_str()); } //////////////////////// static uint32_t play_notify_accept(EngineWrapper *w, uint32_t port) { assert(w->rlog == nullptr); if (w->wlog != nullptr) { wlog_cmd_hash(w, PLAY_NOTIFY_ACCEPT, eng::memhash()); wlog_uint32(w, port); w->wlog->flush(); } return w->engine->drv_notify_accept(port); } static void replay_notify_accept(EngineWrapper *w) { uint32_t port = rlog_uint32(w); if (!w->rlog->good()) { return reset_wrapper(w, "replay log corrupt in replay_notify_accept"); } w->engine->drv_notify_accept(port); } //////////////////////// static void play_invoke_event_update(EngineWrapper *w, double clock) { assert(w->rlog == nullptr); if (w->wlog != nullptr) { wlog_cmd_hash(w, PLAY_INVOKE_EVENT_UPDATE, eng::memhash()); wlog_double(w, clock); w->wlog->flush(); } w->engine->drv_invoke_event_update(clock); } static void replay_invoke_event_update(EngineWrapper *w) { double clock = rlog_double(w); if (!w->rlog->good()) { return reset_wrapper(w, "replay log corrupt in replay_event_update"); } w->engine->drv_invoke_event_update(clock); } //////////////////////// void play_set_lua_source(EngineWrapper *w, uint32_t srcpklen, const char *srcpk) { assert(w->rlog == nullptr); if (w->wlog != nullptr) { wlog_cmd_hash(w, PLAY_SET_LUA_SOURCE, eng::memhash()); wlog_string(w, std::string_view(srcpk, srcpklen)); w->wlog->flush(); } w->engine->drv_set_lua_source(srcpklen, srcpk); } void replay_set_lua_source(EngineWrapper *w) { std::string srcpack = rlog_string(w); if (!w->rlog->good()) { return reset_wrapper(w, "replay log corrupt in replay_set_lua_source"); } w->engine->drv_set_lua_source(srcpack.size(), srcpack.c_str()); } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // // Replay Core // ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// static void replaycore_initialize(EngineWrapper *w, const char *logfn) { if (w->engine != nullptr) { return reset_wrapper(w, "Cannot initialize wrapper, it's already initialized."); return; } // Clear the error message. memset(w->error, 0, DRV_ERRMSG_SIZE); // Open the logfile. w->rlog = new ReplayLogfile(logfn, std::ios_base::in | std::ios_base::binary); if (!w->rlog->good()) { return reset_wrapper(w, "Could not open replay log for reading: %s", logfn); } // Read one step from the logfile, and make sure it's an initialize step. uint8_t code = rlog_uint8(w); int hash = rlog_uint32(w); if (!w->rlog->good()) { return reset_wrapper(w, "logfile corrupt in initial step"); } if (hash != eng::memhash()) { return reset_wrapper(w, "nondeterminism detected in initial step"); } if (code != PLAY_INITIALIZE) { return reset_wrapper(w, "replay log doesn't begin with initialize step"); } // Replay the initialize step from the logfile. // Doing this immediately, rather than waiting for the driver // to call 'step', enforces the invariant that after calling // initialize, there's an engine. replay_initialize(w); } static void replaycore_step(EngineWrapper *w) { if (w->rlog == nullptr) { return; } uint8_t code = rlog_uint8(w); if (w->rlog->eof()) { return reset_wrapper(w, "logfile terminated abruptly"); } int hash = rlog_uint32(w); if (!w->rlog->good()) { return reset_wrapper(w, "logfile corrupt in replay step"); } if (hash != eng::memhash()) { return reset_wrapper(w, "nondeterminism detected"); } switch (code) { case PLAY_CLEAR_NEW_OUTGOING: replay_clear_new_outgoing(w); return; case PLAY_SENT_OUTGOING: replay_sent_outgoing(w); return; case PLAY_RECV_INCOMING: replay_recv_incoming(w); return; case PLAY_NOTIFY_CLOSE: replay_notify_close(w); return; case PLAY_NOTIFY_ACCEPT: replay_notify_accept(w); return; case PLAY_SET_LUA_SOURCE: replay_set_lua_source(w); return; case PLAY_INVOKE_EVENT_UPDATE: replay_invoke_event_update(w); return; case PLAY_RELEASE: release(w); return; default: return reset_wrapper(w, "Replay log corrupt in command dispatcher"); } } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // // General Mutators // ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // // Wrapper Initialization // // To access the engine across a DLL boundary, you first use // GetProcAddress or dlsym to fetch the addresses of 'init_play_engine' // and 'init_replay_engine'. Then, you use those two functions to // initialize a EngineWrapper or a EngineWrapper, which contain the addresses // of all the other functions you need. These are the only two functions // marked 'DLLEXPORT', all other functions are exported from the DLL // indirectly. // ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// #if defined(__linux__) #define DLLEXPORT __attribute__((visibility("default"))) #elif defined(_WIN32) #define DLLEXPORT __declspec(dllexport) #endif static void init_engine_wrapper_helper(EngineWrapper *w) { static bool called_initializer; assert(DrivenEngineInitializerReg::func != nullptr); if (!called_initializer) { DrivenEngineInitializerReg::func(); called_initializer = true; } memset(w, 0, sizeof(EngineWrapper)); w->get_listen_ports = drv_get_listen_ports; w->get_new_outgoing = drv_get_new_outgoing; w->get_target = drv_get_target; w->get_channel_released = drv_get_channel_released; w->get_outgoing = drv_get_outgoing; w->get_outgoing_empty = drv_get_outgoing_empty; w->get_clock = drv_get_clock; w->get_rescan_lua_source = drv_get_rescan_lua_source; w->get_stop_driver = drv_get_stop_driver; w->play_initialize = play_initialize; w->play_clear_new_outgoing = play_clear_new_outgoing; w->play_sent_outgoing = play_sent_outgoing; w->play_recv_incoming = play_recv_incoming; w->play_notify_close = play_notify_close; w->play_notify_accept = play_notify_accept; w->play_invoke_event_update = play_invoke_event_update; w->play_set_lua_source = play_set_lua_source; w->replay_initialize = replaycore_initialize; w->replay_step = replaycore_step; w->hook_dprint = util::hook_dprint; w->release = release; }; extern "C" { DLLEXPORT void init_engine_wrapper(EngineWrapper *w) { init_engine_wrapper_helper(w); } }