/ libi2pd_client / BOB.cpp
BOB.cpp
   1  /*
   2  * Copyright (c) 2013-2026, The PurpleI2P Project
   3  *
   4  * This file is part of Purple i2pd project and licensed under BSD3
   5  *
   6  * See full license text in LICENSE file at top of project tree
   7  */
   8  
   9  #include <string.h>
  10  #include "Log.h"
  11  #include "ClientContext.h"
  12  #include "util.h"
  13  #include "BOB.h"
  14  
  15  namespace i2p
  16  {
  17  namespace client
  18  {
  19  	void BOBI2PTunnelIncomingConnection::Established ()
  20  	{
  21  		if (m_IsQuiet)
  22  			StreamReceive ();
  23  		else
  24  		{
  25  			// send destination first like received from I2P
  26  			std::string dest = GetStream ()->GetRemoteIdentity ()->ToBase64 ();
  27  			dest += "\n";
  28  			if (dest.size() <= I2P_TUNNEL_CONNECTION_BUFFER_SIZE)
  29  				memcpy (GetStreamBuffer (), dest.c_str (), dest.size ());
  30  			else
  31  				memset (GetStreamBuffer (), 0, I2P_TUNNEL_CONNECTION_BUFFER_SIZE);
  32  			HandleStreamReceive (boost::system::error_code (), dest.size ());
  33  		}
  34  		Receive ();
  35  	}
  36  
  37  	BOBI2PInboundTunnel::BOBI2PInboundTunnel (const boost::asio::ip::tcp::endpoint& ep, std::shared_ptr<ClientDestination> localDestination):
  38  		BOBI2PTunnel (localDestination), m_Acceptor (localDestination->GetService (), ep)
  39  	{
  40  	}
  41  
  42  	BOBI2PInboundTunnel::~BOBI2PInboundTunnel ()
  43  	{
  44  		Stop ();
  45  	}
  46  
  47  	void BOBI2PInboundTunnel::Start ()
  48  	{
  49  		m_Acceptor.listen ();
  50  		Accept ();
  51  	}
  52  
  53  	void BOBI2PInboundTunnel::Stop ()
  54  	{
  55  		m_Acceptor.close();
  56  		ClearHandlers ();
  57  	}
  58  
  59  	void BOBI2PInboundTunnel::Accept ()
  60  	{
  61  		auto receiver = std::make_shared<AddressReceiver> ();
  62  		receiver->socket = std::make_shared<boost::asio::ip::tcp::socket> (GetService ());
  63  		m_Acceptor.async_accept (*receiver->socket, std::bind (&BOBI2PInboundTunnel::HandleAccept, this,
  64  			std::placeholders::_1, receiver));
  65  	}
  66  
  67  	void BOBI2PInboundTunnel::HandleAccept (const boost::system::error_code& ecode, std::shared_ptr<AddressReceiver> receiver)
  68  	{
  69  		if (!ecode)
  70  		{
  71  			Accept ();
  72  			ReceiveAddress (receiver);
  73  		}
  74  	}
  75  
  76  	void BOBI2PInboundTunnel::ReceiveAddress (std::shared_ptr<AddressReceiver> receiver)
  77  	{
  78  		receiver->socket->async_read_some (boost::asio::buffer(
  79  			receiver->buffer + receiver->bufferOffset,
  80  			BOB_COMMAND_BUFFER_SIZE - receiver->bufferOffset),
  81  			std::bind(&BOBI2PInboundTunnel::HandleReceivedAddress, this,
  82  			std::placeholders::_1, std::placeholders::_2, receiver));
  83  	}
  84  
  85  	void BOBI2PInboundTunnel::HandleReceivedAddress (const boost::system::error_code& ecode, std::size_t bytes_transferred,
  86  		std::shared_ptr<AddressReceiver> receiver)
  87  	{
  88  		if (ecode)
  89  			LogPrint (eLogError, "BOB: Inbound tunnel read error: ", ecode.message ());
  90  		else
  91  		{
  92  			receiver->bufferOffset += bytes_transferred;
  93  			receiver->buffer[receiver->bufferOffset] = 0;
  94  			char * eol = strchr (receiver->buffer, '\n');
  95  			if (eol)
  96  			{
  97  				*eol = 0;
  98  				if (eol != receiver->buffer && eol[-1] == '\r') eol[-1] = 0; // workaround for Transmission, it sends '\r\n' terminated address
  99  				receiver->data = (uint8_t *)eol + 1;
 100  				receiver->dataLen = receiver->bufferOffset - (eol - receiver->buffer + 1);
 101  				auto addr = context.GetAddressBook ().GetAddress (receiver->buffer);
 102  				if (!addr)
 103  				{
 104  					LogPrint (eLogError, "BOB: Address ", receiver->buffer, " not found");
 105  					return;
 106  				}
 107  				if (addr->IsIdentHash ())
 108  				{
 109  					auto leaseSet = GetLocalDestination ()->FindLeaseSet (addr->identHash);
 110  					if (leaseSet)
 111  						CreateConnection (receiver, leaseSet);
 112  					else
 113  						GetLocalDestination ()->RequestDestination (addr->identHash,
 114  							std::bind (&BOBI2PInboundTunnel::HandleDestinationRequestComplete,
 115  							this, std::placeholders::_1, receiver));
 116  				}
 117  				else
 118  					GetLocalDestination ()->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey,
 119  							std::bind (&BOBI2PInboundTunnel::HandleDestinationRequestComplete,
 120  							this, std::placeholders::_1, receiver));
 121  			}
 122  			else
 123  			{
 124  				if (receiver->bufferOffset < BOB_COMMAND_BUFFER_SIZE)
 125  					ReceiveAddress (receiver);
 126  				else
 127  					LogPrint (eLogError, "BOB: Missing inbound address");
 128  			}
 129  		}
 130  	}
 131  
 132  	void BOBI2PInboundTunnel::HandleDestinationRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet, std::shared_ptr<AddressReceiver> receiver)
 133  	{
 134  		if (leaseSet)
 135  			CreateConnection (receiver, leaseSet);
 136  		else
 137  			LogPrint (eLogError, "BOB: LeaseSet for inbound destination not found");
 138  	}
 139  
 140  	void BOBI2PInboundTunnel::CreateConnection (std::shared_ptr<AddressReceiver> receiver, std::shared_ptr<const i2p::data::LeaseSet> leaseSet)
 141  	{
 142  		LogPrint (eLogDebug, "BOB: New inbound connection");
 143  		auto connection = std::make_shared<I2PTunnelConnection>(this, receiver->socket, leaseSet);
 144  		AddHandler (connection);
 145  		connection->I2PConnect (receiver->data, receiver->dataLen);
 146  	}
 147  
 148  	BOBI2POutboundTunnel::BOBI2POutboundTunnel (const std::string& outhost, uint16_t port,
 149  		std::shared_ptr<ClientDestination> localDestination, bool quiet): BOBI2PTunnel (localDestination),
 150  		m_Endpoint (boost::asio::ip::make_address (outhost), port), m_IsQuiet (quiet)
 151  	{
 152  	}
 153  
 154  	void BOBI2POutboundTunnel::Start ()
 155  	{
 156  		Accept ();
 157  	}
 158  
 159  	void BOBI2POutboundTunnel::Stop ()
 160  	{
 161  		ClearHandlers ();
 162  	}
 163  
 164  	void BOBI2POutboundTunnel::Accept ()
 165  	{
 166  		auto localDestination = GetLocalDestination ();
 167  		if (localDestination)
 168  			localDestination->AcceptStreams (std::bind (&BOBI2POutboundTunnel::HandleAccept, this, std::placeholders::_1));
 169  		else
 170  			LogPrint (eLogError, "BOB: Local destination not set for server tunnel");
 171  	}
 172  
 173  	void BOBI2POutboundTunnel::HandleAccept (std::shared_ptr<i2p::stream::Stream> stream)
 174  	{
 175  		if (stream)
 176  		{
 177  			auto conn = std::make_shared<BOBI2PTunnelIncomingConnection> (this, stream, m_Endpoint, m_IsQuiet);
 178  			AddHandler (conn);
 179  			conn->Connect ();
 180  		}
 181  	}
 182  
 183  	BOBDestination::BOBDestination (std::shared_ptr<ClientDestination> localDestination,
 184  			const std::string &nickname, const std::string &inhost, const std::string &outhost,
 185  			const uint16_t inport, const uint16_t outport, const bool quiet):
 186  		m_LocalDestination (localDestination),
 187  		m_OutboundTunnel (nullptr), m_InboundTunnel (nullptr),
 188  		m_Nickname(nickname), m_InHost(inhost), m_OutHost(outhost),
 189  		m_InPort(inport), m_OutPort(outport), m_Quiet(quiet), m_IsRunning(false)
 190  	{
 191  	}
 192  
 193  	BOBDestination::~BOBDestination ()
 194  	{
 195  		delete m_OutboundTunnel;
 196  		delete m_InboundTunnel;
 197  		i2p::client::context.DeleteLocalDestination (m_LocalDestination);
 198  	}
 199  
 200  	void BOBDestination::Start ()
 201  	{
 202  		if (m_OutboundTunnel) m_OutboundTunnel->Start ();
 203  		if (m_InboundTunnel) m_InboundTunnel->Start ();
 204  		m_IsRunning = true;
 205  	}
 206  
 207  	void BOBDestination::Stop ()
 208  	{
 209  		StopTunnels ();
 210  		m_LocalDestination->Stop ();
 211  	}
 212  
 213  	void BOBDestination::StopTunnels ()
 214  	{
 215  		m_IsRunning = false;
 216  		if (m_OutboundTunnel)
 217  		{
 218  			m_OutboundTunnel->Stop ();
 219  			delete m_OutboundTunnel;
 220  			m_OutboundTunnel = nullptr;
 221  		}
 222  		if (m_InboundTunnel)
 223  		{
 224  			m_InboundTunnel->Stop ();
 225  			delete m_InboundTunnel;
 226  			m_InboundTunnel = nullptr;
 227  		}
 228  	}
 229  
 230  	void BOBDestination::CreateInboundTunnel (uint16_t port, const std::string& inhost)
 231  	{
 232  		if (!m_InboundTunnel)
 233  		{
 234  			// update inport and inhost (user can stop tunnel and change)
 235  			m_InPort = port;
 236  			m_InHost = inhost;
 237  			boost::asio::ip::tcp::endpoint ep(boost::asio::ip::tcp::v4(), port);
 238  			if (!inhost.empty ())
 239  			{
 240  				boost::system::error_code ec;
 241  				auto addr = boost::asio::ip::make_address (inhost, ec);
 242  				if (!ec)
 243  					ep.address (addr);
 244  				else
 245  					LogPrint (eLogError, "BOB: ", ec.message ());
 246  			}
 247  			m_InboundTunnel = new BOBI2PInboundTunnel (ep, m_LocalDestination);
 248  		}
 249  	}
 250  
 251  	void BOBDestination::CreateOutboundTunnel (const std::string& outhost, uint16_t port, bool quiet)
 252  	{
 253  		if (!m_OutboundTunnel)
 254  		{
 255  			// update outport and outhost (user can stop tunnel and change)
 256  			m_OutPort = port;
 257  			m_OutHost = outhost;
 258  			m_OutboundTunnel = new BOBI2POutboundTunnel (outhost, port, m_LocalDestination, quiet);
 259  		}
 260  	}
 261  
 262  	BOBCommandSession::BOBCommandSession (BOBCommandChannel& owner):
 263  		m_Owner (owner), m_Socket (m_Owner.GetService ()),
 264  		m_ReceiveBuffer(BOB_COMMAND_BUFFER_SIZE + 1), m_SendBuffer(BOB_COMMAND_BUFFER_SIZE + 1),
 265  		m_IsOpen (true), m_IsQuiet (false), m_IsActive (false),
 266  		m_InPort (0), m_OutPort (0), m_CurrentDestination (nullptr)
 267  	{
 268  	}
 269  
 270  	BOBCommandSession::~BOBCommandSession ()
 271  	{
 272  	}
 273  
 274  	void BOBCommandSession::Terminate ()
 275  	{
 276  		m_Socket.close ();
 277  		m_IsOpen = false;
 278  	}
 279  
 280  	void BOBCommandSession::Receive ()
 281  	{
 282  		boost::asio::async_read_until(m_Socket, m_ReceiveBuffer, '\n',
 283  			std::bind(&BOBCommandSession::HandleReceivedLine, shared_from_this(),
 284  				std::placeholders::_1, std::placeholders::_2));
 285  	}
 286  
 287  	void BOBCommandSession::HandleReceivedLine(const boost::system::error_code& ecode, std::size_t bytes_transferred)
 288  	{
 289  		if(ecode)
 290  		{
 291  			LogPrint (eLogError, "BOB: Command channel read error: ", ecode.message());
 292  			if (ecode != boost::asio::error::operation_aborted)
 293  				Terminate ();
 294  		}
 295  		else
 296  		{
 297  			std::string line;
 298  
 299  			std::istream is(&m_ReceiveBuffer);
 300  			std::getline(is, line);
 301  
 302  			std::string command, operand;
 303  			std::istringstream iss(line);
 304  			iss >> command >> operand;
 305  
 306  			// process command
 307  			auto& handlers = m_Owner.GetCommandHandlers();
 308  			auto it = handlers.find(command);
 309  			if(it != handlers.end())
 310  			{
 311  				(this->*(it->second))(operand.c_str(), operand.length());
 312  			}
 313  			else
 314  			{
 315  				LogPrint (eLogError, "BOB: Unknown command ", command.c_str());
 316  				SendReplyError ("unknown command");
 317  			}
 318  		}
 319  	}
 320  
 321  	void BOBCommandSession::Send ()
 322  	{
 323  		boost::asio::async_write (m_Socket, m_SendBuffer,
 324  			boost::asio::transfer_all (),
 325  			std::bind(&BOBCommandSession::HandleSent, shared_from_this (),
 326  				std::placeholders::_1, std::placeholders::_2));
 327  	}
 328  
 329  	void BOBCommandSession::HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred)
 330  	{
 331  		if (ecode)
 332  		{
 333  			LogPrint (eLogError, "BOB: Command channel send error: ", ecode.message ());
 334  			if (ecode != boost::asio::error::operation_aborted)
 335  				Terminate ();
 336  		}
 337  		else
 338  		{
 339  			if (m_IsOpen)
 340  				Receive ();
 341  			else
 342  				Terminate ();
 343  		}
 344  	}
 345  
 346  	void BOBCommandSession::SendReplyOK (std::string_view msg)
 347  	{
 348  		std::ostream os(&m_SendBuffer);
 349  		os << "OK";
 350  		if (!msg.empty ())
 351  			os << " " << msg;
 352  		os << std::endl;
 353  		Send ();
 354  	}
 355  
 356  	void BOBCommandSession::SendReplyOK (const std::vector<std::string_view>& strings)
 357  	{
 358  		std::ostream os(&m_SendBuffer);
 359  		os << "OK";
 360  		if (!strings.empty ()) os << " ";
 361  		for (auto& it: strings)
 362  			os << it;
 363  		os << std::endl;
 364  		Send ();
 365  	}
 366  
 367  	void BOBCommandSession::SendReplyError (std::string_view msg)
 368  	{
 369  		std::ostream os(&m_SendBuffer);
 370  		os << "ERROR " << msg << std::endl;
 371  		Send ();
 372  	}
 373  
 374  	void BOBCommandSession::SendVersion ()
 375  	{
 376  		std::ostream os(&m_SendBuffer);
 377  		os << "BOB 00.00.10" << std::endl;
 378  		SendReplyOK(std::string_view()); // empty string
 379  	}
 380  
 381  	void BOBCommandSession::SendRaw (std::string_view data)
 382  	{
 383  		std::ostream os(&m_SendBuffer);
 384  		os << data << std::endl;
 385  	}
 386  
 387  	void BOBCommandSession::BuildStatusLine(bool currentTunnel, std::shared_ptr<BOBDestination> dest, std::string &out)
 388  	{
 389  		// helper lambdas
 390  		const auto issetStr = [](const std::string &str) { return str.empty() ? "not_set" : str; }; // for inhost, outhost
 391  		const auto issetNum = [&issetStr](const int p) { return issetStr(p == 0 ? "" : std::to_string(p)); }; // for inport, outport
 392  		const auto destExists = [](const BOBDestination * const dest) { return dest != nullptr; };
 393  		const auto destReady = [](const BOBDestination * const dest) { return dest && dest->IsRunning(); };
 394  		const auto bool_str = [](const bool v) { return v ? "true" : "false"; }; // bool -> str
 395  		const auto getProxyType = [](const i2p::client::I2PService* proxy) -> std::string {
 396  		if (!proxy) return "NONE";
 397  		if (dynamic_cast<const i2p::proxy::SOCKSProxy*>(proxy)) return "SOCKS";
 398  		if (dynamic_cast<const i2p::proxy::HTTPProxy*>(proxy)) return "HTTPPROXY";
 399  		return "UNKNOWN";
 400  		};
 401  		const auto isProxyRunning = [](const i2p::client::I2PService* proxy) -> bool {
 402  		return proxy != nullptr;
 403  		};
 404  
 405  		// tunnel info
 406  		const std::string nickname = currentTunnel ? m_Nickname : dest->GetNickname();
 407  		const bool quiet = currentTunnel ? m_IsQuiet : dest->GetQuiet();
 408  		const std::string inhost = issetStr(currentTunnel ? m_InHost : dest->GetInHost());
 409  		const std::string outhost = issetStr(currentTunnel ? m_OutHost : dest->GetOutHost());
 410  		const std::string inport = issetNum(currentTunnel ? m_InPort : dest->GetInPort());
 411  		const std::string outport = issetNum(currentTunnel ? m_OutPort : dest->GetOutPort());
 412  		const bool keys = destExists(dest.get ()); // key must exist when destination is created
 413  		const bool starting = destExists(dest.get ()) && !destReady(dest.get ());
 414  		const bool running = destExists(dest.get ()) && destReady(dest.get ());
 415  		const bool stopping = false;
 416  
 417  		const i2p::client::I2PService* proxy = m_Owner.GetProxy(nickname);
 418  		const std::string proxyType = getProxyType(proxy);
 419  		const bool proxyStatus = isProxyRunning(proxy);
 420  
 421  		// build line
 422  		std::stringstream ss;
 423  		ss	<< "DATA "
 424  			<< "NICKNAME: " << nickname          << " " << "STARTING: " << bool_str(starting) << " "
 425  			<< "RUNNING: "  << bool_str(running) << " " << "STOPPING: " << bool_str(stopping) << " "
 426  			<< "KEYS: "     << bool_str(keys)    << " " << "QUIET: "    << bool_str(quiet) << " "
 427  			<< "INPORT: "   << inport            << " " << "INHOST: "   << inhost << " "
 428  			<< "OUTPORT: "  << outport           << " " << "OUTHOST: "  << outhost << " "
 429  			<< "PROXYTYPE: "<< proxyType         << " " << "PROXYSTART: "  << bool_str(proxyStatus);
 430  		out = ss.str();
 431  	}
 432  
 433  	void BOBCommandSession::ZapCommandHandler (const char * operand, size_t len)
 434  	{
 435  		LogPrint (eLogDebug, "BOB: zap");
 436  		Terminate ();
 437  	}
 438  
 439  	void BOBCommandSession::QuitCommandHandler (const char * operand, size_t len)
 440  	{
 441  		LogPrint (eLogDebug, "BOB: quit");
 442  		m_IsOpen = false;
 443  		SendReplyOK ("Bye!");
 444  	}
 445  
 446  	void BOBCommandSession::StartCommandHandler (const char * operand, size_t len)
 447  	{
 448  		LogPrint (eLogDebug, "BOB: start ", m_Nickname);
 449  		if (m_IsActive)
 450  		{
 451  			SendReplyError ("tunnel is active");
 452  			return;
 453  		}
 454  		if (!m_Keys.GetPublic ()) // keys are set ?
 455  		{
 456  			SendReplyError("Keys must be set.");
 457  			return;
 458  		}
 459  		if (m_InPort == 0
 460  			&& m_OutHost.empty() && m_OutPort == 0)
 461  		{
 462  			SendReplyError("(inhost):inport or outhost:outport must be set.");
 463  			return;
 464  		}
 465  		if(!m_InHost.empty())
 466  		{
 467  			// TODO: FIXME: temporary validation, until hostname support is added
 468  			boost::system::error_code ec;
 469  			boost::asio::ip::make_address(m_InHost, ec);
 470  			if (ec)
 471  			{
 472  				SendReplyError("inhost must be a valid IPv4 address.");
 473  				return;
 474  			}
 475  		}
 476  		if(!m_OutHost.empty())
 477  		{
 478  			// TODO: FIXME: temporary validation, until hostname support is added
 479  			boost::system::error_code ec;
 480  			boost::asio::ip::make_address(m_OutHost, ec);
 481  			if (ec)
 482  			{
 483  				SendReplyError("outhost must be a IPv4 address.");
 484  				return;
 485  			}
 486  		}
 487  
 488  		if (!m_CurrentDestination)
 489  		{
 490  			m_CurrentDestination = std::make_shared<BOBDestination> (i2p::client::context.CreateNewLocalDestination (m_Keys, true, &m_Options), // deleted in clear command
 491  				m_Nickname, m_InHost, m_OutHost, m_InPort, m_OutPort, m_IsQuiet);
 492  			m_Owner.AddDestination (m_Nickname, m_CurrentDestination);
 493  		}
 494  		if (!m_tunnelType.has_value())
 495  		{
 496  			if (m_InPort)
 497  				m_CurrentDestination->CreateInboundTunnel (m_InPort, m_InHost);
 498  			if (m_OutPort && !m_OutHost.empty ())
 499  				m_CurrentDestination->CreateOutboundTunnel (m_OutHost, m_OutPort, m_IsQuiet);
 500  			m_CurrentDestination->Start ();
 501  		}
 502  		else
 503  		{
 504  			switch (*m_tunnelType)
 505  			{
 506  				case TunnelType::SOCKS:
 507  					try
 508  					{
 509  						auto SocksProxy = std::make_unique<i2p::proxy::SOCKSProxy>(m_Nickname, m_InHost, m_InPort,
 510  							false, m_OutHost, m_OutPort, m_CurrentDestination->GetLocalDestination());
 511  						SocksProxy->Start();
 512  						m_Owner.SetProxy(m_Nickname, std::move(SocksProxy));
 513  					}
 514  					catch (std::exception& e)
 515  					{
 516  						LogPrint(eLogCritical, "Clients: Exception in SOCKS Proxy: ", e.what());
 517  						ThrowFatal ("Unable to start SOCKS Proxy at ", m_InHost, ":", m_InPort, ": ", e.what ());
 518  					}
 519  					break;
 520  				case TunnelType::HTTP_PROXY:
 521  					try
 522  					{
 523  						auto HttpProxy = std::make_unique<i2p::proxy::HTTPProxy>(m_Nickname, m_InHost, m_InPort,
 524  							m_OutHost, true, true, m_CurrentDestination->GetLocalDestination());
 525  						HttpProxy->Start();
 526  						m_Owner.SetProxy(m_Nickname, std::move(HttpProxy));
 527  					}
 528  					catch (std::exception& e)
 529  					{
 530  						LogPrint(eLogCritical, "Clients: Exception in HTTP Proxy: ", e.what());
 531  						ThrowFatal ("Unable to start HTTP Proxy at ", m_InHost, ":", m_InPort, ": ", e.what ());
 532  					}
 533  					break;
 534  				default:
 535  					SendReplyError("Unsupported tunnel type.");
 536  					return;
 537  			}
 538  		}
 539  		SendReplyOK ("Tunnel starting");
 540  		m_IsActive = true;
 541  	}
 542  
 543  	void BOBCommandSession::StopCommandHandler (const char * operand, size_t len)
 544  	{
 545  		LogPrint (eLogDebug, "BOB: stop ", m_Nickname);
 546  		if (!m_IsActive)
 547  		{
 548  			SendReplyError ("tunnel is inactive");
 549  			return;
 550  		}
 551  		auto dest = m_Owner.FindDestination (m_Nickname);
 552  		auto proxy = m_Owner.GetProxy (m_Nickname);
 553  		if (dest)
 554  		{
 555  			dest->StopTunnels ();
 556  			SendReplyOK ("Tunnel stopping");
 557  			if (proxy)
 558  			{
 559  				m_Owner.RemoveProxy (m_Nickname);
 560  			}
 561  		}
 562  		else
 563  			SendReplyError ("tunnel not found");
 564  		m_IsActive = false;
 565  	}
 566  
 567  	void BOBCommandSession::SetNickCommandHandler (const char * operand, size_t len)
 568  	{
 569  		LogPrint (eLogDebug, "BOB: setnick ", operand);
 570  		if(*operand)
 571  		{
 572  			auto dest = m_Owner.FindDestination (operand);
 573  			if (!dest)
 574  			{
 575  				m_Nickname = operand;
 576  				SendReplyOK ({ "Nickname set to ", m_Nickname });
 577  			}
 578  			else
 579  				SendReplyError ("tunnel is active");
 580  		}
 581  		else
 582  			SendReplyError ("no nickname has been set");
 583  	}
 584  
 585  	void BOBCommandSession::GetNickCommandHandler (const char * operand, size_t len)
 586  	{
 587  		LogPrint (eLogDebug, "BOB: getnick ", operand);
 588  		if(*operand)
 589  		{
 590  			m_CurrentDestination = m_Owner.FindDestination (operand);
 591  			auto proxy = m_Owner.GetProxy (operand);
 592  			if (m_CurrentDestination)
 593  			{
 594  				m_Keys = m_CurrentDestination->GetKeys ();
 595  				m_IsActive = m_CurrentDestination->IsRunning ();
 596  				if(proxy)
 597  					m_IsActive = true;
 598  				m_Nickname = operand;
 599  			}
 600  			if (m_Nickname == operand)
 601  				SendReplyOK ({"Nickname set to ", m_Nickname});
 602  			else
 603  				SendReplyError ("no nickname has been set");
 604  		}
 605  		else
 606  			SendReplyError ("no nickname has been set");
 607  	}
 608  
 609  	void BOBCommandSession::NewkeysCommandHandler (const char * operand, size_t len)
 610  	{
 611  		LogPrint (eLogDebug, "BOB: newkeys");
 612  		i2p::data::SigningKeyType signatureType = i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519;
 613  		i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL;
 614  		if (*operand)
 615  		{
 616  			try
 617  			{
 618  				char * operand1 = (char *)strchr (operand, ' ');
 619  				if (operand1)
 620  				{
 621  					*operand1 = 0; operand1++;
 622  					cryptoType = std::stoi(operand1);
 623  				}
 624  				signatureType = std::stoi(operand);
 625  			}
 626  			catch (std::invalid_argument& ex)
 627  			{
 628  				LogPrint (eLogWarning, "BOB: Error on newkeys: ", ex.what ());
 629  			}
 630  		}
 631  
 632  
 633  		m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (signatureType, cryptoType, true);
 634  		SendReplyOK (m_Keys.GetPublic ()->ToBase64 ());
 635  	}
 636  
 637  	void BOBCommandSession::SetkeysCommandHandler (const char * operand, size_t len)
 638  	{
 639  		LogPrint (eLogDebug, "BOB: setkeys ", operand);
 640  		if (*operand && m_Keys.FromBase64 (operand))
 641  			SendReplyOK (m_Keys.GetPublic ()->ToBase64 ());
 642  		else
 643  			SendReplyError ("invalid keys");
 644  	}
 645  
 646  	void BOBCommandSession::GetkeysCommandHandler (const char * operand, size_t len)
 647  	{
 648  		LogPrint (eLogDebug, "BOB: getkeys");
 649  		if (m_Keys.GetPublic ()) // keys are set ?
 650  			SendReplyOK (m_Keys.ToBase64 ());
 651  		else
 652  			SendReplyError ("keys are not set");
 653  	}
 654  
 655  	void BOBCommandSession::GetdestCommandHandler (const char * operand, size_t len)
 656  	{
 657  		LogPrint (eLogDebug, "BOB: getdest");
 658  		if (m_Keys.GetPublic ()) // keys are set ?
 659  			SendReplyOK (m_Keys.GetPublic ()->ToBase64 ());
 660  		else
 661  			SendReplyError ("keys are not set");
 662  	}
 663  
 664  	void BOBCommandSession::OuthostCommandHandler (const char * operand, size_t len)
 665  	{
 666  		LogPrint (eLogDebug, "BOB: outhost ", operand);
 667  		if (*operand)
 668  		{
 669  			m_OutHost = operand;
 670  			SendReplyOK ("outhost set");
 671  		}
 672  		else
 673  			SendReplyError ("empty outhost");
 674  	}
 675  
 676  	void BOBCommandSession::OutportCommandHandler (const char * operand, size_t len)
 677  	{
 678  		LogPrint (eLogDebug, "BOB: outport ", operand);
 679  		if (*operand)
 680  		{
 681  			int port = std::stoi(operand);
 682  			if (port >= 0 && port < 65536)
 683  			{
 684  				m_OutPort = port;
 685  				SendReplyOK ("outbound port set");
 686  			}
 687  			else
 688  				SendReplyError ("port out of range");
 689  		}
 690  		else
 691  			SendReplyError ("empty outport");
 692  	}
 693  
 694  	void BOBCommandSession::InhostCommandHandler (const char * operand, size_t len)
 695  	{
 696  		LogPrint (eLogDebug, "BOB: inhost ", operand);
 697  		if (*operand)
 698  		{
 699  			m_InHost = operand;
 700  			SendReplyOK ("inhost set");
 701  		}
 702  		else
 703  			SendReplyError ("empty inhost");
 704  	}
 705  
 706  	void BOBCommandSession::InportCommandHandler (const char * operand, size_t len)
 707  	{
 708  		LogPrint (eLogDebug, "BOB: inport ", operand);
 709  		if (*operand)
 710  		{
 711  			int port = std::stoi(operand);
 712  			if (port >= 0 && port < 65536)
 713  			{
 714  				m_InPort = port;
 715  				SendReplyOK ("inbound port set");
 716  			}
 717  			else
 718  				SendReplyError ("port out of range");
 719  		}
 720  		else
 721  			SendReplyError ("empty inport");
 722  	}
 723  
 724  	void BOBCommandSession::QuietCommandHandler (const char * operand, size_t len)
 725  	{
 726  		LogPrint (eLogDebug, "BOB: quiet");
 727  		if (m_Nickname.length () > 0)
 728  		{
 729  			if (!m_IsActive)
 730  			{
 731  				m_IsQuiet = true;
 732  				SendReplyOK ("Quiet set");
 733  			}
 734  			else
 735  				SendReplyError ("tunnel is active");
 736  		}
 737  		else
 738  			SendReplyError ("no nickname has been set");
 739  	}
 740  
 741  	void BOBCommandSession::LookupCommandHandler (const char * operand, size_t len)
 742  	{
 743  		LogPrint (eLogDebug, "BOB: lookup ", operand);
 744  		if (*operand)
 745  		{
 746  			auto addr = context.GetAddressBook ().GetAddress (operand);
 747  			if (!addr)
 748  			{
 749  				SendReplyError ("Address Not found");
 750  				return;
 751  			}
 752  			auto localDestination = (m_CurrentDestination && m_CurrentDestination->IsRunning ()) ?
 753  				m_CurrentDestination->GetLocalDestination () : i2p::client::context.GetSharedLocalDestination ();
 754  			if (!localDestination)
 755  			{
 756  				SendReplyError ("No local destination");
 757  				return;
 758  			}
 759  			if (addr->IsIdentHash ())
 760  			{
 761  				// we might have leaseset already
 762  				auto leaseSet = localDestination->FindLeaseSet (addr->identHash);
 763  				if (leaseSet)
 764  				{
 765  					SendReplyOK (leaseSet->GetIdentity ()->ToBase64 ());
 766  					return;
 767  				}
 768  			}
 769  			// trying to request
 770  			auto s = shared_from_this ();
 771  			auto requstCallback = [s](std::shared_ptr<i2p::data::LeaseSet> ls)
 772  				{
 773  					if (ls)
 774  						s->SendReplyOK (ls->GetIdentity ()->ToBase64 ());
 775  					else
 776  						s->SendReplyError ("LeaseSet Not found");
 777  				};
 778  			if (addr->IsIdentHash ())
 779  				localDestination->RequestDestination (addr->identHash, requstCallback);
 780  			else
 781  				localDestination->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey, requstCallback);
 782  		}
 783  		else
 784  			SendReplyError ("empty lookup address");
 785  	}
 786  
 787  	void BOBCommandSession::LookupLocalCommandHandler (const char * operand, size_t len)
 788  	{
 789  		LogPrint (eLogDebug, "BOB: lookup local ", operand);
 790  		if (*operand)
 791  		{
 792  			auto addr = context.GetAddressBook ().GetAddress (operand);
 793  			if (!addr)
 794  			{
 795  				SendReplyError ("Address Not found");
 796  				return;
 797  			}
 798  			auto ls = i2p::data::netdb.FindLeaseSet (addr->identHash);
 799  			if (ls)
 800  				SendReplyOK (ls->GetIdentity ()->ToBase64 ());
 801  			else
 802  				SendReplyError ("Local LeaseSet Not found");
 803  		}
 804  		else
 805  			SendReplyError ("empty lookup address");
 806  	}
 807  
 808  	void BOBCommandSession::PingCommandHandler (const char * operand, size_t len)
 809  	{
 810  		LogPrint (eLogDebug, "BOB: ping ", operand);
 811  		if (*operand)
 812  		{
 813  			auto addr = context.GetAddressBook ().GetAddress (operand);
 814  			if (!addr)
 815  			{
 816  				SendReplyError ("Address Not found");
 817  				return;
 818  			}
 819  			auto localDestination = (m_CurrentDestination && m_CurrentDestination->IsRunning ()) ?
 820  				m_CurrentDestination->GetLocalDestination () : i2p::client::context.GetSharedLocalDestination ();
 821  			if (!localDestination)
 822  			{
 823  				SendReplyError ("No local destination");
 824  				return;
 825  			}
 826  			if (addr->IsIdentHash ())
 827  			{
 828  				// we might have leaseset already
 829  				auto leaseSet = localDestination->FindLeaseSet (addr->identHash);
 830  				if (leaseSet)
 831  				{
 832  					boost::asio::post (localDestination->GetService (),
 833  						std::bind (&BOBCommandSession::SendPing, shared_from_this (), leaseSet));
 834  					return;
 835  				}
 836  			}
 837  			// request LeaseSet
 838  			if (addr->IsIdentHash ())
 839  				localDestination->RequestDestination (addr->identHash,
 840  					std::bind (&BOBCommandSession::SendPing, shared_from_this (), std::placeholders::_1));
 841  			else
 842  				localDestination->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey,
 843  					std::bind (&BOBCommandSession::SendPing, shared_from_this (), std::placeholders::_1));
 844  		}
 845  		else
 846  			SendReplyError ("empty ping address");
 847  	}
 848  
 849  	void BOBCommandSession::SendPing (std::shared_ptr<const i2p::data::LeaseSet> ls)
 850  	{
 851  		if (!ls)
 852  		{
 853  			SendReplyError ("LeaseSet Not found");
 854  			return;
 855  		}
 856  		auto localDestination = (m_CurrentDestination && m_CurrentDestination->IsRunning ()) ?
 857  				m_CurrentDestination->GetLocalDestination () : i2p::client::context.GetSharedLocalDestination ();
 858  		if (!localDestination)
 859  		{
 860  			SendReplyError ("No local destination");
 861  			return;
 862  		}
 863  		auto streamingDestination = localDestination->GetStreamingDestination ();
 864  		if (!streamingDestination)
 865  		{
 866  			SendReplyError ("No streaming destination");
 867  			return;
 868  		}
 869  		auto timer = std::make_shared<boost::asio::deadline_timer>(localDestination->GetService ());
 870  		timer->expires_from_now (boost::posix_time::milliseconds(BOB_PING_TIMEOUT));
 871  		timer->async_wait ([streamingDestination, s = shared_from_this ()](const boost::system::error_code& ecode)
 872  		{
 873  			if (ecode != boost::asio::error::operation_aborted)
 874  			{
 875  				LogPrint (eLogDebug, "BOB: Pong not received after ", BOB_PING_TIMEOUT, " millliseconds");
 876  				streamingDestination->ResetPongHandler ();
 877  				s->SendReplyError ("timeout");
 878  			}
 879  		});
 880  		auto ts = i2p::util::GetMillisecondsSinceEpoch ();
 881  		streamingDestination->SetPongHandler (
 882  		    [streamingDestination, timer, ts, s = shared_from_this ()](const i2p::data::IdentHash * from)
 883  			{
 884  				int t = i2p::util::GetMillisecondsSinceEpoch () - ts;
 885  				if (t < 0) t = 0;
 886  				streamingDestination->ResetPongHandler ();
 887  				timer->cancel ();
 888  				if (from)
 889  					s->SendReplyOK ({"pong ", "from ", from->ToBase32(), ".b32.i2p: time=", std::to_string (t), " ms"});
 890  				else
 891  					s->SendReplyOK ({"pong: time=", std::to_string (t), " ms"});
 892  			});
 893  		streamingDestination->SendPing (ls);
 894  	}
 895  
 896  	void BOBCommandSession::ClearCommandHandler (const char * operand, size_t len)
 897  	{
 898  		LogPrint (eLogDebug, "BOB: clear");
 899  		m_Owner.DeleteDestination (m_Nickname);
 900  		m_Nickname = "";
 901  		SendReplyOK ("cleared");
 902  	}
 903  
 904  	void BOBCommandSession::ListCommandHandler (const char * operand, size_t len)
 905  	{
 906  		LogPrint (eLogDebug, "BOB: list");
 907  		std::string statusLine;
 908  		bool sentCurrent = false;
 909  		const auto& destinations = m_Owner.GetDestinations ();
 910  		for (const auto& it: destinations)
 911  		{
 912  			BuildStatusLine(false, it.second, statusLine);
 913  			SendRaw(statusLine);
 914  			if(m_Nickname.compare(it.second->GetNickname()) == 0)
 915  				sentCurrent = true;
 916  		}
 917  		if(!sentCurrent && !m_Nickname.empty())
 918  		{
 919  			// add the current tunnel to the list.
 920  			// this is for the incomplete tunnel which has not been started yet.
 921  			BuildStatusLine(true, m_CurrentDestination, statusLine);
 922  			SendRaw(statusLine);
 923  		}
 924  		SendReplyOK ("Listing done");
 925  	}
 926  
 927  	void BOBCommandSession::OptionCommandHandler (const char * operand, size_t len)
 928  	{
 929  		LogPrint (eLogDebug, "BOB: option ", operand);
 930  		const char * value = strchr (operand, '=');
 931  		if (value)
 932  		{
 933  			*(const_cast<char *>(value)) = 0;
 934  			m_Options.Insert (operand, value + 1);
 935  			SendReplyOK ({ "option ", operand, " set to ", value + 1 });
 936  			*(const_cast<char *>(value)) = '=';
 937  		}
 938  		else
 939  			SendReplyError ("malformed");
 940  	}
 941  
 942  	void BOBCommandSession::StatusCommandHandler (const char * operand, size_t len)
 943  	{
 944  		LogPrint (eLogDebug, "BOB: status ", operand);
 945  		const std::string name = operand;
 946  		std::string statusLine;
 947  
 948  		// always prefer destination
 949  		auto dest = m_Owner.FindDestination(name);
 950  		if(dest)
 951  		{
 952  			// tunnel destination exists
 953  			BuildStatusLine(false, dest, statusLine);
 954  			SendReplyOK(statusLine);
 955  		}
 956  		else
 957  		{
 958  			if(m_Nickname == name && !name.empty())
 959  			{
 960  				// tunnel is incomplete / has not been started yet
 961  				BuildStatusLine(true, nullptr, statusLine);
 962  				SendReplyOK(statusLine);
 963  			}
 964  			else
 965  			{
 966  				SendReplyError("no nickname has been set");
 967  			}
 968  		}
 969  	}
 970  	void BOBCommandSession::HelpCommandHandler (const char * operand, size_t len)
 971  	{
 972  		auto helpStrings = m_Owner.GetHelpStrings();
 973  		if(!*operand)
 974  		{
 975  			std::stringstream ss;
 976  			ss << "COMMANDS:";
 977  			for (auto const& x : helpStrings)
 978  			{
 979  				ss << " " << x.first;
 980  			}
 981  			SendReplyOK(ss.str ());
 982  		}
 983  		else
 984  		{
 985  			auto it = helpStrings.find(operand);
 986  			if (it != helpStrings.end ())
 987  			{
 988  				SendReplyOK(it->second);
 989  				return;
 990  			}
 991  			SendReplyError("No such command");
 992  		}
 993  	}
 994  
 995  	void BOBCommandSession::SetTunnelTypeCommandHandler (const char * operand, size_t len)
 996  	{
 997  		std::string_view sv(operand, len);
 998  		LogPrint (eLogDebug, "BOB: settunneltype ", operand);
 999  			if (sv == "socks")
1000  			{
1001  				m_tunnelType = TunnelType::SOCKS;
1002  				SendReplyOK ("tunnel type set to SOCKS");
1003  			}
1004  			else if (sv == "httpproxy")
1005  			{
1006  				m_tunnelType = TunnelType::HTTP_PROXY;
1007  				SendReplyOK ("tunnel type set to HTTP proxy");
1008  			}
1009  			else
1010  			{
1011  				m_tunnelType.reset();
1012  				SendReplyError ("no tunnel type has been set");
1013  			}
1014  	}
1015  
1016  	BOBCommandChannel::BOBCommandChannel (const std::string& address, uint16_t port):
1017  		RunnableService ("BOB"),
1018  		m_Acceptor (GetIOService (), boost::asio::ip::tcp::endpoint(boost::asio::ip::make_address(address), port))
1019  	{
1020  		// command -> handler
1021  		m_CommandHandlers[BOB_COMMAND_ZAP] = &BOBCommandSession::ZapCommandHandler;
1022  		m_CommandHandlers[BOB_COMMAND_QUIT] = &BOBCommandSession::QuitCommandHandler;
1023  		m_CommandHandlers[BOB_COMMAND_START] = &BOBCommandSession::StartCommandHandler;
1024  		m_CommandHandlers[BOB_COMMAND_STOP] = &BOBCommandSession::StopCommandHandler;
1025  		m_CommandHandlers[BOB_COMMAND_SETNICK] = &BOBCommandSession::SetNickCommandHandler;
1026  		m_CommandHandlers[BOB_COMMAND_GETNICK] = &BOBCommandSession::GetNickCommandHandler;
1027  		m_CommandHandlers[BOB_COMMAND_NEWKEYS] = &BOBCommandSession::NewkeysCommandHandler;
1028  		m_CommandHandlers[BOB_COMMAND_GETKEYS] = &BOBCommandSession::GetkeysCommandHandler;
1029  		m_CommandHandlers[BOB_COMMAND_SETKEYS] = &BOBCommandSession::SetkeysCommandHandler;
1030  		m_CommandHandlers[BOB_COMMAND_GETDEST] = &BOBCommandSession::GetdestCommandHandler;
1031  		m_CommandHandlers[BOB_COMMAND_OUTHOST] = &BOBCommandSession::OuthostCommandHandler;
1032  		m_CommandHandlers[BOB_COMMAND_OUTPORT] = &BOBCommandSession::OutportCommandHandler;
1033  		m_CommandHandlers[BOB_COMMAND_INHOST] = &BOBCommandSession::InhostCommandHandler;
1034  		m_CommandHandlers[BOB_COMMAND_INPORT] = &BOBCommandSession::InportCommandHandler;
1035  		m_CommandHandlers[BOB_COMMAND_QUIET] = &BOBCommandSession::QuietCommandHandler;
1036  		m_CommandHandlers[BOB_COMMAND_LOOKUP] = &BOBCommandSession::LookupCommandHandler;
1037  		m_CommandHandlers[BOB_COMMAND_LOOKUP_LOCAL] = &BOBCommandSession::LookupLocalCommandHandler;
1038  		m_CommandHandlers[BOB_COMMAND_PING] = &BOBCommandSession::PingCommandHandler;
1039  		m_CommandHandlers[BOB_COMMAND_CLEAR] = &BOBCommandSession::ClearCommandHandler;
1040  		m_CommandHandlers[BOB_COMMAND_LIST] = &BOBCommandSession::ListCommandHandler;
1041  		m_CommandHandlers[BOB_COMMAND_OPTION] = &BOBCommandSession::OptionCommandHandler;
1042  		m_CommandHandlers[BOB_COMMAND_STATUS] = &BOBCommandSession::StatusCommandHandler;
1043  		m_CommandHandlers[BOB_COMMAND_HELP] = &BOBCommandSession::HelpCommandHandler;
1044  		m_CommandHandlers[BOB_COMMAND_SETTUNNELTYPE] = &BOBCommandSession::SetTunnelTypeCommandHandler;
1045  		// command -> help string
1046  		m_HelpStrings[BOB_COMMAND_ZAP] = BOB_HELP_ZAP;
1047  		m_HelpStrings[BOB_COMMAND_QUIT] = BOB_HELP_QUIT;
1048  		m_HelpStrings[BOB_COMMAND_START] = BOB_HELP_START;
1049  		m_HelpStrings[BOB_COMMAND_STOP] = BOB_HELP_STOP;
1050  		m_HelpStrings[BOB_COMMAND_SETNICK] = BOB_HELP_SETNICK;
1051  		m_HelpStrings[BOB_COMMAND_GETNICK] = BOB_HELP_GETNICK;
1052  		m_HelpStrings[BOB_COMMAND_NEWKEYS] = BOB_HELP_NEWKEYS;
1053  		m_HelpStrings[BOB_COMMAND_GETKEYS] = BOB_HELP_GETKEYS;
1054  		m_HelpStrings[BOB_COMMAND_SETKEYS] = BOB_HELP_SETKEYS;
1055  		m_HelpStrings[BOB_COMMAND_GETDEST] = BOB_HELP_GETDEST;
1056  		m_HelpStrings[BOB_COMMAND_OUTHOST] = BOB_HELP_OUTHOST;
1057  		m_HelpStrings[BOB_COMMAND_OUTPORT] = BOB_HELP_OUTPORT;
1058  		m_HelpStrings[BOB_COMMAND_INHOST] = BOB_HELP_INHOST;
1059  		m_HelpStrings[BOB_COMMAND_INPORT] = BOB_HELP_INPORT;
1060  		m_HelpStrings[BOB_COMMAND_QUIET] = BOB_HELP_QUIET;
1061  		m_HelpStrings[BOB_COMMAND_LOOKUP] = BOB_HELP_LOOKUP;
1062  		m_HelpStrings[BOB_COMMAND_LOOKUP_LOCAL] = BOB_HELP_LOOKUP_LOCAL;
1063  		m_HelpStrings[BOB_COMMAND_CLEAR] = BOB_HELP_CLEAR;
1064  		m_HelpStrings[BOB_COMMAND_LIST] = BOB_HELP_LIST;
1065  		m_HelpStrings[BOB_COMMAND_OPTION] = BOB_HELP_OPTION;
1066  		m_HelpStrings[BOB_COMMAND_STATUS] = BOB_HELP_STATUS;
1067  		m_HelpStrings[BOB_COMMAND_SETTUNNELTYPE] = BOB_HELP_SETTUNNELTYPE;
1068  		m_HelpStrings[BOB_COMMAND_PING] = BOB_HELP_PING;
1069  		m_HelpStrings[BOB_COMMAND_HELP] = BOB_HELP_HELP;
1070  	}
1071  
1072  	BOBCommandChannel::~BOBCommandChannel ()
1073  	{
1074  		if (IsRunning ())
1075  			Stop ();
1076  	}
1077  
1078  	void BOBCommandChannel::Start ()
1079  	{
1080  		Accept ();
1081  		StartIOService ();
1082  	}
1083  
1084  	void BOBCommandChannel::Stop ()
1085  	{
1086  		for (auto& it: m_Destinations)
1087  			it.second->Stop ();
1088  		m_Acceptor.cancel ();
1089  		StopIOService ();
1090  	}
1091  
1092  	void BOBCommandChannel::AddDestination (const std::string& name, std::shared_ptr<BOBDestination> dest)
1093  	{
1094  		m_Destinations.emplace (name, dest);
1095  	}
1096  
1097  	void BOBCommandChannel::DeleteDestination (const std::string& name)
1098  	{
1099  		auto it = m_Destinations.find (name);
1100  		if (it != m_Destinations.end ())
1101  		{
1102  			it->second->Stop ();
1103  			m_Destinations.erase (it);
1104  		}
1105  	}
1106  
1107  	std::shared_ptr<BOBDestination> BOBCommandChannel::FindDestination (const std::string& name)
1108  	{
1109  		auto it = m_Destinations.find (name);
1110  		if (it != m_Destinations.end ())
1111  			return it->second;
1112  		return nullptr;
1113  	}
1114  
1115  	void BOBCommandChannel::SetProxy (const std::string& name, std::unique_ptr<I2PService> proxy)
1116  	{
1117  		m_proxy[name] = std::move(proxy);
1118  	}
1119  
1120  	const I2PService* BOBCommandChannel::GetProxy(const std::string& name) const
1121  	{
1122  		auto it = m_proxy.find(name);
1123  		if (it != m_proxy.end() && it->second)
1124  			return it->second.get();
1125  		return nullptr;
1126  	}
1127  
1128  	void BOBCommandChannel::RemoveProxy(const std::string& name)
1129  	{
1130  		auto it = m_proxy.find (name);
1131  		if (it != m_proxy.end ())
1132  		{
1133  			m_proxy.erase (it);
1134  		}
1135  	}
1136  
1137  	void BOBCommandChannel::Accept ()
1138  	{
1139  		auto newSession = std::make_shared<BOBCommandSession> (*this);
1140  		m_Acceptor.async_accept (newSession->GetSocket (), std::bind (&BOBCommandChannel::HandleAccept, this,
1141  			std::placeholders::_1, newSession));
1142  	}
1143  
1144  	void BOBCommandChannel::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<BOBCommandSession> session)
1145  	{
1146  		if (ecode != boost::asio::error::operation_aborted)
1147  			Accept ();
1148  
1149  		if (!ecode)
1150  		{
1151  			LogPrint (eLogInfo, "BOB: New command connection from ", session->GetSocket ().remote_endpoint ());
1152  			session->SendVersion ();
1153  		}
1154  		else
1155  			LogPrint (eLogError, "BOB: Accept error: ", ecode.message ());
1156  	}
1157  }
1158  }