BaseResponseRequests

This commit is contained in:
Matej Sekoranja
2011-01-16 22:40:30 +01:00
parent 676cb0e3f6
commit 9d2692026a
7 changed files with 308 additions and 112 deletions

View File

@@ -579,7 +579,7 @@ void ChannelSearchManager::initializeSendBuffer()
sendBuffer.put(REQUIRE_REPLY ? (byte)QoS.REPLY_REQUIRED.getMaskValue() : (byte)QoS.DEFAULT.getMaskValue());
*/
_sendBuffer->putByte((int8)DEFAULT);
_sendBuffer->putByte((int8)QOS_DEFAULT);
_sendBuffer->putShort((int16)0); // count
}

View File

@@ -35,39 +35,39 @@ namespace epics {
/**
* Default behavior.
*/
DEFAULT = 0x00,
QOS_DEFAULT = 0x00,
/**
* Require reply (acknowledgment for reliable operation).
*/
REPLY_REQUIRED = 0x01,
QOS_REPLY_REQUIRED = 0x01,
/**
* Best-effort option (no reply).
*/
BESY_EFFORT = 0x02,
QOS_BESY_EFFORT = 0x02,
/**
* Process option.
*/
PROCESS = 0x04,
QOS_PROCESS = 0x04,
/**
* Initialize option.
*/
INIT = 0x08,
QOS_INIT = 0x08,
/**
* Destroy option.
*/
DESTROY = 0x10,
QOS_DESTROY = 0x10,
/**
* Share data option.
*/
SHARE = 0x20,
QOS_SHARE = 0x20,
/**
* Get.
*/
GET = 0x40,
QOS_GET = 0x40,
/**
* Get-put.
*/
GET_PUT = 0x80
QOS_GET_PUT = 0x80
};
typedef int32 pvAccessID;
@@ -548,7 +548,29 @@ namespace epics {
*/
virtual void response(Transport* transport, int8 version, ByteBuffer* payloadBuffer) = 0;
};
};
/**
* A request that expects an response multiple responses.
* Responses identified by its I/O ID.
* This interface needs to be extended (to provide method called on response).
* @author <a href="mailto:matej.sekoranjaATcosylab.com">Matej Sekoranja</a>
* @version $Id: SubscriptionRequest.java,v 1.1 2010/05/03 14:45:39 mrkraimer Exp $
*/
class SubscriptionRequest /*: public ResponseRequest*/ {
public:
/**
* Update (e.g. after some time of unresponsiveness) - report current value.
*/
virtual void updateSubscription() = 0;
/**
* Rescubscribe (e.g. when server was restarted)
* @param transport new transport to be used.
*/
virtual void resubscribeSubscription(Transport* transport) = 0;
};
}
}

View File

