wild services support

This commit is contained in:
Matej Sekoranja
2014-09-12 09:27:10 +02:00
parent e2facc2eac
commit 282b8fe11c
7 changed files with 668 additions and 5 deletions

View File

@@ -237,3 +237,308 @@ testApp/utils/testInetAddressUtils.cpp
testApp/utils/transportRegistryTest.cpp
src/remote/security.h
src/remote/security.cpp
include/pv/baseChannelRequester.h
include/pv/beaconEmitter.h
include/pv/beaconHandler.h
include/pv/beaconServerStatusProvider.h
include/pv/blockingTCP.h
include/pv/blockingUDP.h
include/pv/caChannel.h
include/pv/caProvider.h
include/pv/channelSearchManager.h
include/pv/clientContextImpl.h
include/pv/clientFactory.h
include/pv/codec.h
include/pv/configuration.h
include/pv/hexDump.h
include/pv/inetAddressUtil.h
include/pv/introspectionRegistry.h
include/pv/likely.h
include/pv/logger.h
include/pv/namedLockPattern.h
include/pv/pvAccess.h
include/pv/pvAccessMB.h
include/pv/pvaConstants.h
include/pv/pvaVersion.h
include/pv/referenceCountingLock.h
include/pv/remote.h
include/pv/responseHandlers.h
include/pv/rpcClient.h
include/pv/rpcServer.h
include/pv/rpcService.h
include/pv/security.h
include/pv/serializationHelper.h
include/pv/serverChannelImpl.h
include/pv/serverContext.h
include/pv/simpleChannelSearchManagerImpl.h
include/pv/syncChannelFind.h
include/pv/transportRegistry.h
include/pv/wildcard.h
pvtoolsSrc/eget.cpp
pvtoolsSrc/pvget.cpp
pvtoolsSrc/pvinfo.cpp
pvtoolsSrc/pvput.cpp
pvtoolsSrc/pvutils.cpp
pvtoolsSrc/pvutils.h
src/ca/caChannel.cpp
src/ca/caChannel.h
src/ca/caProvider.cpp
src/ca/caProvider.h
src/client/pvAccess.cpp
src/client/pvAccess.h
src/factory/ChannelAccessFactory.cpp
src/mb/pvAccessMB.cpp
src/mb/pvAccessMB.h
src/pva/clientFactory.cpp
src/pva/clientFactory.h
src/pva/pvaConstants.h
src/pva/pvaVersion.cpp
src/pva/pvaVersion.h
src/remote/abstractResponseHandler.cpp
src/remote/beaconHandler.cpp
src/remote/beaconHandler.h
src/remote/blockingTCP.h
src/remote/blockingTCPAcceptor.cpp
src/remote/blockingTCPConnector.cpp
src/remote/blockingUDP.h
src/remote/blockingUDPConnector.cpp
src/remote/blockingUDPTransport.cpp
src/remote/channelSearchManager.h
src/remote/codec.cpp
src/remote/codec.h
src/remote/remote.h
src/remote/security.cpp
src/remote/security.h
src/remote/serializationHelper.cpp
src/remote/serializationHelper.h
src/remote/simpleChannelSearchManagerImpl.cpp
src/remote/simpleChannelSearchManagerImpl.h
src/remote/transportRegistry.cpp
src/remote/transportRegistry.h
src/remoteClient/clientContextImpl.cpp
src/remoteClient/clientContextImpl.h
src/rpcClient/rpcClient.cpp
src/rpcClient/rpcClient.h
src/rpcService/rpcServer.cpp
src/rpcService/rpcServer.h
src/rpcService/rpcService.cpp
src/rpcService/rpcService.h
src/server/baseChannelRequester.cpp
src/server/baseChannelRequester.h
src/server/beaconEmitter.cpp
src/server/beaconEmitter.h
src/server/beaconServerStatusProvider.cpp
src/server/beaconServerStatusProvider.h
src/server/responseHandlers.cpp
src/server/responseHandlers.h
src/server/serverChannelImpl.cpp
src/server/serverChannelImpl.h
src/server/serverContext.cpp
src/server/serverContext.h
src/utils/configuration.cpp
src/utils/configuration.h
src/utils/hexDump.cpp
src/utils/hexDump.h
src/utils/inetAddressUtil.cpp
src/utils/inetAddressUtil.h
src/utils/introspectionRegistry.cpp
src/utils/introspectionRegistry.h
src/utils/likely.h
src/utils/logger.cpp
src/utils/logger.h
src/utils/namedLockPattern.h
src/utils/referenceCountingLock.cpp
src/utils/referenceCountingLock.h
src/utils/wildcard.cpp
src/utils/wildcard.h
src/v3ioc/PVAClientRegister.cpp
src/v3ioc/PVAServerRegister.cpp
src/v3ioc/syncChannelFind.h
testApp/client/MockClientImpl.cpp
testApp/client/testChannelAccessFactory.cpp
testApp/client/testMockClient.cpp
testApp/client/testStartStop.cpp
testApp/remote/channelAccessIFTest.cpp
testApp/remote/channelAccessIFTest.h
testApp/remote/epicsv4Grayscale.h
testApp/remote/rpcClientExample.cpp
testApp/remote/rpcServiceExample.cpp
testApp/remote/syncTestRequesters.h
testApp/remote/testADCSim.cpp
testApp/remote/testBeaconEmitter.cpp
testApp/remote/testBeaconHandler.cpp
testApp/remote/testBlockingTCPClnt.cpp
testApp/remote/testBlockingTCPSrv.cpp
testApp/remote/testBlockingUDPClnt.cpp
testApp/remote/testBlockingUDPSrv.cpp
testApp/remote/testChannelAccess.cpp
testApp/remote/testChannelConnect.cpp
testApp/remote/testChannelSearchManager.cpp
testApp/remote/testCodec.cpp
testApp/remote/testGetPerformance.cpp
testApp/remote/testMonitorPerformance.cpp
testApp/remote/testNTImage.cpp
testApp/remote/testRemoteClientImpl.cpp
testApp/remote/testServer.cpp
testApp/remote/testServerContext.cpp
testApp/utils/configurationTest.cpp
testApp/utils/introspectionRegistryTest.cpp
testApp/utils/loggerTest.cpp
testApp/utils/namedLockPatternTest.cpp
testApp/utils/testAtomicBoolean.cpp
testApp/utils/testHexDump.cpp
testApp/utils/testInetAddressUtils.cpp
testApp/utils/transportRegistryTest.cpp
include/pv/baseChannelRequester.h
include/pv/beaconEmitter.h
include/pv/beaconHandler.h
include/pv/beaconServerStatusProvider.h
include/pv/blockingTCP.h
include/pv/blockingUDP.h
include/pv/caChannel.h
include/pv/caProvider.h
include/pv/channelSearchManager.h
include/pv/clientContextImpl.h
include/pv/clientFactory.h
include/pv/codec.h
include/pv/configuration.h
include/pv/hexDump.h
include/pv/inetAddressUtil.h
include/pv/introspectionRegistry.h
include/pv/likely.h
include/pv/logger.h
include/pv/namedLockPattern.h
include/pv/pvAccess.h
include/pv/pvAccessMB.h
include/pv/pvaConstants.h
include/pv/pvaVersion.h
include/pv/referenceCountingLock.h
include/pv/remote.h
include/pv/responseHandlers.h
include/pv/rpcClient.h
include/pv/rpcServer.h
include/pv/rpcService.h
include/pv/security.h
include/pv/serializationHelper.h
include/pv/serverChannelImpl.h
include/pv/serverContext.h
include/pv/simpleChannelSearchManagerImpl.h
include/pv/syncChannelFind.h
include/pv/transportRegistry.h
include/pv/wildcard.h
pvtoolsSrc/eget.cpp
pvtoolsSrc/pvget.cpp
pvtoolsSrc/pvinfo.cpp
pvtoolsSrc/pvput.cpp
pvtoolsSrc/pvutils.cpp
pvtoolsSrc/pvutils.h
src/ca/caChannel.cpp
src/ca/caChannel.h
src/ca/caProvider.cpp
src/ca/caProvider.h
src/client/pvAccess.cpp
src/client/pvAccess.h
src/factory/ChannelAccessFactory.cpp
src/mb/pvAccessMB.cpp
src/mb/pvAccessMB.h
src/pva/clientFactory.cpp
src/pva/clientFactory.h
src/pva/pvaConstants.h
src/pva/pvaVersion.cpp
src/pva/pvaVersion.h
src/remote/abstractResponseHandler.cpp
src/remote/beaconHandler.cpp
src/remote/beaconHandler.h
src/remote/blockingTCP.h
src/remote/blockingTCPAcceptor.cpp
src/remote/blockingTCPConnector.cpp
src/remote/blockingUDP.h
src/remote/blockingUDPConnector.cpp
src/remote/blockingUDPTransport.cpp
src/remote/channelSearchManager.h
src/remote/codec.cpp
src/remote/codec.h
src/remote/remote.h
src/remote/security.cpp
src/remote/security.h
src/remote/serializationHelper.cpp
src/remote/serializationHelper.h
src/remote/simpleChannelSearchManagerImpl.cpp
src/remote/simpleChannelSearchManagerImpl.h
src/remote/transportRegistry.cpp
src/remote/transportRegistry.h
src/remoteClient/clientContextImpl.cpp
src/remoteClient/clientContextImpl.h
src/rpcClient/rpcClient.cpp
src/rpcClient/rpcClient.h
src/rpcService/rpcServer.cpp
src/rpcService/rpcServer.h
src/rpcService/rpcService.cpp
src/rpcService/rpcService.h
src/server/baseChannelRequester.cpp
src/server/baseChannelRequester.h
src/server/beaconEmitter.cpp
src/server/beaconEmitter.h
src/server/beaconServerStatusProvider.cpp
src/server/beaconServerStatusProvider.h
src/server/responseHandlers.cpp
src/server/responseHandlers.h
src/server/serverChannelImpl.cpp
src/server/serverChannelImpl.h
src/server/serverContext.cpp
src/server/serverContext.h
src/utils/configuration.cpp
src/utils/configuration.h
src/utils/hexDump.cpp
src/utils/hexDump.h
src/utils/inetAddressUtil.cpp
src/utils/inetAddressUtil.h
src/utils/introspectionRegistry.cpp
src/utils/introspectionRegistry.h
src/utils/likely.h
src/utils/logger.cpp
src/utils/logger.h
src/utils/namedLockPattern.h
src/utils/referenceCountingLock.cpp
src/utils/referenceCountingLock.h
src/utils/wildcard.cpp
src/utils/wildcard.h
src/v3ioc/PVAClientRegister.cpp
src/v3ioc/PVAServerRegister.cpp
src/v3ioc/syncChannelFind.h
testApp/client/MockClientImpl.cpp
testApp/client/testChannelAccessFactory.cpp
testApp/client/testMockClient.cpp
testApp/client/testStartStop.cpp
testApp/remote/channelAccessIFTest.cpp
testApp/remote/channelAccessIFTest.h
testApp/remote/epicsv4Grayscale.h
testApp/remote/rpcClientExample.cpp
testApp/remote/rpcServiceExample.cpp
testApp/remote/rpcWildServiceExample.cpp
testApp/remote/syncTestRequesters.h
testApp/remote/testADCSim.cpp
testApp/remote/testBeaconEmitter.cpp
testApp/remote/testBeaconHandler.cpp
testApp/remote/testBlockingTCPClnt.cpp
testApp/remote/testBlockingTCPSrv.cpp
testApp/remote/testBlockingUDPClnt.cpp
testApp/remote/testBlockingUDPSrv.cpp
testApp/remote/testChannelAccess.cpp
testApp/remote/testChannelConnect.cpp
testApp/remote/testChannelSearchManager.cpp
testApp/remote/testCodec.cpp
testApp/remote/testGetPerformance.cpp
testApp/remote/testMonitorPerformance.cpp
testApp/remote/testNTImage.cpp
testApp/remote/testRemoteClientImpl.cpp
testApp/remote/testServer.cpp
testApp/remote/testServerContext.cpp
testApp/utils/configurationTest.cpp
testApp/utils/introspectionRegistryTest.cpp
testApp/utils/loggerTest.cpp
testApp/utils/namedLockPatternTest.cpp
testApp/utils/testAtomicBoolean.cpp
testApp/utils/testHexDump.cpp
testApp/utils/testInetAddressUtils.cpp
testApp/utils/transportRegistryTest.cpp

