Browse Source

drop websockets support

Signed-off-by: R4SAS <r4sas@i2pmail.org>
pull/1477/head
R4SAS 5 years ago
parent
commit
00db527377
Signed by: r4sas
GPG Key ID: 66F6C87B98EBCFE2
  1. 4
      Makefile
  2. 11
      build/CMakeLists.txt
  3. 29
      daemon/Daemon.cpp
  4. 8
      libi2pd/Config.cpp
  5. 19
      libi2pd/Event.cpp
  6. 7
      libi2pd/Event.h
  7. 6
      libi2pd/NTCPSession.cpp
  8. 6
      libi2pd/SSUData.cpp
  9. 16
      libi2pd/Transports.cpp
  10. 21
      libi2pd/Tunnel.cpp
  11. 14
      libi2pd/Tunnel.h
  12. 15
      libi2pd/TunnelPool.cpp
  13. 488
      libi2pd_client/WebSocks.cpp
  14. 195
      libi2pd_client/Websocket.cpp
  15. 28
      libi2pd_client/Websocket.h
  16. 115
      qt/i2pd_qt/generalsettingswidget.ui
  17. 2
      qt/i2pd_qt/i2pd_qt.pro
  18. 4
      qt/i2pd_qt/mainwindow.cpp

4
Makefile

@ -27,10 +27,6 @@ else
LD_DEBUG = -s LD_DEBUG = -s
endif endif
ifeq ($(WEBSOCKETS),1)
NEEDED_CXXFLAGS += -DWITH_EVENTS
endif
ifneq (, $(findstring darwin, $(SYS))) ifneq (, $(findstring darwin, $(SYS)))
DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp
ifeq ($(HOMEBREW),1) ifeq ($(HOMEBREW),1)

11
build/CMakeLists.txt

@ -20,7 +20,6 @@ option(WITH_MESHNET "Build for cjdns test network" OFF)
option(WITH_ADDRSANITIZER "Build with address sanitizer unix only" OFF) option(WITH_ADDRSANITIZER "Build with address sanitizer unix only" OFF)
option(WITH_THREADSANITIZER "Build with thread sanitizer unix only" OFF) option(WITH_THREADSANITIZER "Build with thread sanitizer unix only" OFF)
option(WITH_I2LUA "Build for i2lua" OFF) option(WITH_I2LUA "Build for i2lua" OFF)
option(WITH_WEBSOCKETS "Build with websocket ui" OFF)
# paths # paths
set ( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules" ) set ( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules" )
@ -86,11 +85,6 @@ set (LIBI2PD_SRC
"${LIBI2PD_SRC_DIR}/ECIESX25519AEADRatchetSession.cpp" "${LIBI2PD_SRC_DIR}/ECIESX25519AEADRatchetSession.cpp"
) )
if (WITH_WEBSOCKETS)
add_definitions(-DWITH_EVENTS)
find_package(websocketpp REQUIRED)
endif ()
if (WIN32 OR MSYS) if (WIN32 OR MSYS)
list (APPEND LIBI2PD_SRC "${CMAKE_SOURCE_DIR}/I2PEndian.cpp") list (APPEND LIBI2PD_SRC "${CMAKE_SOURCE_DIR}/I2PEndian.cpp")
endif () endif ()
@ -127,10 +121,6 @@ set (CLIENT_SRC
"${LIBI2PD_CLIENT_SRC_DIR}/WebSocks.cpp" "${LIBI2PD_CLIENT_SRC_DIR}/WebSocks.cpp"
) )
if(WITH_WEBSOCKETS)
list (APPEND CLIENT_SRC "${LIBI2PD_CLIENT_SRC_DIR}/Websocket.cpp")
endif ()
add_library(libi2pdclient ${CLIENT_SRC}) add_library(libi2pdclient ${CLIENT_SRC})
set_target_properties(libi2pdclient PROPERTIES PREFIX "") set_target_properties(libi2pdclient PROPERTIES PREFIX "")
@ -427,7 +417,6 @@ message(STATUS " MESHNET : ${WITH_MESHNET}")
message(STATUS " ADDRSANITIZER : ${WITH_ADDRSANITIZER}") message(STATUS " ADDRSANITIZER : ${WITH_ADDRSANITIZER}")
message(STATUS " THREADSANITIZER : ${WITH_THREADSANITIZER}") message(STATUS " THREADSANITIZER : ${WITH_THREADSANITIZER}")
message(STATUS " I2LUA : ${WITH_I2LUA}") message(STATUS " I2LUA : ${WITH_I2LUA}")
message(STATUS " WEBSOCKETS : ${WITH_WEBSOCKETS}")
message(STATUS "---------------------------------------") message(STATUS "---------------------------------------")
#Handle paths nicely #Handle paths nicely

29
daemon/Daemon.cpp

