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

Skip to content
Snippets Groups Projects
i2psam.cpp 28.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • str4d's avatar
    str4d committed
    // Copyright (c) 2017 The I2P Project
    
    // Copyright (c) 2013-2015 The Anoncoin Core developers
    
    giv's avatar
    giv committed
    // Copyright (c) 2012-2013 giv
    
    str4d's avatar
    str4d committed
    // Distributed under the MIT software license, see the accompanying
    // file LICENSE or http://www.opensource.org/licenses/mit-license.php.
    
    giv's avatar
    giv committed
    //--------------------------------------------------------------------------------------------------
    
    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>
    
    
    // Was 65536, seemed unnecessarily large
    #define SAM_BUFSIZE         4096
    
    giv's avatar
    giv committed
    #define I2P_DESTINATION_SIZE 516
    
    
    // Define this, if you want more of the original standard output diagnostics
    // #define DEBUG_ON_STDOUT
    
    
    giv's avatar
    giv committed
    namespace SAM
    {
    
    static void print_error(const std::string& err)
    {
    
    giv's avatar
    giv committed
    #ifdef WIN32
        std::cout << err << "(" << WSAGetLastError() << ")" << std::endl;
    #else
        std::cout << err << "(" << errno << ")" << std::endl;
    #endif
    
    giv's avatar
    giv committed
    }
    
    #ifdef WIN32
    
    giv's avatar
    giv committed
    
    
    giv's avatar
    giv committed
    {
        WSADATA wsadata;
        int ret = WSAStartup(MAKEWORD(2,2), &wsadata);
        if (ret != NO_ERROR)
            print_error("Failed to initialize winsock library");
    }
    
    
    giv's avatar
    giv committed
    {
        WSACleanup();
    }
    #endif
    
    
    I2pSocket::I2pSocket(const std::string& SAMHost, uint16_t SAMPort)
        : socket_(INVALID_SOCKET), SAMHost_(SAMHost), SAMPort_(SAMPort)
    
    giv's avatar
    giv committed
    {
    #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
    }
    
    
    I2pSocket::I2pSocket(const sockaddr_in& addr)
        : socket_(INVALID_SOCKET), servAddr_(addr)
    
    giv's avatar
    giv committed
    {
    #ifdef WIN32
        if (instances_++ == 0)
            initWSA();
    #endif
    
        bootstrapI2P();
    
    giv's avatar
    giv committed
    }
    
    
    I2pSocket::I2pSocket(const I2pSocket& rhs)
    
        : socket_(INVALID_SOCKET), servAddr_(rhs.servAddr_)
    
    giv's avatar
    giv committed
    {
    #ifdef WIN32
        if (instances_++ == 0)
            initWSA();
    #endif
    
        bootstrapI2P();
    
    giv's avatar
    giv committed
    }
    
    
    giv's avatar
    giv committed
    {
        close();
    
    #ifdef WIN32
        if (--instances_ == 0)
            freeWSA();
    #endif
    }
    
    
    giv's avatar
    giv committed
    {
    
        // Here is where the only real OS socket is called for creation to talk with the router,
        // the value returned is stored in our variable socket_  which holds the connection
        // to our stream for everything else
    
    giv's avatar
    giv committed
        socket_ = socket(AF_INET, SOCK_STREAM, 0);
    
    giv's avatar
    giv committed
        {
            print_error("Failed to create socket");
            return;
        }
    
    
        if (connect(socket_, (const sockaddr*)&servAddr_, sizeof(servAddr_)) == SOCKET_ERROR)
    
    giv's avatar
    giv committed
        {
            close();
            print_error("Failed to connect to SAM");
            return;
        }
    }
    
    
    giv's avatar
    giv committed
    {
        SOCKET temp = socket_;
    
    giv's avatar
    giv committed
        return temp;
    }
    
    
    // If the handshake works, we're talking to a valid I2P router.
    
    giv's avatar
    giv committed
    {
        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 I2pSocket::write(const std::string& msg)
    
    giv's avatar
    giv committed
    {
        if (!isOk())
        {
            print_error("Failed to send data because socket is closed");
            return;
        }
    
    giv's avatar
    giv committed
        std::cout << "Send: " << msg << std::endl;
    
    giv's avatar
    giv committed
        ssize_t sentBytes = send(socket_, msg.c_str(), msg.length(), 0);
    
    giv's avatar
    giv committed
        {
            close();
            print_error("Failed to send data");
            return;
        }
        if (sentBytes == 0)
        {
            close();
    
            print_error("I2pSocket was closed");
    
    giv's avatar
    giv committed
            return;
        }
    }
    
    
    giv's avatar
    giv committed
    {
        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);
    
    giv's avatar
    giv committed
        {
            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("I2pSocket was closed");
    
    giv's avatar
    giv committed
        }
    
    giv's avatar
    giv committed
        std::cout << "Reply: " << buffer << std::endl;
    
    giv's avatar
    giv committed
        return std::string(buffer);
    }
    
    
    giv's avatar
    giv committed
    {
    
        if (socket_ != INVALID_SOCKET) {
    #ifdef WIN32
    
    giv's avatar
    giv committed
            ::closesocket(socket_);
    
    #else
            ::close(socket_);
    #endif
            socket_ = INVALID_SOCKET;
        }
    
    giv's avatar
    giv committed
    }
    
    
    giv's avatar
    giv committed
    {
    
    giv's avatar
    giv committed
    }
    
    
    const std::string& I2pSocket::getHost() const
    
    giv's avatar
    giv committed
    {
        return SAMHost_;
    }
    
    
    giv's avatar
    giv committed
    {
        return SAMPort_;
    }
    
    
    const std::string& I2pSocket::getVersion() const
    
    giv's avatar
    giv committed
    {
        return version_;
    }
    
    
    const sockaddr_in& I2pSocket::getAddress() const
    
    giv's avatar
    giv committed
    {
        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*/)
        : socket_(SAMHost, SAMPort)
    
    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
    
    
    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)];
    
    
    #ifdef DEBUG_ON_STDOUT
        std::cout << "Generated session ID: " << result << std::endl;
    #endif
    
    giv's avatar
    giv committed
        return result;
    }
    
    
    RequestResult<std::unique_ptr<I2pSocket> > StreamSession::accept(bool silent)
    
    giv's avatar
    giv committed
    {
    
        typedef RequestResult<std::unique_ptr<I2pSocket> > ResultType;
    
    giv's avatar
    giv committed
    
    
        std::unique_ptr<I2pSocket> streamSocket(new I2pSocket(socket_));
    
    giv's avatar
    giv committed
        const Message::eStatus status = accept(*streamSocket, sessionID_, silent);
        switch(status)
        {
        case Message::OK:
    
            return ResultType(std::move(streamSocket));
    
    giv's avatar
    giv committed
        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::unique_ptr<I2pSocket> > StreamSession::connect(const std::string& destination, bool silent)
    
    giv's avatar
    giv committed
    {
    
        typedef RequestResult<std::unique_ptr<I2pSocket> > ResultType;
    
    giv's avatar
    giv committed
    
    
        std::unique_ptr<I2pSocket> streamSocket(new I2pSocket(socket_));
    
    giv's avatar
    giv committed
        const Message::eStatus status = connect(*streamSocket, sessionID_, destination, silent);
        switch(status)
        {
        case Message::OK:
    
            return ResultType(std::move(streamSocket));
    
    giv's avatar
    giv committed
        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;
    
    
        std::unique_ptr<I2pSocket> newSocket(new I2pSocket(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;
    
    
        std::unique_ptr<I2pSocket> newSocket(new I2pSocket(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;
    
    
        std::unique_ptr<I2pSocket> newSocket(new I2pSocket(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(I2pSocket& 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(I2pSocket& 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(I2pSocket& socket, const std::string& requestStr)
    
    giv's avatar
    giv committed
    {
        return rawRequest(socket, requestStr).status;
    }
    
    /*static*/
    
    Message::Answer<const std::string> StreamSession::createStreamSession(I2pSocket& 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(I2pSocket& 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(I2pSocket& 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(I2pSocket& 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(I2pSocket& 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(I2pSocket& 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
    {
    
    #ifdef DEBUG_ON_STDOUT
        std::cout << "getNickname: " << nickname_ << std::endl;
    #endif // DEBUG_ON_STDOUT
    
    giv's avatar
    giv committed
        return nickname_;
    }
    
    
    const std::string& StreamSession::getSessionID() const
    
    giv's avatar
    giv committed
    {
    
    #ifdef DEBUG_ON_STDOUT
        std::cout << "getSessionID: " << sessionID_ << std::endl;
    #endif // DEBUG_ON_STDOUT
    
    giv's avatar
    giv committed
        return sessionID_;
    }
    
    giv's avatar
    giv committed
    
    
    const std::string& StreamSession::getOptions() const
    
    giv's avatar
    giv committed
    {
    
    #ifdef DEBUG_ON_STDOUT
        std::cout << "getOptions: " << i2pOptions_ << std::endl;
    #endif // DEBUG_ON_STDOUT
    
    giv's avatar
    giv committed
        return i2pOptions_;
    
    giv's avatar
    giv committed
    }
    
    
    const FullDestination& StreamSession::getMyDestination() const
    
    giv's avatar
    giv committed
    {
    
    #ifdef DEBUG_ON_STDOUT
        std::cout << "getMyDestination: " << myDestination_.priv << std::endl;
    #endif // DEBUG_ON_STDOUT
    
    giv's avatar
    giv committed
        return myDestination_;
    
    giv's avatar
    giv committed
    }
    
    
    bool StreamSession::isSick() const
    
    giv's avatar
    giv committed
    {
    
    #ifdef DEBUG_ON_STDOUT
        std::cout << "isSick: " << isSick_ << std::endl;
    #endif // DEBUG_ON_STDOUT
    
    giv's avatar
    giv committed
        return isSick_;
    
    giv's avatar
    giv committed
    }
    
    
    const sockaddr_in& StreamSession::getSAMAddress() const
    
    giv's avatar
    giv committed
    {
    
    #ifdef DEBUG_ON_STDOUT
        // std::cout << "getSAMAddress: " << socket_.getAddress() << std::endl;
        std::cout << "getSAMAddress: not yet parsed" << std::endl;
    #endif // DEBUG_ON_STDOUT
    
    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
    {
    
    #ifdef DEBUG_ON_STDOUT
        std::cout << "getSAMHost: " << socket_.getHost() << std::endl;
    #endif // DEBUG_ON_STDOUT
    
    giv's avatar
    giv committed
        return socket_.getHost();
    
    giv's avatar
    giv committed
    }
    
    
    uint16_t StreamSession::getSAMPort() const
    
    giv's avatar
    giv committed
    {
    
    #ifdef DEBUG_ON_STDOUT
        std::cout << "getSAMPort: " << socket_.getPort() << std::endl;
    #endif // DEBUG_ON_STDOUT
    
    giv's avatar
    giv committed
        return socket_.getPort();
    
    giv's avatar
    giv committed
    }
    
    
    const std::string& StreamSession::getSAMVersion() const
    
    giv's avatar
    giv committed
    {
    
    #ifdef DEBUG_ON_STDOUT
        std::cout << "getSAMVersion: " << socket_.getVersion() << std::endl;
    #endif // DEBUG_ON_STDOUT
    
    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();
        }
    
    
    #ifdef DEBUG_ON_STDOUT
        std::cout << "Buffer: " << buffer << std::endl;
    #endif // DEBUG_ON_STDOUT
    
    
    giv's avatar
    giv committed
        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
    
    Jeff Becker's avatar
    Jeff Becker committed
    
    
    // C interface
    
    extern "C" {
    
    #include "i2psam-c.h"
    
      struct i2psam_stream_session
      {
        SAM::StreamSession * impl = nullptr;
      };
    
      struct i2psam_socket
      {
        std::unique_ptr<SAM::I2pSocket> impl;
      };
    
      struct i2psam_destination
      {
        char * pub;
        char * priv;
      };
    
      struct i2psam_stream_session * i2psam_stream_session_new(struct i2psam_stream_settings * settings)
      {
        struct i2psam_stream_session * session = new i2psam_stream_session;
        session->impl = new SAM::StreamSession(settings->nickname,
                                               settings->samhost,
                                               settings->samport,
                                               settings->destination,
                                               settings->i2cp_opts);
        return session;
      }
    
      void i2psam_stream_session_free(struct i2psam_stream_session * session)
      {
        delete session->impl;
        delete session;
      }
    
      const char * i2psam_get_samhost(struct i2psam_stream_session * session)
      {
        return strdup(session->impl->getSAMHost().c_str());
      }
    
      uint16_t i2psam_get_samport(struct i2psam_stream_session * session)
      {
        return session->impl->getSAMPort();
      }
    
      const char * i2psam_get_nickname(struct i2psam_stream_session * session)
      {
        return strdup(session->impl->getNickname().c_str());
      }
    
      const char * i2psam_get_session_id(struct i2psam_stream_session * session)
      {
        return strdup(session->impl->getSessionID().c_str());
      }
    
      const char * i2psam_get_sam_min_version(struct i2psam_stream_session * session)
      {
        return strdup(session->impl->getSAMMinVer().c_str());
      }
    
      const char * i2psam_get_sam_max_version(struct i2psam_stream_session * session)
      {
        return strdup(session->impl->getSAMMaxVer().c_str());
      }
    
      const char * i2psam_get_sam_version(struct i2psam_stream_session * session)
      {
        return strdup(session->impl->getSAMVersion().c_str());
      }
    
      const char * i2psam_get_i2cp_options(struct i2psam_stream_session * session)
      {
        return strdup(session->impl->getOptions().c_str());
      }
    
      int i2psam_is_sick(struct i2psam_stream_session * session)
      {
        if(session->impl->isSick())
          return 1;
        else
          return 0;
      }
    
      struct i2psam_socket * i2psam_accept(struct i2psam_stream_session * session, int silent)
      {
        auto result = session->impl->accept(silent);
        if (result.isOk)
        {
          struct i2psam_socket * socket = new i2psam_socket;
          socket->impl = std::move(result.value);
          return socket;
        }
        else
          return nullptr;
      }
    
      struct i2psam_socket * i2psam_connect(struct i2psam_stream_session * session, const char * destination, int silent)
      {
        auto result = session->impl->connect(destination, silent != 0);
        if(result.isOk)
        {
          struct i2psam_socket * socket = new i2psam_socket;
          socket->impl = std::move(result.value);
          return socket;
        }
        else
          return nullptr;
      }
    
      int i2psam_forward(struct i2psam_stream_session * session, const char * host, uint16_t port, int silent)
      {
        std::string remote(host);
        auto result = session->impl->forward(host, port, silent);
    
        if(result.isOk) return 0;
        else return -1;
      }
    
      const char * i2psam_namelookup(struct i2psam_stream_session * session, const char * name)
      {
        auto result = session->impl->namingLookup(name);
    
        if(result.isOk) return strdup(result.value.c_str());
        else return nullptr;
      }
    
      struct i2psam_destination * i2psam_dest_generate(struct i2psam_stream_session * session)
      {
        auto result = session->impl->destGenerate();
        if(result.isOk)
        {
          struct i2psam_destination * dest = new i2psam_destination;
          dest->pub = strdup(result.value.pub.c_str());
          dest->priv = strdup(result.value.priv.c_str());
          return dest;
        }
        else
          return nullptr;
      }
    
      void i2psam_stop_forwarding(struct i2psam_stream_session * session, const char * host, uint16_t port)
      {
        session->impl->stopForwarding(host, port);
      }
    
      void i2psam_stop_forwarding_all(struct i2psam_stream_session * session)
      {
        session->impl->stopForwardingAll();
      }
    
      struct i2psam_destination * i2psam_get_my_destination(struct i2psam_stream_session * session)
      {
        struct i2psam_destination * dest = new i2psam_destination;
        const auto & mydest = session->impl->getMyDestination();
        dest->pub = strdup(mydest.pub.c_str());
        dest->priv = strdup(mydest.priv.c_str());
        return dest;
      }
    
      void i2psam_write(struct i2psam_socket * sock, const char * data, size_t dlen)
      {
        const std::string buf(data, dlen);
        sock->impl->write(buf);
      }
    
      char * i2psam_read(struct i2psam_socket * sock, size_t * dlen)
      {
        std::string buf = sock->impl->read();
        size_t sz = sizeof(char) * buf.size();
        *dlen = sz;
        if(sz)
        {
          char * ret = (char*) malloc(sz);