Analysis Of The IRC Server-Server Protocol And Synchronization --------------------------------------------------------------
by Carlo Wood March 2002 This report is the result of a research on channel synchronization. During this research net-breaks have not been taken into account. The research for performed in an attempt to introduce a new message DESTRUCT, which is needed when we want to keep empty channels alive for a certain period of time. Channel modes are assumed to be a direct function of the timestamp: when the timestamp is preserved then the modes are synchronized too. In order to achieve this it is necessary to use the BURST message as synchronization message for the (re)-creation of channels, outside the normal net.burst. The following messages are considered: CREATE : Generated by a server when a previously non-existant channel is created by a local user. JOIN : Generated by a server when a local user joins an existant channel. PART : Generated by a server when a local user leaves a channel that it previously joined. DESTRUCT: Generated by a server when a channel without users is removed from memory (making the server forget all modes). BURST : A resynchronization message containing all modes, the timestamp of the channel and all members on the channel. Each of messages has a TimeStamp (TS), except PART. Examples -------- This report contains several examples, each of those are about a network with three servers: A, B and C. The map is as follows: A --- B --- C The notation in the examples is: |A:03/01<0027>|B:02/00<0027>|C:02/01<0027>| A<B:{48 PART<27>}; B>C:{48 PART<27>} {51 |JOIN<27>} Where "A:03/01<0027>" means that server A sees 3 channel members, 1 of which is local. The timestamp of the channel is <0027>. Right of the three servers (that are seperated with '|') are the currently queued messages. "A<B:{48 PART<27>}" means that on the link from B going to A there is queued one message "{48 PART<27>}". Here, '48' is the member prefix, a unique id for a user, and <27> is the timestamp (the same as <0027> thus). When there are two or more messages queued on a link, like in this example on the link from B going to C: "{48 PART<27>} {51 JOIN<27>}" then the right most message is the FIRST message that will go out, independant of the direction ('<' or '>'). Next there are two types of events: local events and flushing of queued messages: |A:03/01<0027>|B:03/01<0027>|C:02/01<0027>| B>C:{51 JOIN<27>} PART |A:03/01<0027>|B:02/00<0027>|C:02/01<0027>| A<B:{48 PART<27>}; B>C:{48 PART<27>} {51 |JOIN<27>} A<B:{48 PART<27>} |A:02/01<0027>|B:02/00<0027>|C:02/01<0027>| B>C:{48 PART<27>} {51 JOIN<27>} This means that first a 'PART' happened on server B (the PART is written directly below server B) and next the queued message "{48 PART<27>}" was processed (which is written directly below the '|' between A and B). PART ---- CREATE, JOIN and PART use a prefix uniquely identifying the user joining or leaving; because a server always first generates a CREATE or JOIN for a user and only then a PART, it is garanteed to find the user on the channel when a PART is received. The processing of a PART is therefore very straight forward: .----------------------------------------------------. | PART | Remove the given member from the channel. | `----------------------------------------------------' In the old protocol the channel was also destructed when a PART caused a channel to become empty. CREATE ------ A CREATE message is the equivalent of a JOIN plus a MODE that gives ops the user, in one message. Because we do not consider modes in this research it follows that a CREATE is equivalent with a JOIN except that sometimes a CREATE is translated into a JOIN (when the timestamp on the message is younger than the age of the channel). JOIN ---- A JOIN is only generated (possibly disguised as a CREATE) when a _local_ user joins a channel. It is always propagated and never bounced. Its counter part is the PART message that always travels in the same direction and from the same source (the server local to the user). Please note that the KICK message was effectively removed from this picture by the introduction of 'zombies': a KICK doesn't remove a user from a channel, it merely requests the server local to the user to send a PART. It is this PART which then really removes the user from the channel. The only other message that joins users to a channel is the BURST message. The BURST message should only ever contain nick names of users that it travels away from; therefore it is impossible that a JOIN for a certain user reaches a server where that user is already joined. The processing of a JOIN is therefore also very straight forward: .----------------------------------------------------. | JOIN | Add the given member to the channel. | `----------------------------------------------------' For the synchronization of the timestamp the follow rules must be followed: .------------------------------------------------------------------. | 1) Copy the timestamp of the message when it is older than the | | current timestamp on the channel. | | 2) Always use the (final) timestamp on the channel for the | | propagated (or bounced) message(s). | `------------------------------------------------------------------' Note that in the case of a CREATE, the CREATE turns into a JOIN (and a deop MODE is bounced back) when the timestamp on the CREATE is younger than the current timestamp on the channel. BURST ----- A BURST message contains a timestamp that is handled according to the rules given above. However, a BURST message is not propagated under certain circumstances (unlike JOIN and PART). A BURST message is not propagated when the timestamp on the message is younger than the current timestamp on the channel (It wouldn't harm when it was though). The BURST message contains all nicks on the channel. Because now we use the BURST message outside a normal burst, it is possible that it follows a JOIN/CREATE and a listed nick is _already_ on the channel. Therefore it must be carefully checked whether or not nicks in a BURST are already joined or not, and if so, not send a JOIN to the channel (again). DESTRUCT -------- A DESTRUCT message should cause a channel to be destructed on ALL servers: if that would fail then there is a desynchronization because information was lost on some servers, but not on all. However, sometimes a DESTRUCT message cannot be propagated: when the channel isn't empty! In that case we need to bounce a fully synchronizing BURST message back upstream. A DESTRUCT message must be ignored when the channel it tries to destruct doesn't exist (which means that it was already destructed and another DESTRUCT message is travelling in the opposite direction). The DESTRUCT message can also ignored when timestamp on the message is younger than the channel, just as with BURST (and also in this case it wouldn't harm if it was propagated, but it just isn't necessary). Finally, a DESTRUCT message that *succeeds* must be propagated in *all* directions, also the direction it came from. This is needed to 'wipe out' a "JOIN/PART" combination that might have crossed the DESTRUCT message. If this wasn't done, it would become possible that channel would exist on some servers but not on others (without synchronizing messages inbetween) and that can again can lead to desychronization. |A:00/00<0092>|B:00/00<0092>|C:00/00<0092>| State OK JOIN |A:01/01<0092>|B:00/00<0092>|C:00/00<0092>| A>B:{214 JOIN<92>} DESTRUCT |A:01/01<0092>|B: <none>|C:00/00<0092>| A>B:{214 JOIN<92>}; A<B:{DESTRUCT<92>}; |B>C:{DESTRUCT<92>} B>C:{DESTRUCT<92>} |A:01/01<0092>|B: <none>|C: <none>| A>B:{214 JOIN<92>}; A<B:{DESTRUCT<92>} PART |A:00/00<0092>|B: <none>|C: <none>| A>B:{214 PART<92>} {214 JOIN<92>}; |A<B:{DESTRUCT<92>} A>B:{214 JOIN<92>} |A:00/00<0092>|B:01/00<0092>|C: <none>| A>B:{214 PART<92>}; A<B:{DESTRUCT<92>}; |B>C:{214 JOIN<92>} A>B:{214 PART<92>} |A:00/00<0092>|B:00/00<0092>|C: <none>| A<B:{DESTRUCT<92>}; B>C:{214 PART<92>} |{214 JOIN<92>} B>C:{214 JOIN<92>} |A:00/00<0092>|B:00/00<0092>|C:01/00<0092>| A<B:{DESTRUCT<92>}; B>C:{214 PART<92>} B>C:{214 PART<92>} |A:00/00<0092>|B:00/00<0092>|C:00/00<0092>| A<B:{DESTRUCT<92>} A<B:{DESTRUCT<92>} |A: <none>|B:00/00<0092>|C:00/00<0092>| State of A differs from state of B! The old protocol ---------------- The old protocol, without the DESTRUCT message, suffered from easy desychronization, for example: |A: <none>|B: <none>| State OK CREATE<17> |A: <none>|B:01/01<0017>| A<B:{22 CREATE<17>} CREATE<18> |A:01/01<0018>|B:01/01<0017>| A>B:{23 CREATE<18>}; A<B:{22 CREATE<17>} PART |A:01/01<0018>|B: <none>| A>B:{23 CREATE<18>}; A<B:{22 PART<17>} {22 CREATE<17>} A<B:{22 CREATE<17>} |A:02/01<0017>|B: <none>| A>B:{23 CREATE<18>}; A<B:{22 PART<17>} A>B:{23 CREATE<18>} |A:02/01<0017>|B:01/00<0018>| A<B:{22 PART<17>} A<B:{22 PART<17>} |A:01/01<0017>|B:01/00<0018>| State of A differs from state of B! Conclusion ---------- With the introduction of a DESTRUCT message it is possible to completely get rid of TimeStamp desynchronization. However, we need to allow BURST messages outside the normal burst then and take care not to send JOIN messages to the users for members that are already on the channel. The C++ program that was developed during this research and that was used to verify all of the above is attached. -- Carlo Wood <[EMAIL PROTECTED]>
// destruct v1.0 // // Copyright (C) 2002, by // // Carlo Wood, Run on IRC <[EMAIL PROTECTED]> // RSA-1024 0x624ACAD5 1997-01-26 Sign & Encrypt // Fingerprint16 = 32 EC A7 B6 AC DB 65 A6 F6 F6 55 DD 1C DC FF 61 // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // #include <deque> #include <vector> #include <iostream> #include <iomanip> #include <cstdlib> #include <cassert> #include <set> #define USEDESTRUCT enum DirectionType { up, down }; std::ostream& operator<<(std::ostream& os, DirectionType const& type) { switch(type) { case up: os << "up"; break; case down: os << "down"; break; } return os; } class Direction { private: DirectionType M_direction_type; public: Direction(DirectionType d) : M_direction_type(d) { } Direction other(void) const; friend bool operator==(Direction const& d, DirectionType t) { return d.M_direction_type == t; } friend bool operator==(DirectionType t, Direction const& d) { return d.M_direction_type == t; } friend bool operator!=(Direction const& d, DirectionType t) { return d.M_direction_type != t; } friend bool operator!=(DirectionType t, Direction const& d) { return d.M_direction_type != t; } bool operator==(Direction const& d) const { return M_direction_type == d.M_direction_type; } friend std::ostream& operator<<(std::ostream& os, Direction const& direction); void reverse_direction(void) { M_direction_type = (M_direction_type == up) ? down : up; } }; std::ostream& operator<<(std::ostream& os, Direction const& direction) { os << direction.M_direction_type; return os; } Direction Direction::other(void) const { return Direction(M_direction_type == up ? down : up); } enum MsgType { create, join, part, burst, #ifdef USEDESTRUCT destruct, #endif none }; std::ostream& operator<<(std::ostream& os, MsgType const& type) { switch(type) { case create: os << "CREATE"; break; case join: os << "JOIN"; break; case part: os << "PART"; break; case burst: os << "BURST"; break; #ifdef USEDESTRUCT case destruct: os << "DESTRUCT"; break; #endif case none: os << "<none>"; break; } return os; } class Message { private: MsgType M_msgtype; int M_timestamp; Direction M_direction; int M_prefix; std::set<int> M_member_list; public: Message(MsgType type, int prefix, int t, Direction d, std::set<int> member_list) : M_msgtype(type), M_timestamp(t), M_direction(d), M_prefix(prefix), M_member_list(member_list) { assert( type == burst ); } Message(MsgType type, int prefix, int t, Direction d) : M_msgtype(type), M_timestamp(t), M_direction(d), M_prefix(prefix) { assert( (prefix == -1 || type == create || type == join || type == part) ); } Message(MsgType type) : M_msgtype(none), M_timestamp(-1000), M_direction(down) { assert(type == none); } MsgType type(void) const { return M_msgtype; } int timestamp(void) const { return M_timestamp; } Direction direction(void) const { return M_direction; } int prefix(void) const { return M_prefix; } void reverse_direction(void) { M_direction.reverse_direction(); } void set_timestamp(int timestamp) { M_timestamp = timestamp; } std::set<int>& member_list(void) { return M_member_list; } std::set<int> const& member_list(void) const { return M_member_list; } friend std::ostream& operator<<(std::ostream& os, Message const& msg); }; std::ostream& operator<<(std::ostream& os, Message const& msg) { os << '{'; if (msg.M_prefix != -1) os << msg.M_prefix << ' '; os << msg.M_msgtype << '<' << msg.M_timestamp << '>'; if (msg.type() == burst) { bool first = true; for (std::set<int>::const_iterator i(msg.member_list().begin()); i != msg.member_list().end(); ++i) { if (first) first = false; else os << ','; os << (*i); } } os << '}'; return os; } struct Network; class Server; class State { private: bool M_exists; // Channel structure exists. int M_timestamp; // Timestamp. std::set<int> M_members_list; // Prefixes of the members. std::set<int> M_local_members_list; // Prefixes of the local members. public: State(void) : M_exists(false), M_timestamp(0) { } Message receive(int link_id, Server& server, Message& msg); Message generate(MsgType type, Network& network); bool exists(void) const { return M_exists; } int timestamp(void) const { return M_timestamp; } int members(void) const { return M_members_list.size(); } int local_members(void) const { return M_local_members_list.size(); } int new_member(Network& network); int delete_member(void); void add_members(std::set<int> const& member_list); // Add remote members. friend bool operator!=(State const& s1, State const& s2); friend bool operator==(State const& s1, State const& s2); friend std::ostream& operator<<(std::ostream& os, State const& state); }; std::ostream& operator<<(std::ostream& os, State const& state) { if (state.M_exists) os << std::setw(2) << state.members() << '/' << std::setw(2) << state.local_members() << '<' << std::setw(4) << std::setfill('0') << state.M_timestamp << '>'; else os << " <none>"; return os; } class Queue { private: std::deque<Message> M_q; public: void push(Message const& m); Message pop(void); bool empty(void) const { return M_q.empty(); } friend std::ostream& operator<<(std::ostream& os, Queue const& queue); int size(void) const { return M_q.size(); } }; std::ostream& operator<<(std::ostream& os, Queue const& queue) { bool first = true; for (std::deque<Message>::const_iterator i(queue.M_q.begin()); i != queue.M_q.end(); ++i) { if (!first) os << ' '; else first = false; os << (*i); } return os; } void Queue::push(Message const& m) { M_q.push_front(m); } Message Queue::pop(void) { Message out(M_q.back()); M_q.pop_back(); return out; } class Server; class Link { private: Queue M_deque; Server const& M_from_server; Server& M_server; int M_id; public: Link(Network& network, Server const& from, Server& server); bool empty(void) const { return M_deque.empty(); } bool flush(void); int id(void) const { return M_id; } void push(Message const& msg) { M_deque.push(msg); } std::string const& from(void) const; std::string const& to(void) const; friend std::ostream& operator<<(std::ostream& os, Link const& link); int size(void) const { return M_deque.size(); } void print_link_on(std::ostream& os) const; }; class Server { typedef std::vector<Link*> links_t; private: links_t links; int M_id; std::string M_name; public: State state; Server(int id) : M_id(id) { M_name = "\e[33m"; M_name += 'A' + id; M_name += "\e[0m"; } bool generate(MsgType type, Network& network); void propagate(int from, Message const& msg); void bounce(int from, Message& msg); void connect(Server& uplink, Network& network); std::string const& name(void) const { return M_name; } }; struct Network { int now; int next_link_id; std::vector<Link*> all_links; std::vector<Server> all_servers; int actions; int events; int next_member; Network(void) { } Network(int n); void connect(int s1, int s2) { all_servers[s1].connect(all_servers[s2], *this); } bool generate(MsgType type, int s) { return all_servers[s].generate(type, *this); } bool shift(int link) { return all_links[link]->flush(); } int number_of_links(void) const { return all_links.size(); } int number_of_servers(void) const { return all_servers.size(); } }; int State::new_member(Network& network) { int prefix = network.next_member++; M_local_members_list.insert(prefix); return prefix; } int State::delete_member(void) { assert( !M_local_members_list.empty() ); std::set<int>::iterator iter(M_local_members_list.begin()); int prefix = *iter; M_local_members_list.erase(iter); return prefix; } Link::Link(Network& network, Server const& from, Server& server) : M_from_server(from), M_server(server), M_id(network.next_link_id++) { } bool Server::generate(MsgType type, Network& network) { if (type == part && state.local_members() == 0) return false; Message result(state.generate(type, network)); if (result.type() == none) return false; std::cout << ' '; for (int i = 0; i < M_id; ++i) std::cout << " "; std::cout << type; if (type == create) std::cout << "<" << result.timestamp() << ">"; std::cout << '\n'; for (links_t::iterator i(links.begin()); i != links.end(); ++i) (*i)->push(result); return true; } void Link::print_link_on(std::ostream& os) const { if (M_from_server.name() < M_server.name()) os << M_from_server.name() << '>' << M_server.name(); else os << M_server.name() << '<' << M_from_server.name(); } std::ostream& operator<<(std::ostream& os, Link const& link) { link.print_link_on(os); os << ':' << link.M_deque; return os; } std::string const& Link::from(void) const { return M_from_server.name(); } std::string const& Link::to(void) const { return M_server.name(); } bool Link::flush(void) { if (empty()) return false; Message what(M_deque.pop()); Message result(M_server.state.receive(M_id, M_server, what)); int indent = (M_id >> 1); std::cout << " "; for (int i = 0; i < indent; ++i) std::cout << " "; print_link_on(std::cout); std::cout << ':' << what; std::cout << '\n'; if (result.type() == none) return true; // Ignored, but processed. if (result.direction() == up) M_server.bounce(M_id, result); else M_server.propagate(M_id, result); return true; } void Server::propagate(int from, Message const& msg) { for (links_t::iterator i(links.begin()); i != links.end(); ++i) if ((from & ~1) != ((*i)->id() & ~1)) (*i)->push(msg); } void Server::bounce(int from, Message& msg) { msg.reverse_direction(); for (links_t::iterator i(links.begin()); i != links.end(); ++i) if ((from & ~1) == ((*i)->id() & ~1)) (*i)->push(msg); } void Server::connect(Server& uplink, Network& network) { Link* p1 = new Link(network, *this, uplink); links.push_back(p1); network.all_links.push_back(p1); Link* p2 = new Link(network, uplink, *this); uplink.links.push_back(p2); network.all_links.push_back(p2); } bool operator!=(State const& s1, State const& s2) { #if 0 if (!s1.M_exists && (!s2.M_exists || s2.members() == 0)) return false; if (!s2.M_exists && (!s1.M_exists || s1.members() == 0)) return false; #endif if (s1.M_exists != s2.M_exists) return true; return s1.M_exists && (s1.M_timestamp != s2.M_timestamp || s1.M_members_list != s2.M_members_list); } bool operator==(State const& s1, State const& s2) { #if 0 if (!s1.M_exists && (!s2.M_exists || s2.members() == 0)) return true; if (!s2.M_exists && (!s1.M_exists || s1.members() == 0)) return true; #endif if (s1.M_exists != s2.M_exists) return false; return !s1.M_exists || (s1.M_timestamp == s2.M_timestamp && s1.M_members_list == s2.M_members_list); } Network::Network(int n) : now(0), next_link_id(0), actions(0), events(0), next_member(0) { for (int i = 0; i < n; ++i) all_servers.push_back(Server(i)); } int random(int max) { return (int)((double)max * rand() / (RAND_MAX + 1.0)); } void State::add_members(std::set<int> const& member_list) { for (std::set<int>::const_iterator iter(member_list.begin()); iter != member_list.end(); ++iter) { std::set<int>::const_iterator li(M_local_members_list.find(*iter)); assert( li == M_local_members_list.end() ); // A BURST msg should never contain a local client. bool new_member = M_members_list.insert(*iter).second; } } Message State::generate(MsgType type, Network& network) { int prefix = -1; switch (type) { case create: // Disallow CREATE event on server where the channel already exist. if (M_exists) return Message(none); // Channel now exist. M_exists = true; // Use a new, unique creation time. M_timestamp = ++network.now; // Use a new, unique, prefix for the new member. prefix = new_member(network); // Add that member to this channel. M_members_list.insert(prefix); assert( members() == 1); break; #ifdef USEDESTRUCT case destruct: // Disallow a DESTRUCT event when the channel doesn't exist or isn't empty. if (!M_exists || members() > 0) return Message(none); // Destruct the channel. M_exists = false; break; #endif case join: // Disallow JOIN event on server that doesn't already have the channel (should be a CREATE). // Also disallow a JOIN event when there is already a local user on the channel: it makes // no sense to test that. if (!M_exists || local_members() > 0) return Message(none); // Use a new, unique, prefix for the new member. prefix = new_member(network); // Add that member to this channel. M_members_list.insert(prefix); break; case part: { // Disallow a PART event for a channel that doesn't exist or doesn't have any local members. if (!M_exists || local_members() == 0) return Message(none); assert( members() >= 0 ); // Get the prefix of a local user. prefix = delete_member(); std::set<int>::iterator iter(M_members_list.find(prefix)); assert( iter != M_members_list.end() ); // Remove that member from this channel. M_members_list.erase(iter); #ifndef USEDESTRUCT // Destruct the channel when it becomes empty. if (members() == 0) M_exists = false; #endif break; } default: abort(); } // Propagate message 'downstream' (to all connected servers) with current timestamp. return Message(type, prefix, M_timestamp, down); } Message State::receive(int link_id, Server& server, Message& msg) { switch(msg.type()) { case none: abort(); case create: case join: { // Always add the new member to the channel. bool new_member = M_members_list.insert(msg.prefix()).second; assert( new_member ); // It *should* always be a NEW member... // Channel doesn't exist? if (!M_exists) { // Create the channel. M_exists = true; // Set the timestamp to the timestamp in the message. M_timestamp = msg.timestamp(); // Number of members on the channel is now 1. assert( members() == 1); break; } if (msg.timestamp() > M_timestamp) { // Timestamp in message is younger than of channel. if (msg.type() == create) { // Propagate a JOIN with current timestamp of channel. return Message(join, msg.prefix(), M_timestamp, down); } // Use current timestamp in propagated message. msg.set_timestamp(M_timestamp); break; } else if (msg.timestamp() < M_timestamp) { // Timestamp in message is older than of channel. // Use timestamp of message. M_timestamp = msg.timestamp(); } break; } case part: { // The channel must always exist. assert( M_exists ); // Try to remove the user from the channel. std::set<int>::iterator iter(M_members_list.find(msg.prefix())); // Must always be found. assert( iter != M_members_list.end() ); // Remove member from the channel. M_members_list.erase(iter); #ifndef USEDESTRUCT // Destruct the channel when it becomes empty. if (members() == 0) M_exists = false; #endif break; } #ifdef USEDESTRUCT case destruct: // Ignore DESTRUCT messages for non-existing channels. if (!M_exists) return Message(none); if (msg.timestamp() > M_timestamp) { #if 0 msg.set_timestamp(M_timestamp); #else // Ignore DESTRUCT when the timestamp of the message is younger than the channel creation time. return Message(none); #endif } if (members() == 0) { #if 1 // Bounce (and propagated) a DESTRUCT that was successful. Message extra_msg(destruct, -1, M_timestamp, up); server.bounce(link_id, extra_msg); #endif // Remove the channel only when it is empty. M_exists = false; } else if (members() > 0) { // When the channel is not empty then bounce a BURST message upstream. return Message(burst, -1, M_timestamp, up, M_members_list); } break; #endif case burst: if (!M_exists) { // If the channel didn't exist, create it, set the creation time and add the given members. M_exists = true; M_timestamp = msg.timestamp(); assert( members() == 0 ); add_members(msg.member_list()); break; } if (msg.timestamp() > M_timestamp) { #if 0 msg.set_timestamp(M_timestamp); #else // Ignore a BURST with a timestamp that is younger than the current creationtime of the channel. return Message(none); #endif } // Add the new members. add_members(msg.member_list()); if (msg.timestamp() < M_timestamp) { // If the timestamp in the message is older than of the current channel, // set the creationtime to it. M_timestamp = msg.timestamp(); } break; } // Propagate message, always use the current creationtime of the channel as timestamp. // (except when the channel doesn't exist anymore or for a PART message, which doesn't // really HAVE a timestamp. assert( msg.type() == part || !M_exists || msg.timestamp() == M_timestamp ); return msg; } enum step_nt { event, shift, done }; struct Step { step_nt what; union { int server; int link; }; MsgType type; }; #define RANDOM int main(void) { Network network(7); srand(0); // Connect servers. network.connect(0, 1); network.connect(1, 2); network.connect(2, 3); network.connect(3, 4); network.connect(2, 5); network.connect(5, 6); Step const step[] = { #ifdef USEDESTRUCT { event, 0, create }, { event, 0, part }, { shift, 0 }, { shift, 0 }, { event, 0, destruct }, { event, 1, join }, { event, 1, part }, { shift, 0 }, { shift, 1 }, { shift, 1 }, { shift, 1 }, { shift, 0 }, #endif { done } }; Network empty_network = network; int min_actions = INT_MAX; int target_actions = 0; int min_events = INT_MAX; int target_events = 0; bool better = false; int k = 0; for (int loop = 0; loop < 1000000; ++loop) { int queued_msgs = 0; for (std::vector<Link*>::iterator i(network.all_links.begin()); i != network.all_links.end(); ++i) queued_msgs += (*i)->size(); // Print state of servers and queues. std::cout << '|'; for (int i = 0; i < network.number_of_servers(); ++i) std::cout << network.all_servers[i].name() << ':' << network.all_servers[i].state << '|'; if (queued_msgs) { // Print state of links. bool first = true; for (std::vector<Link*>::iterator i(network.all_links.begin()); i != network.all_links.end(); ++i) { if ((*i)->empty()) continue; if (first) first = false; else std::cout << ';'; std::cout << ' ' << *(*i); } } else { // Check if all states are equal. for (int i = 1; i < network.number_of_servers(); ++i) if (network.all_servers[0].state != network.all_servers[i].state) { if (network.all_servers[1].state == network.all_servers[i].state) std::cout << " State of " << network.all_servers[0].name() << " differs from state of " << network.all_servers[1].name() << "!"; else std::cout << " State of " << network.all_servers[i].name() << " differs from state of " << network.all_servers[0].name() << "!"; std::cout << '\n'; if (network.actions < min_actions) { min_actions = network.actions; better = true; } if (network.events < min_events) { min_events = network.events; min_actions = network.actions; better = true; } if (loop > 10000 * min_actions) { target_actions = min_actions; target_events = min_events; } std::cout << "\e[31mCur actions " << network.actions << "; Min actions: " << min_actions << "\e[0m\n"; std::cout << "\e[31mCur events " << network.events << "; Min events : " << min_events << "\e[0m\n"; if (network.actions > target_actions || network.events > target_events) { network = empty_network; if (better) { std::cin.get(); better = false; } continue; } abort(); } std::cout << " \e[31mState OK\e[0m"; network.actions = 0; network.events = 0; } std::cout << '\n'; // Now lets do something #ifndef RANDOM if (step[k].what == done) break; if (step[k].what == event) network.generate(step[k].type, step[k].server); else network.shift(step[k].link); ++k; #else // random if ((network.events < min_events && queued_msgs < 4 * network.number_of_servers() && random(2 * network.number_of_servers()) == 0) || queued_msgs == 0) { // Generate a new spontaneous message. #ifdef USEDESTRUCT int const per[4] = { 30, 10, 30, 30 }; // Percentages chance for msg PART, JOIN, CREATE and DESTRUCT. #else int const per[3] = { 43, 14, 43 }; // Percentages chance for msg PART, JOIN and CREATE. #endif bool success; do { MsgType mt; int what = random(100); if (what < per[0]) mt = part; else if (what < per[1] + per[0]) mt = join; else if (what < per[2] + per[1] + per[0]) mt = create; #ifdef USEDESTRUCT else mt = destruct; #endif int server = random(network.number_of_servers()); success = network.generate(mt, server); } while(!success); ++network.events; } else { // Process a queued msg. bool success; do { int link = random(network.number_of_links()); success = network.shift(link); } while(!success); } #endif // RANDOM ++network.actions; } return 0; }