I2P Address: [http://git.idk.i2p]

Skip to content
Snippets Groups Projects
i2psam.cpp 22.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • // Copyright (c) 2013-2014 The Anoncoin Core developers
    
    giv's avatar
    giv committed
    // Copyright (c) 2012-2013 giv
    // Distributed under the MIT/X11 software license, see the accompanying
    // file COPYING or http://www.opensource.org/licenses/mit-license.php.
    //--------------------------------------------------------------------------------------------------
    
    
    // Many builder specific things set in the config file, don't forget to include it this way in your source files.
    #if defined(HAVE_CONFIG_H)
    #include "config/anoncoin-config.h"
    #endif
    
    
    giv's avatar
    giv committed
    #include "i2psam.h"
    
    #include <iostream>
    #include <stdio.h>
    #include <string.h>         // for memset
    #include <stdlib.h>
    #include <time.h>
    #include <stdarg.h>
    
    #ifndef WIN32
    #include <errno.h>
    
    #include <unistd.h>
    
    giv's avatar
    giv committed
    #endif
    
    #ifndef WIN32
    #define closesocket         close
    #endif
    
    #define SAM_BUFSIZE         65536
    #define I2P_DESTINATION_SIZE 516
    
    namespace SAM
    {
    
    static void print_error(const std::string& err)
    {
    
        // ToDo: GR Note: We've got to get these std:cout errors and messages off the console, and into the coin logfile, however
        //       my first attempt to do that, ended in failure.  Primarily because adding 'util.h' to this source causes all kinds of compile time errors
        //       due to it's inclusion of 'compat.h' mostly, where socket stuff is also defined.  All that lost hair, over just trying to get
        //       the LogPrintf function here in this submodule.  Added to the todo list...
        //
        //       After thinking about this some, the i2pwrapper is isolated from all this socket stuff, perhaps we could report to the log file from there
        //       if we simple pipe these messages somewhere else that std::cout, and have the i2pwrapper pick them up and report them for us.  Just an idea.
    
    giv's avatar
    giv committed
    #ifdef WIN32
        std::cout << err << "(" << WSAGetLastError() << ")" << std::endl;
    #else
        std::cout << err << "(" << errno << ")" << std::endl;
    #endif
    }
    
    #ifdef WIN32
    int Socket::instances_ = 0;
    
    void Socket::initWSA()
    {
        WSADATA wsadata;
        int ret = WSAStartup(MAKEWORD(2,2), &wsadata);
        if (ret != NO_ERROR)
            print_error("Failed to initialize winsock library");
    }
    
    void Socket::freeWSA()
    {
        WSACleanup();
    }
    #endif
    
    Socket::Socket(const std::string& SAMHost, uint16_t SAMPort, const std::string& minVer, const std::string &maxVer)
        : socket_(SAM_INVALID_SOCKET), SAMHost_(SAMHost), SAMPort_(SAMPort), minVer_(minVer), maxVer_(maxVer)
    {
    #ifdef WIN32
        if (instances_++ == 0)
            initWSA();
    #endif
    
        memset(&servAddr_, 0, sizeof(servAddr_));
    
        servAddr_.sin_family = AF_INET;
        servAddr_.sin_addr.s_addr = inet_addr(SAMHost.c_str());
        servAddr_.sin_port = htons(SAMPort);
    
        bootstrapI2P();
    
    giv's avatar
    giv committed
    }
    
    Socket::Socket(const sockaddr_in& addr, const std::string &minVer, const std::string& maxVer)
        : socket_(SAM_INVALID_SOCKET), servAddr_(addr), minVer_(minVer), maxVer_(maxVer)
    {
    #ifdef WIN32
        if (instances_++ == 0)
            initWSA();
    #endif
    
        bootstrapI2P();
    
    giv's avatar
    giv committed
    }
    
    Socket::Socket(const Socket& rhs)
        : socket_(SAM_INVALID_SOCKET), servAddr_(rhs.servAddr_), minVer_(rhs.minVer_), maxVer_(rhs.maxVer_)
    {
    #ifdef WIN32
        if (instances_++ == 0)
            initWSA();
    #endif
    
        bootstrapI2P();
    
    giv's avatar
    giv committed
    }
    
    Socket::~Socket()
    {
        close();
    
    #ifdef WIN32
        if (--instances_ == 0)
            freeWSA();
    #endif
    }
    
    
    void Socket::bootstrapI2P()
    {
        init();
        if (isOk())
            handshake();
    }
    
    
    giv's avatar
    giv committed
    void Socket::init()
    {
        socket_ = socket(AF_INET, SOCK_STREAM, 0);
        if (socket_ == SAM_INVALID_SOCKET)
        {
            print_error("Failed to create socket");
            return;
        }
    
        if (connect(socket_, (const sockaddr*)&servAddr_, sizeof(servAddr_)) == SAM_SOCKET_ERROR)
        {
            close();
            print_error("Failed to connect to SAM");
            return;
        }
    }
    
    SOCKET Socket::release()
    {
        SOCKET temp = socket_;
        socket_ = SAM_INVALID_SOCKET;
        return temp;
    }
    
    
    // ToDo: If the handshake works, I'm thinking we must be talking to a valid I2P router.  If that's the case, we should report the version response in the logfile
    //       ...and make sure our coin software understands that is also the case...  GR Note: Haven't looked into it.
    
    giv's avatar
    giv committed
    void Socket::handshake()
    {
        this->write(Message::hello(minVer_, maxVer_));
        const std::string answer = this->read();
        const Message::eStatus answerStatus = Message::checkAnswer(answer);
        if (answerStatus == Message::OK)
            version_ = Message::getValue(answer, "VERSION");
        else
            print_error("Handshake failed");
    }
    
    void Socket::write(const std::string& msg)
    {
        if (!isOk())
        {
            print_error("Failed to send data because socket is closed");
            return;
        }
        std::cout << "Send: " << msg << std::endl;
        ssize_t sentBytes = send(socket_, msg.c_str(), msg.length(), 0);
        if (sentBytes == SAM_SOCKET_ERROR)
        {
            close();
            print_error("Failed to send data");
            return;
        }
        if (sentBytes == 0)
        {
            close();
            print_error("Socket was closed");
            return;
        }
    }
    
    std::string Socket::read()
    {
        if (!isOk())
        {
            print_error("Failed to read data because socket is closed");
            return std::string();
        }
        char buffer[SAM_BUFSIZE];
        memset(buffer, 0, SAM_BUFSIZE);
        ssize_t recievedBytes = recv(socket_, buffer, SAM_BUFSIZE, 0);
        if (recievedBytes == SAM_SOCKET_ERROR)
        {
            close();
    
    giv's avatar
    giv committed
            print_error("Failed to receive data");
    
    giv's avatar
    giv committed
            return std::string();
        }
        if (recievedBytes == 0)
        {
            close();
            print_error("Socket was closed");
        }
        std::cout << "Reply: " << buffer << std::endl;
        return std::string(buffer);
    }
    
    void Socket::close()
    {
        if (socket_ != SAM_INVALID_SOCKET)
            ::closesocket(socket_);
        socket_ = SAM_INVALID_SOCKET;
    }
    
    bool Socket::isOk() const
    {
        return socket_ != SAM_INVALID_SOCKET;
    }
    
    const std::string& Socket::getHost() const
    {
        return SAMHost_;
    }
    
    uint16_t Socket::getPort() const
    {
        return SAMPort_;
    }
    
    const std::string& Socket::getVersion() const
    {
        return version_;
    }
    
    const std::string& Socket::getMinVer() const
    {
        return minVer_;
    }
    
    const std::string& Socket::getMaxVer() const
    {
        return maxVer_;
    }
    
    const sockaddr_in& Socket::getAddress() const
    {
        return servAddr_;
    }
    
    
    //--------------------------------------------------------------------------------------------------
    
    
    StreamSession::StreamSession(
    
    giv's avatar
    giv committed
            const std::string& nickname,
            const std::string& SAMHost     /*= SAM_DEFAULT_ADDRESS*/,
                  uint16_t     SAMPort     /*= SAM_DEFAULT_PORT*/,
            const std::string& destination /*= SAM_GENERATE_MY_DESTINATION*/,
            const std::string& i2pOptions  /*= SAM_DEFAULT_I2P_OPTIONS*/,
            const std::string& minVer      /*= SAM_DEFAULT_MIN_VER*/,
            const std::string& maxVer      /*= SAM_DEFAULT_MAX_VER*/)
    
    giv's avatar
    giv committed
        : socket_(SAMHost, SAMPort, minVer, maxVer)
    
    giv's avatar
    giv committed
        , nickname_(nickname)
        , sessionID_(generateSessionID())
        , i2pOptions_(i2pOptions)
        , isSick_(false)
    {
        myDestination_ = createStreamSession(destination);
    
    giv's avatar
    giv committed
        std::cout << "Created a brand new SAM session (" << sessionID_ << ")" << std::endl;
    
    giv's avatar
    giv committed
    }
    
    
    StreamSession::StreamSession(StreamSession& rhs)
    
    giv's avatar
    giv committed
        : socket_(rhs.socket_)
        , nickname_(rhs.nickname_)
        , sessionID_(generateSessionID())
        , myDestination_(rhs.myDestination_)
        , i2pOptions_(rhs.i2pOptions_)
        , isSick_(false)
    {
        rhs.fallSick();
    
    giv's avatar
    giv committed
        rhs.socket_.close();
    
    giv's avatar
    giv committed
        (void)createStreamSession(myDestination_.priv);
    
        for(ForwardedStreamsContainer::const_iterator it = rhs.forwardedStreams_.begin(), end = rhs.forwardedStreams_.end(); it != end; ++it)
            forward(it->host, it->port, it->silent);
    
    giv's avatar
    giv committed
    
        std::cout << "Created a new SAM session (" << sessionID_ << ")  from another (" << rhs.sessionID_ << ")" << std::endl;
    
    giv's avatar
    giv committed
    }
    
    giv's avatar
    giv committed
    
    
    StreamSession::~StreamSession()
    
    giv's avatar
    giv committed
    {
        stopForwardingAll();
    
    giv's avatar
    giv committed
        std::cout << "Closing SAM session (" << sessionID_ << ") ..." << std::endl;
    
    giv's avatar
    giv committed
    }
    
    /*static*/
    
    std::string StreamSession::generateSessionID()
    
    giv's avatar
    giv committed
    {
        static const int minSessionIDLength = 5;
        static const int maxSessionIDLength = 9;
        static const char sessionIDAlphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        int length = minSessionIDLength - 1;
        std::string result;
    
        srand(time(NULL));
    
        while(length < minSessionIDLength)
            length = rand() % maxSessionIDLength;
    
        while (length-- > 0)
            result += sessionIDAlphabet[rand() % (sizeof(sessionIDAlphabet)-1)];
    
        return result;
    }
    
    
    RequestResult<std::auto_ptr<Socket> > StreamSession::accept(bool silent)
    
    giv's avatar
    giv committed
    {
        typedef RequestResult<std::auto_ptr<Socket> > ResultType;
    
    giv's avatar
    giv committed
    
    
    giv's avatar
    giv committed
        std::auto_ptr<Socket> streamSocket(new Socket(socket_));
    
    giv's avatar
    giv committed
        const Message::eStatus status = accept(*streamSocket, sessionID_, silent);
        switch(status)
        {
        case Message::OK:
            return RequestResult<std::auto_ptr<Socket> >(streamSocket);
        case Message::EMPTY_ANSWER:
        case Message::CLOSED_SOCKET:
        case Message::INVALID_ID:
    
    giv's avatar
    giv committed
        case Message::I2P_ERROR:
    
    giv's avatar
    giv committed
            fallSick();
            break;
        default:
            break;
        }
        return ResultType();
    }
    
    
    RequestResult<std::auto_ptr<Socket> > StreamSession::connect(const std::string& destination, bool silent)
    
    giv's avatar
    giv committed
    {
    
    giv's avatar
    giv committed
        typedef RequestResult<std::auto_ptr<Socket> > ResultType;
    
    
    giv's avatar
    giv committed
        std::auto_ptr<Socket> streamSocket(new Socket(socket_));
    
    giv's avatar
    giv committed
        const Message::eStatus status = connect(*streamSocket, sessionID_, destination, silent);
        switch(status)
        {
        case Message::OK:
            return ResultType(streamSocket);
        case Message::EMPTY_ANSWER:
        case Message::CLOSED_SOCKET:
        case Message::INVALID_ID:
    
    giv's avatar
    giv committed
        case Message::I2P_ERROR:
    
    giv's avatar
    giv committed
            fallSick();
            break;
        default:
            break;
        }
        return ResultType();
    }
    
    
    RequestResult<void> StreamSession::forward(const std::string& host, uint16_t port, bool silent)
    
    giv's avatar
    giv committed
    {
        typedef RequestResult<void> ResultType;
    
    
    giv's avatar
    giv committed
        std::auto_ptr<Socket> newSocket(new Socket(socket_));
    
    giv's avatar
    giv committed
        const Message::eStatus status = forward(*newSocket, sessionID_, host, port, silent);
        switch(status)
        {
        case Message::OK:
            forwardedStreams_.push_back(ForwardedStream(newSocket.get(), host, port, silent));
            newSocket.release();    // release after successful push_back only
            return ResultType(true);
        case Message::EMPTY_ANSWER:
        case Message::CLOSED_SOCKET:
        case Message::INVALID_ID:
    
    giv's avatar
    giv committed
        case Message::I2P_ERROR:
    
    giv's avatar
    giv committed
            fallSick();
            break;
        default:
            break;
        }
        return ResultType();
    
    giv's avatar
    giv committed
    }
    
    
    RequestResult<const std::string> StreamSession::namingLookup(const std::string& name) const
    
    giv's avatar
    giv committed
    {
    
    giv's avatar
    giv committed
        typedef RequestResult<const std::string> ResultType;
        typedef Message::Answer<const std::string> AnswerType;
    
    
    giv's avatar
    giv committed
        std::auto_ptr<Socket> newSocket(new Socket(socket_));
    
    giv's avatar
    giv committed
        const AnswerType answer = namingLookup(*newSocket, name);
        switch(answer.status)
        {
        case Message::OK:
            return ResultType(answer.value);
        case Message::EMPTY_ANSWER:
        case Message::CLOSED_SOCKET:
            fallSick();
            break;
        default:
            break;
        }
        return ResultType();
    }
    
    
    RequestResult<const FullDestination> StreamSession::destGenerate() const
    
    giv's avatar
    giv committed
    {
        typedef RequestResult<const FullDestination> ResultType;
        typedef Message::Answer<const FullDestination> AnswerType;
    
    
    giv's avatar
    giv committed
        std::auto_ptr<Socket> newSocket(new Socket(socket_));
    
    giv's avatar
    giv committed
        const AnswerType answer = destGenerate(*newSocket);
        switch(answer.status)
        {
        case Message::OK:
            return ResultType(answer.value);
        case Message::EMPTY_ANSWER:
        case Message::CLOSED_SOCKET:
            fallSick();
            break;
        default:
            break;
        }
        return ResultType();
    }
    
    
    FullDestination StreamSession::createStreamSession(const std::string& destination)
    
    giv's avatar
    giv committed
    {
        typedef Message::Answer<const std::string> AnswerType;
    
    
    giv's avatar
    giv committed
        const AnswerType answer = createStreamSession(socket_, sessionID_, nickname_, destination, i2pOptions_);
    
    giv's avatar
    giv committed
        if (answer.status != Message::OK)
        {
            fallSick();
    
    giv's avatar
    giv committed
            return FullDestination();
    
    giv's avatar
    giv committed
        }
        return FullDestination(answer.value.substr(0, I2P_DESTINATION_SIZE), answer.value, (destination == SAM_GENERATE_MY_DESTINATION));
    }
    
    
    void StreamSession::fallSick() const
    
    giv's avatar
    giv committed
    {
        isSick_ = true;
    }
    
    
    void StreamSession::stopForwarding(const std::string& host, uint16_t port)
    
    giv's avatar
    giv committed
    {
        for (ForwardedStreamsContainer::iterator it = forwardedStreams_.begin(); it != forwardedStreams_.end(); )
        {
            if (it->port == port && it->host == host)
            {
                delete (it->socket);
                it = forwardedStreams_.erase(it);
            }
            else
                ++it;
        }
    }
    
    
    void StreamSession::stopForwardingAll()
    
    giv's avatar
    giv committed
    {
    
    giv's avatar
    giv committed
        for (ForwardedStreamsContainer::iterator it = forwardedStreams_.begin(); it != forwardedStreams_.end(); ++it)
    
    giv's avatar
    giv committed
            delete (it->socket);
    
    giv's avatar
    giv committed
        forwardedStreams_.clear();
    
    giv's avatar
    giv committed
    }
    
    
    giv's avatar
    giv committed
    /*static*/
    
    Message::Answer<const std::string> StreamSession::rawRequest(Socket& socket, const std::string& requestStr)
    
    giv's avatar
    giv committed
    {
    
        typedef Message::Answer<const std::string> AnswerType;
    
    
    giv's avatar
    giv committed
        if (!socket.isOk())
    
            return AnswerType(Message::CLOSED_SOCKET);
    
    giv's avatar
    giv committed
        socket.write(requestStr);
        const std::string answer = socket.read();
        const Message::eStatus status = Message::checkAnswer(answer);
    
        return AnswerType(status, answer);
    
    giv's avatar
    giv committed
    }
    
    /*static*/
    
    Message::Answer<const std::string> StreamSession::request(Socket& socket, const std::string& requestStr, const std::string& keyOnSuccess)
    
    giv's avatar
    giv committed
    {
    
        typedef Message::Answer<const std::string> AnswerType;
    
        const AnswerType answer = rawRequest(socket, requestStr);
    
    giv's avatar
    giv committed
        return (answer.status == Message::OK) ?
    
                    AnswerType(answer.status, Message::getValue(answer.value, keyOnSuccess)) :
    
    giv's avatar
    giv committed
                    answer;
    
    giv's avatar
    giv committed
    }
    
    
    giv's avatar
    giv committed
    /*static*/
    
    Message::eStatus StreamSession::request(Socket& socket, const std::string& requestStr)
    
    giv's avatar
    giv committed
    {
        return rawRequest(socket, requestStr).status;
    }
    
    /*static*/
    
    Message::Answer<const std::string> StreamSession::createStreamSession(Socket& socket, const std::string& sessionID, const std::string& nickname, const std::string& destination, const std::string& options)
    
    giv's avatar
    giv committed
    {
    
    giv's avatar
    giv committed
        return request(socket, Message::sessionCreate(Message::sssStream, sessionID, nickname, destination, options), "DESTINATION");
    
    giv's avatar
    giv committed
    }
    
    
    giv's avatar
    giv committed
    /*static*/
    
    Message::Answer<const std::string> StreamSession::namingLookup(Socket& socket, const std::string& name)
    
    giv's avatar
    giv committed
    {
        return request(socket, Message::namingLookup(name), "VALUE");
    }
    
    
    giv's avatar
    giv committed
    /*static*/
    
    Message::Answer<const FullDestination> StreamSession::destGenerate(Socket& socket)
    
    giv's avatar
    giv committed
    {
    
    giv's avatar
    giv committed
    // while answer for a DEST GENERATE request doesn't contain a "RESULT" field we parse it manually
    
    giv's avatar
    giv committed
        typedef Message::Answer<const FullDestination> ResultType;
    
    giv's avatar
    giv committed
    
        if (!socket.isOk())
    
    giv's avatar
    giv committed
            return ResultType(Message::CLOSED_SOCKET, FullDestination());
    
    giv's avatar
    giv committed
        socket.write(Message::destGenerate());
        const std::string answer = socket.read();
        const std::string pub = Message::getValue(answer, "PUB");
        const std::string priv = Message::getValue(answer, "PRIV");
    
    giv's avatar
    giv committed
        return (!pub.empty() && !priv.empty()) ? ResultType(Message::OK, FullDestination(pub, priv, /*isGenerated*/ true)) : ResultType(Message::EMPTY_ANSWER, FullDestination());
    
    giv's avatar
    giv committed
    }
    
    
    giv's avatar
    giv committed
    /*static*/
    
    Message::eStatus StreamSession::accept(Socket& socket, const std::string& sessionID, bool silent)
    
    giv's avatar
    giv committed
    {
    
    giv's avatar
    giv committed
        return request(socket, Message::streamAccept(sessionID, silent));
    
    giv's avatar
    giv committed
    }
    
    
    giv's avatar
    giv committed
    /*static*/
    
    Message::eStatus StreamSession::connect(Socket& socket, const std::string& sessionID, const std::string& destination, bool silent)
    
    giv's avatar
    giv committed
    {
    
    giv's avatar
    giv committed
        return request(socket, Message::streamConnect(sessionID, destination, silent));
    
    giv's avatar
    giv committed
    }
    
    
    giv's avatar
    giv committed
    /*static*/
    
    Message::eStatus StreamSession::forward(Socket& socket, const std::string& sessionID, const std::string& host, uint16_t port, bool silent)
    
    giv's avatar
    giv committed
    {
    
    giv's avatar
    giv committed
        return request(socket, Message::streamForward(sessionID, host, port, silent));
    
    giv's avatar
    giv committed
    }
    
    
    const std::string& StreamSession::getNickname() const
    
    giv's avatar
    giv committed
    {
    
    giv's avatar
    giv committed
        return nickname_;
    }
    
    
    const std::string& StreamSession::getSessionID() const
    
    giv's avatar
    giv committed
    {
        return sessionID_;
    }
    
    giv's avatar
    giv committed
    
    
    const std::string& StreamSession::getOptions() const
    
    giv's avatar
    giv committed
    {
        return i2pOptions_;
    
    giv's avatar
    giv committed
    }
    
    
    const FullDestination& StreamSession::getMyDestination() const
    
    giv's avatar
    giv committed
    {
    
    giv's avatar
    giv committed
        return myDestination_;
    
    giv's avatar
    giv committed
    }
    
    
    bool StreamSession::isSick() const
    
    giv's avatar
    giv committed
    {
    
    giv's avatar
    giv committed
        return isSick_;
    
    giv's avatar
    giv committed
    }
    
    
    const sockaddr_in& StreamSession::getSAMAddress() const
    
    giv's avatar
    giv committed
    {
    
    giv's avatar
    giv committed
        return socket_.getAddress();
    
    giv's avatar
    giv committed
    }
    
    giv's avatar
    giv committed
    
    
    const std::string& StreamSession::getSAMHost() const
    
    giv's avatar
    giv committed
    {
    
    giv's avatar
    giv committed
        return socket_.getHost();
    
    giv's avatar
    giv committed
    }
    
    
    uint16_t StreamSession::getSAMPort() const
    
    giv's avatar
    giv committed
    {
    
    giv's avatar
    giv committed
        return socket_.getPort();
    
    giv's avatar
    giv committed
    }
    
    
    const std::string& StreamSession::getSAMMinVer() const
    
    giv's avatar
    giv committed
    {
    
    giv's avatar
    giv committed
        return socket_.getMinVer();
    
    giv's avatar
    giv committed
    }
    
    
    const std::string& StreamSession::getSAMMaxVer() const
    
    giv's avatar
    giv committed
    {
    
    giv's avatar
    giv committed
        return socket_.getMaxVer();
    
    giv's avatar
    giv committed
    }
    
    
    const std::string& StreamSession::getSAMVersion() const
    
    giv's avatar
    giv committed
    {
    
    giv's avatar
    giv committed
        return socket_.getVersion();
    
    giv's avatar
    giv committed
    }
    
    
    giv's avatar
    giv committed
    //--------------------------------------------------------------------------------------------------
    
    
    
    giv's avatar
    giv committed
    std::string Message::createSAMRequest(const char* format, ...)
    {
        char buffer[SAM_BUFSIZE];
    
        // ToDo: GR note: Creating a 65K byte buffer on the stack, and then wasting the time to zero it out
        //                before using it.  Just to send a 30 byte string?, seems really wasteful to me, time more than storage, many mSec...
    
    giv's avatar
    giv committed
        memset(buffer, 0, SAM_BUFSIZE);
    
        va_list args;
        va_start (args, format);
    
    giv's avatar
    giv committed
        const int sizeToSend = vsnprintf(buffer, SAM_BUFSIZE, format, args);
    
    giv's avatar
    giv committed
        va_end(args);
    
        if (sizeToSend < 0)
        {
            print_error("Failed to format message");
            return std::string();
        }
        return std::string(buffer);
    }
    
    std::string Message::hello(const std::string &minVer, const std::string &maxVer)
    {
    ///////////////////////////////////////////////////////////
    //
    //    ->  HELLO VERSION
    //              MIN=$min
    //              MAX=$max
    //
    //    <-  HELLO REPLY
    //              RESULT=OK
    //              VERSION=$version
    //
    ///////////////////////////////////////////////////////////
    
        static const char* helloFormat = "HELLO VERSION MIN=%s MAX=%s\n";
        return createSAMRequest(helloFormat, minVer.c_str(), maxVer.c_str());
    }
    
    std::string Message::sessionCreate(SessionStyle style, const std::string& sessionID, const std::string& nickname, const std::string& destination /*= SAM_GENERATE_MY_DESTINATION*/, const std::string& options /*= ""*/)
    {
    ///////////////////////////////////////////////////////////
    //
    //    ->  SESSION CREATE
    //              STYLE={STREAM,DATAGRAM,RAW}
    //              ID={$nickname}
    //              DESTINATION={$private_destination_key,TRANSIENT}
    //              [option=value]*
    //
    //    <-  SESSION STATUS
    //              RESULT=OK
    //              DESTINATION=$private_destination_key
    //
    ///////////////////////////////////////////////////////////
    
        std::string sessionStyle;
        switch(style)
        {
        case sssStream:   sessionStyle = "STREAM";   break;
        case sssDatagram: sessionStyle = "DATAGRAM"; break;
        case sssRaw:      sessionStyle = "RAW";      break;
        }
    
        static const char* sessionCreateFormat = "SESSION CREATE STYLE=%s ID=%s DESTINATION=%s inbound.nickname=%s %s\n";  // we add inbound.nickname option
        return createSAMRequest(sessionCreateFormat, sessionStyle.c_str(), sessionID.c_str(), destination.c_str(), nickname.c_str(), options.c_str());
    }
    
    std::string Message::streamAccept(const std::string& sessionID, bool silent /*= false*/)
    {
    ///////////////////////////////////////////////////////////
    //
    //    ->  STREAM ACCEPT
    //             ID={$nickname}
    //             [SILENT={true,false}]
    //
    //    <-  STREAM STATUS
    //             RESULT=$result
    //             [MESSAGE=...]
    //
    ///////////////////////////////////////////////////////////
    
        static const char* streamAcceptFormat = "STREAM ACCEPT ID=%s SILENT=%s\n";
        return createSAMRequest(streamAcceptFormat, sessionID.c_str(), silent ? "true" : "false");
    }
    
    std::string Message::streamConnect(const std::string& sessionID, const std::string& destination, bool silent /*= false*/)
    {
    ///////////////////////////////////////////////////////////
    //
    //    ->  STREAM CONNECT
    //             ID={$nickname}
    //             DESTINATION=$peer_public_base64_key
    //             [SILENT={true,false}]
    //
    //    <-  STREAM STATUS
    //             RESULT=$result
    //             [MESSAGE=...]
    //
    ///////////////////////////////////////////////////////////
    
        static const char* streamConnectFormat = "STREAM CONNECT ID=%s DESTINATION=%s SILENT=%s\n";
        return createSAMRequest(streamConnectFormat, sessionID.c_str(), destination.c_str(), silent ? "true" : "false");
    }
    
    std::string Message::streamForward(const std::string& sessionID, const std::string& host, uint16_t port, bool silent /*= false*/)
    {
    ///////////////////////////////////////////////////////////
    //
    //    ->  STREAM FORWARD
    //             ID={$nickname}
    //             PORT={$port}
    //             [HOST={$host}]
    //             [SILENT={true,false}]
    //
    //    <-  STREAM STATUS
    //             RESULT=$result
    //             [MESSAGE=...]
    //
    ///////////////////////////////////////////////////////////
        static const char* streamForwardFormat = "STREAM FORWARD ID=%s PORT=%u HOST=%s SILENT=%s\n";
        return createSAMRequest(streamForwardFormat, sessionID.c_str(), (unsigned)port, host.c_str(), silent ? "true" : "false");
    }
    
    std::string Message::namingLookup(const std::string& name)
    {
    ///////////////////////////////////////////////////////////
    //
    //    -> NAMING LOOKUP
    //            NAME=$name
    //
    //    <- NAMING REPLY
    //            RESULT=OK
    //            NAME=$name
    //            VALUE=$base64key
    //
    ///////////////////////////////////////////////////////////
    
        static const char* namingLookupFormat = "NAMING LOOKUP NAME=%s\n";
        return createSAMRequest(namingLookupFormat, name.c_str());
    }
    
    std::string Message::destGenerate()
    {
    ///////////////////////////////////////////////////////////
    //
    //    -> DEST GENERATE
    //
    //    <- DEST REPLY
    //            PUB=$pubkey
    //            PRIV=$privkey
    //
    ///////////////////////////////////////////////////////////
    
        static const char* destGenerateFormat = "DEST GENERATE\n";
        return createSAMRequest(destGenerateFormat);
    }
    
    #define SAM_MAKESTRING(X) SAM_MAKESTRING2(X)
    #define SAM_MAKESTRING2(X) #X
    
    #define SAM_CHECK_RESULT(value) \
        if (result == SAM_MAKESTRING(value)) return value
    
    Message::eStatus Message::checkAnswer(const std::string& answer)
    {
        if (answer.empty())
            return EMPTY_ANSWER;
    
        const std::string result = getValue(answer, "RESULT");
    
        SAM_CHECK_RESULT(OK);
        SAM_CHECK_RESULT(DUPLICATED_DEST);
        SAM_CHECK_RESULT(DUPLICATED_ID);
        SAM_CHECK_RESULT(I2P_ERROR);
        SAM_CHECK_RESULT(INVALID_ID);
        SAM_CHECK_RESULT(INVALID_KEY);
        SAM_CHECK_RESULT(CANT_REACH_PEER);
        SAM_CHECK_RESULT(TIMEOUT);
        SAM_CHECK_RESULT(NOVERSION);
        SAM_CHECK_RESULT(KEY_NOT_FOUND);
        SAM_CHECK_RESULT(PEER_NOT_FOUND);
        SAM_CHECK_RESULT(ALREADY_ACCEPTING);
    
        return CANNOT_PARSE_ERROR;
    }
    
    #undef SAM_CHECK_RESULT
    #undef SAM_MAKESTRING2
    #undef SAM_MAKESTRING
    
    std::string Message::getValue(const std::string& answer, const std::string& key)
    {
        if (key.empty())
            return std::string();
    
        const std::string keyPattern = key + "=";
        size_t valueStart = answer.find(keyPattern);
        if (valueStart == std::string::npos)
            return std::string();
    
        valueStart += keyPattern.length();
        size_t valueEnd = answer.find_first_of(' ', valueStart);
        if (valueEnd == std::string::npos)
            valueEnd = answer.find_first_of('\n', valueStart);
        return answer.substr(valueStart, valueEnd - valueStart);
    }
    
    
    } // namespace SAM