@ -26,9 +26,6 @@
#include "Timestamp.h" #include "Timestamp.h"
#include "util.h" #include "util.h"
#include "Event.h"
#include "Websocket.h"
namespace i2p namespace i2p
{ {
namespace util namespace util
@ -43,9 +40,6 @@ namespace i2p
std::unique_ptr<i2p::client::I2PControlService> m_I2PControlService; std::unique_ptr<i2p::client::I2PControlService> m_I2PControlService;
std::unique_ptr<i2p::transport::UPnP> UPnP; std::unique_ptr<i2p::transport::UPnP> UPnP;
std::unique_ptr<i2p::util::NTPTimeSync> m_NTPSync; std::unique_ptr<i2p::util::NTPTimeSync> m_NTPSync;
#ifdef WITH_EVENTS
std::unique_ptr<i2p::event::WebsocketServer> m_WebsocketServer;
#endif
}; };
Daemon_Singleton::Daemon_Singleton() : isDaemon(false), running(true), d(*new Daemon_Singleton_Private()) {} Daemon_Singleton::Daemon_Singleton() : isDaemon(false), running(true), d(*new Daemon_Singleton_Private()) {}
@ -273,6 +267,7 @@ namespace i2p
if(!restricted) if(!restricted)
LogPrint(eLogError, "Daemon: no trusted routers of families specififed"); LogPrint(eLogError, "Daemon: no trusted routers of families specififed");
} }
bool hidden; i2p::config::GetOption("trust.hidden", hidden); bool hidden; i2p::config::GetOption("trust.hidden", hidden);
if (hidden) if (hidden)
{ {
@ -344,26 +339,11 @@ namespace i2p
d.m_I2PControlService = std::unique_ptr<i2p::client::I2PControlService>(new i2p::client::I2PControlService (i2pcpAddr, i2pcpPort)); d.m_I2PControlService = std::unique_ptr<i2p::client::I2PControlService>(new i2p::client::I2PControlService (i2pcpAddr, i2pcpPort));
d.m_I2PControlService->Start (); d.m_I2PControlService->Start ();
} }
#ifdef WITH_EVENTS
bool websocket; i2p::config::GetOption("websockets.enabled", websocket);
if(websocket) {
std::string websocketAddr; i2p::config::GetOption("websockets.address", websocketAddr);
uint16_t websocketPort; i2p::config::GetOption("websockets.port", websocketPort);
LogPrint(eLogInfo, "Daemon: starting Websocket server at ", websocketAddr, ":", websocketPort);
d.m_WebsocketServer = std::unique_ptr<i2p::event::WebsocketServer>(new i2p::event::WebsocketServer (websocketAddr, websocketPort));
d.m_WebsocketServer->Start();
i2p::event::core.SetListener(d.m_WebsocketServer->ToListener());
}
#endif
return true; return true;
} }
bool Daemon_Singleton::stop() bool Daemon_Singleton::stop()
{ {
#ifdef WITH_EVENTS
i2p::event::core.SetListener(nullptr);
#endif
LogPrint(eLogInfo, "Daemon: shutting down"); LogPrint(eLogInfo, "Daemon: shutting down");
LogPrint(eLogInfo, "Daemon: stopping Client"); LogPrint(eLogInfo, "Daemon: stopping Client");
i2p::client::context.Stop(); i2p::client::context.Stop();
@ -397,13 +377,6 @@ namespace i2p
d.m_I2PControlService->Stop (); d.m_I2PControlService->Stop ();
d.m_I2PControlService = nullptr; d.m_I2PControlService = nullptr;
} }
#ifdef WITH_EVENTS
if (d.m_WebsocketServer) {
LogPrint(eLogInfo, "Daemon: stopping Websocket server");
d.m_WebsocketServer->Stop();
d.m_WebsocketServer = nullptr;
}
#endif
i2p::crypto::TerminateCrypto (); i2p::crypto::TerminateCrypto ();
i2p::log::Logger().Stop(); i2p::log::Logger().Stop();

8
libi2pd/Config.cpp

@ -218,13 +218,6 @@ namespace config {
("trust.hidden", value<bool>()->default_value(false), "Should we hide our router from other routers?") ("trust.hidden", value<bool>()->default_value(false), "Should we hide our router from other routers?")
; ;
options_description websocket("Websocket Options");
websocket.add_options()
("websockets.enabled", value<bool>()->default_value(false), "Enable websocket server")
("websockets.address", value<std::string>()->default_value("127.0.0.1"), "Address to bind websocket server on")
("websockets.port", value<uint16_t>()->default_value(7666), "Port to bind websocket server on")
;
options_description exploratory("Exploratory Options"); options_description exploratory("Exploratory Options");
exploratory.add_options() exploratory.add_options()
("exploratory.inbound.length", value<int>()->default_value(2), "Exploratory inbound tunnel length") ("exploratory.inbound.length", value<int>()->default_value(2), "Exploratory inbound tunnel length")
@ -274,7 +267,6 @@ namespace config {
.add(reseed) .add(reseed)
.add(addressbook) .add(addressbook)
.add(trust) .add(trust)
.add(websocket)
.add(exploratory) .add(exploratory)
.add(ntcp2) .add(ntcp2)
.add(nettime) .add(nettime)

19
libi2pd/Event.cpp

@ -5,10 +5,6 @@ namespace i2p
{ {
namespace event namespace event
{ {
#ifdef WITH_EVENTS
EventCore core;
#endif
void EventCore::SetListener(EventListener * l) void EventCore::SetListener(EventListener * l)
{ {
m_listener = l; m_listener = l;
@ -44,18 +40,3 @@ namespace i2p
} }
} }
} }
void QueueIntEvent(const std::string & type, const std::string & ident, uint64_t val)
{
#ifdef WITH_EVENTS
i2p::event::core.CollectEvent(type, ident, val);
#endif
}
void EmitEvent(const EventType & e)
{
#if WITH_EVENTS
i2p::event::core.QueueEvent(e);
#endif
}

7
libi2pd/Event.h

@ -41,13 +41,6 @@ namespace i2p
std::map<std::string, CollectedEvent> m_collected; std::map<std::string, CollectedEvent> m_collected;
EventListener * m_listener = nullptr; EventListener * m_listener = nullptr;
}; };
#ifdef WITH_EVENTS
extern EventCore core;
#endif
} }
} }
void QueueIntEvent(const std::string & type, const std::string & ident, uint64_t val);
void EmitEvent(const EventType & ev);
#endif #endif

6
libi2pd/NTCPSession.cpp

