//------------------------------------------------------------------------------ /* This file is part of rippled: https://github.com/ripple/rippled Copyright (c) 2012, 2013 Ripple Labs Inc. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== #ifndef XRPL_TEST_CSF_BASICNETWORK_H_INCLUDED #define XRPL_TEST_CSF_BASICNETWORK_H_INCLUDED #include #include namespace ripple { namespace test { namespace 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 link_type { bool inbound = false; duration delay{}; time_point established{}; link_type() = default; link_type(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 */ 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, link_type{false, delay, now})) return false; auto const result = links_.connect(to, from, link_type{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 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 csf } // namespace test } // namespace ripple #endif