diff --git a/Tunnel.cpp b/Tunnel.cpp new file mode 100644 index 00000000..07f61161 --- /dev/null +++ b/Tunnel.cpp @@ -0,0 +1,487 @@ +#include +#include "RouterContext.h" +#include "Log.h" +#include "Timestamp.h" +#include "I2NPProtocol.h" +#include "Transports.h" +#include "NetDb.h" +#include "Tunnel.h" + +namespace i2p +{ +namespace tunnel +{ + + Tunnel::Tunnel (TunnelConfig * config): m_Config (config), + m_CreationTime (i2p::util::GetSecondsSinceEpoch ()), m_IsEstablished (false) + { + } + + Tunnel::~Tunnel () + { + delete m_Config; + } + + void Tunnel::Build (uint32_t replyMsgID, OutboundTunnel * outboundTunnel) + { + CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); + size_t numRecords = m_Config->GetNumHops (); + I2NPMessage * msg = NewI2NPMessage (); + *msg->GetPayload () = numRecords; + msg->len += numRecords*sizeof (I2NPBuildRequestRecordElGamalEncrypted) + 1; + + I2NPBuildRequestRecordElGamalEncrypted * records = (I2NPBuildRequestRecordElGamalEncrypted *)(msg->GetPayload () + 1); + + TunnelHopConfig * hop = m_Config->GetFirstHop (); + int i = 0; + while (hop) + { + EncryptBuildRequestRecord (*hop->router, + CreateBuildRequestRecord (hop->router->GetIdentHash (), + hop->tunnelID, + hop->nextRouter->GetIdentHash (), + hop->nextTunnelID, + hop->layerKey, hop->ivKey, + hop->replyKey, hop->replyIV, + hop->next ? rnd.GenerateWord32 () : replyMsgID, // we set replyMsgID for last hop only + hop->isGateway, hop->isEndpoint), + records[i]); + i++; + hop = hop->next; + } + + hop = m_Config->GetLastHop ()->prev; + size_t ind = numRecords - 1; + while (hop) + { + for (size_t i = ind; i < numRecords; i++) + { + m_CBCDecryption.SetKeyWithIV (hop->replyKey, 32, hop->replyIV); + m_CBCDecryption.ProcessData((uint8_t *)&records[i], (uint8_t *)&records[i], sizeof (I2NPBuildRequestRecordElGamalEncrypted)); + } + hop = hop->prev; + ind--; + } + FillI2NPMessageHeader (msg, eI2NPVariableTunnelBuild); + + if (outboundTunnel) + { + outboundTunnel->SendTunnelDataMsg (GetNextIdentHash (), 0, msg); + DeleteI2NPMessage (msg); + } + else + { + i2p::transports.SendMessage (GetNextIdentHash (), msg); + } + } + + bool Tunnel::HandleTunnelBuildResponse (uint8_t * msg, size_t len) + { + LogPrint ("TunnelBuildResponse ", (int)msg[0], " records."); + + TunnelHopConfig * hop = m_Config->GetLastHop (); + int num = msg[0]; + while (hop) + { + for (int i = 0; i < num; i++) + { + uint8_t * record = msg + 1 + i*sizeof (I2NPBuildResponseRecord); + m_CBCDecryption.SetKeyWithIV(hop->replyKey, 32, hop->replyIV); + m_CBCDecryption.ProcessData(record, record, sizeof (I2NPBuildResponseRecord)); + } + hop = hop->prev; + num--; + } + + m_IsEstablished = true; + for (int i = 0; i < msg[0]; i++) + { + I2NPBuildResponseRecord * record = (I2NPBuildResponseRecord *)(msg + 1 + i*sizeof (I2NPBuildResponseRecord)); + LogPrint ("Ret code=", (int)record->ret); + if (record->ret) + // if any of participants declined the tunnel is not established + m_IsEstablished = false; + } + return m_IsEstablished; + } + + void Tunnel::LayerDecrypt (const uint8_t * in, size_t len, const uint8_t * layerKey, + const uint8_t * iv, uint8_t * out) + { + m_CBCDecryption.SetKeyWithIV (layerKey, 32, iv); + m_CBCDecryption.ProcessData(out, in, len); + } + + void Tunnel::IVDecrypt (const uint8_t * in, const uint8_t * ivKey, uint8_t * out) + { + m_ECBDecryption.SetKey (ivKey, 32); + m_ECBDecryption.ProcessData(out, in, 16); + } + + void Tunnel::EncryptTunnelMsg (I2NPMessage * tunnelMsg) + { + uint8_t * payload = tunnelMsg->GetPayload () + 4; + TunnelHopConfig * hop = m_Config->GetLastHop (); + while (hop) + { + // iv + data + IVDecrypt (payload, hop->ivKey, payload); + LayerDecrypt (payload + 16, TUNNEL_DATA_ENCRYPTED_SIZE, hop->layerKey, payload, payload+16); + IVDecrypt (payload, hop->ivKey, payload); + hop = hop->prev; + } + } + + void InboundTunnel::HandleTunnelDataMsg (I2NPMessage * msg) + { + EncryptTunnelMsg (msg); + m_Endpoint.HandleDecryptedTunnelDataMsg (msg); + } + + void OutboundTunnel::SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, i2p::I2NPMessage * msg) + { + m_Gateway.SendTunnelDataMsg (gwHash, gwTunnel, msg); + } + + void OutboundTunnel::SendTunnelDataMsg (i2p::I2NPMessage * msg) + { + SendTunnelDataMsg (nullptr, 0, msg); + } + + + Tunnels tunnels; + + Tunnels::Tunnels (): m_IsRunning (false), m_IsTunnelCreated (false), m_NextReplyMsgID (555), + m_ZeroHopsInboundTunnel (nullptr), m_ZeroHopsOutboundTunnel (nullptr), m_Thread (0) + { + } + + Tunnels::~Tunnels () + { + for (auto& it : m_OutboundTunnels) + delete it; + m_OutboundTunnels.clear (); + + for (auto& it : m_InboundTunnels) + delete it.second; + m_InboundTunnels.clear (); + + for (auto& it : m_TransitTunnels) + delete it.second; + m_TransitTunnels.clear (); + + for (auto& it : m_PendingTunnels) + delete it.second; + m_PendingTunnels.clear (); + + delete m_ZeroHopsInboundTunnel; + delete m_ZeroHopsOutboundTunnel; + } + + InboundTunnel * Tunnels::GetInboundTunnel (uint32_t tunnelID) + { + auto it = m_InboundTunnels.find(tunnelID); + if (it != m_InboundTunnels.end ()) + return it->second; + return nullptr; + } + + TransitTunnel * Tunnels::GetTransitTunnel (uint32_t tunnelID) + { + auto it = m_TransitTunnels.find(tunnelID); + if (it != m_TransitTunnels.end ()) + return it->second; + return nullptr; + } + + Tunnel * Tunnels::GetPendingTunnel (uint32_t replyMsgID) + { + auto it = m_PendingTunnels.find(replyMsgID); + if (it != m_PendingTunnels.end ()) + { + Tunnel * t = it->second; + m_PendingTunnels.erase (it); + return t; + } + return nullptr; + } + + InboundTunnel * Tunnels::GetNextInboundTunnel () + { + InboundTunnel * tunnel = nullptr; + size_t minReceived = 0; + for (auto it : m_InboundTunnels) + if (!tunnel || it.second->GetNumReceivedBytes () < minReceived) + { + tunnel = it.second; + minReceived = it.second->GetNumReceivedBytes (); + } + return tunnel; + } + + OutboundTunnel * Tunnels::GetNextOutboundTunnel () + { + OutboundTunnel * tunnel = nullptr; + size_t minSent = 0; + for (auto it : m_OutboundTunnels) + if (!tunnel || it->GetNumSentBytes () < minSent) + { + tunnel = it; + minSent = it->GetNumSentBytes (); + } + return tunnel; + } + + void Tunnels::AddTransitTunnel (TransitTunnel * tunnel) + { + m_TransitTunnels[tunnel->GetTunnelID ()] = tunnel; + } + + void Tunnels::Start () + { + m_IsRunning = true; + m_Thread = new std::thread (std::bind (&Tunnels::Run, this)); + } + + void Tunnels::Stop () + { + m_IsRunning = false; + m_Queue.WakeUp (); + if (m_Thread) + { + m_Thread->join (); + delete m_Thread; + m_Thread = 0; + } + } + + void Tunnels::Run () + { + sleep (1); // wait for other parts are ready + + // we must start with zero hops tunnels + CreateZeroHopsInboundTunnel (); + CreateZeroHopsOutboundTunnel (); + + uint32_t lastTs = 0; + while (m_IsRunning) + { + try + { + I2NPMessage * msg = m_Queue.GetNextWithTimeout (1000); // 1 sec + while (msg) + { + uint32_t tunnelID = be32toh (*(uint32_t *)msg->GetPayload ()); + InboundTunnel * tunnel = GetInboundTunnel (tunnelID); + if (tunnel) + tunnel->HandleTunnelDataMsg (msg); + else + { + TransitTunnel * transitTunnel = GetTransitTunnel (tunnelID); + if (transitTunnel) + transitTunnel->HandleTunnelDataMsg (msg); + else + { + LogPrint ("Tunnel ", tunnelID, " not found"); + i2p::DeleteI2NPMessage (msg); + } + } + msg = m_Queue.Get (); + } + + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + if (ts - lastTs >= 15) // manage tunnels every 15 seconds + { + ManageTunnels (); + lastTs = ts; + } + } + catch (std::exception& ex) + { + LogPrint ("Tunnels: ", ex.what ()); + } + } + } + + void Tunnels::ManageTunnels () + { + // check pending tunnel. if something is still there, wipe it out + // because it wouldn't be reponded anyway + for (auto& it : m_PendingTunnels) + { + LogPrint ("Pending tunnel build request ", it.first, " has not been responded. Deleted"); + delete it.second; + } + m_PendingTunnels.clear (); + + ManageOutboundTunnels (); + ManageInboundTunnels (); + + /* if (!m_IsTunnelCreated) + { + InboundTunnel * inboundTunnel = CreateOneHopInboundTestTunnel (); + if (inboundTunnel) + CreateOneHopOutboundTestTunnel (inboundTunnel); + inboundTunnel = CreateTwoHopsInboundTestTunnel (); + if (inboundTunnel) + CreateTwoHopsOutboundTestTunnel (inboundTunnel); + + m_IsTunnelCreated = true; + }*/ + } + + void Tunnels::ManageOutboundTunnels () + { + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + for (auto it = m_OutboundTunnels.begin (); it != m_OutboundTunnels.end ();) + { + if (ts > (*it)->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + { + LogPrint ("Tunnel ", (*it)->GetTunnelID (), " expired"); + it = m_OutboundTunnels.erase (it); + } + else + it++; + } + + if (m_OutboundTunnels.size () < 10) + { + // trying to create one more oubound tunnel + InboundTunnel * inboundTunnel = m_ZeroHopsInboundTunnel; + if (!m_InboundTunnels.empty ()) + inboundTunnel = m_InboundTunnels.rbegin ()->second; + + if (m_OutboundTunnels.empty () || m_OutboundTunnels.size () < 3) + { + LogPrint ("Creating one hop outbound tunnel..."); + CreateTunnel ( + new TunnelConfig (i2p::data::netdb.GetRandomNTCPRouter (), + inboundTunnel->GetTunnelConfig ())); + } + else + { + OutboundTunnel * outboundTunnel = *m_OutboundTunnels.begin (); + LogPrint ("Creating two hops outbound tunnel..."); + CreateTunnel ( + new TunnelConfig (inboundTunnel->GetTunnelConfig ()->GetFirstHop ()->router, + i2p::data::netdb.GetRandomNTCPRouter (), + inboundTunnel->GetTunnelConfig ()), + outboundTunnel); + } + } + } + + void Tunnels::ManageInboundTunnels () + { + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + for (auto it = m_InboundTunnels.begin (); it != m_InboundTunnels.end ();) + { + if (ts > it->second->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + { + LogPrint ("Tunnel ", it->second->GetTunnelID (), " expired"); + it = m_InboundTunnels.erase (it); + } + else + it++; + } + + if (m_InboundTunnels.size () < 10) + { + // trying to create one more inbound tunnel + if (m_OutboundTunnels.empty () || m_InboundTunnels.size () < 3) + { + LogPrint ("Creating one hop inbound tunnel..."); + CreateTunnel (new TunnelConfig (i2p::data::netdb.GetRandomNTCPRouter ())); + } + else + { + OutboundTunnel * outboundTunnel = *m_OutboundTunnels.rbegin (); + InboundTunnel * inboundTunnel = m_InboundTunnels.rbegin ()->second; + LogPrint ("Creating two hops inbound tunnel..."); + CreateTunnel ( + new TunnelConfig (i2p::data::netdb.GetRandomNTCPRouter (), + inboundTunnel->GetTunnelConfig ()->GetFirstHop ()->router), + outboundTunnel); + } + } + } + + void Tunnels::PostTunnelData (I2NPMessage * msg) + { + if (msg) m_Queue.Put (msg); + } + + template + TTunnel * Tunnels::CreateTunnel (TunnelConfig * config, OutboundTunnel * outboundTunnel) + { + TTunnel * newTunnel = new TTunnel (config); + m_PendingTunnels[m_NextReplyMsgID] = newTunnel; + newTunnel->Build (m_NextReplyMsgID, outboundTunnel); + m_NextReplyMsgID++; // TODO: should be atomic + return newTunnel; + } + + void Tunnels::AddOutboundTunnel (OutboundTunnel * newTunnel) + { + if (newTunnel != m_ZeroHopsOutboundTunnel) + m_OutboundTunnels.push_back (newTunnel); + } + + void Tunnels::AddInboundTunnel (InboundTunnel * newTunnel) + { + if (newTunnel != m_ZeroHopsInboundTunnel) + m_InboundTunnels[newTunnel->GetTunnelID ()] = newTunnel; + } + + void Tunnels::CreateZeroHopsOutboundTunnel () + { + m_ZeroHopsOutboundTunnel = CreateTunnel ( + new TunnelConfig (&i2p::context.GetRouterInfo (), + m_ZeroHopsInboundTunnel->GetTunnelConfig ())); + } + + void Tunnels::CreateZeroHopsInboundTunnel () + { + m_ZeroHopsInboundTunnel = CreateTunnel ( + new TunnelConfig (&i2p::context.GetRouterInfo ())); + } + + OutboundTunnel * Tunnels::CreateOneHopOutboundTestTunnel (InboundTunnel * replyTunnel) + { + return CreateTunnel (replyTunnel->GetTunnelConfig ()->Invert ()); + } + + InboundTunnel * Tunnels::CreateOneHopInboundTestTunnel (OutboundTunnel * outboundTunnel) + { + i2p::ntcp::NTCPSession * peer = i2p::transports.GetNextNTCPSession (); + if (peer) + { + const i2p::data::RouterInfo& router = peer->GetRemoteRouterInfo (); + return CreateTunnel (new TunnelConfig (&router), outboundTunnel); + } + else + LogPrint ("No established peers"); + return 0; + } + + OutboundTunnel * Tunnels::CreateTwoHopsOutboundTestTunnel (InboundTunnel * replyTunnel) + { + return CreateTunnel (replyTunnel->GetTunnelConfig ()->Invert ()); + } + + InboundTunnel * Tunnels::CreateTwoHopsInboundTestTunnel (OutboundTunnel * outboundTunnel) + { + i2p::ntcp::NTCPSession * peer = i2p::transports.GetNextNTCPSession (); + if (peer) + { + const i2p::data::RouterInfo& router = peer->GetRemoteRouterInfo (); + return CreateTunnel ( + new TunnelConfig (&router, &i2p::context.GetRouterInfo ()), + outboundTunnel); + } + else + LogPrint ("No established peers"); + return 0; + } +} +} diff --git a/Tunnel.h b/Tunnel.h new file mode 100644 index 00000000..a2d72355 --- /dev/null +++ b/Tunnel.h @@ -0,0 +1,160 @@ +#ifndef TUNNEL_H__ +#define TUNNEL_H__ + +#include +#include +#include +#include +#include +#include +#include +#include "Queue.h" +#include "TunnelConfig.h" +#include "TransitTunnel.h" +#include "TunnelEndpoint.h" +#include "TunnelGateway.h" +#include "TunnelBase.h" +#include "I2NPProtocol.h" + +namespace i2p +{ +namespace tunnel +{ + const int TUNNEL_EXPIRATION_TIMEOUT = 600; // 10 minutes + + class OutboundTunnel; + class InboundTunnel; + class Tunnel: public TunnelBase + { + public: + + Tunnel (TunnelConfig * config); + ~Tunnel (); + + void Build (uint32_t replyMsgID, OutboundTunnel * outboundTunnel = 0); + + virtual uint32_t GetTunnelID () const = 0; // as known at our side + TunnelConfig * GetTunnelConfig () const { return m_Config; } + uint32_t GetCreationTime () const { return m_CreationTime; }; + bool IsEstablished () const { return m_IsEstablished; }; + + bool HandleTunnelBuildResponse (uint8_t * msg, size_t len); + + // implements TunnelBase + void EncryptTunnelMsg (I2NPMessage * tunnelMsg); + uint32_t GetNextTunnelID () const { return m_Config->GetFirstHop ()->tunnelID; }; + const i2p::data::IdentHash& GetNextIdentHash () const { return m_Config->GetFirstHop ()->router->GetIdentHash (); }; + + private: + + void LayerDecrypt (const uint8_t * in, size_t len, const uint8_t * layerKey, + const uint8_t * iv, uint8_t * out); + void IVDecrypt (const uint8_t * in, const uint8_t * ivKey, uint8_t * out); + + private: + + TunnelConfig * m_Config; + uint32_t m_CreationTime; // seconds since epoch + bool m_IsEstablished; + + CryptoPP::ECB_Mode::Decryption m_ECBDecryption; + CryptoPP::CBC_Mode::Decryption m_CBCDecryption; + }; + + class OutboundTunnel: public Tunnel + { + public: + + OutboundTunnel (TunnelConfig * config): Tunnel (config), m_Gateway (this) {}; + + void SendTunnelDataMsg (i2p::I2NPMessage * msg); //local + void SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, i2p::I2NPMessage * msg); + + uint32_t GetTunnelID () const { return GetNextTunnelID (); }; + TunnelGateway& GetTunnelGateway () { return m_Gateway; }; + size_t GetNumSentBytes () const { return m_Gateway.GetNumSentBytes (); }; + + private: + + TunnelGateway m_Gateway; + }; + + class InboundTunnel: public Tunnel + { + public: + + InboundTunnel (TunnelConfig * config): Tunnel (config) {}; + void HandleTunnelDataMsg (I2NPMessage * msg); + + uint32_t GetTunnelID () const { return GetTunnelConfig ()->GetLastHop ()->nextTunnelID; }; + size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); }; + + private: + + TunnelEndpoint m_Endpoint; + }; + + + class Tunnels + { + public: + + Tunnels (); + ~Tunnels (); + + void Start (); + void Stop (); + + InboundTunnel * GetInboundTunnel (uint32_t tunnelID); + Tunnel * GetPendingTunnel (uint32_t replyMsgID); + InboundTunnel * GetNextInboundTunnel (); + OutboundTunnel * GetNextOutboundTunnel (); + TransitTunnel * GetTransitTunnel (uint32_t tunnelID); + void AddTransitTunnel (TransitTunnel * tunnel); + void AddOutboundTunnel (OutboundTunnel * newTunnel); + void AddInboundTunnel (InboundTunnel * newTunnel); + void PostTunnelData (I2NPMessage * msg); + template + TTunnel * CreateTunnel (TunnelConfig * config, OutboundTunnel * outboundTunnel = 0); + + OutboundTunnel * CreateOneHopOutboundTestTunnel (InboundTunnel * replyTunnel); + InboundTunnel * CreateOneHopInboundTestTunnel (OutboundTunnel * outboundTunnel = 0); + OutboundTunnel * CreateTwoHopsOutboundTestTunnel (InboundTunnel * replyTunnel); + InboundTunnel * CreateTwoHopsInboundTestTunnel (OutboundTunnel * outboundTunnel = 0); + + private: + + void Run (); + void ManageTunnels (); + void ManageOutboundTunnels (); + void ManageInboundTunnels (); + + void CreateZeroHopsOutboundTunnel (); + void CreateZeroHopsInboundTunnel (); + + private: + + bool m_IsRunning; + bool m_IsTunnelCreated; // TODO: temporary + uint32_t m_NextReplyMsgID; // TODO: make it random later + InboundTunnel * m_ZeroHopsInboundTunnel; + OutboundTunnel * m_ZeroHopsOutboundTunnel; + std::thread * m_Thread; + std::map m_PendingTunnels; // by replyMsgID + std::map m_InboundTunnels; + std::list m_OutboundTunnels; + std::map m_TransitTunnels; + i2p::util::Queue m_Queue; + + public: + + // for HTTP only + const decltype(m_OutboundTunnels)& GetOutboundTunnels () const { return m_OutboundTunnels; }; + const decltype(m_InboundTunnels)& GetInboundTunnels () const { return m_InboundTunnels; }; + }; + + extern Tunnels tunnels; +} +} + +#endif diff --git a/TunnelConfig.h b/TunnelConfig.h new file mode 100644 index 00000000..653fe56d --- /dev/null +++ b/TunnelConfig.h @@ -0,0 +1,207 @@ +#ifndef TUNNEL_CONFIG_H__ +#define TUNNEL_CONFIG_H__ + +#include +#include +#include "RouterInfo.h" +#include "RouterContext.h" + +namespace i2p +{ +namespace tunnel +{ + struct TunnelHopConfig + { + const i2p::data::RouterInfo * router, * nextRouter; + uint32_t tunnelID, nextTunnelID; + uint8_t layerKey[32]; + uint8_t ivKey[32]; + uint8_t replyKey[32]; + uint8_t replyIV[16]; + bool isGateway, isEndpoint; + + TunnelHopConfig * next, * prev; + + TunnelHopConfig (const i2p::data::RouterInfo * r) + { + CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); + rnd.GenerateBlock (layerKey, 32); + rnd.GenerateBlock (ivKey, 32); + rnd.GenerateBlock (replyIV, 16); + tunnelID = rnd.GenerateWord32 (); + isGateway = true; + isEndpoint = true; + router = r; + nextRouter = 0; + nextTunnelID = 0; + + next = 0; + prev = 0; + } + + void SetNextRouter (const i2p::data::RouterInfo * r) + { + nextRouter = r; + isEndpoint = false; + CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); + nextTunnelID = rnd.GenerateWord32 (); + } + + void SetReplyHop (TunnelHopConfig * replyFirstHop) + { + nextRouter = replyFirstHop->router; + nextTunnelID = replyFirstHop->tunnelID; + isEndpoint = true; + } + + void SetNext (TunnelHopConfig * n) + { + next = n; + if (next) + { + next->prev = this; + next->isGateway = false; + isEndpoint = false; + nextRouter = next->router; + nextTunnelID = next->tunnelID; + } + } + + void SetPrev (TunnelHopConfig * p) + { + prev = p; + if (prev) + { + prev->next = this; + prev->isEndpoint = false; + isGateway = false; + } + } + }; + + class TunnelConfig + { + public: + + TunnelConfig (const i2p::data::RouterInfo * peer, TunnelConfig * replyTunnelConfig = 0) // one hop + { + m_FirstHop = new TunnelHopConfig (peer); + m_LastHop = m_FirstHop; + + if (replyTunnelConfig) // outbound + { + m_FirstHop->isGateway = false; + m_LastHop->SetReplyHop (replyTunnelConfig->GetFirstHop ()); + } + else + m_FirstHop->SetNextRouter (&i2p::context.GetRouterInfo ()); + } + + TunnelConfig (const i2p::data::RouterInfo * peer1, const i2p::data::RouterInfo * peer2, TunnelConfig * replyTunnelConfig = 0) // two hops + { + m_FirstHop = new TunnelHopConfig (peer1); + m_LastHop = new TunnelHopConfig (peer2); + m_FirstHop->SetNext (m_LastHop); + + if (replyTunnelConfig) + { + m_FirstHop->isGateway = false; + m_LastHop->SetReplyHop (replyTunnelConfig->GetFirstHop ()); + } + else + m_LastHop->SetNextRouter (&i2p::context.GetRouterInfo ()); + } + + ~TunnelConfig () + { + TunnelHopConfig * hop = m_FirstHop; + + while (hop) + { + delete hop; + hop = hop->next; + } + } + + TunnelHopConfig * GetFirstHop () const + { + return m_FirstHop; + } + + TunnelHopConfig * GetLastHop () const + { + return m_LastHop; + } + + size_t GetNumHops () const + { + size_t num = 0; + TunnelHopConfig * hop = m_FirstHop; + while (hop) + { + num++; + hop = hop->next; + } + return num; + } + + void Print (std::stringstream& s) const + { + TunnelHopConfig * hop = m_FirstHop; + if (!m_FirstHop->isGateway) + s << "me"; + s << "-->" << m_FirstHop->tunnelID; + while (hop) + { + s << ":" << hop->router->GetIdentHashAbbreviation () << "-->"; + if (!hop->isEndpoint) + s << hop->nextTunnelID; + else + return; + hop = hop->next; + } + // we didn't reach enpoint that mean we are last hop + s << ":me"; + } + + TunnelConfig * Invert () const + { + TunnelConfig * newConfig = new TunnelConfig (); + TunnelHopConfig * hop = m_FirstHop, * nextNewHop = nullptr; + while (hop) + { + TunnelHopConfig * newHop = new TunnelHopConfig (hop->router); + if (nextNewHop) + newHop->SetNext (nextNewHop); + nextNewHop = newHop; + newHop->isEndpoint = hop->isGateway; + newHop->isGateway = hop->isEndpoint; + + if (!hop->prev) // first hop + { + newConfig->m_LastHop = newHop; + if (hop->isGateway) // inbound tunnel + newHop->SetReplyHop (m_FirstHop); // use it as reply tunnel + } + if (!hop->next) newConfig->m_FirstHop = newHop; // last hop + + hop = hop->next; + } + return newConfig; + } + + private: + + // this constructor can't be called from outside + TunnelConfig (): m_FirstHop (nullptr), m_LastHop (nullptr) + { + } + + private: + + TunnelHopConfig * m_FirstHop, * m_LastHop; + }; +} +} + +#endif