#pragma once #include #include namespace xrpl::test::csf { /** Peer to peer network simulator. The network is formed from a set of Peer objects representing vertices and configurable connections representing edges. The caller is responsible for creating the Peer objects ahead of time. Peer objects cannot be destroyed once the BasicNetwork is constructed. To handle peers going online and offline, callers can simply disconnect all links and reconnect them later. Connections are directed, one end is the inbound Peer and the other is the outbound Peer. Peers may send messages along their connections. To simulate the effects of latency, these messages can be delayed by a configurable duration set when the link is established. Messages always arrive in the order they were sent on a particular connection. A message is modeled using a lambda function. The caller provides the code to execute upon delivery of the message. If a Peer is disconnected, all messages pending delivery at either end of the connection will not be delivered. When creating the Peer set, the caller needs to provide a Scheduler object for managing the timing and delivery of messages. After constructing the network, and establishing connections, the caller uses the scheduler's step* functions to drive messages through the network. The graph of peers and connections is internally represented using Digraph. Clients have const access to that graph to perform additional operations not directly provided by BasicNetwork. Peer Requirements: Peer should be a lightweight type, cheap to copy and/or move. A good candidate is a simple pointer to the underlying user defined type in the simulation. Expression Type Requirements ---------- ---- ------------ P Peer u, v Values of type P P u(v) CopyConstructible u.~P() Destructible u == v bool EqualityComparable u < v bool LessThanComparable std::hash

class std::hash is defined for P ! u bool true if u is not-a-peer */ template class BasicNetwork { using peer_type = Peer; using clock_type = Scheduler::clock_type; using duration = typename clock_type::duration; using time_point = typename clock_type::time_point; struct LinkType { bool inbound = false; duration delay{}; time_point established; LinkType() = default; LinkType(bool inbound, duration delay, time_point established) : inbound(inbound), delay(delay), established(established) { } }; Scheduler& scheduler_; Digraph links_; public: BasicNetwork(BasicNetwork const&) = delete; BasicNetwork& operator=(BasicNetwork const&) = delete; BasicNetwork(Scheduler& s); /** Connect two peers. The link is directed, with `from` establishing the outbound connection and `to` receiving the incoming connection. Preconditions: from != to (self connect disallowed). A link between from and to does not already exist (duplicates disallowed). Effects: Creates a link between from and to. @param `from` The source of the outgoing connection @param `to` The recipient of the incoming connection @param `delay` The time delay of all delivered messages @return `true` if a new connection was established */ bool connect(Peer const& from, Peer const& to, duration const& delay = std::chrono::seconds{0}); /** Break a link. Effects: If a connection is present, both ends are disconnected. Any pending messages on the connection are discarded. @return `true` if a connection was broken. */ bool disconnect(Peer const& peer1, Peer const& peer2); /** Send a message to a peer. Preconditions: A link exists between from and to. Effects: If the link is not broken when the link's `delay` time has elapsed, the function will be invoked with no arguments. @note Its the caller's responsibility to ensure that the body of the function performs activity consistent with `from`'s receipt of a message from `to`. */ template void send(Peer const& from, Peer const& to, Function&& f); /** Return the range of active links. @return A random access range over Digraph::Edge instances */ auto links(Peer const& from) { return links_.outEdges(from); } /** Return the underlying digraph */ [[nodiscard]] Digraph const& graph() const { return links_; } }; //------------------------------------------------------------------------------ template BasicNetwork::BasicNetwork(Scheduler& s) : scheduler_(s) { } template inline bool BasicNetwork::connect(Peer const& from, Peer const& to, duration const& delay) { if (to == from) return false; time_point const now = scheduler_.now(); if (!links_.connect(from, to, LinkType{false, delay, now})) return false; auto const result = links_.connect(to, from, LinkType{true, delay, now}); (void)result; assert(result); return true; } template inline bool BasicNetwork::disconnect(Peer const& peer1, Peer const& peer2) { if (!links_.disconnect(peer1, peer2)) return false; bool const r = links_.disconnect(peer2, peer1); (void)r; assert(r); return true; } template template inline void BasicNetwork::send(Peer const& from, Peer const& to, Function&& f) { auto link = links_.edge(from, to); if (!link) return; time_point const sent = scheduler_.now(); scheduler_.in(link->delay, [from, to, sent, f = std::forward(f), this] { // only process if still connected and connection was // not broken since the message was sent if (auto l = links_.edge(from, to); l && l->established <= sent) { f(); } }); } } // namespace xrpl::test::csf