Saturday 17 November 2012

Success with the new design! And RemoteSessionHandle

It's still early days, there's still a lot to polish, but it connects and disconnects successfully.

On this post, I'll introduce RemoteSessionHandle, the class responsible for implementing RAII on the SSH session remote "resources", i.e., the SSH session on the remote host.

Once again, the interface is simple:

class RemoteSessionHandle
{
public:
    RemoteSessionHandle(boost::function<void(std::string)>& pReportStatus,  
        boost::asio::ip::tcp::socket& pSock, SessionHandle& pSessionHandle);
    ~RemoteSessionHandle();
 

    void CloseSession();
 

    // WE MAY ADD MOVE IN THE FUTURE, BUT NOT COPY
    RemoteSessionHandle(RemoteSessionHandle&&) = delete;
    RemoteSessionHandle& operator=(RemoteSessionHandle&&) = delete;
    RemoteSessionHandle(RemoteSessionHandle const&) = delete;
    RemoteSessionHandle& operator=(RemoteSessionHandle const&) = delete;
private:
    utils::TaskState DoHandshake();
    utils::TaskState DoCloseSession();
 

    utils::CleanupState cleanupState;
    boost::function<void(std::string)>& reportStatus;
    boost::asio::ip::tcp::socket& sock;
    SessionHandle& sessionHandle;
};


Ctor, dtor, and a method to explicitly close the session on the remote host. The private methods are the handlers for the loop functions.

We're storing references to the report callback (to let the UI know what we're doing), to the socket (used in the ctor to create the session), and to an instance of SessionHandle (because the libssh2 calls need the LIBSSH2_SESSION* stored in SessionHandle).

RemoteSessionHandle::RemoteSessionHandle( 
    boost::function<void(std::string)>& pReportStatus,
    boost::asio::ip::tcp::socket& pSock, SessionHandle& pSessionHandle) :
    cleanupState(utils::CleanupState::NoCleanup), 
    reportStatus(pReportStatus), sock(pSock), sessionHandle(pSessionHandle)
{
    if (!reportStatus.empty())
        reportStatus("SSH Handshake");
 

    io_service& ios = sock.get_io_service();
    ios.reset();
    sock.async_read_some(boost::asio::null_buffers(), bind(AsyncLoopSocket,
        protect(bind(&RemoteSessionHandle::DoHandshake, this)), 
        boost::ref(sock), true));
    ios.run();
}


The ctor contains the SSH handshake. We get the io_service from the socket, and use it to drive our loop.

RemoteSessionHandle::~RemoteSessionHandle()
{
    if (!reportStatus.empty())
        reportStatus("Cleanup: SSH remote session.");
 

    // WE'LL ONLY TRY TO CLEANUP IF NO ATTEMPT HAS BEEN MADE YET
    if (cleanupState == utils::CleanupState::NoCleanup)
    {
        try
        {
            CloseSession();
        }
        catch (...)
        { }
    }
}


The dtor's behaviour is similar to SessionHandle's dtor. We only perform the cleanup if no attempt has been previously made.

void RemoteSessionHandle::CloseSession()
{
    io_service ios;
    deadline_timer dt(ios);
 

    dt.expires_from_now(milliseconds(10));
    dt.async_wait(bind(AsyncLoopTimer, 
        protect(bind(&RemoteSessionHandle::DoCloseSession, this)), 
        boost::ref(dt), 10));
 

    cleanupState = utils::CleanupState::CleanupInProgress;
    ios.run();
 

    cleanupState = utils::CleanupState::CleanupDone;
}


CloseSession() sets up the loop with a timer, and manages the cleanup state.

utils::TaskState RemoteSessionHandle::DoHandshake()
{

    int rc = libssh2_session_handshake(sessionHandle.GetSession(),  
        sock.native_handle());
 

    if (rc == LIBSSH2_ERROR_EAGAIN)
    {
        return utils::TASK_WORKING;
    }
 

    if (rc)
    {
        BOOST_THROW_EXCEPTION(SSHHandshakeError() <<
            ssh_error_string("Error " + lexical_cast<string>(rc) +  
            " during SSH Handshake.") << ssh_error_id(rc));
    }
 

    return utils::TASK_DONE;
}
 

utils::TaskState RemoteSessionHandle::DoCloseSession()
{
    int rc = libssh2_session_disconnect(sessionHandle.GetSession(),  
        "Disconnect SSH session");
 

    if (rc == LIBSSH2_ERROR_EAGAIN)
    {
        return utils::TASK_WORKING;
    }
 

    if (rc)
    {
        BOOST_THROW_EXCEPTION(SSHDisconnectSessionError() <<
            ssh_error_string("Error " + lexical_cast<string>(rc) +  
            " disconnecting SSH session.") << ssh_error_id(rc));
    }
 

    return utils::TASK_DONE;

}


DoHandshake() and DoCloseSession() follow the same pattern. There's not much to add here.

This post is very short on words, mostly just stating the obvious. I believe that's a good thing - it means the class is simple.

The code is here.

No comments:

Post a Comment