@ -14,9 +14,6 @@
#include "NTCPSession.h" #include "NTCPSession.h"
#include "HTTP.h" #include "HTTP.h"
#include "util.h" #include "util.h"
#ifdef WITH_EVENTS
#include "Event.h"
#endif
using namespace i2p::crypto; using namespace i2p::crypto;
@ -649,9 +646,6 @@ namespace transport
{ {
if (!m_NextMessage->IsExpired ()) if (!m_NextMessage->IsExpired ())
{ {
#ifdef WITH_EVENTS
QueueIntEvent("transport.recvmsg", GetIdentHashBase64(), 1);
#endif
m_Handler.PutNextMessage (m_NextMessage); m_Handler.PutNextMessage (m_NextMessage);
} }
else else

6
libi2pd/SSUData.cpp

@ -5,9 +5,6 @@
#include "NetDb.hpp" #include "NetDb.hpp"
#include "SSU.h" #include "SSU.h"
#include "SSUData.h" #include "SSUData.h"
#ifdef WITH_EVENTS
#include "Event.h"
#endif
namespace i2p namespace i2p
{ {
@ -241,9 +238,6 @@ namespace transport
m_LastMessageReceivedTime = i2p::util::GetSecondsSinceEpoch (); m_LastMessageReceivedTime = i2p::util::GetSecondsSinceEpoch ();
if (!msg->IsExpired ()) if (!msg->IsExpired ())
{ {
#ifdef WITH_EVENTS
QueueIntEvent("transport.recvmsg", m_Session.GetIdentHashBase64(), 1);
#endif
m_Handler.PutNextMessage (msg); m_Handler.PutNextMessage (msg);
} }
else else

16
libi2pd/Transports.cpp

@ -6,10 +6,6 @@
#include "Transports.h" #include "Transports.h"
#include "Config.h" #include "Config.h"
#include "HTTP.h" #include "HTTP.h"
#ifdef WITH_EVENTS
#include "Event.h"
#include "util.h"
#endif
using namespace i2p::data; using namespace i2p::data;
@ -346,9 +342,6 @@ namespace transport
void Transports::SendMessages (const i2p::data::IdentHash& ident, const std::vector<std::shared_ptr<i2p::I2NPMessage> >& msgs) void Transports::SendMessages (const i2p::data::IdentHash& ident, const std::vector<std::shared_ptr<i2p::I2NPMessage> >& msgs)
{ {
#ifdef WITH_EVENTS
QueueIntEvent("transport.send", ident.ToBase64(), msgs.size());
#endif
m_Service->post (std::bind (&Transports::PostMessages, this, ident, msgs)); m_Service->post (std::bind (&Transports::PostMessages, this, ident, msgs));
} }
@ -597,9 +590,6 @@ namespace transport
auto it = m_Peers.find (ident); auto it = m_Peers.find (ident);
if (it != m_Peers.end ()) if (it != m_Peers.end ())
{ {
#ifdef WITH_EVENTS
EmitEvent({{"type" , "transport.connected"}, {"ident", ident.ToBase64()}, {"inbound", "false"}});
#endif
bool sendDatabaseStore = true; bool sendDatabaseStore = true;
if (it->second.delayedMessages.size () > 0) if (it->second.delayedMessages.size () > 0)
{ {
@ -625,9 +615,6 @@ namespace transport
session->Done(); session->Done();
return; return;
} }
#ifdef WITH_EVENTS
EmitEvent({{"type" , "transport.connected"}, {"ident", ident.ToBase64()}, {"inbound", "true"}});
#endif
session->SendI2NPMessages ({ CreateDatabaseStoreMsg () }); // send DatabaseStore session->SendI2NPMessages ({ CreateDatabaseStoreMsg () }); // send DatabaseStore
std::unique_lock<std::mutex> l(m_PeersMutex); std::unique_lock<std::mutex> l(m_PeersMutex);
m_Peers.insert (std::make_pair (ident, Peer{ 0, nullptr, { session }, i2p::util::GetSecondsSinceEpoch (), {} })); m_Peers.insert (std::make_pair (ident, Peer{ 0, nullptr, { session }, i2p::util::GetSecondsSinceEpoch (), {} }));
@ -642,9 +629,6 @@ namespace transport
auto remoteIdentity = session->GetRemoteIdentity (); auto remoteIdentity = session->GetRemoteIdentity ();
if (!remoteIdentity) return; if (!remoteIdentity) return;
auto ident = remoteIdentity->GetIdentHash (); auto ident = remoteIdentity->GetIdentHash ();
#ifdef WITH_EVENTS
EmitEvent({{"type" , "transport.disconnected"}, {"ident", ident.ToBase64()}});
#endif
auto it = m_Peers.find (ident); auto it = m_Peers.find (ident);
if (it != m_Peers.end ()) if (it != m_Peers.end ())
{ {

21
libi2pd/Tunnel.cpp

@ -14,9 +14,6 @@
#include "Config.h" #include "Config.h"
#include "Tunnel.h" #include "Tunnel.h"
#include "TunnelPool.h" #include "TunnelPool.h"
#ifdef WITH_EVENTS
#include "Event.h"
#endif
namespace i2p namespace i2p
{ {
@ -35,9 +32,6 @@ namespace tunnel
void Tunnel::Build (uint32_t replyMsgID, std::shared_ptr<OutboundTunnel> outboundTunnel) void Tunnel::Build (uint32_t replyMsgID, std::shared_ptr<OutboundTunnel> outboundTunnel)
{ {
#ifdef WITH_EVENTS
std::string peers = i2p::context.GetIdentity()->GetIdentHash().ToBase64();
#endif
auto numHops = m_Config->GetNumHops (); auto numHops = m_Config->GetNumHops ();
int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : numHops; int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : numHops;
auto msg = NewI2NPShortMessage (); auto msg = NewI2NPShortMessage ();
@ -64,15 +58,9 @@ namespace tunnel
hop->CreateBuildRequestRecord (records + idx*TUNNEL_BUILD_RECORD_SIZE, msgID, ctx); hop->CreateBuildRequestRecord (records + idx*TUNNEL_BUILD_RECORD_SIZE, msgID, ctx);
hop->recordIndex = idx; hop->recordIndex = idx;
i++; i++;
#ifdef WITH_EVENTS
peers += ":" + hop->ident->GetIdentHash().ToBase64();
#endif
hop = hop->next; hop = hop->next;
} }
BN_CTX_free (ctx); BN_CTX_free (ctx);
#ifdef WITH_EVENTS
EmitTunnelEvent("tunnel.build", this, peers);
#endif
// fill up fake records with random data // fill up fake records with random data
for (int i = numHops; i < numRecords; i++) for (int i = numHops; i < numRecords; i++)
{ {
@ -207,9 +195,6 @@ namespace tunnel
void Tunnel::SetState(TunnelState state) void Tunnel::SetState(TunnelState state)
{ {
m_State = state; m_State = state;
#ifdef WITH_EVENTS
EmitTunnelEvent("tunnel.state", this, state);
#endif
} }
@ -614,9 +599,6 @@ namespace tunnel
hop = hop->next; hop = hop->next;
} }
} }
#ifdef WITH_EVENTS
EmitTunnelEvent("tunnel.state", tunnel.get(), eTunnelStateBuildFailed);
#endif
// for i2lua // for i2lua
if(pool) pool->OnTunnelBuildResult(tunnel, eBuildResultTimeout); if(pool) pool->OnTunnelBuildResult(tunnel, eBuildResultTimeout);
// delete // delete
@ -628,9 +610,6 @@ namespace tunnel
break; break;
case eTunnelStateBuildFailed: case eTunnelStateBuildFailed:
LogPrint (eLogDebug, "Tunnel: pending build request ", it->first, " failed, deleted"); LogPrint (eLogDebug, "Tunnel: pending build request ", it->first, " failed, deleted");
#ifdef WITH_EVENTS
EmitTunnelEvent("tunnel.state", tunnel.get(), eTunnelStateBuildFailed);
#endif
// for i2lua // for i2lua
if(pool) pool->OnTunnelBuildResult(tunnel, eBuildResultRejected); if(pool) pool->OnTunnelBuildResult(tunnel, eBuildResultRejected);

14
libi2pd/Tunnel.h

@ -25,43 +25,29 @@ namespace i2p
{ {
namespace tunnel namespace tunnel
{ {
template<typename TunnelT> template<typename TunnelT>
static void EmitTunnelEvent(const std::string & ev, const TunnelT & t) static void EmitTunnelEvent(const std::string & ev, const TunnelT & t)
{ {
#ifdef WITH_EVENTS
EmitEvent({{"type", ev}, {"tid", std::to_string(t->GetTunnelID())}});
#else
(void) ev; (void) ev;
(void) t; (void) t;
#endif
} }
template<typename TunnelT, typename T> template<typename TunnelT, typename T>
static void EmitTunnelEvent(const std::string & ev, TunnelT * t, const T & val) static void EmitTunnelEvent(const std::string & ev, TunnelT * t, const T & val)
{ {
#ifdef WITH_EVENTS
EmitEvent({{"type", ev}, {"tid", std::to_string(t->GetTunnelID())}, {"value", std::to_string(val)}, {"inbound", std::to_string(t->IsInbound())}});
#else
(void) ev; (void) ev;
(void) t; (void) t;
(void) val; (void) val;
#endif
} }
template<typename TunnelT> template<typename TunnelT>
static void EmitTunnelEvent(const std::string & ev, TunnelT * t, const std::string & val) static void EmitTunnelEvent(const std::string & ev, TunnelT * t, const std::string & val)
{ {
#ifdef WITH_EVENTS
EmitEvent({{"type", ev}, {"tid", std::to_string(t->GetTunnelID())}, {"value", val}, {"inbound", std::to_string(t->IsInbound())}});
#else
(void) ev; (void) ev;
(void) t; (void) t;
(void) val; (void) val;
#endif
} }
const int TUNNEL_EXPIRATION_TIMEOUT = 660; // 11 minutes const int TUNNEL_EXPIRATION_TIMEOUT = 660; // 11 minutes
const int TUNNEL_EXPIRATION_THRESHOLD = 60; // 1 minute const int TUNNEL_EXPIRATION_THRESHOLD = 60; // 1 minute
const int TUNNEL_RECREATION_THRESHOLD = 90; // 1.5 minutes const int TUNNEL_RECREATION_THRESHOLD = 90; // 1.5 minutes

15
libi2pd/TunnelPool.cpp

@ -11,9 +11,6 @@
#include "Tunnel.h" #include "Tunnel.h"
#include "TunnelPool.h" #include "TunnelPool.h"
#include "Destination.h" #include "Destination.h"
#ifdef WITH_EVENTS
#include "Event.h"
#endif
namespace i2p namespace i2p
{ {
@ -86,9 +83,6 @@ namespace tunnel
{ {
if (!m_IsActive) return; if (!m_IsActive) return;
{ {
#ifdef WITH_EVENTS
EmitTunnelEvent("tunnels.created", createdTunnel);
#endif
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex); std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
m_InboundTunnels.insert (createdTunnel); m_InboundTunnels.insert (createdTunnel);
} }
@ -102,9 +96,6 @@ namespace tunnel
{ {
if (expiredTunnel) if (expiredTunnel)
{ {
#ifdef WITH_EVENTS
EmitTunnelEvent("tunnels.expired", expiredTunnel);
#endif
expiredTunnel->SetTunnelPool (nullptr); expiredTunnel->SetTunnelPool (nullptr);
for (auto& it: m_Tests) for (auto& it: m_Tests)
if (it.second.second == expiredTunnel) it.second.second = nullptr; if (it.second.second == expiredTunnel) it.second.second = nullptr;
@ -118,9 +109,6 @@ namespace tunnel
{ {
if (!m_IsActive) return; if (!m_IsActive) return;
{ {
#ifdef WITH_EVENTS
EmitTunnelEvent("tunnels.created", createdTunnel);
#endif
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex); std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
m_OutboundTunnels.insert (createdTunnel); m_OutboundTunnels.insert (createdTunnel);
} }
@ -133,9 +121,6 @@ namespace tunnel
{ {
if (expiredTunnel) if (expiredTunnel)
{ {
#ifdef WITH_EVENTS
EmitTunnelEvent("tunnels.expired", expiredTunnel);
#endif
expiredTunnel->SetTunnelPool (nullptr); expiredTunnel->SetTunnelPool (nullptr);
for (auto& it: m_Tests) for (auto& it: m_Tests)
if (it.second.first == expiredTunnel) it.second.first = nullptr; if (it.second.first == expiredTunnel) it.second.first = nullptr;

488
libi2pd_client/WebSocks.cpp

@ -2,491 +2,6 @@
#include "Log.h" #include "Log.h"
#include <string> #include <string>
#ifdef WITH_EVENTS
#include "ClientContext.h"
#include "Identity.h"
#include "Destination.h"
#include "Streaming.h"
#include <functional>
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>
#include <boost/property_tree/ini_parser.hpp>
#define GCC47_BOOST149 ((BOOST_VERSION == 104900) && (__GNUC__ == 4) && (__GNUC_MINOR__ >= 7))
#if !GCC47_BOOST149
#include <boost/property_tree/json_parser.hpp>
#endif
namespace i2p
{
namespace client
{
typedef websocketpp::server<websocketpp::config::asio> WebSocksServerImpl;
typedef std::function<void(std::shared_ptr<i2p::stream::Stream>)> StreamConnectFunc;
struct IWebSocksConn : public I2PServiceHandler
{
IWebSocksConn(I2PService * parent) : I2PServiceHandler(parent) {}
virtual void Close() = 0;
virtual void GotMessage(const websocketpp::connection_hdl & conn, WebSocksServerImpl::message_ptr msg) = 0;
};
typedef std::shared_ptr<IWebSocksConn> WebSocksConn_ptr;
WebSocksConn_ptr CreateWebSocksConn(const websocketpp::connection_hdl & conn, WebSocksImpl * parent);
class WebSocksImpl
{
typedef std::mutex mutex_t;
typedef std::unique_lock<mutex_t> lock_t;
typedef std::shared_ptr<ClientDestination> Destination_t;
public:
typedef WebSocksServerImpl ServerImpl;
typedef ServerImpl::message_ptr MessagePtr;
WebSocksImpl(const std::string & addr, int port) :
Parent(nullptr),
m_Run(false),
m_Addr(addr),
m_Port(port),
m_Thread(nullptr)
{
m_Server.init_asio();
m_Server.set_open_handler(std::bind(&WebSocksImpl::ConnOpened, this, std::placeholders::_1));
}
void InitializeDestination(WebSocks * parent)
{
Parent = parent;
m_Dest = Parent->GetLocalDestination();
}
ServerImpl::connection_ptr GetConn(const websocketpp::connection_hdl & conn)
{
return m_Server.get_con_from_hdl(conn);
}
void CloseConn(const websocketpp::connection_hdl & conn)
{
auto c = GetConn(conn);
if(c) c->close(websocketpp::close::status::normal, "closed");
}
void CreateStreamTo(const std::string & addr, int port, StreamConnectFunc complete)
{
auto & addressbook = i2p::client::context.GetAddressBook();
auto a = addressbook.GetAddress (addr);
if (a && a->IsIdentHash ())
{
// address found
m_Dest->CreateStream(complete, a->identHash, port);
}
else
{
// not found
complete(nullptr);
}
}
void ConnOpened(websocketpp::connection_hdl conn)
{
auto ptr = CreateWebSocksConn(conn, this);
Parent->AddHandler(ptr);
m_Conns.push_back(ptr);
}
void Start()
{
if(m_Run) return; // already started
m_Server.listen(boost::asio::ip::address::from_string(m_Addr), m_Port);
m_Server.start_accept();
m_Run = true;
m_Thread = new std::thread([&] (){
while(m_Run) {
try {
m_Server.run();
} catch( std::exception & ex) {
LogPrint(eLogError, "Websocks runtime exception: ", ex.what());
}
}
});
m_Dest->Start();
}
void Stop()
{
for(const auto & conn : m_Conns)
conn->Close();
m_Dest->Stop();
m_Run = false;
m_Server.stop();
if(m_Thread) {
m_Thread->join();
delete m_Thread;
}
m_Thread = nullptr;
}
boost::asio::ip::tcp::endpoint GetLocalEndpoint()
{
return boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(m_Addr), m_Port);
}
i2p::datagram::DatagramDestination * GetDatagramDest() const
{
auto dgram = m_Dest->GetDatagramDestination();
if(!dgram) dgram = m_Dest->CreateDatagramDestination();
return dgram;
}
WebSocks * Parent;
private:
std::vector<WebSocksConn_ptr> m_Conns;
bool m_Run;
ServerImpl m_Server;
std::string m_Addr;
int m_Port;
std::thread * m_Thread;
Destination_t m_Dest;
};
struct WebSocksConn : public IWebSocksConn , public std::enable_shared_from_this<WebSocksConn>
{
enum ConnState
{
eWSCInitial,
eWSCTryConnect,
eWSCFailConnect,
eWSCOkayConnect,
eWSCDatagram,
eWSCClose,
eWSCEnd
};
typedef WebSocksServerImpl ServerImpl;
typedef ServerImpl::message_ptr Message_t;
typedef websocketpp::connection_hdl ServerConn;
typedef std::shared_ptr<ClientDestination> Destination_t;
typedef std::shared_ptr<i2p::stream::StreamingDestination> StreamDest_t;
typedef std::shared_ptr<i2p::stream::Stream> Stream_t;
ServerConn m_Conn;
Stream_t m_Stream;
ConnState m_State;
WebSocksImpl * m_Parent;
std::string m_RemoteAddr;
int m_RemotePort;
uint8_t m_RecvBuf[2048];
bool m_IsDatagram;
i2p::datagram::DatagramDestination * m_Datagram;
WebSocksConn(const ServerConn & conn, WebSocksImpl * parent) :
IWebSocksConn(parent->Parent),
m_Conn(conn),
m_Stream(nullptr),
m_State(eWSCInitial),
m_Parent(parent),
m_IsDatagram(false),
m_Datagram(nullptr)
{
}
~WebSocksConn()
{
Close();
}
void HandleDatagram(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)
{
auto conn = m_Parent->GetConn(m_Conn);
if(conn)
{
std::stringstream ss;
ss << from.GetIdentHash().ToBase32();
ss << ".b32.i2p:";
ss << std::to_string(fromPort);
ss << "\n";
ss.write((char *)buf, len);
conn->send(ss.str());
}
}
void BeginDatagram()
{
m_Datagram = m_Parent->GetDatagramDest();
m_Datagram->SetReceiver(
std::bind(
&WebSocksConn::HandleDatagram,
this,
std::placeholders::_1,
std::placeholders::_2,
std::placeholders::_3,
std::placeholders::_4,
std::placeholders::_5), m_RemotePort);
}
void EnterState(ConnState state)
{
LogPrint(eLogDebug, "websocks: state ", m_State, " -> ", state);
switch(m_State)
{
case eWSCInitial:
if (state == eWSCClose) {
m_State = eWSCClose;
// connection was opened but never used
LogPrint(eLogInfo, "websocks: connection closed but never used");
Close();
return;
} else if (state == eWSCTryConnect) {
// we will try to connect
m_State = eWSCTryConnect;
m_Parent->CreateStreamTo(m_RemoteAddr, m_RemotePort, std::bind(&WebSocksConn::ConnectResult, this, std::placeholders::_1));
} else if (state == eWSCDatagram) {
if (m_RemotePort >= 0 && m_RemotePort <= 65535)
{
LogPrint(eLogDebug, "websocks: datagram mode initiated");
m_State = eWSCDatagram;
BeginDatagram();
SendResponse("");
}
else
SendResponse("invalid port");
} else {
LogPrint(eLogWarning, "websocks: invalid state change ", m_State, " -> ", state);
}
return;
case eWSCTryConnect:
if(state == eWSCOkayConnect) {
// we connected okay
LogPrint(eLogDebug, "websocks: connected to ", m_RemoteAddr, ":", m_RemotePort);
SendResponse("");
m_State = eWSCOkayConnect;
} else if(state == eWSCFailConnect) {
// we did not connect okay
LogPrint(eLogDebug, "websocks: failed to connect to ", m_RemoteAddr, ":", m_RemotePort);
SendResponse("failed to connect");
m_State = eWSCFailConnect;
EnterState(eWSCInitial);
} else if(state == eWSCClose) {
// premature close
LogPrint(eLogWarning, "websocks: websocket connection closed prematurely");
m_State = eWSCClose;
} else {
LogPrint(eLogWarning, "websocks: invalid state change ", m_State, " -> ", state);
}
return;
case eWSCFailConnect:
if (state == eWSCInitial) {
// reset to initial state so we can try connecting again
m_RemoteAddr = "";
m_RemotePort = 0;
LogPrint(eLogDebug, "websocks: reset websocket conn to initial state");
m_State = eWSCInitial;
} else if (state == eWSCClose) {
// we are going to close the connection
m_State = eWSCClose;
Close();
} else {
LogPrint(eLogWarning, "websocks: invalid state change ", m_State, " -> ", state);
}
return;
case eWSCDatagram:
if(state != eWSCClose) {
LogPrint(eLogWarning, "websocks: invalid state change ", m_State, " -> ", state);
}
m_State = eWSCClose;
Close();
return;
case eWSCOkayConnect:
if(state == eWSCClose) {
// graceful close
m_State = eWSCClose;
Close();
} else {
LogPrint(eLogWarning, "websocks: invalid state change ", m_State, " -> ", state);
}
return;
case eWSCClose:
if(state == eWSCEnd) {
LogPrint(eLogDebug, "websocks: socket ended");
Kill();
auto me = shared_from_this();
Done(me);
} else {
LogPrint(eLogWarning, "websocks: invalid state change ", m_State, " -> ", state);
}
return;
default:
LogPrint(eLogError, "websocks: bad state ", m_State);
}
}
void StartForwarding()
{
LogPrint(eLogDebug, "websocks: begin forwarding data");
uint8_t b[1];
m_Stream->Send(b, 0);
AsyncRecv();
}
void HandleAsyncRecv(const boost::system::error_code &ec, std::size_t n)
{
if(ec) {
// error
LogPrint(eLogWarning, "websocks: connection error ", ec.message());
EnterState(eWSCClose);
} else {
// forward data
LogPrint(eLogDebug, "websocks recv ", n);
std::string str((char*)m_RecvBuf, n);
auto conn = m_Parent->GetConn(m_Conn);
if(!conn) {
LogPrint(eLogWarning, "websocks: connection is gone");
EnterState(eWSCClose);
return;
}
conn->send(str);
AsyncRecv();
}
}
void AsyncRecv()
{
m_Stream->AsyncReceive(
boost::asio::buffer(m_RecvBuf, sizeof(m_RecvBuf)),
std::bind(&WebSocksConn::HandleAsyncRecv, this, std::placeholders::_1, std::placeholders::_2), 60);
}
/** @brief send error message or empty string for success */
void SendResponse(const std::string & errormsg)
{
boost::property_tree::ptree resp;
if(errormsg.size()) {
resp.put("error", errormsg);
resp.put("success", 0);
} else {
resp.put("success", 1);
}
std::ostringstream ss;
write_json(ss, resp);
auto conn = m_Parent->GetConn(m_Conn);
if(conn) conn->send(ss.str());
}
void ConnectResult(Stream_t stream)
{
m_Stream = stream;
if(m_State == eWSCClose) {
// premature close of websocket
Close();
return;
}
if(m_Stream) {
// connect good
EnterState(eWSCOkayConnect);
StartForwarding();
} else {
// connect failed
EnterState(eWSCFailConnect);
}
}
virtual void GotMessage(const websocketpp::connection_hdl & conn, WebSocksServerImpl::message_ptr msg)
{
(void) conn;
std::string payload = msg->get_payload();
if(m_State == eWSCOkayConnect)
{
// forward to server
LogPrint(eLogDebug, "websocks: forward ", payload.size());
m_Stream->Send((uint8_t*)payload.c_str(), payload.size());
} else if (m_State == eWSCInitial) {
// recv connect request
auto itr = payload.find(":");
if(itr == std::string::npos) {
// no port
m_RemotePort = 0;
m_RemoteAddr = payload;
} else {
// includes port
m_RemotePort = std::stoi(payload.substr(itr+1));
m_RemoteAddr = payload.substr(0, itr);
}
m_IsDatagram = m_RemoteAddr == "DATAGRAM";
if(m_IsDatagram)
EnterState(eWSCDatagram);
else
EnterState(eWSCTryConnect);
} else if (m_State == eWSCDatagram) {
// send datagram
// format is "host:port\npayload"
auto idx = payload.find("\n");
std::string line = payload.substr(0, idx);
auto itr = line.find(":");
auto & addressbook = i2p::client::context.GetAddressBook();
std::string addr;
int port = 0;
if (itr == std::string::npos)
{
addr = line;
}
else
{
addr = line.substr(0, itr);
port = std::atoi(line.substr(itr+1).c_str());
}
auto a = addressbook.GetAddress (addr);
if (a && a->IsIdentHash ())
{
const char * data = payload.c_str() + idx + 1;
size_t len = payload.size() - (1 + line.size());
m_Datagram->SendDatagramTo((const uint8_t*)data, len, a->identHash, m_RemotePort, port);
}
} else {
// wtf?
LogPrint(eLogWarning, "websocks: got message in invalid state ", m_State);
}
}
virtual void Close()
{
if(m_State == eWSCClose) {
LogPrint(eLogDebug, "websocks: closing connection");
if(m_Stream) m_Stream->Close();
if(m_Datagram) m_Datagram->ResetReceiver(m_RemotePort);
m_Parent->CloseConn(m_Conn);
EnterState(eWSCEnd);
} else {
EnterState(eWSCClose);
}
}
};
WebSocksConn_ptr CreateWebSocksConn(const websocketpp::connection_hdl & conn, WebSocksImpl * parent)
{
auto ptr = std::make_shared<WebSocksConn>(conn, parent);
auto c = parent->GetConn(conn);
c->set_message_handler(std::bind(&WebSocksConn::GotMessage, ptr.get(), std::placeholders::_1, std::placeholders::_2));
return ptr;
}
}
}
#else
// no websocket support
namespace i2p namespace i2p
{ {
namespace client namespace client
@ -504,7 +19,7 @@ namespace client
void Start() void Start()
{ {
LogPrint(eLogInfo, "WebSockets not enabled on compile time"); LogPrint(eLogInfo, "[Tunnels] starting websocks tunnel at %s:%d is rejected: WebSockets is deprecated", m_Addr, m_Port);
} }
void Stop() void Stop()
@ -527,7 +42,6 @@ namespace client
} }
} }
#endif
namespace i2p namespace i2p
{ {
namespace client namespace client

195
libi2pd_client/Websocket.cpp

@ -1,195 +0,0 @@
#ifdef WITH_EVENTS
#include "Websocket.h"
#include "Log.h"
#include <set>
#include <functional>
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>
#include <boost/property_tree/ini_parser.hpp>
#define GCC47_BOOST149 ((BOOST_VERSION == 104900) && (__GNUC__ == 4) && (__GNUC_MINOR__ >= 7))
#if !GCC47_BOOST149
#include <boost/property_tree/json_parser.hpp>
#endif
#include <stdexcept>
namespace i2p
{
namespace event
{
typedef websocketpp::server<websocketpp::config::asio> ServerImpl;
typedef websocketpp::connection_hdl ServerConn;
class WebsocketServerImpl : public EventListener
{
private:
typedef ServerImpl::message_ptr MessagePtr;
public:
WebsocketServerImpl(const std::string & addr, int port) :
m_run(false),
m_ws_thread(nullptr),
m_ev_thread(nullptr),
m_WebsocketTicker(m_Service)
{
m_server.init_asio();
m_server.set_open_handler(std::bind(&WebsocketServerImpl::ConnOpened, this, std::placeholders::_1));
m_server.set_close_handler(std::bind(&WebsocketServerImpl::ConnClosed, this, std::placeholders::_1));
m_server.set_message_handler(std::bind(&WebsocketServerImpl::OnConnMessage, this, std::placeholders::_1, std::placeholders::_2));
m_server.listen(boost::asio::ip::address::from_string(addr), port);
}
~WebsocketServerImpl()
{
}
void Start() {
m_run = true;
m_server.start_accept();
m_ws_thread = new std::thread([&] () {
while(m_run) {
try {
m_server.run();
} catch (std::exception & e ) {
LogPrint(eLogError, "Websocket server: ", e.what());
}
}
});
m_ev_thread = new std::thread([&] () {
while(m_run) {
try {
m_Service.run();
break;
} catch (std::exception & e ) {
LogPrint(eLogError, "Websocket service: ", e.what());
}
}
});
ScheduleTick();
}
void Stop() {
m_run = false;
m_Service.stop();
m_server.stop();
if(m_ev_thread) {
m_ev_thread->join();
delete m_ev_thread;
}
m_ev_thread = nullptr;
if(m_ws_thread) {
m_ws_thread->join();
delete m_ws_thread;
}
m_ws_thread = nullptr;
}
void ConnOpened(ServerConn c)
{
std::lock_guard<std::mutex> lock(m_connsMutex);
m_conns.insert(c);
}
void ConnClosed(ServerConn c)
{
std::lock_guard<std::mutex> lock(m_connsMutex);
m_conns.erase(c);
}
void OnConnMessage(ServerConn conn, ServerImpl::message_ptr msg)
{
(void) conn;
(void) msg;
}
void HandleTick(const boost::system::error_code & ec)
{
if(ec != boost::asio::error::operation_aborted)
LogPrint(eLogError, "Websocket ticker: ", ec.message());
// pump collected events to us
i2p::event::core.PumpCollected(this);
ScheduleTick();
}
void ScheduleTick()
{
LogPrint(eLogDebug, "Websocket schedule tick");
boost::posix_time::seconds dlt(1);
m_WebsocketTicker.expires_from_now(dlt);
m_WebsocketTicker.async_wait(std::bind(&WebsocketServerImpl::HandleTick, this, std::placeholders::_1));
}
/** @brief called from m_ev_thread */
void HandlePumpEvent(const EventType & ev, const uint64_t & val)
{
EventType e;
for (const auto & i : ev)
e[i.first] = i.second;
e["number"] = std::to_string(val);
HandleEvent(e);
}
/** @brief called from m_ws_thread */
void HandleEvent(const EventType & ev)
{
std::lock_guard<std::mutex> lock(m_connsMutex);
boost::property_tree::ptree event;
for (const auto & item : ev) {
event.put(item.first, item.second);
}
std::ostringstream ss;
write_json(ss, event);
std::string s = ss.str();
ConnList::iterator it;
for (it = m_conns.begin(); it != m_conns.end(); ++it) {
ServerImpl::connection_ptr con = m_server.get_con_from_hdl(*it);
con->send(s);
}
}
private:
typedef std::set<ServerConn, std::owner_less<ServerConn> > ConnList;
bool m_run;
std::thread * m_ws_thread;
std::thread * m_ev_thread;
std::mutex m_connsMutex;
ConnList m_conns;
ServerImpl m_server;
boost::asio::io_service m_Service;
boost::asio::deadline_timer m_WebsocketTicker;
};
WebsocketServer::WebsocketServer(const std::string & addr, int port) : m_impl(new WebsocketServerImpl(addr, port)) {}
WebsocketServer::~WebsocketServer()
{
delete m_impl;
}
void WebsocketServer::Start()
{
m_impl->Start();
}
void WebsocketServer::Stop()
{
m_impl->Stop();
}
EventListener * WebsocketServer::ToListener()
{
return m_impl;
}
}
}
#endif

28
libi2pd_client/Websocket.h

@ -1,28 +0,0 @@
#ifndef WEBSOCKET_H__
#define WEBSOCKET_H__
#include "Event.h"
namespace i2p
{
namespace event
{
class WebsocketServerImpl;
class WebsocketServer
{
public:
WebsocketServer(const std::string & addr, int port);
~WebsocketServer();
void Start();
void Stop();
EventListener * ToListener();
private:
WebsocketServerImpl * m_impl;
};
}
}
#endif

115
qt/i2pd_qt/generalsettingswidget.ui

@ -2135,121 +2135,6 @@ Comma separated list of base64 identities:</string>
</widget> </widget>
</widget> </widget>
</item> </item>
<item row="25" column="0">
<widget class="QGroupBox" name="groupBox_websock">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>105</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>105</height>
</size>
</property>
<property name="title">
<string>Websockets server</string>
</property>
<widget class="QCheckBox" name="checkBoxWebsocketsEnable">
<property name="geometry">
<rect>
<x>0</x>
<y>20</y>
<width>85</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Enable</string>
</property>
</widget>
<widget class="QWidget" name="horizontalLayoutWidget_32">
<property name="geometry">
<rect>
<x>0</x>
<y>40</y>
<width>661</width>
<height>31</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_38">
<item>
<widget class="QLabel" name="label_40">
<property name="text">
<string>Address to bind websocket server on:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit_webSock_addr"/>
</item>
<item>
<spacer name="horizontalSpacer_26">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="horizontalLayoutWidget_33">
<property name="geometry">
<rect>
<x>0</x>
<y>70</y>
<width>661</width>
<height>31</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_39">
<item>
<widget class="QLabel" name="label_41">
<property name="text">
<string>Port to bind websocket server on:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit_webSock_port">
<property name="maximumSize">
<size>
<width>80</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_27">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
<item row="11" column="0"> <item row="11" column="0">
<widget class="QGroupBox" name="webconsoleGroupBox"> <widget class="QGroupBox" name="webconsoleGroupBox">
<property name="minimumSize"> <property name="minimumSize">

2
qt/i2pd_qt/i2pd_qt.pro

@ -74,7 +74,6 @@ SOURCES += DaemonQT.cpp mainwindow.cpp \
../../libi2pd_client/MatchedDestination.cpp \ ../../libi2pd_client/MatchedDestination.cpp \
../../libi2pd_client/SAM.cpp \ ../../libi2pd_client/SAM.cpp \
../../libi2pd_client/SOCKS.cpp \ ../../libi2pd_client/SOCKS.cpp \
../../libi2pd_client/Websocket.cpp \
../../libi2pd_client/WebSocks.cpp \ ../../libi2pd_client/WebSocks.cpp \
../../daemon/Daemon.cpp \ ../../daemon/Daemon.cpp \
../../daemon/HTTPServer.cpp \ ../../daemon/HTTPServer.cpp \
@ -161,7 +160,6 @@ HEADERS += DaemonQT.h mainwindow.h \
../../libi2pd_client/MatchedDestination.h \ ../../libi2pd_client/MatchedDestination.h \
../../libi2pd_client/SAM.h \ ../../libi2pd_client/SAM.h \
../../libi2pd_client/SOCKS.h \ ../../libi2pd_client/SOCKS.h \
../../libi2pd_client/Websocket.h \
../../libi2pd_client/WebSocks.h \ ../../libi2pd_client/WebSocks.h \
../../daemon/Daemon.h \ ../../daemon/Daemon.h \
../../daemon/HTTPServer.h \ ../../daemon/HTTPServer.h \

4
qt/i2pd_qt/mainwindow.cpp

@ -258,10 +258,6 @@ MainWindow::MainWindow(std::shared_ptr<std::iostream> logStream_, QWidget *paren
initStringBox( OPTION("trust","routers",[]{return "";}), uiSettings->lineEditTrustRouters); initStringBox( OPTION("trust","routers",[]{return "";}), uiSettings->lineEditTrustRouters);
initCheckBox( OPTION("trust","hidden",[]{return "false";}), uiSettings->checkBoxTrustHidden); initCheckBox( OPTION("trust","hidden",[]{return "false";}), uiSettings->checkBoxTrustHidden);
initCheckBox( OPTION("websockets","enabled",[]{return "false";}), uiSettings->checkBoxWebsocketsEnable);
initIPAddressBox( OPTION("websockets","address",[]{return "127.0.0.1";}), uiSettings->lineEdit_webSock_addr, tr("Websocket server -> IP address"));
initTCPPortBox( OPTION("websockets","port",[]{return "7666";}), uiSettings->lineEdit_webSock_port, tr("Websocket server -> Port"));
# undef OPTION # undef OPTION
//widgetlocks.add(new widgetlock(widget,lockbtn)); //widgetlocks.add(new widgetlock(widget,lockbtn));

Loading…
Cancel
Save