CHASM

Crytpograph-Hash-Algorithm-Secured Mirroring

The Refractions of Design: Part IV

My original intent for this thread happened to be discussing the iterations of my design, so this is how I will conclude.

Only having a working knowledge of C++ definitely added to my inability to settle on a proper design -- or maybe I should say my lack of knowledge in object-oriented and structured programming. The initial design was completely imperative in style, with a lot of shared state and an inadequate use of classes. There was a plan in place and I knew where I wanted to take it, but looking back it was not the best approach for the problem.

For example, the hello message is the first message sent or received in the protocol, so I had a function:

void v0 :: upstream :: priv :: recv_hello(const boost::system::error_code& ec,
                                          const std::size_t bytes)
{
    ...
    // setup recv_msg for hello
    m_recv_msg.reset(new v0::msg_manifest());
    boost::shared_ptr<v0::msg_manifest> msg =
               boost::dynamic_pointer_cast<msg_manifest>(m_recv_msg);
    ...
}

There were similar functions for every message in the protocol, all shared a number of variables that were data members of the upstream class. Two were message variables descending from an abstract base class developed for messages; one was for sending and one was for receiving. The two statements shown above characterize their usage: a pointer to the base class is reset and then dynamically cast into the type of message expected. Every function followed a basic pattern: 1) setup the buffer, and 2) do a read or write on the socket -- if anyone has looked at the code, there is a fatal flaw in the way I read in the msg; however, I feel that could have easily been corrected.

Another major difference between the designs, is that I originally did not expect to know the read or write size, so I used a message parser class. Boost.Tribool came in handy for this purpose and the code had the pattern:

void v0 :: upstream :: priv :: recv_hello(const boost::system::error_code& ec,
                                          const std::size_t bytes)
{
    ...
    boost::tribool result(m_msg_parser.parse_hello(bytes, msg));
    if (result)
    {
        // the next state
        send_manifest_req_to_the_oracle(boost::system::error_code());
    }
    else if (!result)
    {
        m_socket.close();
        start_accept();
    }
    else
    {
        m_socket.async_read_some(boost::asio::buffer(m_msg_parser.buffer()),
                                 boost::bind(&v0::upstream::priv::recv_hello,
                                             shared_from_this(),
                                             boost::asio::placeholders::error,
                                             boost::asio::placeholders::bytes_transferred));
    }
    ...
}

The message parser class was responsible for reading in the raw data using a stateful, somewhat optimistic, algorithm for the specific message that was being received. It would then set the specific fields for the given message type to be used by subsequent functions in the upstream class.

The aforementioned design is what I was referring to as the jumping-off point in Part I. This code was all scrapped but was the genesis for the current design briefly discussed in Part III. A more thorough explanation of the current design can be found in doc/chasm-p2p-design.rst within the repository.

In conclusion, I can not say for a fact that my initial design was good or bad, it happened to not be right for the project. The design process has been an enlightening experience; it has provided an insightful look into the process that I was severely lacking. The open-source development model is such a great way to learn, I can not even imagine ever working on closed-source software again.

-mfm

Copyright © 2010 Robert Escriva ¦ Powered by Firmant