View File

@@ -5,9 +5,12 @@
*/
#include <stdexcept>
#include <vector>
#include <utility>
#define epicsExportSharedSymbols
#include <pv/rpcServer.h>
#include <pv/wildcard.h>
using namespace epics::pvData;
using std::string;
@@ -362,14 +365,15 @@ public:
virtual void cancel() {}
virtual void destroy() {}
virtual ChannelFind::shared_pointer channelFind(std::string const & channelName,
ChannelFindRequester::shared_pointer const & channelFindRequester)
{
bool found;
{
Lock guard(m_mutex);
found = (m_services.find(channelName) != m_services.end());
found = (m_services.find(channelName) != m_services.end()) ||
findWildService(channelName);
}
ChannelFind::shared_pointer thisPtr(shared_from_this());
channelFindRequester->channelFindResult(Status::Ok, thisPtr, found);
@@ -403,13 +407,21 @@ public:
ChannelRequester::shared_pointer const & channelRequester,
short /*priority*/)
{
RPCService::shared_pointer service;
RPCServiceMap::const_iterator iter;
{
Lock guard(m_mutex);
iter = m_services.find(channelName);
}
if (iter == m_services.end())
if (iter != m_services.end())
service = iter->second;
// check for wild services
if (!service)
service = findWildService(channelName);
if (!service)
{
Channel::shared_pointer nullChannel;
channelRequester->channelCreated(noSuchChannelStatus, nullChannel);
@@ -422,7 +434,7 @@ public:
shared_from_this(),
channelName,
channelRequester,
iter->second));
service));
Channel::shared_pointer rpcChannel = tp;
channelRequester->channelCreated(Status::Ok, rpcChannel);
return rpcChannel;
@@ -442,17 +454,58 @@ public:
{
Lock guard(m_mutex);
m_services[serviceName] = service;
if (isWildcardPattern(serviceName))
m_wildServices.push_back(std::make_pair(serviceName, service));
}
void unregisterService(std::string const & serviceName)
{
Lock guard(m_mutex);
m_services.erase(serviceName);
if (isWildcardPattern(serviceName))
{
for (RPCWildServiceList::iterator iter = m_wildServices.begin();
iter != m_wildServices.end();
iter++)
if (iter->first == serviceName)
{
m_wildServices.erase(iter);
break;
}
}
}
private:
// assumes sync on services
RPCService::shared_pointer findWildService(string const & wildcard)
{
if (!m_wildServices.empty())
for (RPCWildServiceList::iterator iter = m_wildServices.begin();
iter != m_wildServices.end();
iter++)
if (Wildcard::wildcardfit(iter->first.c_str(), wildcard.c_str()))
return iter->second;
return RPCService::shared_pointer();
}
// (too) simple check
bool isWildcardPattern(string const & pattern)
{
return
(pattern.find('*') != string::npos ||
pattern.find('?') != string::npos ||
(pattern.find('[') != string::npos && pattern.find(']') != string::npos));
}
typedef std::map<string, RPCService::shared_pointer> RPCServiceMap;
RPCServiceMap m_services;
typedef std::vector<std::pair<string, RPCService::shared_pointer> > RPCWildServiceList;
RPCWildServiceList m_wildServices;
epics::pvData::Mutex m_mutex;
};

