Commit 164e9a73 authored by Pavel Vainerman's avatar Pavel Vainerman

[Переход на libPoco]: Modbus{Master,Slave} добился прохождения тестов

parent fc4f125f
...@@ -35,7 +35,6 @@ class MBSlave ...@@ -35,7 +35,6 @@ class MBSlave
void execute(); /*!< основной цикл работы */ void execute(); /*!< основной цикл работы */
void setLog( std::shared_ptr<DebugStream> dlog ); void setLog( std::shared_ptr<DebugStream> dlog );
protected: protected:
......
...@@ -68,6 +68,11 @@ void MBTCPServer::setLog(std::shared_ptr<DebugStream>& dlog ) ...@@ -68,6 +68,11 @@ void MBTCPServer::setLog(std::shared_ptr<DebugStream>& dlog )
sslot->setLog(dlog); sslot->setLog(dlog);
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
void MBTCPServer::setMaxSessions( size_t max )
{
sslot->setMaxSessions(max);
}
// -------------------------------------------------------------------------
void MBTCPServer::execute() void MBTCPServer::execute()
{ {
sslot->run( vaddr, false ); sslot->run( vaddr, false );
......
...@@ -31,6 +31,8 @@ class MBTCPServer ...@@ -31,6 +31,8 @@ class MBTCPServer
void execute(); /*!< основной цикл работы */ void execute(); /*!< основной цикл работы */
void setLog( std::shared_ptr<DebugStream>& dlog ); void setLog( std::shared_ptr<DebugStream>& dlog );
void setMaxSessions( size_t max );
protected: protected:
// действия при завершении работы // действия при завершении работы
void sigterm( int signo ); void sigterm( int signo );
......
...@@ -16,6 +16,7 @@ static struct option longopts[] = ...@@ -16,6 +16,7 @@ static struct option longopts[] =
{ "port", required_argument, 0, 'p' }, { "port", required_argument, 0, 'p' },
{ "const-reply", required_argument, 0, 'c' }, { "const-reply", required_argument, 0, 'c' },
{ "after-send-pause", required_argument, 0, 's' }, { "after-send-pause", required_argument, 0, 's' },
{ "max-sessions", required_argument, 0, 'm' },
{ NULL, 0, 0, 0 } { NULL, 0, 0, 0 }
}; };
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
...@@ -30,6 +31,7 @@ static void print_help() ...@@ -30,6 +31,7 @@ static void print_help()
printf("[-p|--port] port - Server port. Default: 502.\n"); printf("[-p|--port] port - Server port. Default: 502.\n");
printf("[-c|--const-reply] val - Reply 'val' for all queries\n"); printf("[-c|--const-reply] val - Reply 'val' for all queries\n");
printf("[-s|--after-send-pause] msec - Pause after send request. Default: 0\n"); printf("[-s|--after-send-pause] msec - Pause after send request. Default: 0\n");
printf("[-m|--max-sessions] num - Set the maximum number of sessions. Default: 10\n");
} }
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
int main( int argc, char** argv ) int main( int argc, char** argv )
...@@ -44,12 +46,13 @@ int main( int argc, char** argv ) ...@@ -44,12 +46,13 @@ int main( int argc, char** argv )
auto dlog = make_shared<DebugStream>(); auto dlog = make_shared<DebugStream>();
int replyVal = -1; int replyVal = -1;
timeout_t afterpause = 0; timeout_t afterpause = 0;
size_t maxSessions = 10;
try try
{ {
while(1) while(1)
{ {
opt = getopt_long(argc, argv, "hva:p:i:c:s:", longopts, &optindex); opt = getopt_long(argc, argv, "hva:p:i:c:s:m:", longopts, &optindex);
if( opt == -1 ) if( opt == -1 )
break; break;
...@@ -84,6 +87,10 @@ int main( int argc, char** argv ) ...@@ -84,6 +87,10 @@ int main( int argc, char** argv )
afterpause = uni_atoi(optarg); afterpause = uni_atoi(optarg);
break; break;
case 'm':
maxSessions = uni_atoi(optarg);
break;
case '?': case '?':
default: default:
printf("? argumnet\n"); printf("? argumnet\n");
...@@ -110,6 +117,7 @@ int main( int argc, char** argv ) ...@@ -110,6 +117,7 @@ int main( int argc, char** argv )
mbs.setLog(dlog); mbs.setLog(dlog);
mbs.setVerbose(verb); mbs.setVerbose(verb);
mbs.setAfterSendPause(afterpause); mbs.setAfterSendPause(afterpause);
mbs.setMaxSessions(maxSessions);
if( replyVal != -1 ) if( replyVal != -1 )
mbs.setReply(replyVal); mbs.setReply(replyVal);
......
...@@ -92,7 +92,7 @@ ...@@ -92,7 +92,7 @@
<DeviceList> <DeviceList>
</DeviceList> </DeviceList>
<GateList> <GateList>
<item ip="192.168.3.12" port="2048" timeout="3000" recv_timeout="1000"/> <item ip="localhost" port="2048" timeout="3000" recv_timeout="1000"/>
<item ip="192.168.3.12" port="2049" timeout="3000" recv_timeout="1000"/> <item ip="192.168.3.12" port="2049" timeout="3000" recv_timeout="1000"/>
</GateList> </GateList>
</MBPerfTestMaster> </MBPerfTestMaster>
......
...@@ -220,7 +220,20 @@ void MBTCPMaster::sigterm( int signo ) ...@@ -220,7 +220,20 @@ void MBTCPMaster::sigterm( int signo )
std::clog << (p ? p.__cxa_exception_type()->name() : "null") << std::endl; std::clog << (p ? p.__cxa_exception_type()->name() : "null") << std::endl;
} }
} }
// -----------------------------------------------------------------------------
bool MBTCPMaster::deactivateObject()
{
setProcActive(false);
if( pollThread )
{
pollThread->stop();
if( pollThread->isRunning() )
pollThread->join();
}
return MBExchange::deactivateObject();
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void MBTCPMaster::help_print( int argc, const char* const* argv ) void MBTCPMaster::help_print( int argc, const char* const* argv )
{ {
......
...@@ -237,6 +237,7 @@ class MBTCPMaster: ...@@ -237,6 +237,7 @@ class MBTCPMaster:
virtual void sysCommand( const UniSetTypes::SystemMessage* sm ) override; virtual void sysCommand( const UniSetTypes::SystemMessage* sm ) override;
virtual std::shared_ptr<ModbusClient> initMB( bool reopen = false ) override; virtual std::shared_ptr<ModbusClient> initMB( bool reopen = false ) override;
virtual void sigterm( int signo ) override; virtual void sigterm( int signo ) override;
virtual bool deactivateObject() override;
std::string iaddr; std::string iaddr;
int port; int port;
......
...@@ -335,6 +335,9 @@ bool MBTCPMultiMaster::MBSlaveInfo::init( std::shared_ptr<DebugStream>& mblog ) ...@@ -335,6 +335,9 @@ bool MBTCPMultiMaster::MBSlaveInfo::init( std::shared_ptr<DebugStream>& mblog )
initOK = true; initOK = true;
} }
mbinfo << myname << "(init): connect " << mbtcp->isConnection() << endl;
return mbtcp->isConnection(); return mbtcp->isConnection();
} }
catch( ModbusRTU::mbException& ex ) catch( ModbusRTU::mbException& ex )
...@@ -507,6 +510,28 @@ void MBTCPMultiMaster::sigterm( int signo ) ...@@ -507,6 +510,28 @@ void MBTCPMultiMaster::sigterm( int signo )
// std::clog << (p ? p.__cxa_exception_type()->name() : "null") << std::endl; // std::clog << (p ? p.__cxa_exception_type()->name() : "null") << std::endl;
// } // }
} }
// -----------------------------------------------------------------------------
bool MBTCPMultiMaster::deactivateObject()
{
setProcActive(false);
if( pollThread )
{
pollThread->stop();
if( pollThread->isRunning() )
pollThread->join();
}
if( checkThread )
{
checkThread->stop();
if( checkThread->isRunning() )
checkThread->join();
}
return MBExchange::deactivateObject();
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void MBTCPMultiMaster::help_print( int argc, const char* const* argv ) void MBTCPMultiMaster::help_print( int argc, const char* const* argv )
......
...@@ -262,6 +262,7 @@ class MBTCPMultiMaster: ...@@ -262,6 +262,7 @@ class MBTCPMultiMaster:
virtual void initIterators() override; virtual void initIterators() override;
virtual std::shared_ptr<ModbusClient> initMB( bool reopen = false ) override; virtual std::shared_ptr<ModbusClient> initMB( bool reopen = false ) override;
virtual void sigterm( int signo ) override; virtual void sigterm( int signo ) override;
virtual bool deactivateObject() override;
void poll_thread(); void poll_thread();
void check_thread(); void check_thread();
......
...@@ -58,6 +58,8 @@ int main( int argc, const char** argv ) ...@@ -58,6 +58,8 @@ int main( int argc, const char** argv )
int count = conf->getArgPInt("--count", 50); int count = conf->getArgPInt("--count", 50);
cerr << "RUN " << count << " MBTCPMultiMaster.." << endl;
for( int i = 1; i <= count; i++ ) for( int i = 1; i <= count; i++ )
{ {
ostringstream prefix; ostringstream prefix;
......
...@@ -9,8 +9,8 @@ ...@@ -9,8 +9,8 @@
#include "ModbusServerSlot.h" #include "ModbusServerSlot.h"
#include "ModbusServer.h" #include "ModbusServer.h"
#include "PassiveTimer.h" #include "PassiveTimer.h"
#include "USocket.h"
#include "UTCPCore.h" #include "UTCPCore.h"
#include "UTCPStream.h"
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
/*! /*!
* \brief The ModbusTCPSession class * \brief The ModbusTCPSession class
...@@ -33,7 +33,7 @@ class ModbusTCPSession: ...@@ -33,7 +33,7 @@ class ModbusTCPSession:
{ {
public: public:
ModbusTCPSession( int sock, const std::unordered_set<ModbusRTU::ModbusAddr>& vmbaddr, timeout_t timeout ); ModbusTCPSession( const Poco::Net::StreamSocket& s, const std::unordered_set<ModbusRTU::ModbusAddr>& vmbaddr, timeout_t timeout );
virtual ~ModbusTCPSession(); virtual ~ModbusTCPSession();
void cleanInputStream(); void cleanInputStream();
...@@ -129,7 +129,7 @@ class ModbusTCPSession: ...@@ -129,7 +129,7 @@ class ModbusTCPSession:
ev::io io; ev::io io;
ev::timer ioTimeout; ev::timer ioTimeout;
std::shared_ptr<USocket> sock; std::shared_ptr<UTCPStream> sock;
std::queue<UTCPCore::Buffer*> qsend; std::queue<UTCPCore::Buffer*> qsend;
double sessTimeout = { 10.0 }; double sessTimeout = { 10.0 };
......
...@@ -35,11 +35,8 @@ ModbusTCPMaster::ModbusTCPMaster(): ...@@ -35,11 +35,8 @@ ModbusTCPMaster::ModbusTCPMaster():
force_disconnect(true) force_disconnect(true)
{ {
setCRCNoCheckit(true); setCRCNoCheckit(true);
/*
dlog->addLevel(Debug::INFO); // dlog->level(Debug::ANY);
dlog->addLevel(Debug::WARN);
dlog->addLevel(Debug::CRIT);
*/
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
...@@ -64,10 +61,12 @@ void ModbusTCPMaster::setChannelTimeout( timeout_t msec ) ...@@ -64,10 +61,12 @@ void ModbusTCPMaster::setChannelTimeout( timeout_t msec )
Poco::Timespan old = tcp->getReceiveTimeout();; Poco::Timespan old = tcp->getReceiveTimeout();;
//timeout_t old = tcp->getReceiveTimeout(); //timeout_t old = tcp->getReceiveTimeout();
Poco::Timespan tmsec(msec*1000);
if( old == msec ) if( old == msec )
return; return;
tcp->setReceiveTimeout(msec*1000); tcp->setReceiveTimeout(tmsec);
int oldKeepAlive = keepAliveTimeout; int oldKeepAlive = keepAliveTimeout;
keepAliveTimeout = (msec > 1000 ? msec / 1000 : 1); keepAliveTimeout = (msec > 1000 ? msec / 1000 : 1);
...@@ -122,7 +121,7 @@ mbErrCode ModbusTCPMaster::query( ModbusAddr addr, ModbusMessage& msg, ...@@ -122,7 +121,7 @@ mbErrCode ModbusTCPMaster::query( ModbusAddr addr, ModbusMessage& msg,
for( size_t i = 0; i < 2; i++ ) for( size_t i = 0; i < 2; i++ )
{ {
//if( tcp->isPending(ost::Socket::pendingOutput, timeout) ) //if( tcp->isPending(ost::Socket::pendingOutput, timeout) )
if( tcp->poll(timeout*1000,Poco::Net::Socket::SELECT_READ ) ) if( tcp->poll(timeout*1000,Poco::Net::Socket::SELECT_WRITE) )
{ {
mbErrCode res = send(msg); mbErrCode res = send(msg);
...@@ -266,7 +265,7 @@ mbErrCode ModbusTCPMaster::query( ModbusAddr addr, ModbusMessage& msg, ...@@ -266,7 +265,7 @@ mbErrCode ModbusTCPMaster::query( ModbusAddr addr, ModbusMessage& msg,
// при штатном обмене..лучше дождаться конца "посылки".. // при штатном обмене..лучше дождаться конца "посылки"..
// поэтому применяем disconnect(), а не forceDisconnect() // поэтому применяем disconnect(), а не forceDisconnect()
// (с учётом выставленной опции setLinger(true)) // (с учётом выставленной опции setLinger(true))
tcp->shutdown(); tcp->close();
} }
return res; return res;
...@@ -360,7 +359,8 @@ bool ModbusTCPMaster::checkConnection( const std::string& ip, int port, int time ...@@ -360,7 +359,8 @@ bool ModbusTCPMaster::checkConnection( const std::string& ip, int port, int time
t.create(ip, port, timeout_msec); t.create(ip, port, timeout_msec);
t.setKeepAliveParams( (timeout_msec > 1000 ? timeout_msec / 1000 : 1), 1, 1); t.setKeepAliveParams( (timeout_msec > 1000 ? timeout_msec / 1000 : 1), 1, 1);
t.setNoDelay(true); t.setNoDelay(true);
t.shutdown(); //t.shutdown();
t.close();
return true; return true;
} }
catch(...) catch(...)
...@@ -419,7 +419,8 @@ void ModbusTCPMaster::connect( const Poco::Net::SocketAddress& addr, int _port ) ...@@ -419,7 +419,8 @@ void ModbusTCPMaster::connect( const Poco::Net::SocketAddress& addr, int _port )
{ {
if( tcp ) if( tcp )
{ {
disconnect(); //disconnect();
forceDisconnect();
tcp.reset(); tcp.reset();
} }
...@@ -432,31 +433,43 @@ void ModbusTCPMaster::connect( const Poco::Net::SocketAddress& addr, int _port ) ...@@ -432,31 +433,43 @@ void ModbusTCPMaster::connect( const Poco::Net::SocketAddress& addr, int _port )
try try
{ {
tcp = make_shared<UTCPStream>(); tcp = make_shared<UTCPStream>();
tcp->connect(addr,500); tcp->create(iaddr,port,500);
//tcp->connect(addr,500);
tcp->setReceiveTimeout(replyTimeOut_ms*1000); tcp->setReceiveTimeout(replyTimeOut_ms*1000);
tcp->setKeepAlive(true); // tcp->setKeepAliveParams((replyTimeOut_ms > 1000 ? replyTimeOut_ms / 1000 : 1)); tcp->setKeepAlive(true); // tcp->setKeepAliveParams((replyTimeOut_ms > 1000 ? replyTimeOut_ms / 1000 : 1));
tcp->setNoDelay(true); tcp->setNoDelay(true);
} }
catch( Poco::Net::NetException& ex)
{
if( dlog->debugging(Debug::CRIT) )
{
ostringstream s;
s << "(ModbusTCPMaster): create connection " << iaddr << ":" << port << " error: " << ex.displayText();
dlog->crit() << iaddr << std::endl;
}
tcp = nullptr;
}
catch( const std::exception& e ) catch( const std::exception& e )
{ {
if( dlog->debugging(Debug::CRIT) ) if( dlog->debugging(Debug::CRIT) )
{ {
ostringstream s; ostringstream s;
s << "(ModbusTCPMaster): connection " << s.str() << " error: " << e.what(); s << "(ModbusTCPMaster): connection " << iaddr << ":" << port << " error: " << e.what();
dlog->crit() << iaddr << std::endl; dlog->crit() << iaddr << std::endl;
} }
tcp = nullptr;
} }
catch( ... ) catch( ... )
{ {
if( dlog->debugging(Debug::CRIT) ) if( dlog->debugging(Debug::CRIT) )
{ {
ostringstream s; ostringstream s;
s << "(ModbusTCPMaster): connection " << s.str() << " error: catch ..."; s << "(ModbusTCPMaster): connection " << iaddr << ":" << port << " error: catch ...";
dlog->crit() << s.str() << std::endl; dlog->crit() << s.str() << std::endl;
} }
tcp = nullptr;
} }
// }
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
void ModbusTCPMaster::disconnect() void ModbusTCPMaster::disconnect()
...@@ -467,7 +480,7 @@ void ModbusTCPMaster::disconnect() ...@@ -467,7 +480,7 @@ void ModbusTCPMaster::disconnect()
if( !tcp ) if( !tcp )
return; return;
tcp->shutdown(); tcp->close();
tcp.reset(); tcp.reset();
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
...@@ -480,11 +493,20 @@ void ModbusTCPMaster::forceDisconnect() ...@@ -480,11 +493,20 @@ void ModbusTCPMaster::forceDisconnect()
return; return;
tcp->forceDisconnect(); tcp->forceDisconnect();
tcp.reset(); tcp = nullptr;
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
bool ModbusTCPMaster::isConnection() const bool ModbusTCPMaster::isConnection() const
{ {
return tcp && tcp->isConnected(); return tcp && tcp->isConnected();
#if 0
if( !tcp )
return false;
if( tcp->poll({0,5},Poco::Net::Socket::SELECT_READ) )
return (tcp->available() > 0);
return false;
#endif
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
...@@ -245,7 +245,9 @@ void ModbusTCPServer::ioAccept(ev::io& watcher, int revents) ...@@ -245,7 +245,9 @@ void ModbusTCPServer::ioAccept(ev::io& watcher, int revents)
try try
{ {
auto s = make_shared<ModbusTCPSession>(watcher.fd, *vmbaddr, sessTimeout); Poco::Net::StreamSocket ss = sock->acceptConnection();
auto s = make_shared<ModbusTCPSession>(ss, *vmbaddr, sessTimeout);
s->connectReadCoil( sigc::mem_fun(this, &ModbusTCPServer::readCoilStatus) ); s->connectReadCoil( sigc::mem_fun(this, &ModbusTCPServer::readCoilStatus) );
s->connectReadInputStatus( sigc::mem_fun(this, &ModbusTCPServer::readInputStatus) ); s->connectReadInputStatus( sigc::mem_fun(this, &ModbusTCPServer::readInputStatus) );
s->connectReadOutput( sigc::mem_fun(this, &ModbusTCPServer::readOutputRegisters) ); s->connectReadOutput( sigc::mem_fun(this, &ModbusTCPServer::readOutputRegisters) );
......
...@@ -42,7 +42,7 @@ ModbusTCPSession::~ModbusTCPSession() ...@@ -42,7 +42,7 @@ ModbusTCPSession::~ModbusTCPSession()
ioTimeout.stop(); ioTimeout.stop();
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
ModbusTCPSession::ModbusTCPSession( int sfd, const std::unordered_set<ModbusAddr>& a, timeout_t timeout ): ModbusTCPSession::ModbusTCPSession(const Poco::Net::StreamSocket& s, const std::unordered_set<ModbusAddr>& a, timeout_t timeout ):
vaddr(a), vaddr(a),
timeout(timeout), timeout(timeout),
peername(""), peername(""),
...@@ -51,7 +51,7 @@ ModbusTCPSession::ModbusTCPSession( int sfd, const std::unordered_set<ModbusAddr ...@@ -51,7 +51,7 @@ ModbusTCPSession::ModbusTCPSession( int sfd, const std::unordered_set<ModbusAddr
{ {
try try
{ {
sock = make_shared<USocket>(sfd); sock = make_shared<UTCPStream>(s);
// если стремиться к "оптимизации по скорости" // если стремиться к "оптимизации по скорости"
// то getpeername "медленная" операция и может стоит от неё отказаться. // то getpeername "медленная" операция и может стоит от неё отказаться.
......
...@@ -76,7 +76,7 @@ void TCPCheck::check_thread() ...@@ -76,7 +76,7 @@ void TCPCheck::check_thread()
t.create(ip, port, tout_msec); t.create(ip, port, tout_msec);
t.setKeepAliveParams( (tout_msec > 1000 ? tout_msec / 1000 : 1) ); t.setKeepAliveParams( (tout_msec > 1000 ? tout_msec / 1000 : 1) );
setResult(true); setResult(true);
t.shutdown(); t.close();
} }
catch( ... ) {} catch( ... ) {}
} }
......
...@@ -42,7 +42,7 @@ UTCPSocket::UTCPSocket( int sock ): ...@@ -42,7 +42,7 @@ UTCPSocket::UTCPSocket( int sock ):
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
UTCPSocket::UTCPSocket( const string& host, int port ): UTCPSocket::UTCPSocket( const string& host, int port ):
Poco::Net::ServerSocket(Poco::Net::SocketAddress(host,port)) Poco::Net::ServerSocket(Poco::Net::SocketAddress(host,port),true)
{ {
init(); init();
} }
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include <fcntl.h> #include <fcntl.h>
#include <errno.h> #include <errno.h>
#include <cstring> #include <cstring>
#include <Poco/Net/NetException.h>
#include "UTCPStream.h" #include "UTCPStream.h"
#include "PassiveTimer.h" #include "PassiveTimer.h"
#include "UniSetTypes.h" #include "UniSetTypes.h"
...@@ -56,12 +57,20 @@ bool UTCPStream::isSetLinger() const ...@@ -56,12 +57,20 @@ bool UTCPStream::isSetLinger() const
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
void UTCPStream::forceDisconnect() void UTCPStream::forceDisconnect()
{ {
try
{
bool on; bool on;
int sec; int sec;
Poco::Net::StreamSocket::getLinger(on,sec); Poco::Net::StreamSocket::getLinger(on,sec);
setLinger(false,0); setLinger(false,0);
shutdown(); close();
//shutdown();
Poco::Net::StreamSocket::setLinger(on,sec); Poco::Net::StreamSocket::setLinger(on,sec);
}
catch( Poco::Net::NetException& )
{
}
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
bool UTCPStream::setNoDelay(bool enable) bool UTCPStream::setNoDelay(bool enable)
...@@ -103,6 +112,13 @@ void UTCPStream::create(const std::string& hname, int port, timeout_t tout_msec ...@@ -103,6 +112,13 @@ void UTCPStream::create(const std::string& hname, int port, timeout_t tout_msec
bool UTCPStream::isConnected() bool UTCPStream::isConnected()
{ {
//return ( Poco::Net::StreamSocket::sockfd() > 0 ); //return ( Poco::Net::StreamSocket::sockfd() > 0 );
try
{
return ( Poco::Net::StreamSocket::peerAddress().addr() != 0 ); return ( Poco::Net::StreamSocket::peerAddress().addr() != 0 );
}
catch( Poco::Net::NetException& ex )
{
}
return false;
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment