#ifndef C_TOXCORE_TESTING_SUPPORT_SIMULATION_H #define C_TOXCORE_TESTING_SUPPORT_SIMULATION_H #include #include #include #include #include #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #define NOMINMAX #include #else #include #include #endif #include #include "../../../toxcore/attributes.h" #include "../../../toxcore/mem.h" #include "../../../toxcore/rng.h" #include "../../../toxcore/tox.h" #include "../../../toxcore/tox_private.h" #include "../doubles/fake_clock.hh" #include "../doubles/fake_memory.hh" #include "../doubles/fake_network_stack.hh" #include "../doubles/fake_random.hh" #include "../doubles/network_universe.hh" #include "environment.hh" namespace tox::test { class SimulatedNode; struct LogMetadata { Tox_Log_Level level; const char *_Nonnull file; uint32_t line; const char *_Nonnull func; const char *_Nonnull message; uint32_t node_id; }; using LogPredicate = std::function; struct LogFilter { LogPredicate pred; LogFilter() = default; explicit LogFilter(LogPredicate p) : pred(std::move(p)) { } bool operator()(const LogMetadata &md) const { return !pred || pred(md); } }; LogFilter operator&&(const LogFilter &lhs, const LogFilter &rhs); LogFilter operator||(const LogFilter &lhs, const LogFilter &rhs); LogFilter operator!(const LogFilter &target); namespace log_filter { LogFilter level(Tox_Log_Level min_level); struct LevelPlaceholder { LogFilter operator>(Tox_Log_Level rhs) const; LogFilter operator>=(Tox_Log_Level rhs) const; LogFilter operator<(Tox_Log_Level rhs) const; LogFilter operator<=(Tox_Log_Level rhs) const; LogFilter operator==(Tox_Log_Level rhs) const; LogFilter operator!=(Tox_Log_Level rhs) const; }; LevelPlaceholder level(); LogFilter file(std::string pattern); LogFilter func(std::string pattern); LogFilter message(std::string pattern); LogFilter node(uint32_t id); } // namespace log_filter /** * @brief The Simulation World. * Holds the Clock and the Universe. */ class Simulation { public: static constexpr uint32_t kDefaultTickIntervalMs = 50; Simulation(); ~Simulation(); // Time Control void advance_time(uint64_t ms); void run_until(std::function condition, uint64_t timeout_ms = 5000); // Logging void set_log_filter(LogFilter filter); const LogFilter &log_filter() const { return log_filter_; } // Synchronization Barrier // These methods coordinate the lock-step execution of multiple Tox runners. /** * @brief Registers a new runner with the simulation barrier. * @return The current generation ID of the simulation. */ uint64_t register_runner(); /** * @brief Unregisters a runner from the simulation barrier. * * This ensures the simulation does not block waiting for a terminated runner. */ void unregister_runner(); using TickListenerId = int; /** * @brief Registers a callback to be invoked when a new simulation tick starts. * * @param listener The function to call with the new generation ID. * @return An ID handle for unregistering the listener. */ TickListenerId register_tick_listener(std::function listener); /** * @brief Unregisters a tick listener. */ void unregister_tick_listener(TickListenerId id); /** * @brief Blocks until the simulation advances to the next tick. * * Called by runner threads to wait for the global clock to advance. * * @param last_gen The generation ID of the last processed tick. * @param stop_token Atomic flag to signal termination while waiting. * @param timeout_ms Maximum time to wait for the tick. * @return The new generation ID, or `last_gen` on timeout/stop. */ uint64_t wait_for_tick( uint64_t last_gen, const std::atomic &stop_token, uint64_t timeout_ms = 10); /** * @brief Signals that a runner has completed its work for the current tick. * * @param next_delay_ms The requested delay until the next tick (from `tox_iteration_interval`). */ void tick_complete(uint32_t next_delay_ms = kDefaultTickIntervalMs); // Global Access FakeClock &clock() { return *clock_; } const FakeClock &clock() const { return *clock_; } NetworkUniverse &net() { return *net_; } const NetworkUniverse &net() const { return *net_; } // Node Factory std::unique_ptr create_node(); private: std::unique_ptr clock_; std::unique_ptr net_; LogFilter log_filter_; uint32_t node_count_ = 0; // Barrier State std::mutex barrier_mutex_; std::condition_variable barrier_cv_; uint64_t current_generation_ = 0; int registered_runners_ = 0; std::atomic active_runners_{0}; std::atomic next_step_min_{kDefaultTickIntervalMs}; struct TickListener { TickListenerId id; std::function callback; }; std::vector tick_listeners_; TickListenerId next_listener_id_ = 0; }; /** * @brief Represents a single node in the simulation. * Implements the Environment interface for dependency injection. */ class SimulatedNode : public Environment { public: explicit SimulatedNode(Simulation &sim, uint32_t node_id); ~SimulatedNode() override; // Environment Interface NetworkSystem &network() override; ClockSystem &clock() override; RandomSystem &random() override; MemorySystem &memory() override; // Direct Access to Fakes FakeNetworkStack &fake_network() { return *network_; } FakeRandom &fake_random() { return *random_; } FakeMemory &fake_memory() { return *memory_; } // Tox Creation Helper // Returns a configured Tox instance bound to this node's environment. // The user owns the Tox instance. struct ToxDeleter { void operator()(Tox *_Nonnull t) const { tox_kill(t); } }; using ToxPtr = std::unique_ptr; ToxPtr create_tox(const Tox_Options *_Nullable options = nullptr); Simulation &simulation() { return sim_; } // For fuzzing compatibility (exposes first bound UDP socket as "endpoint") FakeUdpSocket *_Nullable get_primary_socket(); private: Simulation &sim_; std::unique_ptr network_; std::unique_ptr random_; std::unique_ptr memory_; // C-compatible views (must stay valid for the lifetime of Tox) public: struct Network c_network; struct Random c_random; struct Memory c_memory; struct IP ip; }; } // namespace tox::test #endif // C_TOXCORE_TESTING_SUPPORT_SIMULATION_H