View File

@@ -10,6 +10,7 @@ INC += namedLockPattern.h
INC += referenceCountingLock.h
INC += configuration.h
INC += likely.h
INC += wildcard.h
LIBSRCS += hexDump.cpp
LIBSRCS += inetAddressUtil.cpp
@@ -17,3 +18,4 @@ LIBSRCS += logger.cpp
LIBSRCS += introspectionRegistry.cpp
LIBSRCS += configuration.cpp
LIBSRCS += referenceCountingLock.cpp
LIBSRCS += wildcard.cpp

168
src/utils/wildcard.cpp Normal file
View File

@@ -0,0 +1,168 @@
/*******************************************************************
* This implementation was adpoted from:
*
* Copyright (C) 1996, 1997, 1998, 1999, 2000 Florian Schintke
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA02111-1307 USA
*
* F.Schintke, the author of the original code, has authorized to
* distribute these files under LGPL License.
*
* ----------------------
* Implementation of the UN*X wildcards
* Supported wild-characters: '*', '?'; sets: [a-z], '!' negation
* Examples:
* '[a-g]l*i?n' matches 'florian'
* '[!abc]*e' matches 'smile'
* '[-z] matches 'a'
*
*/
#define epicsExportSharedSymbols
#include <pv/wildcard.h>
using namespace epics::pvAccess;
int
Wildcard::wildcardfit (const char *wildcard, const char *test)
{
int fit = 1;
for (; ('\000' != *wildcard) && (1 == fit) && ('\000' != *test); wildcard++)
{
switch (*wildcard)
{
case '[':
wildcard++; /* leave out the opening square bracket */
fit = set (&wildcard, &test);
/* we don't need to decrement the wildcard as in case */
/* of asterisk because the closing ] is still there */
break;
case '?':
test++;
break;
case '*':
fit = asterisk (&wildcard, &test);
/* the asterisk was skipped by asterisk() but the loop will */
/* increment by itself. So we have to decrement */
wildcard--;
break;
default:
fit = (int) (*wildcard == *test);
test++;
}
}
while ((*wildcard == '*') && (1 == fit))
/* here the teststring is empty otherwise you cannot */
/* leave the previous loop */
wildcard++;
return (int) ((1 == fit) && ('\0' == *test) && ('\0' == *wildcard));
}
int
Wildcard::set (const char **wildcard, const char **test)
{
int fit = 0;
int negation = 0;
int at_beginning = 1;
if ('!' == **wildcard)
{
negation = 1;
(*wildcard)++;
}
while ((']' != **wildcard) || (1 == at_beginning))
{
if (0 == fit)
{
if (('-' == **wildcard)
&& ((*(*wildcard - 1)) < (*(*wildcard + 1)))
&& (']' != *(*wildcard + 1))
&& (0 == at_beginning))
{
if (((**test) >= (*(*wildcard - 1)))
&& ((**test) <= (*(*wildcard + 1))))
{
fit = 1;
(*wildcard)++;
}
}
else if ((**wildcard) == (**test))
{
fit = 1;
}
}
(*wildcard)++;
at_beginning = 0;
}
if (1 == negation)
/* change from zero to one and vice versa */
fit = 1 - fit;
if (1 == fit)
(*test)++;
return (fit);
}
int
Wildcard::asterisk (const char **wildcard, const char **test)
{
/* Warning: uses multiple returns */
int fit = 1;
/* erase the leading asterisk */
(*wildcard)++;
while (('\000' != (**test))
&& (('?' == **wildcard)
|| ('*' == **wildcard)))
{
if ('?' == **wildcard)
(*test)++;
(*wildcard)++;
}
/* Now it could be that test is empty and wildcard contains */
/* aterisks. Then we delete them to get a proper state */
while ('*' == (**wildcard))
(*wildcard)++;
if (('\0' == (**test)) && ('\0' != (**wildcard)))
return (fit = 0);
if (('\0' == (**test)) && ('\0' == (**wildcard)))
return (fit = 1);
else
{
/* Neither test nor wildcard are empty! */
/* the first character of wildcard isn't in [*?] */
if (0 == wildcardfit(*wildcard, (*test)))
{
do
{
(*test)++;
/* skip as much characters as possible in the teststring */
/* stop if a character match occurs */
while (((**wildcard) != (**test))
&& ('[' != (**wildcard))
&& ('\0' != (**test)))
(*test)++;
}
while ((('\0' != **test))?
(0 == wildcardfit (*wildcard, (*test)))
: (0 != (fit = 0)));
}
if (('\0' == **test) && ('\0' == **wildcard))
fit = 1;
return (fit);
}
}