@@ -16,6 +16,7 @@ namespace epics {
namespace pvAccess {
class BeaconHandler;
class ClientContextImpl;
class ChannelImpl :
public Channel ,
@@ -28,7 +29,15 @@ namespace epics {
virtual void destroyChannel(bool force) = 0;
virtual void connectionCompleted(pvAccessID sid/*, rights*/) = 0;
virtual void createChannelFailed() = 0;
virtual ClientContextImpl* getContext() = 0;
virtual pvAccessID getServerChannelID() = 0;
virtual void registerResponseRequest(ResponseRequest* responseRequest) = 0;
virtual void unregisterResponseRequest(ResponseRequest* responseRequest) = 0;
virtual Transport* checkAndGetTransport() = 0;
static Status* channelDestroyed;
static Status* channelDisconnected;
};

View File

@@ -41,9 +41,7 @@ FieldConstPtr IntrospectionRegistry::getIntrospectionInterface(const short id)
Lock guard(&_mutex);
_registryIter = _registry.find(id);
if(_registryIter == _registry.end())
{
throw EpicsException("missing interface with given id");
}
return NULL;
return _registryIter->second;
}
@@ -121,32 +119,27 @@ bool IntrospectionRegistry::registryContainsValue(FieldConstPtr field, short& ke
void IntrospectionRegistry::serialize(FieldConstPtr field, ByteBuffer* buffer, SerializableControl* control)
{
checkBufferAndSerializeControl(buffer, control);
serialize(field, NULL, buffer, control, this);
}
FieldConstPtr IntrospectionRegistry::deserialize(ByteBuffer* buffer, DeserializableControl* control)
{
checkBufferAndDeserializeControl(buffer, control);
return deserialize(buffer, control, this);
}
void IntrospectionRegistry::serializeFull(FieldConstPtr field, ByteBuffer* buffer, SerializableControl* control)
{
checkBufferAndSerializeControl(buffer, control);
serialize(field, NULL, buffer, control, NULL);
}
FieldConstPtr IntrospectionRegistry::deserializeFull(ByteBuffer* buffer, DeserializableControl* control)
{
checkBufferAndDeserializeControl(buffer, control);
return deserialize(buffer, control, NULL);
}
void IntrospectionRegistry::serialize(FieldConstPtr field, StructureConstPtr parent, ByteBuffer* buffer,
SerializableControl* control, IntrospectionRegistry* registry)
{
checkBufferAndSerializeControl(buffer, control);
if (field == NULL)
{
control->ensureBuffer(1);
@@ -225,11 +218,6 @@ void IntrospectionRegistry::serialize(FieldConstPtr field, StructureConstPtr par
void IntrospectionRegistry::serializeStructureField(ByteBuffer* buffer, SerializableControl* control,
IntrospectionRegistry* registry, StructureConstPtr structure)
{
checkBufferAndSerializeControl(buffer, control);
if(structure == NULL)
{
throw EpicsException("null structure provided");
}
SerializeHelper::serializeString(structure->getFieldName(), buffer, control);
FieldConstPtrArray fields = structure->getFields();
SerializeHelper::writeSize(structure->getNumberFields(), buffer, control);
@@ -241,7 +229,6 @@ void IntrospectionRegistry::serializeStructureField(ByteBuffer* buffer, Serializ
FieldConstPtr IntrospectionRegistry::deserialize(ByteBuffer* buffer, DeserializableControl* control, IntrospectionRegistry* registry)
{
checkBufferAndDeserializeControl(buffer, control);
control->ensureData(1);
const int8 typeCode = buffer->getByte();
if(typeCode == IntrospectionRegistry::NULL_TYPE_CODE)
@@ -250,10 +237,6 @@ FieldConstPtr IntrospectionRegistry::deserialize(ByteBuffer* buffer, Deserializa
}
else if(typeCode == IntrospectionRegistry::ONLY_ID_TYPE_CODE)
{
if (registry == NULL)
{
throw EpicsException("deserialization provided chached ID, but no registry provided");
}
control->ensureData(sizeof(int16)/sizeof(int8));
return registry->getIntrospectionInterface(buffer->getShort());
}
@@ -261,10 +244,6 @@ FieldConstPtr IntrospectionRegistry::deserialize(ByteBuffer* buffer, Deserializa
// could also be a mask
if(typeCode == IntrospectionRegistry::FULL_WITH_ID_TYPE_CODE)
{
if(registry == NULL)
{
throw EpicsException("deserialization provided chached ID, but no registry provided");
}
control->ensureData(sizeof(int16)/sizeof(int8));
const short key = buffer->getShort();
FieldConstPtr field = deserialize(buffer, control, registry);
@@ -301,15 +280,14 @@ FieldConstPtr IntrospectionRegistry::deserialize(ByteBuffer* buffer, Deserializa
}
default:
{
std::string msg = "unsupported type: " + type;
throw EpicsException(msg);
// TODO log
return NULL;
}
}
}
StructureConstPtr IntrospectionRegistry::deserializeStructureField(ByteBuffer* buffer, DeserializableControl* control, IntrospectionRegistry* registry)
{
checkBufferAndDeserializeControl(buffer, control);
const String structureFieldName = SerializeHelper::deserializeString(buffer, control);
const int32 size = SerializeHelper::readSize(buffer, control);
FieldConstPtrArray fields = NULL;
@@ -329,7 +307,6 @@ StructureConstPtr IntrospectionRegistry::deserializeStructureField(ByteBuffer* b
void IntrospectionRegistry::serializeStructure(ByteBuffer* buffer, SerializableControl* control, PVStructurePtr pvStructure)
{
checkBufferAndSerializeControl(buffer, control);
if (pvStructure == NULL)
{
serialize(NULL, buffer, control);
@@ -343,7 +320,6 @@ void IntrospectionRegistry::serializeStructure(ByteBuffer* buffer, SerializableC
PVStructurePtr IntrospectionRegistry::deserializeStructure(ByteBuffer* buffer, DeserializableControl* control)
{
checkBufferAndDeserializeControl(buffer, control);
PVStructurePtr pvStructure = NULL;
FieldConstPtr structureField = deserialize(buffer, control);
if (structureField != NULL)
@@ -356,21 +332,18 @@ PVStructurePtr IntrospectionRegistry::deserializeStructure(ByteBuffer* buffer, D
void IntrospectionRegistry::serializePVRequest(ByteBuffer* buffer, SerializableControl* control, PVStructurePtr pvRequest)
{
checkBufferAndSerializeControl(buffer, control);
// for now ordinary structure, later can be changed
serializeStructure(buffer, control, pvRequest);
}
PVStructurePtr IntrospectionRegistry::deserializePVRequest(ByteBuffer* buffer, DeserializableControl* control)
{
checkBufferAndDeserializeControl(buffer, control);
// for now ordinary structure, later can be changed
return deserializeStructure(buffer, control);
}
PVStructurePtr IntrospectionRegistry::deserializeStructureAndCreatePVStructure(ByteBuffer* buffer, DeserializableControl* control)
{
checkBufferAndDeserializeControl(buffer, control);
FieldConstPtr field = deserialize(buffer, control);
if (field == NULL)
{
@@ -381,46 +354,14 @@ PVStructurePtr IntrospectionRegistry::deserializeStructureAndCreatePVStructure(B
void IntrospectionRegistry::serializeStatus(ByteBuffer* buffer, SerializableControl* control, Status* status)
{
checkBufferAndSerializeControl(buffer, control);
if(status != NULL)
{
status->serialize(buffer, control);
}
else
{
throw EpicsException("null status provided");
}
status->serialize(buffer, control);
}
Status* IntrospectionRegistry::deserializeStatus(ByteBuffer* buffer, DeserializableControl* control)
{
checkBufferAndDeserializeControl(buffer, control);
return _statusCreate->deserializeStatus(buffer, control);
}
void IntrospectionRegistry::checkBufferAndSerializeControl(ByteBuffer* buffer, SerializableControl* control)
{
if(buffer == NULL)
{
throw EpicsException("null buffer provided");
}
if(control == NULL)
{
throw EpicsException("null control provided");
}
}
void IntrospectionRegistry::checkBufferAndDeserializeControl(ByteBuffer* buffer, DeserializableControl* control)
{
if(buffer == NULL)
{
throw EpicsException("null buffer provided");
}
if(control == NULL)
{
throw EpicsException("null control provided");
}
}
}}

View File

@@ -242,8 +242,6 @@ typedef std::map<const short,const Field*> registryMap_t;
static FieldCreate* _fieldCreate;
bool registryContainsValue(FieldConstPtr field, short& key);
static void checkBufferAndSerializeControl(ByteBuffer* buffer, SerializableControl* control);
static void checkBufferAndDeserializeControl(ByteBuffer* buffer, DeserializableControl* control);
};
}}

View File

@@ -28,6 +28,195 @@
using namespace epics::pvData;
using namespace epics::pvAccess;
static Status* g_statusOK = getStatusCreate()->getStatusOK();
static StatusCreate* statusCreate = getStatusCreate();
static Status* okStatus = g_statusOK;
static Status* destroyedStatus = statusCreate->createStatus(STATUSTYPE_ERROR, "request destroyed");
static Status* channelNotConnected = statusCreate->createStatus(STATUSTYPE_ERROR, "channel not connected");
static Status* otherRequestPendingStatus = statusCreate->createStatus(STATUSTYPE_ERROR, "other request pending");
static PVDataCreate* pvDataCreate = getPVDataCreate();
/**
* Base channel request.
* @author <a href="mailto:matej.sekoranjaATcosylab.com">Matej Sekoranja</a>
* @version $Id: BaseRequestImpl.java,v 1.1 2010/05/03 14:45:40 mrkraimer Exp $
*/
class BaseRequestImpl :
public DataResponse,
public SubscriptionRequest,
public TransportSender {
protected:
ChannelImpl* m_channel;
ClientContextImpl* m_context;
pvAccessID m_ioid;
Requester* m_requester;
// TODO sync
volatile bool m_destroyed;
volatile bool m_remotelyDestroyed;
/* negative... */
static const int NULL_REQUEST = -1;
static const int PURE_DESTROY_REQUEST = -2;
int32 m_pendingRequest;
Mutex m_mutex;
public:
BaseRequestImpl(ChannelImpl* channel, Requester* requester) :
m_channel(channel), m_context(channel->getContext()),
m_requester(requester), m_destroyed(false), m_remotelyDestroyed(false),
m_pendingRequest(NULL_REQUEST)
{
// register response request
m_ioid = m_context->registerResponseRequest(this);
channel->registerResponseRequest(this);
}
bool startRequest(int32 qos) {
Lock guard(&m_mutex);
// we allow pure destroy...
if (m_pendingRequest != NULL_REQUEST && qos != PURE_DESTROY_REQUEST)
return false;
m_pendingRequest = qos;
return true;
}
void stopRequest() {
Lock guard(&m_mutex);
m_pendingRequest = NULL_REQUEST;
}
int32 getPendingRequest() {
Lock guard(&m_mutex);
return m_pendingRequest;
}
Requester* getRequester() {
return m_requester;
}
pvAccessID getIOID() {
return m_ioid;
}
virtual bool initResponse(Transport* transport, int8 version, ByteBuffer* payloadBuffer, int8 qos, Status* status);
virtual bool destroyResponse(Transport* transport, int8 version, ByteBuffer* payloadBuffer, int8 qos, Status* status);
virtual bool normalResponse(Transport* transport, int8 version, ByteBuffer* payloadBuffer, int8 qos, Status* status);
virtual void response(Transport* transport, int8 version, ByteBuffer* payloadBuffer) {
// TODO?
// try
// {
transport->ensureData(1);
int8 qos = payloadBuffer->getByte();
Status* status = statusCreate->deserializeStatus(payloadBuffer, transport);
if (qos & QOS_INIT)
{
initResponse(transport, version, payloadBuffer, qos, status);
}
else if (qos & QOS_DESTROY)
{
m_remotelyDestroyed = true;
if (!destroyResponse(transport, version, payloadBuffer, qos, status))
cancel();
}
else
{
normalResponse(transport, version, payloadBuffer, qos, status);
}
// TODO
if (status != okStatus)
delete status;
}
virtual void cancel() {
destroy();
}
virtual void destroy() {
{
Lock guard(&m_mutex);
if (m_destroyed)
return;
m_destroyed = true;
}
// unregister response request
m_context->unregisterResponseRequest(this);
m_channel->unregisterResponseRequest(this);
// destroy remote instance
if (!m_remotelyDestroyed)
{
startRequest(PURE_DESTROY_REQUEST);
m_channel->checkAndGetTransport()->enqueueSendRequest(this);
}
}
virtual void timeout() {
cancel();
// TODO notify?
}
void reportStatus(Status* status) {
// destroy, since channel (parent) was destroyed
if (status == ChannelImpl::channelDestroyed)
destroy();
else if (status == ChannelImpl::channelDisconnected)
stopRequest();
// TODO notify?
}
virtual void updateSubscription() {
// default is noop
}
virtual void lock() {
// noop
}
virtual void send(ByteBuffer* buffer, TransportSendControl* control) {
int8 qos = getPendingRequest();
if (qos == -1)
return;
else if (qos == PURE_DESTROY_REQUEST)
{
control->startMessage((int8)15, 8);
buffer->putInt(m_channel->getServerChannelID());
buffer->putInt(m_ioid);
}
stopRequest();
}
virtual void unlock() {
// noop
}
};
PVDATA_REFCOUNT_MONITOR_DEFINE(mockChannelProcess);
@@ -77,7 +266,7 @@ class ChannelImplProcess : public ChannelProcess
m_valueField = static_cast<PVScalar*>(field);
// TODO pvRequest
m_channelProcessRequester->channelProcessConnect(getStatusCreate()->getStatusOK(), this);
m_channelProcessRequester->channelProcessConnect(g_statusOK, this);
}
virtual void process(bool lastRequest)
@@ -153,7 +342,7 @@ class ChannelImplProcess : public ChannelProcess
break;
}
m_channelProcessRequester->processDone(getStatusCreate()->getStatusOK());
m_channelProcessRequester->processDone(g_statusOK);
if (lastRequest)
destroy();
@@ -171,41 +360,39 @@ class ChannelImplProcess : public ChannelProcess
PVDATA_REFCOUNT_MONITOR_DEFINE(mockChannelGet);
PVDATA_REFCOUNT_MONITOR_DEFINE(channelGet);
class ChannelImplGet : public ChannelGet
{
private:
ChannelImpl* m_channel;
ChannelGetRequester* m_channelGetRequester;
PVStructure* m_pvStructure;
PVStructure* m_pvRequest;
PVStructure* m_data;
BitSet* m_bitSet;
volatile bool m_first;
private:
~ChannelImplGet()
{
PVDATA_REFCOUNT_MONITOR_DESTRUCT(mockChannelGet);
PVDATA_REFCOUNT_MONITOR_DESTRUCT(channelGet);
}
public:
ChannelImplGet(ChannelGetRequester* channelGetRequester, PVStructure *pvStructure, PVStructure *pvRequest) :
m_channelGetRequester(channelGetRequester), m_pvStructure(pvStructure),
m_bitSet(new BitSet(pvStructure->getNumberFields())), m_first(true)
ChannelImplGet(ChannelImpl* channel, ChannelGetRequester* channelGetRequester, PVStructure *pvRequest) :
m_channel(channel), m_channelGetRequester(channelGetRequester), m_pvRequest(pvRequest),
m_data(0), m_bitSet(0)
{
PVDATA_REFCOUNT_MONITOR_CONSTRUCT(mockChannelGet);
PVDATA_REFCOUNT_MONITOR_CONSTRUCT(channelGet);
// TODO pvRequest
m_channelGetRequester->channelGetConnect(getStatusCreate()->getStatusOK(), this, m_pvStructure, m_bitSet);
m_channelGetRequester->channelGetConnect(g_statusOK, this, m_data, m_bitSet);
}
virtual void get(bool lastRequest)
{
m_channelGetRequester->getDone(getStatusCreate()->getStatusOK());
if (m_first)
{
m_first = false;
m_bitSet->set(0); // TODO
}
m_channelGetRequester->getDone(g_statusOK);
if (lastRequest)
destroy();
@@ -213,7 +400,7 @@ class ChannelImplGet : public ChannelGet
virtual void destroy()
{
delete m_bitSet;
// delete m_bitSet;
delete this;
}
@@ -250,19 +437,19 @@ class ChannelImplPut : public ChannelPut
PVDATA_REFCOUNT_MONITOR_CONSTRUCT(mockChannelPut);
// TODO pvRequest
m_channelPutRequester->channelPutConnect(getStatusCreate()->getStatusOK(), this, m_pvStructure, m_bitSet);
m_channelPutRequester->channelPutConnect(g_statusOK, this, m_pvStructure, m_bitSet);
}
virtual void put(bool lastRequest)
{
m_channelPutRequester->putDone(getStatusCreate()->getStatusOK());
m_channelPutRequester->putDone(g_statusOK);
if (lastRequest)
destroy();
}
virtual void get()
{
m_channelPutRequester->getDone(getStatusCreate()->getStatusOK());
m_channelPutRequester->getDone(g_statusOK);
}
virtual void destroy()
@@ -312,7 +499,7 @@ class MockMonitor : public Monitor, public MonitorElement
m_changedBitSet->set(0);
// TODO pvRequest
m_monitorRequester->monitorConnect(getStatusCreate()->getStatusOK(), this, const_cast<Structure*>(m_pvStructure->getStructure()));
m_monitorRequester->monitorConnect(g_statusOK, this, const_cast<Structure*>(m_pvStructure->getStructure()));
}
virtual Status* start()
@@ -707,6 +894,10 @@ typedef std::map<pvAccessID, ResponseRequest*> IOIDResponseRequestMap;
channel->connectionCompleted(sid);
}
// TODO not nice
if (status != g_statusOK)
delete status;
}
};
@@ -901,6 +1092,11 @@ class TestChannelImpl : public ChannelImpl {
*/
IOIDResponseRequestMap m_responseRequests;
/**
* Mutex for response requests.
*/
Mutex m_responseRequestsMutex;
/**
* Allow reconnection flag.
*/
@@ -926,7 +1122,6 @@ class TestChannelImpl : public ChannelImpl {
*/
pvAccessID m_serverChannelID;
/**
* Context sync. mutex.
*/
@@ -1054,6 +1249,10 @@ class TestChannelImpl : public ChannelImpl {
return m_channelID;
}
virtual ClientContextImpl* getContext() {
return m_context;
}
virtual pvAccessID getSearchInstanceID() {
return m_channelID;
}
@@ -1062,6 +1261,23 @@ class TestChannelImpl : public ChannelImpl {
return m_name;
}
virtual pvAccessID getServerChannelID() {
Lock guard(&m_channelMutex);
return m_serverChannelID;
}
virtual void registerResponseRequest(ResponseRequest* responseRequest)
{
Lock guard(&m_responseRequestsMutex);
m_responseRequests[responseRequest->getIOID()] = responseRequest;
}
virtual void unregisterResponseRequest(ResponseRequest* responseRequest)
{
Lock guard(&m_responseRequestsMutex);
m_responseRequests.erase(responseRequest->getIOID());
}
void connect() {
Lock guard(&m_channelMutex);
// if not destroyed...
@@ -1325,6 +1541,17 @@ class TestChannelImpl : public ChannelImpl {
initiateSearch();
}
virtual Transport* checkAndGetTransport()
{
Lock guard(&m_channelMutex);
// TODO C-fy
if (m_connectionState == DESTROYED)
throw std::runtime_error("Channel destroyed.");
else if (m_connectionState != CONNECTED)
throw std::runtime_error("Channel not connected.");
return m_transport; // TODO transport can be 0 !!!!!!!!!!
}
virtual void transportResponsive(Transport* transport) {
Lock guard(&m_channelMutex);
if (m_connectionState == DISCONNECTED)
@@ -1441,7 +1668,7 @@ class TestChannelImpl : public ChannelImpl {
virtual void getField(GetFieldRequester *requester,epics::pvData::String subField)
{
requester->getDone(getStatusCreate()->getStatusOK(),m_pvStructure->getSubField(subField)->getField());
requester->getDone(g_statusOK,m_pvStructure->getSubField(subField)->getField());
}
virtual ChannelProcess* createChannelProcess(
@@ -1455,7 +1682,7 @@ class TestChannelImpl : public ChannelImpl {
ChannelGetRequester *channelGetRequester,
epics::pvData::PVStructure *pvRequest)
{
return new ChannelImplGet(channelGetRequester, m_pvStructure, pvRequest);
return new ChannelImplGet(this, channelGetRequester, pvRequest);
}
virtual ChannelPut* createChannelPut(
@@ -1601,7 +1828,7 @@ class TestChannelImpl : public ChannelImpl {
// TODO support addressList
Channel* channel = m_context->createChannelInternal(channelName, channelRequester, priority, 0);
if (channel)
channelRequester->channelCreated(getStatusCreate()->getStatusOK(), channel);
channelRequester->channelCreated(g_statusOK, channel);
return channel;
// NOTE it's up to internal code to respond w/ error to requester and return 0 in case of errors

View File

@@ -186,15 +186,7 @@ void testRegistryReset()
registry->reset();
short id = 0;
try //there is no elements so exception will be thrown
{
static_cast<ScalarConstPtr>(registry->getIntrospectionInterface(id));
}
catch(EpicsException& e)
{
return;
}
assert(false);
assert(static_cast<ScalarConstPtr>(registry->getIntrospectionInterface(id)) == 0);
}
void serialize(FieldConstPtr field, IntrospectionRegistry* registry)
@@ -415,10 +407,17 @@ void testSerializeStatus()
int main(int argc, char *argv[])
{
cout << "DONE0" << endl;
cout << "DONE0" << endl;
cout << "DONE0" << endl;
cout << "DONE0" << endl;
cout << "DONE0" << endl;
/*
pvDataCreate = getPVDataCreate();
statusCreate = getStatusCreate();
fieldCreate = getFieldCreate();
standardField = getStandardField();
cout << "DONE1" << endl;
flusher = new SerializableControlImpl();
control = new DeserializableControlImpl();
@@ -452,6 +451,6 @@ int main(int argc, char *argv[])
if(serverRegistry) delete serverRegistry;
getShowConstructDestruct()->showDeleteStaticExit(stdout);
cout << "DONE" << endl;
cout << "DONE" << endl;*/
return 0;
}