83
src/utils/wildcard.h Normal file
View File

@@ -0,0 +1,83 @@
/*******************************************************************
* This implementation was adpoted from:
*
* Copyright (C) 1996, 1997, 1998, 1999, 2000 Florian Schintke
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA02111-1307 USA
*
* F.Schintke, the author of the original code, has authorized to
* distribute these files under LGPL License.
*
* ----------------------
* Implementation of the UN*X wildcards
* Supported wild-characters: '*', '?'; sets: [a-z], '!' negation
* Examples:
* '[a-g]l*i?n' matches 'florian'
* '[!abc]*e' matches 'smile'
* '[-z] matches 'a'
*
*/
#ifndef WILDCARD_H
#define WILDCARD_H
#include <shareLib.h>
namespace epics { namespace pvAccess {
/**
* Class which implements UNIX style wildcards and tests to see
* if strings match the wildcard.
*/
class epicsShareClass Wildcard
{
public:
/**
* This function implements the UN*X wildcards.
* @param wildcard Wildcard to be used.
* @param test Value which we want to see if it matches the wildcard.
* @return 0 if wildcard does not match *test. 1 - if wildcard
* matches test.
*/
static int wildcardfit (const char *wildcard, const char *test);
private:
/**
* Scans a set of characters and returns 0 if the set mismatches at this
* position in the teststring and 1 if it is matching
* wildcard is set to the closing ] and test is unmodified if mismatched
* and otherwise the char pointer is pointing to the next character
* @param wildcard UNIX style wildcard to be used
* @param test String we will test against the wildcard.
* @return 0 if the set mismatches. 1 otherwise.
*/
static int set (const char **wildcard, const char **test);
/**
* Scans an asterisk.
* @param wildcard UNIX style wildcard to be used
* @param test String we will test against the wildcard.
* @return ???
*/
static int asterisk (const char **wildcard, const char **test);
};
} }
#endif

View File

@@ -68,6 +68,10 @@ PROD_HOST += rpcServiceExample
rpcServiceExample_SRCS += rpcServiceExample.cpp
rpcServiceExample_LIBS += pvAccess pvData pvMB Com
PROD_HOST += rpcWildServiceExample
rpcWildServiceExample_SRCS += rpcWildServiceExample.cpp
rpcWildServiceExample_LIBS += pvAccess pvData pvMB Com
PROD_HOST += rpcClientExample
rpcClientExample_SRCS += rpcClientExample.cpp
rpcClientExample_LIBS += pvAccess pvData pvMB Com

View File

@@ -0,0 +1,48 @@
/**
* Copyright - See the COPYRIGHT that is included with this distribution.
* pvAccessCPP is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
*/
#include <pv/pvData.h>
#include <pv/rpcServer.h>
using namespace epics::pvData;
using namespace epics::pvAccess;
static Structure::const_shared_pointer resultStructure =
getFieldCreate()->createFieldBuilder()->
add("channelName", pvString)->
createStructure();
class WildServiceImpl :
public RPCService
{
PVStructure::shared_pointer request(PVStructure::shared_pointer const & pvArguments)
throw (RPCRequestException)
{
// requires NTURI as argument
if (pvArguments->getStructure()->getID() != "uri:ev4:nt/2012/pwd:NTURI")
throw RPCRequestException(Status::STATUSTYPE_ERROR, "RPC argument must be a NTURI normative type");
std::string channelName = pvArguments->getSubField<PVString>("path")->get();
// create return structure and set data
PVStructure::shared_pointer result = getPVDataCreate()->createPVStructure(resultStructure);
result->getSubField<PVString>("channelName")->put(channelName);
return result;
}
};
int main()
{
RPCServer server;
server.registerService("wild*", RPCService::shared_pointer(new WildServiceImpl()));
// you can register as many services as you want here ...
server.printInfo();
server.run();
return 0;
}