diff --git a/pvAccessApp/Makefile b/pvAccessApp/Makefile index b770b6c..405c85a 100644 --- a/pvAccessApp/Makefile +++ b/pvAccessApp/Makefile @@ -45,18 +45,17 @@ INC += channelSearchManager.h INC += simpleChannelSearchManagerImpl.h INC += transportRegistry.h INC += serializationHelper.h +INC += codec.h LIBSRCS += blockingUDPTransport.cpp LIBSRCS += blockingUDPConnector.cpp LIBSRCS += beaconHandler.cpp -LIBSRCS += blockingTCPTransport.cpp -LIBSRCS += blockingClientTCPTransport.cpp LIBSRCS += blockingTCPConnector.cpp -LIBSRCS += blockingServerTCPTransport.cpp LIBSRCS += simpleChannelSearchManagerImpl.cpp LIBSRCS += abstractResponseHandler.cpp LIBSRCS += blockingTCPAcceptor.cpp LIBSRCS += transportRegistry.cpp LIBSRCS += serializationHelper.cpp +LIBSRCS += codec.cpp SRC_DIRS += $(PVACCESS)/remoteClient INC += clientContextImpl.h diff --git a/pvAccessApp/remote/blockingClientTCPTransport.cpp b/pvAccessApp/remote/blockingClientTCPTransport.cpp deleted file mode 100644 index ff6f2c6..0000000 --- a/pvAccessApp/remote/blockingClientTCPTransport.cpp +++ /dev/null @@ -1,240 +0,0 @@ -/** - * 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 -#include -#include - -#include - -#include -#include -#include - -using namespace std; -using namespace epics::pvData; - -namespace epics { - namespace pvAccess { - -#define EXCEPTION_GUARD(code) try { code; } \ - catch (std::exception &e) { LOG(logLevelError, "Unhandled exception caught from code at %s:%d: %s", __FILE__, __LINE__, e.what()); } \ - catch (...) { LOG(logLevelError, "Unhandled exception caught from code at %s:%d.", __FILE__, __LINE__); } - - BlockingClientTCPTransport::BlockingClientTCPTransport( - Context::shared_pointer const & context, SOCKET channel, - auto_ptr& responseHandler, int receiveBufferSize, - TransportClient::shared_pointer client, int8 /*remoteTransportRevision*/, - float beaconInterval, int16 priority) : - BlockingTCPTransport(context, channel, responseHandler, receiveBufferSize, priority), - _connectionTimeout(beaconInterval*1000), - _unresponsiveTransport(false), - _verifyOrEcho(true) - { -// _autoDelete = false; - - // initialize owners list, send queue - acquire(client); - - // use immediate for clients - setFlushStrategy(DELAYED); - - // setup connection timeout timer (watchdog) - epicsTimeGetCurrent(&_aliveTimestamp); - } - - void BlockingClientTCPTransport::start() - { - TimerCallbackPtr tcb = std::tr1::dynamic_pointer_cast(shared_from_this()); - _context->getTimer()->schedulePeriodic(tcb, _connectionTimeout, _connectionTimeout); - BlockingTCPTransport::start(); - } - - BlockingClientTCPTransport::~BlockingClientTCPTransport() { - } - - void BlockingClientTCPTransport::callback() { - epicsTimeStamp currentTime; - epicsTimeGetCurrent(¤tTime); - - _mutex.lock(); - // no exception expected here - double diff = epicsTimeDiffInSeconds(¤tTime, &_aliveTimestamp); - _mutex.unlock(); - - if(diff>2*_connectionTimeout) { - unresponsiveTransport(); - } - // use some k (3/4) to handle "jitter" - else if(diff>=((3*_connectionTimeout)/4)) { - // send echo - TransportSender::shared_pointer transportSender = std::tr1::dynamic_pointer_cast(shared_from_this()); - enqueueSendRequest(transportSender); - } - } - - void BlockingClientTCPTransport::unresponsiveTransport() { - Lock lock(_mutex); - if(!_unresponsiveTransport) { - _unresponsiveTransport = true; - - TransportClientMap_t::iterator it = _owners.begin(); - for(; it!=_owners.end(); it++) { - TransportClient::shared_pointer client = it->second.lock(); - if (client) - { - EXCEPTION_GUARD(client->transportUnresponsive()); - } - } - } - } - - bool BlockingClientTCPTransport::acquire(TransportClient::shared_pointer const & client) { - Lock lock(_mutex); - if(_closed.get()) return false; - - char ipAddrStr[48]; - ipAddrToDottedIP(&_socketAddress.ia, ipAddrStr, sizeof(ipAddrStr)); - LOG(logLevelDebug, "Acquiring transport to %s.", ipAddrStr); - - _owners[client->getID()] = TransportClient::weak_pointer(client); - //_owners.insert(TransportClient::weak_pointer(client)); - - return true; - } - - // _mutex is held when this method is called - void BlockingClientTCPTransport::internalClose(bool forced) { - BlockingTCPTransport::internalClose(forced); - - TimerCallbackPtr tcb = std::tr1::dynamic_pointer_cast(shared_from_this()); - _context->getTimer()->cancel(tcb); - } - - void BlockingClientTCPTransport::internalPostClose(bool forced) { - BlockingTCPTransport::internalPostClose(forced); - - // _owners cannot change when transport is closed - closedNotifyClients(); - } - - /** - * Notifies clients about disconnect. - */ - void BlockingClientTCPTransport::closedNotifyClients() { - - // check if still acquired - size_t refs = _owners.size(); - if(refs>0) { - char ipAddrStr[48]; - ipAddrToDottedIP(&_socketAddress.ia, ipAddrStr, sizeof(ipAddrStr)); - LOG( - logLevelDebug, - "Transport to %s still has %d client(s) active and closing...", - ipAddrStr, refs); - - TransportClientMap_t::iterator it = _owners.begin(); - for(; it!=_owners.end(); it++) { - TransportClient::shared_pointer client = it->second.lock(); - if (client) - { - EXCEPTION_GUARD(client->transportClosed()); - } - } - - } - - _owners.clear(); - } - - //void BlockingClientTCPTransport::release(TransportClient::shared_pointer const & client) { - void BlockingClientTCPTransport::release(pvAccessID clientID) { - Lock lock(_mutex); - if(_closed.get()) return; - - char ipAddrStr[48]; - ipAddrToDottedIP(&_socketAddress.ia, ipAddrStr, sizeof(ipAddrStr)); - - LOG(logLevelDebug, "Releasing transport to %s.", ipAddrStr); - - _owners.erase(clientID); - //_owners.erase(TransportClient::weak_pointer(client)); - - // not used anymore, close it - // TODO consider delayed destruction (can improve performance!!!) - if(_owners.size()==0) close(); // TODO close(false) - } - - void BlockingClientTCPTransport::aliveNotification() { - Lock guard(_mutex); - epicsTimeGetCurrent(&_aliveTimestamp); - if(_unresponsiveTransport) responsiveTransport(); - } - - void BlockingClientTCPTransport::responsiveTransport() { - Lock lock(_mutex); - if(_unresponsiveTransport) { - _unresponsiveTransport = false; - - Transport::shared_pointer thisSharedPtr = shared_from_this(); - TransportClientMap_t::iterator it = _owners.begin(); - for(; it!=_owners.end(); it++) { - TransportClient::shared_pointer client = it->second.lock(); - if (client) - { - EXCEPTION_GUARD(client->transportResponsive(thisSharedPtr)); - } - } - } - } - - void BlockingClientTCPTransport::changedTransport() { - outgoingIR.reset(); - - Lock lock(_mutex); - TransportClientMap_t::iterator it = _owners.begin(); - for(; it!=_owners.end(); it++) { - TransportClient::shared_pointer client = it->second.lock(); - if (client) - { - EXCEPTION_GUARD(client->transportChanged()); - } - } - } - - void BlockingClientTCPTransport::send(ByteBuffer* buffer, - TransportSendControl* control) { - if(_verifyOrEcho) { - /* - * send verification response message - */ - - control->startMessage(CMD_CONNECTION_VALIDATION, 2*sizeof(int32)+sizeof(int16)); - - // receive buffer size - buffer->putInt(static_cast(getReceiveBufferSize())); - - // socket receive buffer size - buffer->putInt(static_cast(getSocketReceiveBufferSize())); - - // connection priority - buffer->putShort(getPriority()); - - // send immediately - control->flush(true); - - _verifyOrEcho = false; - } - else { - control->startMessage(CMD_ECHO, 0); - // send immediately - control->flush(true); - } - - } - - } -} diff --git a/pvAccessApp/remote/blockingServerTCPTransport.cpp b/pvAccessApp/remote/blockingServerTCPTransport.cpp deleted file mode 100644 index 9a7bc94..0000000 --- a/pvAccessApp/remote/blockingServerTCPTransport.cpp +++ /dev/null @@ -1,136 +0,0 @@ -/** - * 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 -#include -#include - -#include -#include - -/* standard */ -#include - -using namespace epics::pvData; -using namespace std; - -namespace epics { -namespace pvAccess { - - BlockingServerTCPTransport::BlockingServerTCPTransport( - Context::shared_pointer const & context, SOCKET channel, - auto_ptr& responseHandler, int receiveBufferSize) : - BlockingTCPTransport(context, channel, responseHandler, receiveBufferSize, PVA_DEFAULT_PRIORITY), - _lastChannelSID(0) - { - // for performance testing - setFlushStrategy(DELAYED); - _delay = 0.000; - - // NOTE: priority not yet known, default priority is used to register/unregister - // TODO implement priorities in Reactor... not that user will - // change it.. still getPriority() must return "registered" priority! - - //start(); - } - - BlockingServerTCPTransport::~BlockingServerTCPTransport() { - } - - void BlockingServerTCPTransport::destroyAllChannels() { - Lock lock(_channelsMutex); - if(_channels.size()==0) return; - - char ipAddrStr[64]; - ipAddrToDottedIP(&_socketAddress.ia, ipAddrStr, sizeof(ipAddrStr)); - - LOG( - logLevelDebug, - "Transport to %s still has %u channel(s) active and closing...", - ipAddrStr, (unsigned int)_channels.size()); - - map::iterator it = _channels.begin(); - for(; it!=_channels.end(); it++) - it->second->destroy(); - - _channels.clear(); - } - - void BlockingServerTCPTransport::internalClose(bool force) { - Transport::shared_pointer thisSharedPtr = shared_from_this(); - BlockingTCPTransport::internalClose(force); - destroyAllChannels(); - } - - void BlockingServerTCPTransport::internalPostClose(bool forced) { - BlockingTCPTransport::internalPostClose(forced); - } - - pvAccessID BlockingServerTCPTransport::preallocateChannelSID() { - Lock lock(_channelsMutex); - // search first free (theoretically possible loop of death) - pvAccessID sid = ++_lastChannelSID; - while(_channels.find(sid)!=_channels.end()) - sid = ++_lastChannelSID; - return sid; - } - - void BlockingServerTCPTransport::registerChannel(pvAccessID sid, ServerChannel::shared_pointer const & channel) { - Lock lock(_channelsMutex); - _channels[sid] = channel; - } - - void BlockingServerTCPTransport::unregisterChannel(pvAccessID sid) { - Lock lock(_channelsMutex); - _channels.erase(sid); - } - - ServerChannel::shared_pointer BlockingServerTCPTransport::getChannel(pvAccessID sid) { - Lock lock(_channelsMutex); - - map::iterator it = _channels.find(sid); - if(it!=_channels.end()) return it->second; - - return ServerChannel::shared_pointer(); - } - - int BlockingServerTCPTransport::getChannelCount() { - Lock lock(_channelsMutex); - return static_cast(_channels.size()); - } - - void BlockingServerTCPTransport::send(ByteBuffer* buffer, - TransportSendControl* control) { - - // - // set byte order control message - // - - control->ensureBuffer(PVA_MESSAGE_HEADER_SIZE); - buffer->putByte(PVA_MAGIC); - buffer->putByte(PVA_VERSION); - buffer->putByte(0x01 | ((EPICS_BYTE_ORDER == EPICS_ENDIAN_BIG) ? 0x80 : 0x00)); // control + big endian - buffer->putByte(2); // set byte order - buffer->putInt(0); - - - // - // send verification message - // - control->startMessage(CMD_CONNECTION_VALIDATION, 2*sizeof(int32)); - - // receive buffer size - buffer->putInt(static_cast(getReceiveBufferSize())); - - // socket receive buffer size - buffer->putInt(static_cast(getSocketReceiveBufferSize())); - - // send immediately - control->flush(true); - } - - } -} diff --git a/pvAccessApp/remote/blockingTCP.h b/pvAccessApp/remote/blockingTCP.h index 35d9945..b70c758 100644 --- a/pvAccessApp/remote/blockingTCP.h +++ b/pvAccessApp/remote/blockingTCP.h @@ -40,584 +40,9 @@ #include #include -// not implemented anyway -#define FLOW_CONTROL 0 - namespace epics { namespace pvAccess { - //class MonitorSender; - - enum ReceiveStage { - READ_FROM_SOCKET, PROCESS_HEADER, PROCESS_PAYLOAD, UNDEFINED_STAGE - }; - - class BlockingTCPTransport : - public Transport, - public TransportSendControl, - public std::tr1::enable_shared_from_this - { - protected: - BlockingTCPTransport(Context::shared_pointer const & context, SOCKET channel, - std::auto_ptr& responseHandler, int receiveBufferSize, - epics::pvData::int16 priority); - - public: - virtual bool isClosed() { - return _closed.get(); - } - - virtual epics::pvData::int8 getRevision() const { - return PVA_PROTOCOL_REVISION; - } - - virtual void setRemoteRevision(epics::pvData::int8 revision) { - _remoteTransportRevision = revision; - } - - virtual void setRemoteTransportReceiveBufferSize(std::size_t remoteTransportReceiveBufferSize) { - _remoteTransportReceiveBufferSize = remoteTransportReceiveBufferSize; - } - - virtual void setRemoteTransportSocketReceiveBufferSize(std::size_t socketReceiveBufferSize) { - _remoteTransportSocketReceiveBufferSize = socketReceiveBufferSize; - } - - virtual epics::pvData::String getType() const { - return epics::pvData::String("TCP"); - } - - virtual void aliveNotification() { - // noop - } - - virtual void changedTransport() { - // noop - } - - virtual const osiSockAddr* getRemoteAddress() const { - return &_socketAddress; - } - - virtual epics::pvData::int16 getPriority() const { - return _priority; - } - - virtual std::size_t getReceiveBufferSize() const { - return _socketBuffer->getSize(); - } - - /** - * Get remote transport receive buffer size (in bytes). - * @return remote transport receive buffer size - */ - virtual std::size_t getRemoteTransportReceiveBufferSize() const { - return _remoteTransportReceiveBufferSize; - } - - virtual std::size_t getSocketReceiveBufferSize() const; - - virtual bool verify(epics::pvData::int32 timeoutMs) { - return _verifiedEvent.wait(timeoutMs/1000.0); - - //epics::pvData::Lock lock(_verifiedMutex); - //return _verified; - } - - virtual void verified() { - epics::pvData::Lock lock(_verifiedMutex); - _verified = true; - _verifiedEvent.signal(); - } - - virtual void setRecipient(const osiSockAddr& /*sendTo*/) { - // noop - } - - virtual void flush(bool lastMessageCompleted); - virtual void startMessage(epics::pvData::int8 command, std::size_t ensureCapacity); - virtual void endMessage(); - - virtual void flushSerializeBuffer() { - flush(false); - } - - virtual void ensureBuffer(std::size_t size); - - virtual void alignBuffer(std::size_t alignment); - - virtual void ensureData(std::size_t size); - - virtual void alignData(std::size_t alignment); - - virtual bool directSerialize(epics::pvData::ByteBuffer *existingBuffer, const char* toSerialize, - std::size_t elementCount, std::size_t elementSize); - - virtual bool directDeserialize(epics::pvData::ByteBuffer *existingBuffer, char* deserializeTo, - std::size_t elementCount, std::size_t elementSize); - - void processReadIntoDirectBuffer(std::size_t bytesToRead); - - virtual void close(); - - virtual void setByteOrder(int /*byteOrder*/) - { - // not used this this implementation - } - - FlushStrategy getFlushStrategy() { - return _flushStrategy; - } - - void setFlushStrategy(FlushStrategy flushStrategy) { - _flushStrategy = flushStrategy; - } - - //void requestFlush(); - - /** - * Close and free connection resources. - */ - void freeConnectionResorces(); - - /** - * Starts the receive and send threads - */ - virtual void start(); - - virtual void enqueueSendRequest(TransportSender::shared_pointer const & sender); - - //void enqueueMonitorSendRequest(TransportSender::shared_pointer const & sender); - - virtual void enqueueOnlySendRequest(TransportSender::shared_pointer const & sender); - - virtual void flushSendQueue(); - - virtual void cachedSerialize( - const std::tr1::shared_ptr& field, epics::pvData::ByteBuffer* buffer) - { - outgoingIR.serialize(field, buffer, this); - } - - virtual std::tr1::shared_ptr - cachedDeserialize(epics::pvData::ByteBuffer* buffer) - { - return incomingIR.deserialize(buffer, this); - } - - protected: - - virtual void processReadCached(bool nestedCall, - ReceiveStage inStage, std::size_t requiredBytes); - - /** - * Called to any resources just before closing transport - * @param[in] force flag indicating if forced (e.g. forced - * disconnect) is required - */ - virtual void internalClose(bool force); - - /** - * Called to any resources just after closing transport and without any locks held on transport - * @param[in] force flag indicating if forced (e.g. forced - * disconnect) is required - */ - virtual void internalPostClose(bool force); - - /** - * Send a buffer through the transport. - * NOTE: TCP sent buffer/sending has to be synchronized (not done by this method). - * @param buffer[in] buffer to be sent - * @return success indicator - */ - virtual bool send(epics::pvData::ByteBuffer* buffer); - - virtual ~BlockingTCPTransport(); - - -#if FLOW_CONTROL - /** - * Default marker period. - */ - static const std::size_t MARKER_PERIOD = 1024; -#endif - static const std::size_t MAX_ENSURE_DATA_BUFFER_SIZE = 1024; - -// TODO - double _delay; - - /****** finally initialized at construction time and after start (called by the same thread) ********/ - - /** - * Corresponding channel. - */ - SOCKET _channel; - - /** - * Cached socket address. - */ - osiSockAddr _socketAddress; - - /** - * Priority. - * NOTE: Priority cannot just be changed, since it is registered - * in transport registry with given priority. - */ - epics::pvData::int16 _priority; - // TODO to be implemeneted - - /** - * PVAS response handler. - */ - std::auto_ptr _responseHandler; - - // TODO review int vs std::size_t - - /** - * Send buffer size. - */ - std::size_t _maxPayloadSize; - - /** - * Send buffer size. - */ - int _socketSendBufferSize; - - /** - * Marker "period" in bytes (every X bytes marker should be set). - */ - epics::pvData::int64 _markerPeriodBytes; - - - FlushStrategy _flushStrategy; - - - epicsThreadId _rcvThreadId; - - epicsThreadId _sendThreadId; - - // TODO - //MonitorSender* _monitorSender; - - Context::shared_pointer _context; - - bool _autoDelete; - - - - /**** after verification ****/ - - /** - * Remote side transport revision (minor). - */ - epics::pvData::int8 _remoteTransportRevision; - - /** - * Remote side transport receive buffer size. - */ - size_t _remoteTransportReceiveBufferSize; - - /** - * Remote side transport socket receive buffer size. - */ - size_t _remoteTransportSocketReceiveBufferSize; - - - - /*** send thread only - no need to sync ***/ - // NOTE: now all send-related external calls are TransportSender IF - // and its reference is only valid when called from send thread - - // initialized at construction time - std::deque _sendQueue; - epics::pvData::Mutex _sendQueueMutex; - - // initialized at construction time -// std::deque _monitorSendQueue; -// epics::pvData::Mutex _monitorMutex; - - /** - * Send buffer. - */ - epics::pvData::ByteBuffer* _sendBuffer; - -#if FLOW_CONTROL - /** - * Next planned marker position. - */ - epics::pvData::int64 _nextMarkerPosition; -#endif - - /** - * Send pending flag. - */ - bool _sendPending; - - /** - * Last message start position. - */ - int _lastMessageStartPosition; - - epics::pvData::int8 _lastSegmentedMessageType; - epics::pvData::int8 _lastSegmentedMessageCommand; - - bool _flushRequested; - - int _sendBufferSentPosition; - - epics::pvData::int8 _byteOrderFlag; - - /** - * Outgoing (codes generated by this party) introspection registry. - */ - IntrospectionRegistry outgoingIR; - - - - - - - /*** receive thread only - no need to sync ***/ - - // initialized at construction time - epics::pvData::ByteBuffer* _socketBuffer; - - std::size_t _startPosition; - - std::size_t _storedPayloadSize; - std::size_t _storedPosition; - std::size_t _storedLimit; - - epics::pvData::int8 _version; - epics::pvData::int8 _packetType; - epics::pvData::int8 _command; - std::size_t _payloadSize; - - ReceiveStage _stage; - - std::size_t _directPayloadRead; - char * _directBuffer; - -#if FLOW_CONTROL - - /** - * Total bytes received. - */ - epics::pvData::int64 _totalBytesReceived; -#endif - - /** - * Incoming (codes generated by other party) introspection registry. - */ - IntrospectionRegistry incomingIR; - - - - /*** send/receive thread shared ***/ - - /** - * Connection status - * NOTE: synced by _mutex - */ - AtomicBoolean _closed; - - // NOTE: synced by _mutex - bool _sendThreadExited; - - epics::pvData::Mutex _mutex; - - - bool _verified; - epics::pvData::Mutex _verifiedMutex; - - - - - epics::pvData::Event _sendQueueEvent; - - epics::pvData::Event _verifiedEvent; - - - - - -#if FLOW_CONTROL - /** - * Marker to send. - * NOTE: synced by _flowControlMutex - */ - int _markerToSend; - - /** - * Total bytes sent. - * NOTE: synced by _flowControlMutex - */ - epics::pvData::int64 _totalBytesSent; - - /** - * Calculated remote free buffer size. - * NOTE: synced by _flowControlMutex - */ - epics::pvData::int64 _remoteBufferFreeSpace; - - epics::pvData::Mutex _flowControlMutex; -#endif - - private: - - /** - * Internal method that clears and releases buffer. - * sendLock and sendBufferLock must be hold while calling this method. - */ - void clearAndReleaseBuffer(); - - void endMessage(bool hasMoreSegments); - - bool flush(); - - void processSendQueue(); - - static void rcvThreadRunner(void* param); - - static void sendThreadRunner(void* param); - - /** - * Free all send buffers (return them to the cached buffer allocator). - */ - void freeSendBuffers(); - }; - - - class BlockingClientTCPTransport : public BlockingTCPTransport, - public TransportSender, - public epics::pvData::TimerCallback { - - public: - POINTER_DEFINITIONS(BlockingClientTCPTransport); - - private: - BlockingClientTCPTransport(Context::shared_pointer const & context, SOCKET channel, - std::auto_ptr& responseHandler, int receiveBufferSize, - TransportClient::shared_pointer client, epics::pvData::int8 remoteTransportRevision, - float beaconInterval, epics::pvData::int16 priority); - - public: - static shared_pointer create(Context::shared_pointer const & context, SOCKET channel, - std::auto_ptr& responseHandler, int receiveBufferSize, - TransportClient::shared_pointer client, epics::pvData::int8 remoteTransportRevision, - float beaconInterval, epics::pvData::int16 priority) - { - shared_pointer thisPointer( - new BlockingClientTCPTransport(context, channel, responseHandler, receiveBufferSize, - client, remoteTransportRevision, beaconInterval, priority) - ); - thisPointer->start(); - return thisPointer; - } - - virtual void start(); - - virtual ~BlockingClientTCPTransport(); - - virtual void timerStopped() { - // noop - } - - virtual void callback(); - - /** - * Acquires transport. - * @param client client (channel) acquiring the transport - * @return true if transport was granted, false otherwise. - */ - virtual bool acquire(TransportClient::shared_pointer const & client); - - /** - * Releases transport. - * @param client client (channel) releasing the transport - */ - virtual void release(pvAccessID clientId); - //virtual void release(TransportClient::shared_pointer const & client); - - /** - * Alive notification. - * This method needs to be called (by newly received data or beacon) - * at least once in this period, if not echo will be issued - * and if there is not response to it, transport will be considered as unresponsive. - */ - virtual void aliveNotification(); - - /** - * Changed transport (server restared) notify. - */ - virtual void changedTransport(); - - virtual void lock() { - // noop - } - - virtual void unlock() { - // noop - } - - virtual void acquire() { - // noop, since does not make sence on itself - } - - virtual void release() { - // noop, since does not make sence on itself - } - - virtual void send(epics::pvData::ByteBuffer* buffer, - TransportSendControl* control); - - protected: - - virtual void internalClose(bool force); - virtual void internalPostClose(bool force); - - private: - - /** - * Owners (users) of the transport. - */ - // TODO consider using TR1 hash map - typedef std::map TransportClientMap_t; - TransportClientMap_t _owners; - - /** - * Connection timeout (no-traffic) flag. - */ - double _connectionTimeout; - - /** - * Unresponsive transport flag. - */ - bool _unresponsiveTransport; - - /** - * Timestamp of last "live" event on this transport. - */ - epicsTimeStamp _aliveTimestamp; - - bool _verifyOrEcho; - - /** - * Unresponsive transport notify. - */ - void unresponsiveTransport(); - - /** - * Notifies clients about disconnect. - */ - void closedNotifyClients(); - - /** - * Responsive transport notify. - */ - void responsiveTransport(); - }; - /** * Channel Access TCP connector. * @author Matej Sekoranja @@ -672,152 +97,6 @@ namespace epics { }; - class BlockingServerTCPTransport : public BlockingTCPTransport, - public ChannelHostingTransport, - public TransportSender { - public: - POINTER_DEFINITIONS(BlockingServerTCPTransport); - - private: - BlockingServerTCPTransport(Context::shared_pointer const & context, SOCKET channel, - std::auto_ptr& responseHandler, int receiveBufferSize); - public: - static shared_pointer create(Context::shared_pointer const & context, SOCKET channel, - std::auto_ptr& responseHandler, int receiveBufferSize) - { - shared_pointer thisPointer( - new BlockingServerTCPTransport(context, channel, responseHandler, receiveBufferSize) - ); - thisPointer->start(); - return thisPointer; - } - - virtual bool acquire(std::tr1::shared_ptr const & /*client*/) - { - return false; - } - - virtual void release(pvAccessID /*clientId*/) {} - - /** - * Preallocate new channel SID. - * @return new channel server id (SID). - */ - virtual pvAccessID preallocateChannelSID(); - - /** - * De-preallocate new channel SID. - * @param sid preallocated channel SID. - */ - virtual void depreallocateChannelSID(pvAccessID /*sid*/) { - // noop - } - - /** - * Register a new channel. - * @param sid preallocated channel SID. - * @param channel channel to register. - */ - virtual void registerChannel(pvAccessID sid, ServerChannel::shared_pointer const & channel); - - /** - * Unregister a new channel (and deallocates its handle). - * @param sid SID - */ - virtual void unregisterChannel(pvAccessID sid); - - /** - * Get channel by its SID. - * @param sid channel SID - * @return channel with given SID, NULL otherwise - */ - virtual ServerChannel::shared_pointer getChannel(pvAccessID sid); - - /** - * Get channel count. - * @return channel count. - */ - virtual int getChannelCount(); - - virtual epics::pvData::PVField::shared_pointer getSecurityToken() { - return epics::pvData::PVField::shared_pointer(); - } - - virtual void lock() { - // noop - } - - virtual void unlock() { - // noop - } - - virtual void acquire() { - // noop, since does not make sence on itself - } - - virtual void release() { - // noop, since does not make sence on itself - } - - /** - * Verify transport. Server side is self-verified. - */ - virtual bool verify(epics::pvData::int32 /*timeoutMs*/) { - TransportSender::shared_pointer transportSender = std::tr1::dynamic_pointer_cast(shared_from_this()); - enqueueSendRequest(transportSender); - verified(); - return true; - } - - /** - * PVA connection validation request. - * A server sends a validate connection message when it receives a new connection. - * The message indicates that the server is ready to receive requests; the client must - * not send any messages on the connection until it has received the validate connection message - * from the server. No reply to the message is expected by the server. - * The purpose of the validate connection message is two-fold: - * It informs the client of the protocol version supported by the server. - * It prevents the client from writing a request message to its local transport - * buffers until after the server has acknowledged that it can actually process the - * request. This avoids a race condition caused by the server's TCP/IP stack - * accepting connections in its backlog while the server is in the process of shutting down: - * if the client were to send a request in this situation, the request - * would be lost but the client could not safely re-issue the request because that - * might violate at-most-once semantics. - * The validate connection message guarantees that a server is not in the middle - * of shutting down when the server's TCP/IP stack accepts an incoming connection - * and so avoids the race condition. - * @see org.epics.ca.impl.remote.TransportSender#send(java.nio.ByteBuffer, org.epics.ca.impl.remote.TransportSendControl) - */ - virtual void send(epics::pvData::ByteBuffer* buffer, - TransportSendControl* control); - - virtual ~BlockingServerTCPTransport(); - - protected: - - virtual void internalClose(bool force); - virtual void internalPostClose(bool force); - - private: - /** - * Last SID cache. - */ - pvAccessID _lastChannelSID; - - /** - * Channel table (SID -> channel mapping). - */ - std::map _channels; - - epics::pvData::Mutex _channelsMutex; - - /** - * Destroy all channels. - */ - void destroyAllChannels(); - }; - class ResponseHandlerFactory { public: diff --git a/pvAccessApp/remote/blockingTCPAcceptor.cpp b/pvAccessApp/remote/blockingTCPAcceptor.cpp index becc4f1..bd99b9a 100644 --- a/pvAccessApp/remote/blockingTCPAcceptor.cpp +++ b/pvAccessApp/remote/blockingTCPAcceptor.cpp @@ -5,6 +5,7 @@ */ #include +#include "codec.h" #include #include @@ -180,17 +181,26 @@ namespace pvAccess { } // TODO tune buffer sizes?! + + // get TCP send buffer size + osiSocklen_t intLen = sizeof(int); + int _socketSendBufferSize; + retval = getsockopt(newClient, SOL_SOCKET, SO_SNDBUF, (char *)&_socketSendBufferSize, &intLen); + if(retval<0) { + epicsSocketConvertErrnoToString(strBuffer, sizeof(strBuffer)); + LOG(logLevelDebug, "Error getting SO_SNDBUF: %s", strBuffer); + } /** * Create transport, it registers itself to the registry. - * Each transport should have its own response handler since it is not "shareable" */ std::auto_ptr responseHandler = _responseHandlerFactory->createResponseHandler(); - BlockingServerTCPTransport::shared_pointer transport = - BlockingServerTCPTransport::create( + detail::BlockingServerTCPTransportCodec::shared_pointer transport = + detail::BlockingServerTCPTransportCodec::create( _context, newClient, responseHandler, + _socketSendBufferSize, _receiveBufferSize); // validate connection diff --git a/pvAccessApp/remote/blockingTCPConnector.cpp b/pvAccessApp/remote/blockingTCPConnector.cpp index 44397d8..2e12404 100644 --- a/pvAccessApp/remote/blockingTCPConnector.cpp +++ b/pvAccessApp/remote/blockingTCPConnector.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -78,8 +79,7 @@ namespace epics { Context::shared_pointer context = _context.lock(); // first try to check cache w/o named lock... - Transport::shared_pointer tt = context->getTransportRegistry()->get("TCP", &address, priority); - BlockingClientTCPTransport::shared_pointer transport = std::tr1::static_pointer_cast(tt); + Transport::shared_pointer transport = context->getTransportRegistry()->get("TCP", &address, priority); if(transport.get()) { LOG(logLevelDebug, "Reusing existing connection to PVA server: %s", @@ -92,8 +92,7 @@ namespace epics { if(lockAcquired) { try { // ... transport created during waiting in lock - tt = context->getTransportRegistry()->get("TCP", &address, priority); - transport = std::tr1::static_pointer_cast(tt); + transport = context->getTransportRegistry()->get("TCP", &address, priority); if(transport.get()) { LOG(logLevelDebug, "Reusing existing connection to PVA server: %s", @@ -141,8 +140,18 @@ namespace epics { // create transport // TODO introduce factory - transport = BlockingClientTCPTransport::create( - context, socket, responseHandler, _receiveBufferSize, + // get TCP send buffer size + osiSocklen_t intLen = sizeof(int); + int _socketSendBufferSize; + retval = getsockopt(socket, SOL_SOCKET, SO_SNDBUF, (char *)&_socketSendBufferSize, &intLen); + if(retval<0) { + char strBuffer[64]; + epicsSocketConvertErrnoToString(strBuffer, sizeof(strBuffer)); + LOG(logLevelDebug, "Error getting SO_SNDBUF: %s", strBuffer); + } + + transport = detail::BlockingClientTCPTransportCodec::create( + context, socket, responseHandler, _receiveBufferSize, _socketSendBufferSize, client, transportRevision, _beaconInterval, priority); // verify diff --git a/pvAccessApp/remote/blockingTCPTransport.cpp b/pvAccessApp/remote/blockingTCPTransport.cpp deleted file mode 100644 index a99c655..0000000 --- a/pvAccessApp/remote/blockingTCPTransport.cpp +++ /dev/null @@ -1,1342 +0,0 @@ -/** - * 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. - */ - -#ifdef _WIN32 -#define NOMINMAX -#include -typedef SSIZE_T ssize_t; -#endif - -#define __STDC_LIMIT_MACROS 1 -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include - -using namespace epics::pvData; - -using std::max; -using std::min; -using std::ostringstream; - -// TODO to be completely replaced by codec based implementation (see Java) -namespace epics { -namespace pvAccess { - - /* - class MonitorSender : public TransportSender, public NoDefaultMethods { - public: - MonitorSender(Mutex* monitorMutex, GrowingCircularBuffer< - TransportSender*>* monitorSendQueue) : - _monitorMutex(monitorMutex), - _monitorSendQueue(monitorSendQueue) { - } - - virtual ~MonitorSender() { - } - - virtual void lock() { - } - - virtual void unlock() { - } - - virtual void acquire() { - } - - virtual void release() { - } - - virtual void - send(ByteBuffer* buffer, TransportSendControl* control); - - private: - Mutex* _monitorMutex; - GrowingCircularBuffer* _monitorSendQueue; - }; - */ - - PVACCESS_REFCOUNT_MONITOR_DEFINE(blockingTCPTransport); - - //const double BlockingTCPTransport::_delay = 0.000; - - BlockingTCPTransport::BlockingTCPTransport(Context::shared_pointer const & context, - SOCKET channel, std::auto_ptr& responseHandler, - int receiveBufferSize, int16 priority) : - _delay(0.0), - _channel(channel), - _priority(priority), - _responseHandler(responseHandler), -#if FLOW_CONTROL - _markerPeriodBytes(MARKER_PERIOD), -#endif - _flushStrategy(DELAYED), - _rcvThreadId(0), - _sendThreadId(0), - //_monitorSender(new MonitorSender(&_monitorMutex,_monitorSendQueue)), - _context(context), - _autoDelete(true), - _remoteTransportRevision(0), - _remoteTransportReceiveBufferSize(MAX_TCP_RECV), - _remoteTransportSocketReceiveBufferSize(MAX_TCP_RECV), - _sendQueue(), - //_monitorSendQueue(), -#if FLOW_CONTROL - _nextMarkerPosition(_markerPeriodBytes), -#endif - _sendPending(false), - _lastMessageStartPosition(0), - _lastSegmentedMessageType(0), - _lastSegmentedMessageCommand(0), - _flushRequested(false), - _sendBufferSentPosition(0), - _byteOrderFlag((EPICS_BYTE_ORDER == EPICS_ENDIAN_BIG) ? 0x80 : 0x00), - _storedPayloadSize(0), - _storedPosition(0), - _storedLimit(0), - _version(0), - _packetType(0), - _command(0), - _payloadSize(0), - _stage(READ_FROM_SOCKET), - _directPayloadRead(0), - _directBuffer(0), -#if FLOW_CONTROL - _totalBytesReceived(0), -#endif - _closed(), - _sendThreadExited(false), - _verified(false) -#if FLOW_CONTROL - , - _markerToSend(0), - _totalBytesSent(0), - _remoteBufferFreeSpace(INT64_MAX) -#endif - { - PVACCESS_REFCOUNT_MONITOR_CONSTRUCT(blockingTCPTransport); - - // TODO minor tweak: deque size is not preallocated... - - unsigned int bufferSize = max((int)(MAX_TCP_RECV+MAX_ENSURE_DATA_BUFFER_SIZE), receiveBufferSize); - // size must be "aligned" - bufferSize = (bufferSize + (PVA_ALIGNMENT - 1)) & (~(PVA_ALIGNMENT - 1)); - - _socketBuffer = new ByteBuffer(bufferSize); - _socketBuffer->setPosition(_socketBuffer->getLimit()); - _startPosition = _socketBuffer->getPosition(); - - // allocate buffer - _sendBuffer = new ByteBuffer(bufferSize); - _maxPayloadSize = _sendBuffer->getSize() - 2*PVA_MESSAGE_HEADER_SIZE; // one for header, one for flow control - - // get TCP send buffer size - osiSocklen_t intLen = sizeof(int); - int retval = getsockopt(_channel, SOL_SOCKET, SO_SNDBUF, (char *)&_socketSendBufferSize, &intLen); - if(unlikely(retval<0)) { - _socketSendBufferSize = MAX_TCP_RECV; - char errStr[64]; - epicsSocketConvertErrnoToString(errStr, sizeof(errStr)); - LOG(logLevelDebug, - "Unable to retrieve socket send buffer size: %s", - errStr); - } - - // get remote address - osiSocklen_t saSize = sizeof(sockaddr); - retval = getpeername(_channel, &(_socketAddress.sa), &saSize); - if(unlikely(retval<0)) { - char errStr[64]; - epicsSocketConvertErrnoToString(errStr, sizeof(errStr)); - LOG(logLevelError, - "Error fetching socket remote address: %s", - errStr); - } - - // set receive timeout so that we do not have problems at shutdown (recvfrom would block) - struct timeval timeout; - memset(&timeout, 0, sizeof(struct timeval)); - timeout.tv_sec = 1; - timeout.tv_usec = 0; - - if (unlikely(::setsockopt (_channel, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < 0)) - { - char errStr[64]; - epicsSocketConvertErrnoToString(errStr, sizeof(errStr)); - LOG(logLevelError, - "Failed to set SO_RCVTIMEO for TDP socket %s: %s.", - inetAddressToString(_socketAddress).c_str(), errStr); - } - - // TODO this will create marker with invalid endian flag - // prepare buffer - clearAndReleaseBuffer(); - } - - BlockingTCPTransport::~BlockingTCPTransport() { - PVACCESS_REFCOUNT_MONITOR_DESTRUCT(blockingTCPTransport); - - close(); - - // TODO use auto_ptr class members - - delete _socketBuffer; - delete _sendBuffer; - } - - // TODO consider epics::pvData::Thread - void BlockingTCPTransport::start() { - - // TODO this was in constructor - // add to registry - Transport::shared_pointer thisSharedPtr = shared_from_this(); - _context->getTransportRegistry()->put(thisSharedPtr); - - - String socketAddressString = inetAddressToString(_socketAddress); - - // - // start receive thread - // - - String threadName = "TCP-receive " + socketAddressString; - LOG(logLevelDebug, "Starting thread: %s", threadName.c_str()); - - _rcvThreadId = epicsThreadCreate(threadName.c_str(), - epicsThreadPriorityMedium, - epicsThreadGetStackSize(epicsThreadStackBig), - BlockingTCPTransport::rcvThreadRunner, this); - - // - // start send thread - // - - threadName = "TCP-send " + socketAddressString; - LOG(logLevelDebug, "Starting thread: %s",threadName.c_str()); - - _sendThreadId = epicsThreadCreate(threadName.c_str(), - epicsThreadPriorityMedium, - epicsThreadGetStackSize(epicsThreadStackSmall), - BlockingTCPTransport::sendThreadRunner, this); - } - - void BlockingTCPTransport::clearAndReleaseBuffer() { -#if FLOW_CONTROL - // NOTE: take care that nextMarkerPosition is set right - // fix position to be correct when buffer is cleared - // do not include pre-buffered flow control message; not 100% correct, but OK - _nextMarkerPosition -= _sendBuffer->getPosition() - PVA_MESSAGE_HEADER_SIZE; -#endif - - _sendQueueMutex.lock(); - _flushRequested = false; - _sendQueueMutex.unlock(); - - _sendBuffer->clear(); - - _sendPending = false; - -#if FLOW_CONTROL - // prepare ACK marker - _sendBuffer->putByte(PVA_MAGIC); - _sendBuffer->putByte(PVA_VERSION); - _sendBuffer->putByte(0x01 | _byteOrderFlag); // control data - _sendBuffer->putByte(1); // marker ACK - _sendBuffer->putInt(0); -#endif - } - - void BlockingTCPTransport::close() { - Lock lock(_mutex); - - // already closed check - if(_closed.get()) return; - _closed.set(); - - // remove from registry - Transport::shared_pointer thisSharedPtr = shared_from_this(); - _context->getTransportRegistry()->remove(thisSharedPtr).get(); - - // TODO !!! - bool force = true; - - // clean resources - internalClose(force); - - // notify send queue - _sendQueueEvent.signal(); - - lock.unlock(); - - // post close without a lock - internalPostClose(force); - } - - void BlockingTCPTransport::internalClose(bool /*force*/) { - // close the socket - if(_channel!=INVALID_SOCKET) { - epicsSocketDestroy(_channel); - } - } - - void BlockingTCPTransport::internalPostClose(bool /*force*/) { - } - - size_t BlockingTCPTransport::getSocketReceiveBufferSize() const { - // Get value of the SO_RCVBUF option for this DatagramSocket, - // that is the buffer size used by the platform for input on - // this DatagramSocket. - - int sockBufSize; - osiSocklen_t intLen = sizeof(int); - - int retval = getsockopt(_channel, SOL_SOCKET, SO_RCVBUF, (char *)&sockBufSize, &intLen); - if(unlikely(retval<0)) - { - char errStr[64]; - epicsSocketConvertErrnoToString(errStr, sizeof(errStr)); - LOG(logLevelError, - "Socket getsockopt SO_RCVBUF error: %s", - errStr); - } - - return (size_t)sockBufSize; - } - - void BlockingTCPTransport::flush(bool lastMessageCompleted) { - - // automatic end - endMessage(!lastMessageCompleted); - - bool moreToSend = true; - while(moreToSend) { - moreToSend = !flush(); - - // all sent, exit - if(!moreToSend) break; - // TODO check if this is OK - else if (_closed.get()) THROW_BASE_EXCEPTION("transport closed"); - - // TODO solve this sleep in a better way - epicsThreadSleep(0.01); - } - - _lastMessageStartPosition = _sendBuffer->getPosition(); - - // start with last header - if (unlikely(!lastMessageCompleted && _lastSegmentedMessageType!=0)) - startMessage(_lastSegmentedMessageCommand, 0); - } - - void BlockingTCPTransport::startMessage(int8 command, size_t ensureCapacity) { - _lastMessageStartPosition = -1; - ensureBuffer(PVA_MESSAGE_HEADER_SIZE+ensureCapacity); - _lastMessageStartPosition = _sendBuffer->getPosition(); - _sendBuffer->putByte(PVA_MAGIC); - _sendBuffer->putByte(PVA_VERSION); - _sendBuffer->putByte(_lastSegmentedMessageType | _byteOrderFlag); // data + endianess - _sendBuffer->putByte(command); // command - _sendBuffer->putInt(0); // temporary zero payload - - } - - void BlockingTCPTransport::endMessage() { - endMessage(false); - } - - void BlockingTCPTransport::ensureBuffer(size_t size) { - if(likely(_sendBuffer->getRemaining()>=size)) return; - - // too large for buffer... - if(unlikely(_maxPayloadSizegetRemaining()getRemaining()<(alignment-1))) - ensureBuffer(alignment-1); - - _sendBuffer->align(alignment); - } - - void BlockingTCPTransport::endMessage(bool hasMoreSegments) { - if(likely(_lastMessageStartPosition>=0)) { - - // align - // alignBuffer(PVA_ALIGNMENT); - - // set paylaod size - const size_t payloadSize = _sendBuffer->getPosition()-_lastMessageStartPosition-PVA_MESSAGE_HEADER_SIZE; - - // TODO by spec? - // ignore empty segmented messages - if (payloadSize == 0 && _lastSegmentedMessageType != 0) - { - _sendBuffer->setPosition(_lastMessageStartPosition); - if (!hasMoreSegments) - _lastSegmentedMessageType = 0; - return; - - } - - _sendBuffer->putInt(_lastMessageStartPosition+sizeof(int16)+2, (int32)payloadSize); - - int flagsPosition = _lastMessageStartPosition+sizeof(int16); - // set segmented bit - if(likely(hasMoreSegments)) { - // first segment - if(unlikely(_lastSegmentedMessageType==0)) { - int8 type = _sendBuffer->getByte(flagsPosition); - - // set first segment bit - _sendBuffer->putByte(flagsPosition, (int8)(type|0x10)); - - // first + last segment bit == in-between segment - _lastSegmentedMessageType = (int8)(type|0x30); - _lastSegmentedMessageCommand = _sendBuffer->getByte( - flagsPosition+1); - } - } - else { - // last segment - if(unlikely(_lastSegmentedMessageType!=0)) { - // set last segment bit (by clearing first segment bit) - _sendBuffer->putByte(flagsPosition, - (int8)(_lastSegmentedMessageType&0xEF)); - _lastSegmentedMessageType = 0; - } - } - -#if FLOW_CONTROL - // manage markers - int position = _sendBuffer->getPosition(); - int bytesLeft = _sendBuffer->getRemaining(); - - if(unlikely(position>=_nextMarkerPosition && - bytesLeft>=PVA_MESSAGE_HEADER_SIZE)) { - _sendBuffer->putByte(PVA_MAGIC); - _sendBuffer->putByte(PVA_VERSION); - _sendBuffer->putByte(0x01 | _byteOrderFlag); // control data - _sendBuffer->putByte(0); // marker - s_sendBuffer->putInt((int)(_totalBytesSent+position+PVA_MESSAGE_HEADER_SIZE)); - _nextMarkerPosition = position+_markerPeriodBytes; - } -#endif - } - } - - void BlockingTCPTransport::ensureData(size_t size) { - // enough of data? - const size_t remainingBytes = _socketBuffer->getRemaining(); - if (likely(remainingBytes>=size)) return; - - // too large for buffer... - if (unlikely(MAX_ENSURE_DATA_BUFFER_SIZEgetPosition()-_storedPosition; - - // no more data and we have some payload left => read buffer - if (likely(_storedPayloadSize>=size)) - { - //LOG(logLevelInfo, - // "storedPayloadSize >= size, remaining: %d", - // _socketBuffer->getRemaining()); - - // just read up remaining payload, move current (getPosition(); - _storedLimit = _socketBuffer->getLimit(); - _socketBuffer->setLimit(min(_storedPosition+_storedPayloadSize, - _storedLimit)); - } - else - { - // copy remaining bytes to safe region of buffer, if any - for(size_t i = 0; iputByte(i, _socketBuffer->getByte()); - - // extend limit to what was read - _socketBuffer->setLimit(_storedLimit); - - _stage = PROCESS_HEADER; - processReadCached(true, UNDEFINED_STAGE, size-remainingBytes); - - if (unlikely(remainingBytes > 0)) - { - // copy saved back to before position - for(int i = static_cast(remainingBytes-1), j = _socketBuffer->getPosition()-1; - i>=0; - i--, j--) - _socketBuffer->putByte(j, _socketBuffer->getByte(i)); - _startPosition = _socketBuffer->getPosition()-remainingBytes; - _socketBuffer->setPosition(_startPosition); - _storedPosition = _startPosition; - } - else - { - _storedPosition = _socketBuffer->getPosition(); - } - - _storedLimit = _socketBuffer->getLimit(); - _socketBuffer->setLimit(min(_storedPosition+_storedPayloadSize, - _storedLimit)); - - // add if missing, since UNDEFINED_STAGE and return less... - if(unlikely(!_closed.get()&&(_socketBuffer->getRemaining()getRemaining()<(alignment-1))) - ensureData(alignment-1); - - _socketBuffer->align(alignment); - } - - bool BlockingTCPTransport::directSerialize(ByteBuffer* /*existingBuffer*/, const char* toSerialize, - std::size_t elementCount, std::size_t elementSize) - { - // TODO overflow check, size_t type, other is int32 for payloadSize header field !!! - // TODO do not ignore or new field in max message size in connection validation - std::size_t count = elementCount * elementSize; - - // TODO find smart limit - // check if direct mode actually pays off - if (count < 1024) - return false; - - // first end current message indicating the we will segment - endMessage(true); - - // append segmented message header - startMessage(_lastSegmentedMessageCommand, 0); - // set segmented message size - _sendBuffer->putInt(_lastMessageStartPosition+sizeof(int16)+2, (int32)count); - - // flush (TODO this is code is duplicated) - bool moreToSend = true; - while (moreToSend) { - moreToSend = !flush(); - - // all sent, exit - if(!moreToSend) break; - // TODO check if this is OK - else if (_closed.get()) THROW_BASE_EXCEPTION("transport closed"); - - // TODO solve this sleep in a better way - epicsThreadSleep(0.01); - } - _lastMessageStartPosition = _sendBuffer->getPosition(); - - // TODO think if alignment is preserved after... - - try { - //LOG(logLevelInfo, - // "Sending (direct) %d bytes in the packet to %s.", - // count, - // inetAddressToString(_socketAddress).c_str()); - const char* ptr = toSerialize; - while(count>0) { - ssize_t bytesSent = ::send(_channel, - ptr, - count, 0); - - if(unlikely(bytesSent<0)) { - - int socketError = SOCKERRNO; - - // spurious EINTR check - if (socketError==SOCK_EINTR) - continue; - - // TODO check this (copy below)... consolidate!!! - if (socketError==SOCK_ENOBUFS) { - // TODO improve this - epicsThreadSleep(0.01); - continue; - } - - // connection lost - - char errStr[64]; - epicsSocketConvertErrnoToString(errStr, sizeof(errStr)); - ostringstream temp; - temp<<"error in sending TCP data: "<getRemaining()); - existingBuffer->getArray(_directBuffer, availableBytes); - _directPayloadRead -= availableBytes; - - if (_directPayloadRead == 0) - return true; - - _directBuffer += availableBytes; - - // subtract what was already processed - size_t pos = _socketBuffer->getPosition(); - _storedPayloadSize -= pos -_storedPosition; - _storedPosition = pos; - - // no more data and we have some payload left => read buffer - if (likely(_storedPayloadSize > 0)) - { - size_t bytesToRead = std::min(_directPayloadRead, _storedPayloadSize); - processReadIntoDirectBuffer(bytesToRead); - // std::cout << "d: " << bytesToRead << std::endl; - _storedPayloadSize -= bytesToRead; - _directPayloadRead -= bytesToRead; - } - - if (_directPayloadRead == 0) - return true; - - _stage = PROCESS_HEADER; - processReadCached(true, UNDEFINED_STAGE, _directPayloadRead); - - _storedPosition = _socketBuffer->getPosition(); - _storedLimit = _socketBuffer->getLimit(); - _socketBuffer->setLimit( - min(_storedPosition + _storedPayloadSize, _storedLimit) - ); - - } - - return true; - } - - void BlockingTCPTransport::processReadIntoDirectBuffer(size_t bytesToRead) - { - while (bytesToRead > 0) - { - ssize_t bytesRead = recv(_channel, _directBuffer, bytesToRead, 0); - - // std::cout << "d: " << bytesRead << std::endl; - - if(unlikely(bytesRead<=0)) - { - - if (bytesRead<0) - { - int socketError = SOCKERRNO; - - // interrupted or timeout - if (socketError == EINTR || - socketError == EAGAIN || - socketError == EWOULDBLOCK) - continue; - } - - // error (disconnect, end-of-stream) detected - close(); - - THROW_BASE_EXCEPTION("bytesRead < 0"); - - return; - } - - bytesToRead -= bytesRead; - _directBuffer += bytesRead; - - } - } - - void BlockingTCPTransport::processReadCached(bool nestedCall, - ReceiveStage inStage, size_t requiredBytes) { - try { - // TODO we need to throw exception in nextedCall not just bail out!!!! - while(likely(!_closed.get())) { - if(_stage==READ_FROM_SOCKET||inStage!=UNDEFINED_STAGE) { - - // add to bytes read -#if FLOW_CONTROL - int currentPosition = _socketBuffer->getPosition(); - _totalBytesReceived += (currentPosition - _startPosition); -#endif - // preserve alignment - int currentStartPosition = _startPosition = - MAX_ENSURE_DATA_BUFFER_SIZE; // "TODO uncomment align" + (unsigned int)currentPosition % PVA_ALIGNMENT; - - // copy remaining bytes, if any - int remainingBytes = _socketBuffer->getRemaining(); - - int endPosition = currentStartPosition + remainingBytes; - // TODO memmove - for(int i = MAX_ENSURE_DATA_BUFFER_SIZE; iputByte(i, _socketBuffer->getByte()); - - _socketBuffer->setPosition(endPosition); - _socketBuffer->setLimit(_socketBuffer->getSize()); - - // read at least requiredBytes bytes - - size_t requiredPosition = (currentStartPosition+requiredBytes); - while(_socketBuffer->getPosition()getPosition(); - - ssize_t bytesRead = recv(_channel, (char*)(_socketBuffer->getArray()+pos), -// _socketBuffer->getRemaining(), 0); -// TODO we assume that caller is smart and requiredBytes > remainingBytes -// if in direct read mode, try to read only header so that rest can be read directly to direct buffers -(_directPayloadRead > 0 && inStage == PROCESS_HEADER) ? (requiredBytes-remainingBytes) : _socketBuffer->getRemaining(), 0); -//std::cout << "i: " << bytesRead << std::endl; - - if(unlikely(bytesRead<=0)) { - - if (bytesRead<0) - { - int socketError = SOCKERRNO; - - // interrupted or timeout - if (socketError == EINTR || - socketError == EAGAIN || - socketError == EWOULDBLOCK) - continue; - } - - // error (disconnect, end-of-stream) detected - close(); - - if(nestedCall) - THROW_BASE_EXCEPTION("bytesRead < 0"); - - return; - } - - _socketBuffer->setPosition(pos+bytesRead); - } - - std::size_t pos = _socketBuffer->getPosition(); - _storedLimit = pos; - _socketBuffer->setLimit(pos); - _socketBuffer->setPosition(currentStartPosition); - - /* - hexDump("\n\n\n", "READ", - (const int8*)_socketBuffer->getArray(), - _socketBuffer->getPosition(), _socketBuffer->getRemaining()); - */ - - // notify liveness - aliveNotification(); - - // exit - if(inStage!=UNDEFINED_STAGE) return; - - _stage = PROCESS_HEADER; - } - - if(likely(_stage==PROCESS_HEADER)) { - - // reveal what's already in buffer - _socketBuffer->setLimit(_storedLimit); - - // ensure PVAConstants.PVA_MESSAGE_HEADER_SIZE bytes of data - if(unlikely(((int)_socketBuffer->getRemaining())getByte(); - _version = _socketBuffer->getByte(); - if(unlikely(magic != PVA_MAGIC)) - { - // error... disconnect - LOG( - logLevelError, - "Invalid header received from client %s, disconnecting...", - inetAddressToString(_socketAddress).c_str()); - close(); - return; - } - - // data vs. control packet - _packetType = _socketBuffer->getByte(); - - // command - _command = _socketBuffer->getByte(); - - // read payload size - _payloadSize = _socketBuffer->getInt(); - - int8 type = (int8)(_packetType&0x0F); - if(likely(type==0)) - { - // data - _stage = PROCESS_PAYLOAD; - } - else if(unlikely(type==1)) - { - // control - // marker request sent - if (_command == CMD_SET_MARKER) { -#if FLOW_CONTROL - _flowControlMutex.lock(); - if(_markerToSend==0) - _markerToSend = _payloadSize; - // TODO send back response - _flowControlMutex.unlock(); -#endif - } - - // marker received back - else if (_command == CMD_ACK_MARKER) - { -#if FLOW_CONTROL - _flowControlMutex.lock(); - int difference = (int)_totalBytesSent-_payloadSize+PVA_MESSAGE_HEADER_SIZE; - // overrun check - if(difference<0) difference += INT_MAX; - _remoteBufferFreeSpace - = _remoteTransportReceiveBufferSize - +_remoteTransportSocketReceiveBufferSize - -difference; - // TODO if this is calculated wrong, this can be critical !!! - _flowControlMutex.unlock(); -#endif - } - // set byte order - else if (_command == CMD_SET_ENDIANESS) - { - // check 7-th bit - - int endianess = (_packetType < 0 ? EPICS_ENDIAN_BIG : EPICS_ENDIAN_LITTLE); - _socketBuffer->setEndianess(endianess); - - // TODO register as TransportSender and add to the queue - // current implementation is OK, but not nice - _sendQueueMutex.lock(); - _sendBuffer->setEndianess(endianess); - _byteOrderFlag = (endianess == EPICS_ENDIAN_BIG) ? 0x80 : 0x00; - _sendQueueMutex.unlock(); - } - - // no payload - //stage = ReceiveStage.PROCESS_HEADER; - continue; - } - else { - LOG( - logLevelError, - "Unknown packet type %d, received from client %s, disconnecting...", - type, - inetAddressToString(_socketAddress).c_str()); - close(); - return; - } - } - - if(likely(_stage==PROCESS_PAYLOAD)) { - // read header - - // last segment bit set (means in-between segment or last segment) - bool notFirstSegment = (_packetType&0x20)!=0; - - _storedPayloadSize = _payloadSize; - - // if segmented, exit reading code - if(nestedCall&¬FirstSegment) return; - - // ignore segmented messages with no payload - if (likely(!notFirstSegment || _payloadSize > 0)) - { - - // NOTE: nested data (w/ payload) messages between segmented messages are not supported - _storedPosition = _socketBuffer->getPosition(); - _storedLimit = _socketBuffer->getLimit(); - _socketBuffer->setLimit(min(_storedPosition+_storedPayloadSize, _storedLimit)); - try { - // handle response - Transport::shared_pointer thisPointer = shared_from_this(); - _responseHandler->handleResponse(&_socketAddress, - thisPointer, _version, _command, _payloadSize, - _socketBuffer); - } catch (std::exception& ex) { - - LOG( - logLevelDebug, - "Unexpected exception in responseHandler: %s", - ex.what() - ); - - } catch(...) { - - LOG( - logLevelDebug, - "Unexpected exception in responseHandler!" - ); - - } - - _socketBuffer->setLimit(_storedLimit); - size_t newPosition = _storedPosition+_storedPayloadSize; - if(unlikely(newPosition>_storedLimit)) { - newPosition -= _storedLimit; - _socketBuffer->setPosition(_storedLimit); - processReadCached(true, PROCESS_PAYLOAD,newPosition); - newPosition += _startPosition; - } - _socketBuffer->setPosition(newPosition); - // TODO discard all possible segments?!!! - - } - - _stage = PROCESS_HEADER; - - continue; - } - - } - } catch(...) { - // close connection - close(); - - if(nestedCall) throw; - } - } - - bool BlockingTCPTransport::flush() { - // request issues, has not sent anything yet (per partes) - if(likely(!_sendPending)) { - _sendPending = true; - - // start sending from the start - _sendBufferSentPosition = 0; - -#if FLOW_CONTROL - // if not set skip marker otherwise set it - _flowControlMutex.lock(); - int markerValue = _markerToSend; - _markerToSend = 0; - _flowControlMutex.unlock(); - if(markerValue==0) - _sendBufferSentPosition = PVA_MESSAGE_HEADER_SIZE; - else - _sendBuffer->putInt(4, markerValue); -#endif - } - - bool success = false; - try { - // remember current position - int currentPos = _sendBuffer->getPosition(); - - // set to send position - _sendBuffer->setPosition(_sendBufferSentPosition); - _sendBuffer->setLimit(currentPos); - - success = send(_sendBuffer); - - // all sent? - if(likely(success)) - clearAndReleaseBuffer(); - else { - // remember position - _sendBufferSentPosition = _sendBuffer->getPosition(); - - // .. reset to previous state - _sendBuffer->setPosition(currentPos); - _sendBuffer->setLimit(_sendBuffer->getSize()); - } - //} catch(std::exception& e) { - // LOG(logLevelError, "%s", e.what()); - // // error, release lock - // clearAndReleaseBuffer(); - } catch(...) { - clearAndReleaseBuffer(); - } - return success; - } - - bool BlockingTCPTransport::send(ByteBuffer* buffer) { - try { - // TODO simply use value from marker???!!! - // On Windows, limiting the buffer size is important to prevent - // poor throughput performances when transferring large amount of - // data. See Microsoft KB article KB823764. - // We do it also for other systems just to be safe. - int maxBytesToSend = std::min(_socketSendBufferSize, - static_cast(_remoteTransportSocketReceiveBufferSize))/2; - - int limit = buffer->getLimit(); - int bytesToSend = limit-buffer->getPosition(); - - //LOG(logLevelInfo,"Total bytes to send: %d", bytesToSend); - //printf("Total bytes to send: %d\n", bytesToSend); - - // limit sending - if(bytesToSend>maxBytesToSend) { - bytesToSend = maxBytesToSend; - buffer->setLimit(buffer->getPosition()+bytesToSend); - } - - //LOG(logLevelInfo, - // "Sending %d of total %d bytes in the packet to %s.", - // bytesToSend, limit, - // inetAddressToString(_socketAddress).c_str()); - - while(buffer->getRemaining()>0) { - ssize_t bytesSent = ::send(_channel, - &buffer->getArray()[buffer->getPosition()], - buffer->getRemaining(), 0); - - if(unlikely(bytesSent<0)) { - - int socketError = SOCKERRNO; - - // spurious EINTR check - if (socketError==SOCK_EINTR) - continue; - - // TODO check this (copy below)... consolidate!!! - if (socketError==SOCK_ENOBUFS) { - /* buffers full, reset the limit and indicate that there - * is more data to be sent - */ - if(bytesSent==maxBytesToSend) buffer->setLimit(limit); - return false; - } - - // connection lost - - char errStr[64]; - epicsSocketConvertErrnoToString(errStr, sizeof(errStr)); - ostringstream temp; - temp<<"error in sending TCP data: "<getPosition(), limit); - - /* buffers full, reset the limit and indicate that there - * is more data to be sent - */ - if(bytesSent==maxBytesToSend) buffer->setLimit(limit); - - //LOG(logLevelInfo, - // "Send buffer full for %s, waiting...", - // inetAddressToString(_socketAddress)); - return false; - } - - buffer->setPosition(buffer->getPosition()+bytesSent); - -#if FLOW_CONTROL - _flowControlMutex.lock(); - _totalBytesSent += bytesSent; - _flowControlMutex.unlock(); -#endif - - // readjust limit - if(bytesToSend==maxBytesToSend) { - bytesToSend = limit-buffer->getPosition(); - if(bytesToSend>maxBytesToSend) bytesToSend - = maxBytesToSend; - buffer->setLimit(buffer->getPosition()+bytesToSend); - } - - //LOG(logLevelInfo, - // "Sent, position %d of total %d bytes.", - // buffer->getPosition(), limit); - } // while - } catch(...) { - close(); - throw; - } - - // all sent - return true; - } - - void BlockingTCPTransport::processSendQueue() { - while(unlikely(!_closed.get())) { - - _sendQueueMutex.lock(); - // TODO optimize - TransportSender::shared_pointer sender; - if (likely(!_sendQueue.empty())) - { - sender = _sendQueue.front(); - _sendQueue.pop_front(); - } - _sendQueueMutex.unlock(); - - // wait for new message - while(likely(sender.get()==0&&!_flushRequested&&!_closed.get())) { - if(_flushStrategy==DELAYED) { - if(_delay>0) epicsThreadSleep(_delay); - if(unlikely(_sendQueue.empty())) { - // if (hasMonitors || sendBuffer.position() > PVAConstants.PVA_MESSAGE_HEADER_SIZE) -#if FLOW_CONTROL - if(((int)_sendBuffer->getPosition())>PVA_MESSAGE_HEADER_SIZE) -#else - if(((int)_sendBuffer->getPosition())>0) -#endif - _flushRequested = true; - else - _sendQueueEvent.wait(); - } - } - else - _sendQueueEvent.wait(); - - _sendQueueMutex.lock(); - if (likely(!_sendQueue.empty())) - { - sender = _sendQueue.front(); - _sendQueue.pop_front(); - } - else - sender.reset(); - _sendQueueMutex.unlock(); - } - - // always do flush from this thread - if(unlikely(_flushRequested)) { - /* - if (hasMonitors) - { - monitorSender.send(sendBuffer, this); - } - */ - - flush(); - } - - if(likely(sender.get() != 0)) { - sender->lock(); - try { - _lastMessageStartPosition = _sendBuffer->getPosition(); - sender->send(_sendBuffer, this); - - if(_flushStrategy==IMMEDIATE) - flush(true); - else - endMessage(false);// automatic end (to set payload) - } catch(std::exception &) { - //LOG(logLevelError, "%s", e.what()); - _sendBuffer->setPosition(_lastMessageStartPosition); - } catch(...) { - _sendBuffer->setPosition(_lastMessageStartPosition); - } - sender->unlock(); - } // if(sender!=NULL) - } // while(!_closed.get()) - } - - void BlockingTCPTransport::freeSendBuffers() { - // TODO ? - } - - void BlockingTCPTransport::freeConnectionResorces() { - freeSendBuffers(); - - LOG(logLevelDebug, "Connection to %s closed.", - inetAddressToString(_socketAddress).c_str()); -/* - if(_channel!=INVALID_SOCKET) { - epicsSocketDestroy(_channel); - _channel = INVALID_SOCKET; - } -*/ - } - - void BlockingTCPTransport::rcvThreadRunner(void* param) { - BlockingTCPTransport* obj = (BlockingTCPTransport*)param; - Transport::shared_pointer ptr = obj->shared_from_this(); // hold reference - -try{ - obj->processReadCached(false, UNDEFINED_STAGE, PVA_MESSAGE_HEADER_SIZE); -} catch (...) { -printf("rcvThreadRunnner exception\n"); -} - - /* - if(obj->_autoDelete) { - while(true) - { - bool exited; - obj->_mutex.lock(); - exited = obj->_sendThreadExited; - obj->_mutex.unlock(); - if (exited) - break; - epicsThreadSleep(0.1); - } - delete obj; - } - */ - } - - void BlockingTCPTransport::sendThreadRunner(void* param) { - BlockingTCPTransport* obj = (BlockingTCPTransport*)param; - Transport::shared_pointer ptr = obj->shared_from_this(); // hold reference -try { - obj->processSendQueue(); -} catch (std::exception& ex) { - printf("sendThreadRunnner exception %s\n", ex.what()); // TODO -} catch (...) { -printf("sendThreadRunnner exception\n"); -} - - obj->freeConnectionResorces(); - - // TODO possible crash on unlock - obj->_mutex.lock(); - obj->_sendThreadExited = true; - obj->_mutex.unlock(); - } - - void BlockingTCPTransport::enqueueSendRequest(TransportSender::shared_pointer const & sender) { - Lock lock(_sendQueueMutex); - if(unlikely(_closed.get())) return; - _sendQueue.push_back(sender); - _sendQueueEvent.signal(); - } - - void BlockingTCPTransport::enqueueOnlySendRequest(TransportSender::shared_pointer const & sender) { - Lock lock(_sendQueueMutex); - if(unlikely(_closed.get())) return; - _sendQueue.push_back(sender); - } - - class FlushTransportSender : public TransportSender { - public: - virtual void send(epics::pvData::ByteBuffer*, TransportSendControl* control) - { - control->flush(true); - } - - virtual void lock() {} - virtual void unlock() {} - }; - - static TransportSender::shared_pointer flushTransportSender(new FlushTransportSender()); - - void BlockingTCPTransport::flushSendQueue() { - enqueueSendRequest(flushTransportSender); - } - - /* - void BlockingTCPTransport::enqueueMonitorSendRequest(TransportSender::shared_pointer sender) { - Lock lock(_monitorMutex); - if(unlikely(_closed.get())) return; - _monitorSendQueue.insert(sender); - if(_monitorSendQueue.size()==1) enqueueSendRequest(_monitorSender); - } - - - void MonitorSender::send(ByteBuffer* buffer, TransportSendControl* control) { - control->startMessage(19, 0); - - while(true) { - TransportSender* sender; - _monitorMutex->lock(); - if(_monitorSendQueue->size()>0) - sender = _monitorSendQueue->extract(); - else - sender = NULL; - _monitorMutex->unlock(); - - if(sender==NULL) { - control->ensureBuffer(sizeof(int32)); - buffer->putInt(INVALID_IOID); - break; - } - sender->send(buffer, control); - sender->release(); - } - } -*/ - } -} diff --git a/pvAccessApp/remote/codec.cpp b/pvAccessApp/remote/codec.cpp new file mode 100644 index 0000000..e26f25a --- /dev/null +++ b/pvAccessApp/remote/codec.cpp @@ -0,0 +1,1613 @@ +/** +* 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. +*/ +#ifdef _WIN32 +#define NOMINMAX +#endif + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include + +using namespace epics::pvData; +using namespace epics::pvAccess; + + +namespace epics { + namespace pvAccess { + namespace detail { + + const std::size_t AbstractCodec::MAX_MESSAGE_PROCESS = 100; + const std::size_t AbstractCodec::MAX_MESSAGE_SEND = 100; + const std::size_t AbstractCodec::MAX_ENSURE_SIZE = 1024; + const std::size_t AbstractCodec::MAX_ENSURE_DATA_SIZE = MAX_ENSURE_SIZE/2; + const std::size_t AbstractCodec::MAX_ENSURE_BUFFER_SIZE = MAX_ENSURE_SIZE; + const std::size_t AbstractCodec::MAX_ENSURE_DATA_BUFFER_SIZE = 1024; + + AbstractCodec::AbstractCodec( + std::tr1::shared_ptr const & receiveBuffer, + std::tr1::shared_ptr const & sendBuffer, + int32_t socketSendBufferSize, + bool blockingProcessQueue): + //PROTECTED + _readMode(NORMAL), _version(0), _flags(0), _command(0), _payloadSize(0), + _remoteTransportSocketReceiveBufferSize(MAX_TCP_RECV), _totalBytesSent(0), + _blockingProcessQueue(false), _senderThread(0), + _writeMode(PROCESS_SEND_QUEUE), + _writeOpReady(false),_lowLatency(false), + _socketBuffer(receiveBuffer), + _sendBuffer(sendBuffer), + //PRIVATE + _storedPayloadSize(0), _storedPosition(0), _startPosition(0), + _maxSendPayloadSize(0), + _lastMessageStartPosition(std::numeric_limits::max()),_lastSegmentedMessageType(0), + _lastSegmentedMessageCommand(0), _nextMessagePayloadOffset(0), + _byteOrderFlag(EPICS_BYTE_ORDER == EPICS_ENDIAN_BIG ? 0x80 : 0x00), + _socketSendBufferSize(0) + { + if (receiveBuffer->getSize() < 2*MAX_ENSURE_SIZE) + throw std::invalid_argument( + "receiveBuffer.capacity() < 2*MAX_ENSURE_SIZE"); + + // require aligned buffer size + //(not condition, but simplifies alignment code) + + if (receiveBuffer->getSize() % PVA_ALIGNMENT != 0) + throw std::invalid_argument( + "receiveBuffer.capacity() % PVAConstants.PVA_ALIGNMENT != 0"); + + if (sendBuffer->getSize() < 2*MAX_ENSURE_SIZE) + throw std::invalid_argument("sendBuffer() < 2*MAX_ENSURE_SIZE"); + + // require aligned buffer size + //(not condition, but simplifies alignment code) + if (sendBuffer->getSize() % PVA_ALIGNMENT != 0) + throw std::invalid_argument( + "sendBuffer() % PVAConstants.PVA_ALIGNMENT != 0"); + + // initialize to be empty + _socketBuffer->setPosition(_socketBuffer->getLimit()); + _startPosition = _socketBuffer->getPosition(); + + // clear send + _sendBuffer->clear(); + + // start msg + control + _maxSendPayloadSize = + _sendBuffer->getSize() - 2*PVA_MESSAGE_HEADER_SIZE; + _socketSendBufferSize = socketSendBufferSize; + _blockingProcessQueue = blockingProcessQueue; + } + + + void AbstractCodec::processRead() { + switch (_readMode) + { + case NORMAL: + processReadNormal(); + break; + case SEGMENTED: + processReadSegmented(); + break; + case SPLIT: + throw std::logic_error("ReadMode == SPLIT not supported"); + } + + } + + + void AbstractCodec::processHeader() { + + // magic code + int8_t magicCode = _socketBuffer->getByte(); + + // version + _version = _socketBuffer->getByte(); + + // flags + _flags = _socketBuffer->getByte(); + + // command + _command = _socketBuffer->getByte(); + + // read payload size + _payloadSize = _socketBuffer->getInt(); + + // check magic code + if (magicCode != PVA_MAGIC) + { + LOG(logLevelError, + "Invalid header received from the client at %s:%d: %s," + " disconnecting...", + __FILE__, __LINE__, inetAddressToString(*getLastReadBufferSocketAddress()).c_str()); + invalidDataStreamHandler(); + throw invalid_data_stream_exception("invalid header received"); + } + + } + + + void AbstractCodec::processReadNormal() { + + try + { + std::size_t messageProcessCount = 0; + while (messageProcessCount++ < MAX_MESSAGE_PROCESS) + { + // read as much as available, but at least for a header + // readFromSocket checks if reading from socket is really necessary + if (!readToBuffer(PVA_MESSAGE_HEADER_SIZE, false)) { + return; + } + + // read header fields + processHeader(); + bool isControl = ((_flags & 0x01) == 0x01); + if (isControl) { + processControlMessage(); + } + else + { + // segmented sanity check + bool notFirstSegment = (_flags & 0x20) != 0; + if (notFirstSegment) + { + LOG(logLevelWarn, + "Not-a-frst segmented message received in normal mode" + " from the client at %s:%d: %s, disconnecting...", + __FILE__, __LINE__, inetAddressToString(*getLastReadBufferSocketAddress()).c_str()); + invalidDataStreamHandler(); + throw invalid_data_stream_exception( + "not-a-first segmented message received in normal mode"); + } + + _storedPayloadSize = _payloadSize; + _storedPosition = _socketBuffer->getPosition(); + _storedLimit = _socketBuffer->getLimit(); + _socketBuffer->setLimit(std::min + (_storedPosition + _storedPayloadSize, _storedLimit)); + bool postProcess = true; + try + { + // handle response + processApplicationMessage(); + + if (!isOpen()) + return; + + postProcess = false; + postProcessApplicationMessage(); + } + catch(...) + { + if (!isOpen()) + return; + + if (postProcess) + { + postProcessApplicationMessage(); + } + + throw; + } + } + } + + } + catch (invalid_data_stream_exception & ) + { + // noop, should be already handled (and logged) + } + catch (connection_closed_exception & ) + { + // noop, should be already handled (and logged) + } + } + + void AbstractCodec::postProcessApplicationMessage() + { + // can be closed by now + // isOpen() should be efficiently implemented + while (true) + //while (isOpen()) + { + // set position as whole message was read + //(in case code haven't done so) + std::size_t newPosition = + alignedValue( + _storedPosition + _storedPayloadSize, PVA_ALIGNMENT); + + // aligned buffer size ensures that there is enough space + //in buffer, + // however data might not be fully read + + // discard the rest of the packet + if (newPosition > _storedLimit) + { + // processApplicationMessage() did not read up + //quite some buffer + + // we only handle unused alignment bytes + int bytesNotRead = + newPosition - _socketBuffer->getPosition(); + + if (bytesNotRead < PVA_ALIGNMENT) + { + // make alignment bytes as real payload to enable SPLIT + // no end-of-socket or segmented scenario can happen + // due to aligned buffer size + _storedPayloadSize += bytesNotRead; + // reveal currently existing padding + _socketBuffer->setLimit(_storedLimit); + ensureData(bytesNotRead); + _storedPayloadSize -= bytesNotRead; + continue; + } + + // TODO we do not handle this for now (maybe never) + LOG(logLevelWarn, + "unprocessed read buffer from client at %s:%d: %s," + " disconnecting...", + __FILE__, __LINE__, inetAddressToString(*getLastReadBufferSocketAddress()).c_str()); + invalidDataStreamHandler(); + throw invalid_data_stream_exception( + "unprocessed read buffer"); + } + _socketBuffer->setLimit(_storedLimit); + _socketBuffer->setPosition(newPosition); + break; + } + } + + void AbstractCodec::processReadSegmented() { + + while (true) + { + // read as much as available, but at least for a header + // readFromSocket checks if reading from socket is really necessary + readToBuffer(PVA_MESSAGE_HEADER_SIZE, true); + + // read header fields + processHeader(); + + bool isControl = ((_flags & 0x01) == 0x01); + if (isControl) + processControlMessage(); + else + { + // last segment bit set (means in-between segment or last segment) + // we expect this, no non-control messages between + //segmented message are supported + // NOTE: for now... it is easy to support non-semgented + //messages between segmented messages + bool notFirstSegment = (_flags & 0x20) != 0; + if (!notFirstSegment) + { + LOG(logLevelWarn, + "Not-a-first segmented message expected from the client at" + " %s:%d: %s, disconnecting...", + __FILE__, __LINE__, inetAddressToString(*getLastReadBufferSocketAddress()).c_str()); + invalidDataStreamHandler(); + throw new invalid_data_stream_exception( + "not-a-first segmented message expected"); + } + + _storedPayloadSize = _payloadSize; + + // return control to caller code + return; + } + } + + } + + + bool AbstractCodec::readToBuffer( + std::size_t requiredBytes, + bool persistent) { + + // do we already have requiredBytes available? + std::size_t remainingBytes = _socketBuffer->getRemaining(); + if (remainingBytes >= requiredBytes) { + return true; + } + + // assumption: remainingBytes < MAX_ENSURE_DATA_BUFFER_SIZE && + // requiredBytes < (socketBuffer.capacity() - PVA_ALIGNMENT) + + // + // copy unread part to the beginning of the buffer + // to make room for new data (as much as we can read) + // NOTE: requiredBytes is expected to be small (order of 10 bytes) + // + + // a new start position, we are careful to preserve alignment + _startPosition = + MAX_ENSURE_SIZE + _socketBuffer->getPosition() % PVA_ALIGNMENT; + + std::size_t endPosition = _startPosition + remainingBytes; + + for (std::size_t i = _startPosition; i < endPosition; i++) + _socketBuffer->putByte(i, _socketBuffer->getByte()); + + // update buffer to the new position + _socketBuffer->setLimit(_socketBuffer->getSize()); + _socketBuffer->setPosition(endPosition); + + // read at least requiredBytes bytes + std::size_t requiredPosition = _startPosition + requiredBytes; + while (_socketBuffer->getPosition() < requiredPosition) + { + int bytesRead = read(_socketBuffer.get()); + + if (bytesRead < 0) + { + close(); + throw connection_closed_exception("bytesRead < 0"); + } + // non-blocking IO support + else if (bytesRead == 0) + { + if (persistent) + readPollOne(); + else + { + // set pointers (aka flip) + _socketBuffer->setLimit(_socketBuffer->getPosition()); + _socketBuffer->setPosition(_startPosition); + + return false; + } + } + } + + // set pointers (aka flip) + _socketBuffer->setLimit(_socketBuffer->getPosition()); + _socketBuffer->setPosition(_startPosition); + + return true; + } + + + void AbstractCodec::ensureData(std::size_t size) { + + // enough of data? + if (_socketBuffer->getRemaining() >= size) + return; + + // to large for buffer... + if (size > MAX_ENSURE_DATA_SIZE) {// half for SPLIT, half for SEGMENTED + std::ostringstream msg; + msg << "requested for buffer size " << size + << ", but maximum " << MAX_ENSURE_DATA_SIZE << " is allowed."; + LOG(logLevelWarn, + "%s at %s:%d,", msg.str().c_str(), __FILE__, __LINE__); + std::string s = msg.str(); + throw std::invalid_argument(s); + } + + try + { + + // subtract what was already processed + std::size_t pos = _socketBuffer->getPosition(); + _storedPayloadSize -= pos - _storedPosition; + + // SPLIT message case + // no more data and we have some payload left => read buffer + // NOTE: (storedPayloadSize >= size) does not work if size + //spans over multiple messages + if (_storedPayloadSize >= (_storedLimit-pos)) + { + // just read up remaining payload + // this will move current (getPosition(); + _storedLimit = _socketBuffer->getLimit(); + _socketBuffer->setLimit( + std::min( + _storedPosition + _storedPayloadSize, _storedLimit)); + + // check needed, if not enough data is available or + // we run into segmented message + ensureData(size); + } + // SEGMENTED message case + else + { + // TODO check flags + //if (flags && SEGMENTED_FLAGS_MASK == 0) + // throw IllegalStateException("segmented message expected, + //but current message flag does not indicate it"); + + + // copy remaining bytes of payload to safe area + //[0 to MAX_ENSURE_DATA_BUFFER_SIZE/2), if any + // remaining is relative to payload since buffer is + //bounded from outside + std::size_t remainingBytes = _socketBuffer->getRemaining(); + for (std::size_t i = 0; i < remainingBytes; i++) + _socketBuffer->putByte(i, _socketBuffer->getByte()); + + // restore limit (there might be some data already present + //and readToBuffer needs to know real limit) + _socketBuffer->setLimit(_storedLimit); + + // remember alignment offset of end of the message (to be restored) + std::size_t storedAlignmentOffset = + _socketBuffer->getPosition() % PVA_ALIGNMENT; + + // skip post-message alignment bytes + if (storedAlignmentOffset > 0) + { + std::size_t toSkip = PVA_ALIGNMENT - storedAlignmentOffset; + readToBuffer(toSkip, true); + std::size_t currentPos = _socketBuffer->getPosition(); + _socketBuffer->setPosition(currentPos + toSkip); + } + + // we expect segmented message, we expect header + // that (and maybe some control packets) needs to be "removed" + // so that we get combined payload + ReadMode storedMode = _readMode; _readMode = SEGMENTED; + processRead(); + _readMode = storedMode; + + // make sure we have all the data (maybe we run into SPLIT) + readToBuffer(size - remainingBytes + storedAlignmentOffset, true); + + // skip storedAlignmentOffset bytes (sender should padded start of + //segmented message) + // SPLIT cannot mess with this, since start of the message, + //i.e. current position, is always aligned + _socketBuffer->setPosition( + _socketBuffer->getPosition() + storedAlignmentOffset); + + // copy before position (i.e. start of the payload) + for (int32_t i = remainingBytes - 1, + j = _socketBuffer->getPosition() - 1; i >= 0; i--, j--) + _socketBuffer->putByte(j, _socketBuffer->getByte(i)); + + _startPosition = _socketBuffer->getPosition() - remainingBytes; + _socketBuffer->setPosition(_startPosition); + + _storedPayloadSize += remainingBytes - storedAlignmentOffset; + _storedPosition = _startPosition; + _storedLimit = _socketBuffer->getLimit(); + _socketBuffer->setLimit( + std::min( + _storedPosition + _storedPayloadSize, _storedLimit)); + + // sequential small segmented messages in the buffer + ensureData(size); + } + } + catch (io_exception &) { + try { + close(); + } catch (io_exception & ) { + // noop, best-effort close + } + throw connection_closed_exception( + "Failed to ensure data to read buffer."); + } + } + + + std::size_t AbstractCodec::alignedValue( + std::size_t value, + std::size_t alignment) { + + std::size_t k = (alignment - 1); + return (value + k) & (~k); + } + + + void AbstractCodec::alignData(std::size_t alignment) { + + std::size_t k = (alignment - 1); + std::size_t pos = _socketBuffer->getPosition(); + std::size_t newpos = (pos + k) & (~k); + if (pos == newpos) + return; + + std::size_t diff = _socketBuffer->getLimit() - newpos; + if (diff > 0) + { + _socketBuffer->setPosition(newpos); + return; + } + + ensureData(diff); + + // position has changed, recalculate + newpos = (_socketBuffer->getPosition() + k) & (~k); + _socketBuffer->setPosition(newpos); + } + + + void AbstractCodec::alignBuffer(std::size_t alignment) { + + std::size_t k = (alignment - 1); + std::size_t pos = _sendBuffer->getPosition(); + std::size_t newpos = (pos + k) & (~k); + if (pos == newpos) + return; + + // there is always enough of space + // since sendBuffer capacity % PVA_ALIGNMENT == 0 + _sendBuffer->setPosition(newpos); + } + + + void AbstractCodec::startMessage( + epics::pvData::int8 command, + std::size_t ensureCapacity) { + + _lastMessageStartPosition = + std::numeric_limits::max(); // TODO revise this + ensureBuffer( + PVA_MESSAGE_HEADER_SIZE + ensureCapacity + _nextMessagePayloadOffset); + _lastMessageStartPosition = _sendBuffer->getPosition(); + _sendBuffer->putByte(PVA_MAGIC); + _sendBuffer->putByte(PVA_VERSION); + _sendBuffer->putByte( + (_lastSegmentedMessageType | _byteOrderFlag)); // data + endian + _sendBuffer->putByte(command); // command + _sendBuffer->putInt(0); // temporary zero payload + + // apply offset + if (_nextMessagePayloadOffset > 0) + _sendBuffer->setPosition( + _sendBuffer->getPosition() + _nextMessagePayloadOffset); + } + + + void AbstractCodec::putControlMessage( + epics::pvData::int8 command, + epics::pvData::int32 data) { + + _lastMessageStartPosition = + std::numeric_limits::max(); // TODO revise this + ensureBuffer(PVA_MESSAGE_HEADER_SIZE); + _sendBuffer->putByte(PVA_MAGIC); + _sendBuffer->putByte(PVA_VERSION); + _sendBuffer->putByte((0x01 | _byteOrderFlag)); // control + endian + _sendBuffer->putByte(command); // command + _sendBuffer->putInt(data); // data + } + + + void AbstractCodec::endMessage() { + endMessage(false); + } + + + void AbstractCodec::endMessage(bool hasMoreSegments) { + + if (_lastMessageStartPosition != std::numeric_limits::max()) + { + std::size_t lastPayloadBytePosition = _sendBuffer->getPosition(); + + // align + alignBuffer(PVA_ALIGNMENT); + + // set paylaod size (non-aligned) + std::size_t payloadSize = + lastPayloadBytePosition - + _lastMessageStartPosition - PVA_MESSAGE_HEADER_SIZE; + + _sendBuffer->putInt(_lastMessageStartPosition + 4, payloadSize); + + // set segmented bit + if (hasMoreSegments) { + // first segment + if (_lastSegmentedMessageType == 0) + { + std::size_t flagsPosition = _lastMessageStartPosition + 2; + epics::pvData::int8 type = _sendBuffer->getByte(flagsPosition); + // set first segment bit + _sendBuffer->putByte(flagsPosition, (type | 0x10)); + // first + last segment bit == in-between segment + _lastSegmentedMessageType = type | 0x30; + _lastSegmentedMessageCommand = + _sendBuffer->getByte(flagsPosition + 1); + } + _nextMessagePayloadOffset = lastPayloadBytePosition % PVA_ALIGNMENT; + } + else + { + // last segment + if (_lastSegmentedMessageType != + std::numeric_limits::max()) + { + std::size_t flagsPosition = _lastMessageStartPosition + 2; + // set last segment bit (by clearing first segment bit) + _sendBuffer->putByte(flagsPosition, + (_lastSegmentedMessageType & 0xEF)); + _lastSegmentedMessageType = 0; + } + _nextMessagePayloadOffset = 0; + } + + // TODO + /* + // manage markers + final int position = sendBuffer.position(); + final int bytesLeft = sendBuffer.remaining(); + if (position >= nextMarkerPosition && bytesLeft >= + PVAConstants.PVA_MESSAGE_HEADER_SIZE) + { + sendBuffer.put(PVAConstants.PVA_MAGIC); + sendBuffer.put(PVAConstants.PVA_VERSION); + sendBuffer.put((byte)(0x01 | byteOrderFlag)); // control data + sendBuffer.put((byte)0); // marker + sendBuffer.putInt((int)(totalBytesSent + position + + PVAConstants.PVA_MESSAGE_HEADER_SIZE)); + nextMarkerPosition = position + markerPeriodBytes; + } + */ + _lastMessageStartPosition = std::numeric_limits::max(); + } + } + + void AbstractCodec::ensureBuffer(std::size_t size) { + + if (_sendBuffer->getRemaining() >= size) + return; + + // too large for buffer... + if (_maxSendPayloadSize < size) { + std::ostringstream msg; + msg << "requested for buffer size " << + size << ", but only " << _maxSendPayloadSize << " available."; + std::string s = msg.str(); + LOG(logLevelWarn, + "%s at %s:%d,", msg.str().c_str(), __FILE__, __LINE__); + throw std::invalid_argument(s); + } + + while (_sendBuffer->getRemaining() < size) + flush(false); + } + + // assumes startMessage was called (or header is in place), because endMessage(true) is later called that peeks and sets _lastSegmentedMessageType + void AbstractCodec::flushSerializeBuffer() { + flush(false); + } + + + void AbstractCodec::flush(bool lastMessageCompleted) { + + // automatic end + endMessage(!lastMessageCompleted); + + _sendBuffer->flip(); + + try { + send(_sendBuffer.get()); + } catch (io_exception &) { + try { + if (isOpen()) + close(); + } catch (io_exception &) { + // noop, best-effort close + } + throw connection_closed_exception("Failed to send buffer."); + } + + _sendBuffer->clear(); + + _lastMessageStartPosition = std::numeric_limits::max(); + + // start with last header + if (!lastMessageCompleted && _lastSegmentedMessageType != 0) + startMessage(_lastSegmentedMessageCommand, 0); + } + + + void AbstractCodec::processWrite() { + + // TODO catch ConnectionClosedException, InvalidStreamException? + switch (_writeMode) + { + case PROCESS_SEND_QUEUE: + processSendQueue(); + break; + case WAIT_FOR_READY_SIGNAL: + _writeOpReady = true; + break; + } + } + + + void AbstractCodec::send(ByteBuffer *buffer) + { + + // On Windows, limiting the buffer size is important to prevent + // poor throughput performances when transferring large amount of + // data. See Microsoft KB article KB823764. + // We do it also for other systems just to be safe. + std::size_t maxBytesToSend = + std::min( + _socketSendBufferSize, _remoteTransportSocketReceiveBufferSize) / 2; + + std::size_t limit = buffer->getLimit(); + std::size_t bytesToSend = limit - buffer->getPosition(); + + // limit sending + if (bytesToSend > maxBytesToSend) + { + bytesToSend = maxBytesToSend; + buffer->setLimit(buffer->getPosition() + bytesToSend); + } + + int tries = 0; + while (buffer->getRemaining() > 0) + { + + //int p = buffer.position(); + int bytesSent = write(buffer); + + /* + if (IS_LOGGABLE(logLevelTrace)) { + hexDump(std::string("AbstractCodec::send WRITE"), + (const int8 *)buffer->getArray(), + buffer->getPosition(), buffer->getRemaining()); + } + */ + + if (bytesSent < 0) + { + // connection lost + close(); + throw connection_closed_exception("bytesSent < 0"); + } + else if (bytesSent == 0) + { + sendBufferFull(tries++); + continue; + } + + _totalBytesSent += bytesSent; + + // readjust limit + if (bytesToSend == maxBytesToSend) + { + bytesToSend = limit - buffer->getPosition(); + + if(bytesToSend > maxBytesToSend) + bytesToSend = maxBytesToSend; + + buffer->setLimit(buffer->getPosition() + bytesToSend); + } + tries = 0; + } + } + + + void AbstractCodec::processSendQueue() + { + + { + std::size_t senderProcessed = 0; + while (senderProcessed++ < MAX_MESSAGE_SEND) + { + TransportSender::shared_pointer sender = _sendQueue.take(-1); + if (sender.get() == 0) + { + // flush + if (_sendBuffer->getPosition() > 0) + flush(true); + + sendCompleted(); // do not schedule sending + + if (_blockingProcessQueue) { + if (terminated()) // termination + break; + sender = _sendQueue.take(0); + // termination (we want to process even if shutdown) + if (sender.get() == 0) + break; + } + else + return; + } + + processSender(sender); + } + } + + // flush + if (_sendBuffer->getPosition() > 0) + flush(true); + } + + + void AbstractCodec::clearSendQueue() + { + _sendQueue.clean(); + } + + + void AbstractCodec::enqueueSendRequest( + TransportSender::shared_pointer const & sender) { + _sendQueue.put(sender); + scheduleSend(); + } + + + void AbstractCodec::setSenderThread() + { + _senderThread = epicsThreadGetIdSelf(); + } + + + void AbstractCodec::processSender( + TransportSender::shared_pointer const & sender) + { + + ScopedLock lock(sender); + + try { + _lastMessageStartPosition = _sendBuffer->getPosition(); + + sender->send(_sendBuffer.get(), this); + + // automatic end (to set payload size) + endMessage(false); + } + catch (std::exception &e ) { + + std::ostringstream msg; + msg << "an exception caught while processing a send message: " + << e.what(); + LOG(logLevelWarn, "%s at %s:%d", + msg.str().c_str(), __FILE__, __LINE__); + + try { + close(); + } catch (io_exception & ) { + // noop + } + + throw connection_closed_exception(msg.str()); + } + } + + + void AbstractCodec::enqueueSendRequest( + TransportSender::shared_pointer const & sender, + std::size_t requiredBufferSize) { + + if (_senderThread == epicsThreadGetIdSelf() && + _sendQueue.empty() && + _sendBuffer->getRemaining() >= requiredBufferSize) + { + processSender(sender); + if (_sendBuffer->getPosition() > 0) + { + if (_lowLatency) + flush(true); + else + scheduleSend(); + } + } + else + enqueueSendRequest(sender); + } + + + void AbstractCodec::setRecipient(osiSockAddr const & sendTo) { + _sendTo = sendTo; + } + + + void AbstractCodec::setByteOrder(int byteOrder) + { + _socketBuffer->setEndianess(byteOrder); + // TODO sync + _sendBuffer->setEndianess(byteOrder); + _byteOrderFlag = EPICS_ENDIAN_BIG == byteOrder ? 0x80 : 0x00; + } + + + + // + // + // BlockingAbstractCodec + // + // + // + + void BlockingAbstractCodec::readPollOne() { + throw std::logic_error("should not be called for blocking IO"); + } + + + void BlockingAbstractCodec::writePollOne() { + throw std::logic_error("should not be called for blocking IO"); + } + + + void BlockingAbstractCodec::close() { + + if (_isOpen.getAndSet(false)) + { + // always close in the same thread, same way, etc. + // wakeup processSendQueue + + // clean resources + internalClose(true); + + _sendQueue.wakeup(); + + // post close + internalPostClose(true); + } + } + + void BlockingAbstractCodec::internalClose(bool /*force*/) { + } + + void BlockingAbstractCodec::internalPostClose(bool /*force*/) { + } + + bool BlockingAbstractCodec::terminated() { + return !isOpen(); + } + + + bool BlockingAbstractCodec::isOpen() { + return _isOpen.get(); + } + + + // NOTE: must not be called from constructor (e.g. needs shared_from_this()) + void BlockingAbstractCodec::start() { + + _readThread = epicsThreadCreate( + "BlockingAbstractCodec-readThread", + epicsThreadPriorityMedium, + epicsThreadGetStackSize( + epicsThreadStackMedium), + BlockingAbstractCodec::receiveThread, + this); + + _sendThread = epicsThreadCreate( + "BlockingAbstractCodec-_sendThread", + epicsThreadPriorityMedium, + epicsThreadGetStackSize( + epicsThreadStackMedium), + BlockingAbstractCodec::sendThread, + this); + + } + + + void BlockingAbstractCodec::receiveThread(void *param) + { + + BlockingAbstractCodec *bac = static_cast(param); + Transport::shared_pointer ptr = bac->shared_from_this(); + + while (bac->isOpen()) + { + try { + bac->processRead(); + } catch (io_exception &e) { + LOG(logLevelWarn, + "an exception caught while in receiveThread at %s:%d: %s", + __FILE__, __LINE__, e.what()); + } + } + + bac->_shutdownEvent.signal(); + } + + + void BlockingAbstractCodec::sendThread(void *param) + { + + BlockingAbstractCodec *bac = static_cast(param); + Transport::shared_pointer ptr = bac->shared_from_this(); + + bac->setSenderThread(); + + while (bac->isOpen()) + { + try { + bac->processWrite(); + } catch (io_exception &e) { + LOG(logLevelWarn, + "an exception caught while in sendThread at %s:%d: %s", + __FILE__, __LINE__, e.what()); + } + } + + // wait read thread to die + bac->_shutdownEvent.wait(); + + // call internal destroy + bac->internalDestroy(); + + } + + + void BlockingAbstractCodec::sendBufferFull(int tries) { + // TODO constants + epicsThreadSleep(std::max(tries * 0.1, 1)); + } + + + // + // + // BlockingSocketAbstractCodec + // + // + // + + + BlockingSocketAbstractCodec::BlockingSocketAbstractCodec( + SOCKET channel, + int32_t sendBufferSize, + int32_t receiveBufferSize): + BlockingAbstractCodec( + std::tr1::shared_ptr(new ByteBuffer((std::max((std::size_t)( + MAX_TCP_RECV + MAX_ENSURE_DATA_BUFFER_SIZE), receiveBufferSize) + + (PVA_ALIGNMENT - 1)) & (~(PVA_ALIGNMENT - 1)))), + std::tr1::shared_ptr(new ByteBuffer((std::max((std::size_t)( MAX_TCP_RECV + + MAX_ENSURE_DATA_BUFFER_SIZE), receiveBufferSize) + (PVA_ALIGNMENT - 1)) + & (~(PVA_ALIGNMENT - 1)))), sendBufferSize), + _channel(channel) + { + // get remote address + osiSocklen_t saSize = sizeof(sockaddr); + int retval = getpeername(_channel, &(_socketAddress.sa), &saSize); + if(unlikely(retval<0)) { + char errStr[64]; + epicsSocketConvertErrnoToString(errStr, sizeof(errStr)); + LOG(logLevelError, + "Error fetching socket remote address: %s", + errStr); + } + + // set receive timeout so that we do not have problems at + //shutdown (recvfrom would block) + struct timeval timeout; + memset(&timeout, 0, sizeof(struct timeval)); + timeout.tv_sec = 1; + timeout.tv_usec = 0; + + // TODO remove this and implement use epicsSocketSystemCallInterruptMechanismQuery + if (unlikely(::setsockopt (_channel, SOL_SOCKET, SO_RCVTIMEO, + (char*)&timeout, sizeof(timeout)) < 0)) + { + char errStr[64]; + epicsSocketConvertErrnoToString(errStr, sizeof(errStr)); + LOG(logLevelError, + "Failed to set SO_RCVTIMEO for TDP socket %s: %s.", + inetAddressToString(_socketAddress).c_str(), errStr); + } + + } + + // must be called only once, when there will be no operation on socket (e.g. just before tx/rx thread exists) + void BlockingSocketAbstractCodec::internalDestroy() { + + if(_channel != INVALID_SOCKET) { + epicsSocketDestroy(_channel); + _channel = INVALID_SOCKET; + } + + } + + + void BlockingSocketAbstractCodec::invalidDataStreamHandler() { + close(); + } + + + int BlockingSocketAbstractCodec::write( + epics::pvData::ByteBuffer *src) { + + std::size_t remaining; + while((remaining=src->getRemaining()) > 0) { + + int bytesSent = ::send(_channel, + &src->getArray()[src->getPosition()], + remaining, 0); + + // NOTE: do not log here, you might override SOCKERRNO relevant to recv() operation above + + if(unlikely(bytesSent<0)) { + + int socketError = SOCKERRNO; + + // spurious EINTR check + if (socketError==SOCK_EINTR) + continue; + } + + if (bytesSent > 0) { + src->setPosition(src->getPosition() + bytesSent); + } + + return bytesSent; + + } + + return 0; + } + + + std::size_t BlockingSocketAbstractCodec::getSocketReceiveBufferSize() + const { + + osiSocklen_t intLen = sizeof(int); + int socketRecvBufferSize; + int retval = getsockopt(_channel, SOL_SOCKET, SO_RCVBUF, + (char *)&socketRecvBufferSize, &intLen); + + if(retval<0) { + if (IS_LOGGABLE(logLevelDebug)) + { + char strBuffer[64]; + epicsSocketConvertErrnoToString(strBuffer, sizeof(strBuffer)); + LOG(logLevelDebug, "Error getting SO_SNDBUF: %s", strBuffer); + } + } + + return socketRecvBufferSize; + } + + + int BlockingSocketAbstractCodec::read(epics::pvData::ByteBuffer* dst) { + + std::size_t remaining; + while((remaining=dst->getRemaining()) > 0) { + + // read + std::size_t pos = dst->getPosition(); + + int bytesRead = recv(_channel, + (char*)(dst->getArray()+pos), remaining, 0); + + // NOTE: do not log here, you might override SOCKERRNO relevant to recv() operation above + + /* + if (IS_LOGGABLE(logLevelTrace)) { + hexDump(std::string("READ"), + (const int8 *)(dst->getArray()+pos), bytesRead); + } + */ + + if(unlikely(bytesRead<=0)) { + + if (bytesRead<0) + { + int socketError = SOCKERRNO; + + // interrupted or timeout + if (socketError == EINTR || + socketError == EAGAIN || + socketError == EWOULDBLOCK) + continue; + } + + return -1; // 0 means connection loss for blocking transport, notify codec by returning -1 + } + + dst->setPosition(dst->getPosition() + bytesRead); + return bytesRead; + } + + return 0; + } + + + BlockingServerTCPTransportCodec::BlockingServerTCPTransportCodec( + Context::shared_pointer const & context, + SOCKET channel, + std::auto_ptr& responseHandler, + int32_t sendBufferSize, + int32_t receiveBufferSize) : + BlockingTCPTransportCodec(context, channel, responseHandler, + sendBufferSize, receiveBufferSize, PVA_DEFAULT_PRIORITY), + _lastChannelSID(0) + { + + // NOTE: priority not yet known, default priority is used to + //register/unregister + // TODO implement priorities in Reactor... not that user will + // change it.. still getPriority() must return "registered" priority! + } + + + BlockingServerTCPTransportCodec::~BlockingServerTCPTransportCodec() { + } + + + pvAccessID BlockingServerTCPTransportCodec::preallocateChannelSID() { + + Lock lock(_channelsMutex); + // search first free (theoretically possible loop of death) + pvAccessID sid = ++_lastChannelSID; + while(_channels.find(sid)!=_channels.end()) + sid = ++_lastChannelSID; + return sid; + } + + + void BlockingServerTCPTransportCodec::registerChannel( + pvAccessID sid, + ServerChannel::shared_pointer const & channel) { + + Lock lock(_channelsMutex); + _channels[sid] = channel; + + } + + + void BlockingServerTCPTransportCodec::unregisterChannel(pvAccessID sid) { + + Lock lock(_channelsMutex); + _channels.erase(sid); + } + + + ServerChannel::shared_pointer + BlockingServerTCPTransportCodec::getChannel(pvAccessID sid) { + + Lock lock(_channelsMutex); + + std::map::iterator it = + _channels.find(sid); + + if(it!=_channels.end()) return it->second; + + return ServerChannel::shared_pointer(); + } + + + int BlockingServerTCPTransportCodec::getChannelCount() { + + Lock lock(_channelsMutex); + return static_cast(_channels.size()); + } + + + void BlockingServerTCPTransportCodec::send(ByteBuffer* buffer, + TransportSendControl* control) { + + // + // set byte order control message + // + + ensureBuffer(PVA_MESSAGE_HEADER_SIZE); + buffer->putByte(PVA_MAGIC); + buffer->putByte(PVA_VERSION); + buffer->putByte( + 0x01 | ((EPICS_BYTE_ORDER == EPICS_ENDIAN_BIG) + ? 0x80 : 0x00)); // control + big endian + buffer->putByte(2); // set byte order + buffer->putInt(0); + + + // + // send verification message + // + control->startMessage(CMD_CONNECTION_VALIDATION, 2*sizeof(int32)); + + // receive buffer size + buffer->putInt(static_cast(getReceiveBufferSize())); + + // socket receive buffer size + buffer->putInt(static_cast(getSocketReceiveBufferSize())); + + // send immediately + control->flush(true); + } + + void BlockingServerTCPTransportCodec::destroyAllChannels() { + Lock lock(_channelsMutex); + if(_channels.size()==0) return; + + if (IS_LOGGABLE(logLevelDebug)) + { + char ipAddrStr[64]; + ipAddrToDottedIP(&_socketAddress.ia, ipAddrStr, sizeof(ipAddrStr)); + LOG( + logLevelDebug, + "Transport to %s still has %zd channel(s) active and closing...", + ipAddrStr, _channels.size()); + } + + std::map::iterator it = _channels.begin(); + for(; it!=_channels.end(); it++) + it->second->destroy(); + + _channels.clear(); + } + + void BlockingServerTCPTransportCodec::internalClose(bool force) { + Transport::shared_pointer thisSharedPtr = shared_from_this(); + BlockingTCPTransportCodec::internalClose(force); + destroyAllChannels(); + } + + + + + + + + BlockingClientTCPTransportCodec::BlockingClientTCPTransportCodec( + Context::shared_pointer const & context, + SOCKET channel, + std::auto_ptr& responseHandler, + int32_t sendBufferSize, + int32_t receiveBufferSize, + TransportClient::shared_pointer const & client, + epics::pvData::int8 remoteTransportRevision, + float beaconInterval, + int16_t priority ) : + BlockingTCPTransportCodec(context, channel, responseHandler, + sendBufferSize, receiveBufferSize, priority), + _connectionTimeout(beaconInterval*1000), + _unresponsiveTransport(false), + _verifyOrEcho(true), + _verified(false) + { + // initialize owners list, send queue + acquire(client); + + // use immediate for clients + //setFlushStrategy(DELAYED); + + // setup connection timeout timer (watchdog) - moved to start() method + epicsTimeGetCurrent(&_aliveTimestamp); + } + + void BlockingClientTCPTransportCodec::start() + { + TimerCallbackPtr tcb = std::tr1::dynamic_pointer_cast(shared_from_this()); + _context->getTimer()->schedulePeriodic(tcb, _connectionTimeout, _connectionTimeout); + BlockingTCPTransportCodec::start(); + } + + BlockingClientTCPTransportCodec::~BlockingClientTCPTransportCodec() { + } + + + + + + + + + + void BlockingClientTCPTransportCodec::callback() { + epicsTimeStamp currentTime; + epicsTimeGetCurrent(¤tTime); + + _mutex.lock(); + // no exception expected here + double diff = epicsTimeDiffInSeconds(¤tTime, &_aliveTimestamp); + _mutex.unlock(); + + if(diff>2*_connectionTimeout) { + unresponsiveTransport(); + } + // use some k (3/4) to handle "jitter" + else if(diff>=((3*_connectionTimeout)/4)) { + // send echo + TransportSender::shared_pointer transportSender = std::tr1::dynamic_pointer_cast(shared_from_this()); + enqueueSendRequest(transportSender); + } + } + +#define EXCEPTION_GUARD(code) try { code; } \ + catch (std::exception &e) { LOG(logLevelError, "Unhandled exception caught from code at %s:%d: %s", __FILE__, __LINE__, e.what()); } \ + catch (...) { LOG(logLevelError, "Unhandled exception caught from code at %s:%d.", __FILE__, __LINE__); } + + void BlockingClientTCPTransportCodec::unresponsiveTransport() { + Lock lock(_mutex); + if(!_unresponsiveTransport) { + _unresponsiveTransport = true; + + TransportClientMap_t::iterator it = _owners.begin(); + for(; it!=_owners.end(); it++) { + TransportClient::shared_pointer client = it->second.lock(); + if (client) + { + EXCEPTION_GUARD(client->transportUnresponsive()); + } + } + } + } + + bool BlockingClientTCPTransportCodec::acquire(TransportClient::shared_pointer const & client) { + Lock lock(_mutex); + if(isClosed()) return false; + + if (IS_LOGGABLE(logLevelDebug)) + { + char ipAddrStr[48]; + ipAddrToDottedIP(&_socketAddress.ia, ipAddrStr, sizeof(ipAddrStr)); + LOG(logLevelDebug, "Acquiring transport to %s.", ipAddrStr); + } + + _owners[client->getID()] = TransportClient::weak_pointer(client); + //_owners.insert(TransportClient::weak_pointer(client)); + + return true; + } + + // _mutex is held when this method is called + void BlockingClientTCPTransportCodec::internalClose(bool forced) { + BlockingTCPTransportCodec::internalClose(forced); + + TimerCallbackPtr tcb = std::tr1::dynamic_pointer_cast(shared_from_this()); + _context->getTimer()->cancel(tcb); + } + + void BlockingClientTCPTransportCodec::internalPostClose(bool forced) { + BlockingTCPTransportCodec::internalPostClose(forced); + + // _owners cannot change when transport is closed + closedNotifyClients(); + } + + /** + * Notifies clients about disconnect. + */ + void BlockingClientTCPTransportCodec::closedNotifyClients() { + + // check if still acquired + size_t refs = _owners.size(); + if(refs>0) { + + if (IS_LOGGABLE(logLevelDebug)) + { + char ipAddrStr[48]; + ipAddrToDottedIP(&_socketAddress.ia, ipAddrStr, sizeof(ipAddrStr)); + LOG( + logLevelDebug, + "Transport to %s still has %d client(s) active and closing...", + ipAddrStr, refs); + } + + TransportClientMap_t::iterator it = _owners.begin(); + for(; it!=_owners.end(); it++) { + TransportClient::shared_pointer client = it->second.lock(); + if (client) + { + EXCEPTION_GUARD(client->transportClosed()); + } + } + + } + + _owners.clear(); + } + + //void BlockingClientTCPTransportCodec::release(TransportClient::shared_pointer const & client) { + void BlockingClientTCPTransportCodec::release(pvAccessID clientID) { + Lock lock(_mutex); + if(isClosed()) return; + + if (IS_LOGGABLE(logLevelDebug)) + { + char ipAddrStr[48]; + ipAddrToDottedIP(&_socketAddress.ia, ipAddrStr, sizeof(ipAddrStr)); + LOG(logLevelDebug, "Releasing transport to %s.", ipAddrStr); + } + + _owners.erase(clientID); + //_owners.erase(TransportClient::weak_pointer(client)); + + // not used anymore, close it + // TODO consider delayed destruction (can improve performance!!!) + if(_owners.size()==0) close(); // TODO close(false) + } + + void BlockingClientTCPTransportCodec::aliveNotification() { + Lock guard(_mutex); + epicsTimeGetCurrent(&_aliveTimestamp); + if(_unresponsiveTransport) responsiveTransport(); + } + + bool BlockingClientTCPTransportCodec::verify(epics::pvData::int32 timeoutMs) { + return _verifiedEvent.wait(timeoutMs/1000.0); + } + + void BlockingClientTCPTransportCodec::verified() { + epics::pvData::Lock lock(_verifiedMutex); + _verified = true; + _verifiedEvent.signal(); + } + + void BlockingClientTCPTransportCodec::responsiveTransport() { + Lock lock(_mutex); + if(_unresponsiveTransport) { + _unresponsiveTransport = false; + + Transport::shared_pointer thisSharedPtr = shared_from_this(); + TransportClientMap_t::iterator it = _owners.begin(); + for(; it!=_owners.end(); it++) { + TransportClient::shared_pointer client = it->second.lock(); + if (client) + { + EXCEPTION_GUARD(client->transportResponsive(thisSharedPtr)); + } + } + } + } + + void BlockingClientTCPTransportCodec::changedTransport() { + _outgoingIR.reset(); + + Lock lock(_mutex); + TransportClientMap_t::iterator it = _owners.begin(); + for(; it!=_owners.end(); it++) { + TransportClient::shared_pointer client = it->second.lock(); + if (client) + { + EXCEPTION_GUARD(client->transportChanged()); + } + } + } + + void BlockingClientTCPTransportCodec::send(ByteBuffer* buffer, + TransportSendControl* control) { + if(_verifyOrEcho) { + /* + * send verification response message + */ + + control->startMessage(CMD_CONNECTION_VALIDATION, 2*sizeof(int32)+sizeof(int16)); + + // receive buffer size + buffer->putInt(static_cast(getReceiveBufferSize())); + + // socket receive buffer size + buffer->putInt(static_cast(getSocketReceiveBufferSize())); + + // connection priority + buffer->putShort(getPriority()); + + // send immediately + control->flush(true); + + _verifyOrEcho = false; + } + else { + control->startMessage(CMD_ECHO, 0); + // send immediately + control->flush(true); + } + + } + + + } + } +} diff --git a/pvAccessApp/remote/codec.h b/pvAccessApp/remote/codec.h new file mode 100644 index 0000000..ce4a5d3 --- /dev/null +++ b/pvAccessApp/remote/codec.h @@ -0,0 +1,800 @@ +/** +* 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. +*/ + +#ifndef CODEC_H_ +#define CODEC_H_ + +#include +#include +#include + +#ifdef epicsExportSharedSymbols +# define abstractCodecEpicsExportSharedSymbols +# undef epicsExportSharedSymbols +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef abstractCodecEpicsExportSharedSymbols +# define epicsExportSharedSymbols +# undef abstractCodecEpicsExportSharedSymbols +#endif + +#include +#include +#include +#include +#include +#include + +namespace epics { + namespace pvAccess { + namespace detail { + + // TODO replace mutex with atomic (CAS) operations + template + class AtomicValue + { + public: + AtomicValue(): _value(0) {}; + + T getAndSet(T value) + { + mutex.lock(); + T tmp = _value; _value = value; + mutex.unlock(); + return tmp; + } + + T get() { mutex.lock(); T tmp = _value; mutex.unlock(); return tmp; } + + private: + T _value; + epics::pvData::Mutex mutex; + }; + + + template + class queue { + public: + + queue(void) { } + //TODO + /*queue(queue const &T) = delete; + queue(queue &&T) = delete; + queue& operator=(const queue &T) = delete; + */ + ~queue(void) + { + } + + + bool empty(void) + { + epics::pvData::Lock lock(_queueMutex); + return _queue.empty(); + } + + void clean() + { + epics::pvData::Lock lock(_queueMutex); + _queue.clear(); + } + + + void wakeup() + { + if (!_wakeup.getAndSet(true)) + { + _queueEvent.signal(); + } + } + + + void put(T const & elem) + { + { + epics::pvData::Lock lock(_queueMutex); + _queue.push_back(elem); + } + + _queueEvent.signal(); + } + + + T take(int timeOut) + { + while (true) + { + + bool isEmpty = empty(); + + if (isEmpty) + { + + if (timeOut < 0) { + return T(); + } + + while (isEmpty) + { + + if (timeOut == 0) { + _queueEvent.wait(); + } + else { + _queueEvent.wait(timeOut); + } + + isEmpty = empty(); + if (isEmpty) + { + if (timeOut > 0) { // TODO spurious wakeup, but not critical + return T(); + } + else // if (timeout == 0) cannot be negative + { + if (_wakeup.getAndSet(false)) { + return T(); + } + } + } + } + } + else + { + epics::pvData::Lock lock(_queueMutex); + T sender = _queue.front(); + _queue.pop_front(); + return sender; + } + } + } + + private: + + std::deque _queue; + epics::pvData::Event _queueEvent; + epics::pvData::Mutex _queueMutex; + AtomicValue _wakeup; + epics::pvData::Mutex _stdMutex; + }; + + + class io_exception: public std::runtime_error { + public: + explicit io_exception(const std::string &s): std::runtime_error(s) {} + }; + + + class invalid_data_stream_exception: public std::runtime_error { + public: + explicit invalid_data_stream_exception( + const std::string &s): std::runtime_error(s) {} + }; + + + class connection_closed_exception: public std::runtime_error { + public: + explicit connection_closed_exception(const std::string &s): std::runtime_error(s) {} + }; + + + enum ReadMode { NORMAL, SPLIT, SEGMENTED }; + + enum WriteMode { PROCESS_SEND_QUEUE, WAIT_FOR_READY_SIGNAL }; + + + class AbstractCodec : + public TransportSendControl, + public Transport + { + public: + + static const std::size_t MAX_MESSAGE_PROCESS; + static const std::size_t MAX_MESSAGE_SEND; + static const std::size_t MAX_ENSURE_SIZE; + static const std::size_t MAX_ENSURE_DATA_SIZE; + static const std::size_t MAX_ENSURE_BUFFER_SIZE; + static const std::size_t MAX_ENSURE_DATA_BUFFER_SIZE; + + AbstractCodec( + std::tr1::shared_ptr const & receiveBuffer, + std::tr1::shared_ptr const & sendBuffer, + int32_t socketSendBufferSize, + bool blockingProcessQueue); + + virtual void processControlMessage() = 0; + virtual void processApplicationMessage() = 0; + virtual const osiSockAddr* getLastReadBufferSocketAddress() = 0; + virtual void invalidDataStreamHandler() = 0; + virtual void readPollOne()=0; + virtual void writePollOne() = 0; + virtual void scheduleSend() = 0; + virtual void sendCompleted() = 0; + virtual bool terminated() = 0; + virtual int write(epics::pvData::ByteBuffer* src) = 0; + virtual int read(epics::pvData::ByteBuffer* dst) = 0; + virtual bool isOpen() = 0; + virtual void close() = 0; + + + virtual ~AbstractCodec() + { + } + + void alignBuffer(std::size_t alignment); + void ensureData(std::size_t size); + void alignData(std::size_t alignment); + void startMessage( + epics::pvData::int8 command, + std::size_t ensureCapacity); + void putControlMessage( + epics::pvData::int8 command, + epics::pvData::int32 data); + void endMessage(); + void ensureBuffer(std::size_t size); + void flushSerializeBuffer(); + void flush(bool lastMessageCompleted); + void processWrite(); + void processRead(); + void processSendQueue(); + void clearSendQueue(); + void enqueueSendRequest(TransportSender::shared_pointer const & sender); + void enqueueSendRequest(TransportSender::shared_pointer const & sender, + std::size_t requiredBufferSize); + void setSenderThread(); + void setRecipient(osiSockAddr const & sendTo); + void setByteOrder(int byteOrder); + + static std::size_t alignedValue(std::size_t value, std::size_t alignment); + + protected: + + virtual void sendBufferFull(int tries) = 0; + void send(epics::pvData::ByteBuffer *buffer); + + + ReadMode _readMode; + int8_t _version; + int8_t _flags; + int8_t _command; + int32_t _payloadSize; // TODO why not size_t? + epics::pvData::int32 _remoteTransportSocketReceiveBufferSize; + int64_t _totalBytesSent; + bool _blockingProcessQueue; + //TODO initialize union + osiSockAddr _sendTo; + epicsThreadId _senderThread; + WriteMode _writeMode; + bool _writeOpReady; + bool _lowLatency; + + std::tr1::shared_ptr _socketBuffer; + std::tr1::shared_ptr _sendBuffer; + + queue _sendQueue; + + private: + + void processHeader(); + void processReadNormal(); + void postProcessApplicationMessage(); + void processReadSegmented(); + bool readToBuffer(std::size_t requiredBytes, bool persistent); + void endMessage(bool hasMoreSegments); + void processSender( + epics::pvAccess::TransportSender::shared_pointer const & sender); + + std::size_t _storedPayloadSize; + std::size_t _storedPosition; + std::size_t _storedLimit; + std::size_t _startPosition; + + std::size_t _maxSendPayloadSize; + std::size_t _lastMessageStartPosition; + std::size_t _lastSegmentedMessageType; + int8_t _lastSegmentedMessageCommand; + std::size_t _nextMessagePayloadOffset; + + epics::pvData::int8 _byteOrderFlag; + int32_t _socketSendBufferSize; + }; + + + class BlockingAbstractCodec: + public AbstractCodec, + public std::tr1::enable_shared_from_this + { + + public: + + POINTER_DEFINITIONS(BlockingAbstractCodec); + + BlockingAbstractCodec( + std::tr1::shared_ptr const & receiveBuffer, + std::tr1::shared_ptr const & sendBuffer, + int32_t socketSendBufferSize): + AbstractCodec(receiveBuffer, sendBuffer, socketSendBufferSize, true), + _readThread(0), _sendThread(0) { _isOpen.getAndSet(true);} + + void readPollOne(); + void writePollOne(); + void scheduleSend() {} + void sendCompleted() {} + void close(); + bool terminated(); + bool isOpen(); + void start(); + + static void receiveThread(void* param); + static void sendThread(void* param); + + protected: + void sendBufferFull(int tries); + virtual void internalDestroy() = 0; + + /** + * Called to any resources just before closing transport + * @param[in] force flag indicating if forced (e.g. forced + * disconnect) is required + */ + virtual void internalClose(bool force); + + /** + * Called to any resources just after closing transport and without any locks held on transport + * @param[in] force flag indicating if forced (e.g. forced + * disconnect) is required + */ + virtual void internalPostClose(bool force); + + private: + AtomicValue _isOpen; + volatile epicsThreadId _readThread; + volatile epicsThreadId _sendThread; + epics::pvData::Event _shutdownEvent; + }; + + + class BlockingSocketAbstractCodec: + public BlockingAbstractCodec + { + + public: + + BlockingSocketAbstractCodec( + SOCKET channel, + int32_t sendBufferSize, + int32_t receiveBufferSize); + + int read(epics::pvData::ByteBuffer* dst); + int write(epics::pvData::ByteBuffer* src); + const osiSockAddr* getLastReadBufferSocketAddress() { return &_socketAddress; } + void invalidDataStreamHandler(); + std::size_t getSocketReceiveBufferSize() const; + + protected: + + void internalDestroy(); + + SOCKET _channel; + osiSockAddr _socketAddress; + }; + + + class BlockingTCPTransportCodec : + public BlockingSocketAbstractCodec + + { + + public: + + epics::pvData::String getType() const { + return epics::pvData::String("TCP"); + } + + + void internalDestroy() { + BlockingSocketAbstractCodec::internalDestroy(); + Transport::shared_pointer thisSharedPtr = this->shared_from_this(); + _context->getTransportRegistry()->remove(thisSharedPtr); + } + + + void changedTransport() {} + + + void processControlMessage() { + if (_command == 2) + { + // check 7-th bit + setByteOrder(_flags < 0 ? EPICS_ENDIAN_BIG : EPICS_ENDIAN_LITTLE); + } + } + + + void processApplicationMessage() { + _responseHandler->handleResponse(&_socketAddress, shared_from_this(), + _version, _command, _payloadSize, _socketBuffer.get()); + } + + + const osiSockAddr* getRemoteAddress() const { + return &_socketAddress; + } + + + epics::pvData::int8 getRevision() const { + return PVA_PROTOCOL_REVISION; + } + + + std::size_t getReceiveBufferSize() const { + return _socketBuffer->getSize(); + } + + + epics::pvData::int16 getPriority() const { + return _priority; + } + + + void setRemoteRevision(epics::pvData::int8 revision) { + _remoteTransportRevision = revision; + } + + + void setRemoteTransportReceiveBufferSize( + std::size_t remoteTransportReceiveBufferSize) { + _remoteTransportReceiveBufferSize = remoteTransportReceiveBufferSize; + } + + + void setRemoteTransportSocketReceiveBufferSize( + std::size_t socketReceiveBufferSize) { + _remoteTransportSocketReceiveBufferSize = socketReceiveBufferSize; + } + + + std::tr1::shared_ptr + cachedDeserialize(epics::pvData::ByteBuffer* buffer) + { + return _incomingIR.deserialize(buffer, this); + } + + + void cachedSerialize( + const std::tr1::shared_ptr& field, + epics::pvData::ByteBuffer* buffer) + { + _outgoingIR.serialize(field, buffer, this); + } + + + bool directSerialize( + epics::pvData::ByteBuffer * /*existingBuffer*/, + const char* /*toSerialize*/, + std::size_t /*elementCount*/, std::size_t /*elementSize*/) + { + // TODO !!!! + return false; + } + + + bool directDeserialize(epics::pvData::ByteBuffer * /*existingBuffer*/, + char* /*deserializeTo*/, + std::size_t /*elementCount*/, std::size_t /*elementSize*/) { + // TODO !!! + return false; + } + + + void flushSendQueue() { }; + + + bool isClosed() { + return !isOpen(); + } + + + void activate() { + Transport::shared_pointer thisSharedPtr = shared_from_this(); + _context->getTransportRegistry()->put(thisSharedPtr); + + start(); + } + + protected: + + BlockingTCPTransportCodec( + Context::shared_pointer const & context, + SOCKET channel, + std::auto_ptr& responseHandler, + int32_t sendBufferSize, + int32_t receiveBufferSize, + epics::pvData::int16 priority + ): + BlockingSocketAbstractCodec(channel, sendBufferSize, receiveBufferSize), + _context(context), _responseHandler(responseHandler), + _remoteTransportReceiveBufferSize(MAX_TCP_RECV), + _remoteTransportRevision(0), _priority(priority) + { + } + + Context::shared_pointer _context; + + IntrospectionRegistry _incomingIR; + IntrospectionRegistry _outgoingIR; + + private: + + std::auto_ptr _responseHandler; + size_t _remoteTransportReceiveBufferSize; + epics::pvData::int8 _remoteTransportRevision; + epics::pvData::int16 _priority; + }; + + + class BlockingServerTCPTransportCodec : + public BlockingTCPTransportCodec, + public ChannelHostingTransport, + public TransportSender { + + public: + POINTER_DEFINITIONS(BlockingServerTCPTransportCodec); + + protected: + BlockingServerTCPTransportCodec( + Context::shared_pointer const & context, + SOCKET channel, + std::auto_ptr& responseHandler, + int32_t sendBufferSize, + int32_t receiveBufferSize ); + + public: + static shared_pointer create( + Context::shared_pointer const & context, + SOCKET channel, + std::auto_ptr& responseHandler, + int sendBufferSize, + int receiveBufferSize) + { + shared_pointer thisPointer( + new BlockingServerTCPTransportCodec( + context, channel, responseHandler, + sendBufferSize, receiveBufferSize) + ); + thisPointer->activate(); + return thisPointer; + } + + public: + + bool acquire(std::tr1::shared_ptr const & client) + { + return false; + } + + void release(pvAccessID /*clientId*/) {} + + pvAccessID preallocateChannelSID(); + + void depreallocateChannelSID(pvAccessID /*sid*/) { + // noop + } + + void registerChannel( + pvAccessID sid, + ServerChannel::shared_pointer const & channel); + + void unregisterChannel(pvAccessID sid); + + ServerChannel::shared_pointer getChannel(pvAccessID sid); + + int getChannelCount(); + + epics::pvData::PVField::shared_pointer getSecurityToken() { + return epics::pvData::PVField::shared_pointer(); + } + + void lock() { + // noop + } + + void unlock() { + // noop + } + + bool verify(epics::pvData::int32 timeoutMs) { + TransportSender::shared_pointer transportSender = + std::tr1::dynamic_pointer_cast(shared_from_this()); + enqueueSendRequest(transportSender); + verified(); + return true; + } + + void verified() { + } + + void aliveNotification() { + // noop on server-side + } + + void send(epics::pvData::ByteBuffer* buffer, + TransportSendControl* control); + + virtual ~BlockingServerTCPTransportCodec(); + + protected: + + void destroyAllChannels(); + virtual void internalClose(bool force); + + private: + + /** + * Last SID cache. + */ + pvAccessID _lastChannelSID; + + /** + * Channel table (SID -> channel mapping). + */ + std::map _channels; + + epics::pvData::Mutex _channelsMutex; + + }; + + class BlockingClientTCPTransportCodec : + public BlockingTCPTransportCodec, + public TransportSender, + public epics::pvData::TimerCallback { + + public: + POINTER_DEFINITIONS(BlockingClientTCPTransportCodec); + + protected: + BlockingClientTCPTransportCodec( + Context::shared_pointer const & context, + SOCKET channel, + std::auto_ptr& responseHandler, + int32_t sendBufferSize, + int32_t receiveBufferSize, + TransportClient::shared_pointer const & client, + epics::pvData::int8 remoteTransportRevision, + float beaconInterval, + int16_t priority); + + public: + static shared_pointer create( + Context::shared_pointer const & context, + SOCKET channel, + std::auto_ptr& responseHandler, + int32_t sendBufferSize, + int32_t receiveBufferSize, + TransportClient::shared_pointer const & client, + int8_t remoteTransportRevision, + float beaconInterval, + int16_t priority ) + { + shared_pointer thisPointer( + new BlockingClientTCPTransportCodec( + context, channel, responseHandler, + sendBufferSize, receiveBufferSize, + client, remoteTransportRevision, + beaconInterval, priority) + ); + thisPointer->activate(); + return thisPointer; + } + + public: + + void start(); + + virtual ~BlockingClientTCPTransportCodec(); + + virtual void timerStopped() { + // noop + } + + virtual void callback(); + + bool acquire(TransportClient::shared_pointer const & client); + + void release(pvAccessID clientId); + + void changedTransport(); + + void lock() { + // noop + } + + void unlock() { + // noop + } + + bool verify(epics::pvData::int32 timeoutMs); + + void verified(); + + void aliveNotification(); + + void send(epics::pvData::ByteBuffer* buffer, + TransportSendControl* control); + + protected: + + virtual void internalClose(bool force); + virtual void internalPostClose(bool force); + + private: + + /** + * Owners (users) of the transport. + */ + // TODO consider using TR1 hash map + typedef std::map TransportClientMap_t; + TransportClientMap_t _owners; + + /** + * Connection timeout (no-traffic) flag. + */ + double _connectionTimeout; + + /** + * Unresponsive transport flag. + */ + bool _unresponsiveTransport; + + /** + * Timestamp of last "live" event on this transport. + */ + epicsTimeStamp _aliveTimestamp; + + bool _verifyOrEcho; + + /** + * Unresponsive transport notify. + */ + void unresponsiveTransport(); + + /** + * Notifies clients about disconnect. + */ + void closedNotifyClients(); + + /** + * Responsive transport notify. + */ + void responsiveTransport(); + + + epics::pvData::Mutex _mutex; + + bool _verified; + epics::pvData::Mutex _verifiedMutex; + epics::pvData::Event _verifiedEvent; + + }; + + } + } +} + +#endif /* CODEC_H_ */ diff --git a/pvAccessApp/remoteClient/clientContextImpl.cpp b/pvAccessApp/remoteClient/clientContextImpl.cpp index ddd682c..9a162cb 100644 --- a/pvAccessApp/remoteClient/clientContextImpl.cpp +++ b/pvAccessApp/remoteClient/clientContextImpl.cpp @@ -4117,7 +4117,7 @@ TODO auto_ptr handler(new ClientResponseHandler(shared_from_this())); Transport::shared_pointer t = m_connector->connect(client, handler, *serverAddress, minorRevision, priority); // TODO !!! - static_pointer_cast(t)->setFlushStrategy(m_flushStrategy); + //static_pointer_cast(t)->setFlushStrategy(m_flushStrategy); return t; } catch (...) diff --git a/pvAccessApp/server/serverContext.cpp b/pvAccessApp/server/serverContext.cpp index 832067b..d16f9bb 100644 --- a/pvAccessApp/server/serverContext.cpp +++ b/pvAccessApp/server/serverContext.cpp @@ -204,15 +204,15 @@ void ServerContextImpl::internalInitialize() _timer.reset(new Timer("pvAccess-server timer", lowerPriority)); _transportRegistry.reset(new TransportRegistry()); - // setup broadcast UDP transport - initializeBroadcastTransport(); - ServerContextImpl::shared_pointer thisServerContext = shared_from_this(); _acceptor.reset(new BlockingTCPAcceptor(thisServerContext, thisServerContext, _serverPort, _receiveBufferSize)); _serverPort = ntohs(_acceptor->getBindAddress()->ia.sin_port); - _beaconEmitter.reset(new BeaconEmitter(_broadcastTransport, thisServerContext)); + // setup broadcast UDP transport + initializeBroadcastTransport(); + + _beaconEmitter.reset(new BeaconEmitter(_broadcastTransport, thisServerContext)); } void ServerContextImpl::initializeBroadcastTransport() diff --git a/pvAccessCPP.files b/pvAccessCPP.files index 6fbd1d9..a0e4c1e 100644 --- a/pvAccessCPP.files +++ b/pvAccessCPP.files @@ -23,12 +23,9 @@ pvAccessApp/mb/pvAccessMB.h pvAccessApp/remote/abstractResponseHandler.cpp pvAccessApp/remote/beaconHandler.cpp pvAccessApp/remote/beaconHandler.h -pvAccessApp/remote/blockingClientTCPTransport.cpp -pvAccessApp/remote/blockingServerTCPTransport.cpp pvAccessApp/remote/blockingTCP.h pvAccessApp/remote/blockingTCPAcceptor.cpp pvAccessApp/remote/blockingTCPConnector.cpp -pvAccessApp/remote/blockingTCPTransport.cpp pvAccessApp/remote/blockingUDP.h pvAccessApp/remote/blockingUDPConnector.cpp pvAccessApp/remote/blockingUDPTransport.cpp @@ -40,6 +37,8 @@ pvAccessApp/remote/simpleChannelSearchManagerImpl.cpp pvAccessApp/remote/simpleChannelSearchManagerImpl.h pvAccessApp/remote/transportRegistry.cpp pvAccessApp/remote/transportRegistry.h +pvAccessApp/remote/codec.cpp +pvAccessApp/remote/codec.h pvAccessApp/remoteClient/clientContextImpl.cpp pvAccessApp/remoteClient/clientContextImpl.h pvAccessApp/remoteClient/clientContextImpl.h.orig @@ -101,6 +100,8 @@ testApp/remote/testNTImage.cpp testApp/remote/testRemoteClientImpl.cpp testApp/remote/testServer.cpp testApp/remote/testServerContext.cpp +testApp/remote/testChannelAccess.cpp +testApp/remote/testCodec.cpp testApp/utils/testAtomicBoolean.cpp testApp/utils/configurationTest.cpp testApp/utils/testHexDump.cpp diff --git a/pvAccessCPP.includes b/pvAccessCPP.includes index 471a178..d8138cf 100644 --- a/pvAccessCPP.includes +++ b/pvAccessCPP.includes @@ -7,4 +7,6 @@ /home/msekoranja/epicsV4/pvAccessCPP/pvAccessApp/rpcService /home/msekoranja/epicsV4/pvAccessCPP/pvAccessApp/server /home/msekoranja/epicsV4/pvAccessCPP/pvAccessApp/utils -/home/msekoranja/epicsV4/pvAccessCPP/testApp/remote \ No newline at end of file +/home/msekoranja/epicsV4/pvAccessCPP/testApp/remote +/home/msekoranja/epicsV4/pvAccessCPP/testApp/client +/home/msekoranja/epicsV4/pvAccessCPP/testApp/utils diff --git a/testApp/remote/Makefile b/testApp/remote/Makefile index fa415d2..e448fa3 100644 --- a/testApp/remote/Makefile +++ b/testApp/remote/Makefile @@ -51,6 +51,12 @@ testChannelAccess_SRCS = testChannelAccess channelAccessIFTest testChannelAccess_LIBS += pvAccess pvData pvMB Com TESTS += testChannelAccess +TESTPROD_HOST += testCodec +testCodec_SRCS = testCodec +testCodec_LIBS += pvData pvAccess pvMB Com +TESTS += testCodec + + PROD_HOST += pvget pvget_SRCS += pvget.cpp pvget_LIBS += pvAccess pvData pvMB Com diff --git a/testApp/remote/channelAccessIFTest.cpp b/testApp/remote/channelAccessIFTest.cpp index d9f8202..cdd1307 100755 --- a/testApp/remote/channelAccessIFTest.cpp +++ b/testApp/remote/channelAccessIFTest.cpp @@ -432,7 +432,6 @@ void ChannelAccessIFTest::test_channel() { testOk(!channel->isConnected(), "%s: yet again destroyed channel should not be connected ", CURRENT_FUNCTION); testOk(channel->getConnectionState() == Channel::DESTROYED , "%s: yet again destroyed channel connection state DESTROYED ", CURRENT_FUNCTION); - } diff --git a/testApp/remote/testChannelAccess.cpp b/testApp/remote/testChannelAccess.cpp index 535e1df..0f1021d 100755 --- a/testApp/remote/testChannelAccess.cpp +++ b/testApp/remote/testChannelAccess.cpp @@ -88,6 +88,7 @@ class ChannelAccessIFRemoteTest: public ChannelAccessIFTest { MAIN(testChannelProvider) { + SET_LOG_LEVEL(logLevelError); ChannelAccessIFRemoteTest caRemoteTest; return caRemoteTest.runAllTest(); } diff --git a/testApp/remote/testCodec.cpp b/testApp/remote/testCodec.cpp new file mode 100644 index 0000000..8adb5c3 --- /dev/null +++ b/testApp/remote/testCodec.cpp @@ -0,0 +1,3203 @@ +/* +* testCodec.cpp +*/ + +#ifdef _WIN32 +#define NOMINMAX +#endif + + +#include +#include +#include +#include + +#include +#include + +using namespace epics::pvData; +using namespace epics::pvAccess::detail; + +namespace epics { + + namespace pvAccess { + + class PVAMessage { + + public: + + PVAMessage(int8_t version, + int8_t flags, + int8_t command, + int32_t payloadSize) { + _version = version; + _flags = flags; + _command = command; + _payloadSize = payloadSize; + } + + int8_t _version; + int8_t _flags; + int8_t _command; + int32_t _payloadSize; + std::tr1::shared_ptr _payload; + + //memberwise copy constructor/assigment operator + //provided by the compiler + }; + + + class ReadPollOneCallback { + public: + virtual void readPollOne() = 0; + }; + + + class WritePollOneCallback { + public: + virtual void writePollOne() = 0 ; + }; + + + class TestCodec: public AbstractCodec { + + public: + + TestCodec( + std::size_t receiveBufferSize, + std::size_t sendBufferSize, + bool blocking = false): + AbstractCodec( + std::tr1::shared_ptr(new ByteBuffer(receiveBufferSize)), + std::tr1::shared_ptr(new ByteBuffer(sendBufferSize)), + sendBufferSize/10, + blocking ), + _closedCount(0), + _invalidDataStreamCount(0), + _scheduleSendCount(0), + _sendCompletedCount(0), + _sendBufferFullCount(0), + _readPollOneCount(0), + _writePollOneCount(0), + _throwExceptionOnSend(false), + _readPayload(false), + _disconnected(false), + _forcePayloadRead(-1), + _readBuffer(new ByteBuffer(receiveBufferSize)), + _writeBuffer(sendBufferSize), + _dummyAddress() + { + } + + + void reset() + { + _closedCount = 0; + _invalidDataStreamCount = 0; + _scheduleSendCount = 0; + _sendCompletedCount = 0; + _sendBufferFullCount = 0; + _readPollOneCount = 0; + _writePollOneCount = 0; + _readBuffer->clear(); + _writeBuffer.clear(); + _receivedAppMessages.clear(); + _receivedControlMessages.clear(); + } + + + int read(ByteBuffer *buffer) { + + if (_disconnected) + return -1; + + std::size_t startPos = _readBuffer->getPosition(); + //buffer.put(readBuffer); + //while (buffer.hasRemaining() && readBuffer.hasRemaining()) + // buffer.put(readBuffer.get()); + + std::size_t bufferRemaining = buffer->getRemaining(); + std::size_t readBufferRemaining = + _readBuffer->getRemaining(); + + if (bufferRemaining >= readBufferRemaining) { + + while(_readBuffer->getRemaining() > 0) { + buffer->putByte(_readBuffer->getByte()); + } + + } + else + { + // TODO this could be optimized + for (std::size_t i = 0; i < bufferRemaining; i++) { + buffer->putByte(_readBuffer->getByte()); + } + } + return _readBuffer->getPosition() - startPos; + } + + + int write(ByteBuffer *buffer) { + if (_disconnected) + return -1; // TODO: not by the JavaDoc API spec + + if (_throwExceptionOnSend) + throw io_exception("text IO exception"); + + // we could write remaining int8_ts, but for + //test this is enought + if (buffer->getRemaining() > _writeBuffer.getRemaining()) + return 0; + + std::size_t startPos = buffer->getPosition(); + + while(buffer->getRemaining() > 0) { + _writeBuffer.putByte(buffer->getByte()); + } + + return buffer->getPosition() - startPos; + } + + + void transferToReadBuffer() + { + flushSerializeBuffer(); + _writeBuffer.flip(); + + _readBuffer->clear(); + + while(_writeBuffer.getRemaining() > 0) { + _readBuffer->putByte(_writeBuffer.getByte()); + } + + _readBuffer->flip(); + + _writeBuffer.clear(); + } + + + void addToReadBuffer() + { + flushSerializeBuffer(); + _writeBuffer.flip(); + + while(_writeBuffer.getRemaining() > 0) { + _readBuffer->putByte(_writeBuffer.getByte()); + } + + _readBuffer->flip(); + + _writeBuffer.clear(); + } + + + void processControlMessage() { + // alignment check + if (_socketBuffer->getPosition() % PVA_ALIGNMENT != 0) + throw std::logic_error("message not aligned"); + + _receivedControlMessages.push_back( + PVAMessage(_version, _flags, _command, _payloadSize)); + } + + + void processApplicationMessage() { + // alignment check + if (_socketBuffer->getPosition() % PVA_ALIGNMENT != 0) + throw std::logic_error("message not aligned"); + + PVAMessage caMessage(_version, _flags, + _command, _payloadSize); + + if (_readPayload && _payloadSize > 0) + { + // no fragmentation supported by this implementation + std::size_t toRead = + _forcePayloadRead >= 0 + ? _forcePayloadRead : _payloadSize; + + caMessage._payload.reset(new ByteBuffer(toRead)); + while (toRead > 0) + { + std::size_t partitalRead = + std::min(toRead, MAX_ENSURE_DATA_SIZE); + ensureData(partitalRead); + std::size_t pos = caMessage._payload->getPosition(); + + + while(_socketBuffer->getRemaining() > 0) { + caMessage._payload->putByte(_socketBuffer->getByte()); + } + + std::size_t read = + caMessage._payload->getPosition() - pos; + + toRead -= read; + } + } + _receivedAppMessages.push_back(caMessage); + } + + + void readPollOne() { + _readPollOneCount++; + if (_readPollOneCallback.get() != 0) + _readPollOneCallback->readPollOne(); + } + + + void writePollOne() { + _writePollOneCount++; + if (_writePollOneCallback.get() != 0) + _writePollOneCallback->writePollOne(); + } + + + void endBlockedProcessSendQueue() { + //TODO not thread safe + _blockingProcessQueue = false; + _sendQueue.wakeup(); + } + + + void close() { _closedCount++; } + + bool isOpen() { return _closedCount == 0; } + + ReadMode getReadMode() { return _readMode; } + + WriteMode getWriteMode() { return _writeMode;} + + std::tr1::shared_ptr getSendBuffer() + { + return _sendBuffer; + } + + const osiSockAddr* getLastReadBufferSocketAddress() + { + return &_dummyAddress; + } + + void invalidDataStreamHandler() { _invalidDataStreamCount++; } + + void scheduleSend() { _scheduleSendCount++; } + + void sendCompleted() { _sendCompletedCount++; } + + bool terminated() { return false; } + + void cachedSerialize( + const std::tr1::shared_ptr& field, + ByteBuffer* buffer) {field->serialize(buffer, this); } + + bool acquire( + std::tr1::shared_ptr const & client) + { + return false; + } + + bool directSerialize( + ByteBuffer *existingBuffer, + const char* toSerialize, + std::size_t elementCount, + std::size_t elementSize) {return false; } + + bool directDeserialize( + ByteBuffer *existingBuffer, + char* deserializeTo, + std::size_t elementCount, + std::size_t elementSize) { return false; } + + std::tr1::shared_ptr + cachedDeserialize(ByteBuffer* buffer) + { + return std::tr1::shared_ptr(); + } + + void release(pvAccessID clientId) {} + + epics::pvData::String getType() const + { + return epics::pvData::String("TCP"); + } + + const osiSockAddr* getRemoteAddress() const { return 0; } + + epics::pvData::int8 getRevision() const + { + return PVA_PROTOCOL_REVISION; + } + + std::size_t getReceiveBufferSize() const { return 16384; } + + epics::pvData::int16 getPriority() const { return 0; } + + std::size_t getSocketReceiveBufferSize() const + { + return 16384; + } + + void setRemoteRevision(epics::pvData::int8 revision) {} + + void setRemoteTransportSocketReceiveBufferSize( + std::size_t socketReceiveBufferSize) {} + + void setRemoteTransportReceiveBufferSize( + std::size_t remoteTransportReceiveBufferSize) {} + + void changedTransport() {} + + void flushSendQueue() { }; + + bool verify(epics::pvData::int32 timeoutMs) { return true;} + + void verified() {} + + void aliveNotification() {} + + bool isClosed() { return false; } + + + std::size_t _closedCount; + std::size_t _invalidDataStreamCount; + std::size_t _scheduleSendCount; + std::size_t _sendCompletedCount; + std::size_t _sendBufferFullCount; + std::size_t _readPollOneCount; + std::size_t _writePollOneCount; + bool _throwExceptionOnSend; + bool _readPayload; + bool _disconnected; + int _forcePayloadRead; + + std::auto_ptr _readBuffer; + epics::pvData::ByteBuffer _writeBuffer; + + std::vector _receivedAppMessages; + std::vector _receivedControlMessages; + + std::auto_ptr _readPollOneCallback; + std::auto_ptr _writePollOneCallback; + + osiSockAddr _dummyAddress; + + protected: + + void sendBufferFull(int tries) { + _sendBufferFullCount++; + _writeOpReady = false; + _writeMode = WAIT_FOR_READY_SIGNAL; + this->writePollOne(); + _writeMode = PROCESS_SEND_QUEUE; + } + }; + + + class CodecTest { + + public: + + int runAllTest() { + testPlan(5882); + testHeaderProcess(); + testInvalidHeaderMagic(); + testInvalidHeaderSegmentedInNormal(); + testInvalidHeaderPayloadNotRead(); + testHeaderSplitRead(); + testNonEmptyPayload(); + testNormalAlignment(); + testSplitAlignment(); + testSegmentedMessage(); + //testSegmentedInvalidInBetweenFlagsMessage(); + testSegmentedMessageAlignment(); + testSegmentedSplitMessage(); + testStartMessage(); + testStartMessageNonEmptyPayload(); + testStartMessageNormalAlignment(); + testStartMessageSegmentedMessage(); + testStartMessageSegmentedMessageAlignment(); + testReadNormalConnectionLoss(); + testSegmentedSplitConnectionLoss(); + testSendConnectionLoss(); + testEnqueueSendRequest(); + testEnqueueSendDirectRequest(); + testSendException(); + testSendHugeMessagePartes(); + testRecipient(); + testClearSendQueue(); + testInvalidArguments(); + testDefaultModes(); + testEnqueueSendRequestExceptionThrown(); + testBlockingProcessQueueTest(); + return testDone(); + } + + virtual ~CodecTest() {} + + protected: + + static const std::size_t DEFAULT_BUFFER_SIZE = 10240; + + private: + + void testHeaderProcess() { + + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + + TestCodec codec(DEFAULT_BUFFER_SIZE,DEFAULT_BUFFER_SIZE); + + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0x01); + codec._readBuffer->put((int8_t)0x23); + codec._readBuffer->putInt(0x456789AB); + codec._readBuffer->flip(); + + + codec.processRead(); + + testOk(codec._invalidDataStreamCount == 0, + "%s: codec._invalidDataStreamCount == 0", + CURRENT_FUNCTION); + testOk(codec._closedCount == 0, + "%s: codec._closedCount == 0", CURRENT_FUNCTION); + testOk(codec._receivedControlMessages.size() == 1, + "%s: codec._receivedControlMessages.size() == 1 ", + CURRENT_FUNCTION); + testOk(codec._receivedAppMessages.size() == 0, + "%s: codec._receivedAppMessages.size() == 0", + CURRENT_FUNCTION); + + PVAMessage header = codec._receivedControlMessages[0]; + + testOk(header._version == PVA_VERSION, + "%s: header._version == PVA_VERSION", CURRENT_FUNCTION); + + testOk(codec._invalidDataStreamCount == 0, + "%s: codec._invalidDataStreamCount == 0", + CURRENT_FUNCTION); + testOk(header._flags == 0x01, + "%s: header._flags == 0x01", CURRENT_FUNCTION); + testOk(header._command == 0x23, + "%s: header._command == 0x23", CURRENT_FUNCTION); + testOk(header._payloadSize == 0x456789AB, + "%s: header._payloadSize == 0x456789AB", CURRENT_FUNCTION); + + codec.reset(); + + // two at the time, app and control + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0x00); + codec._readBuffer->put((int8_t)0x20); + codec._readBuffer->putInt(0x00000000); + + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0x81); + codec._readBuffer->put((int8_t)0xEE); + codec._readBuffer->putInt(0xDDCCBBAA); + codec._readBuffer->flip(); + + codec.processRead(); + + testOk(0 == codec._invalidDataStreamCount, + "%s: 0 == codec._invalidDataStreamCount", + CURRENT_FUNCTION); + testOk(0 == codec._closedCount, + "%s: 0 == codec._closedCount", CURRENT_FUNCTION); + testOk(1 == codec._receivedControlMessages.size(), + "%s: 1 == codec._receivedControlMessages.size()", + CURRENT_FUNCTION); + testOk(1 == codec._receivedAppMessages.size(), + "%s: 1 == codec._receivedAppMessages.size()", + CURRENT_FUNCTION); + + + // app, no payload + header = codec._receivedAppMessages[0]; + + testOk(header._version == PVA_VERSION, + "%s: header._version == PVA_VERSION", CURRENT_FUNCTION); + testOk(header._flags == (int8_t)0x00, + "%s: header._flags == 0x00", CURRENT_FUNCTION); + testOk(header._command == (int8_t)0x20, + "%s: header._command == 0x20", CURRENT_FUNCTION); + testOk(header._payloadSize == 0x00000000, + "%s: header._payloadSize == 0x00000000", CURRENT_FUNCTION); + + // control + header = codec._receivedControlMessages[0]; + + testOk(header._version == PVA_VERSION, + "%s: header._version == PVA_VERSION", CURRENT_FUNCTION); + testOk(header._flags == (int8_t)0x81, + "%s: header._flags == 0x81", CURRENT_FUNCTION); + testOk(header._command == (int8_t)0xEE, + "%s: header._command == 0xEE", CURRENT_FUNCTION); + testOk(header._payloadSize == (int32_t)0xDDCCBBAA, + "%s: header._payloadSize == 0xDDCCBBAA", + CURRENT_FUNCTION); + } + + + void testInvalidHeaderMagic() + { + + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + + TestCodec codec(DEFAULT_BUFFER_SIZE,DEFAULT_BUFFER_SIZE); + + codec._readBuffer->put((int8_t)00); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0x01); + codec._readBuffer->put((int8_t)0x23); + codec._readBuffer->putInt(0x456789AB); + codec._readBuffer->flip(); + + codec.processRead(); + + testOk(codec._invalidDataStreamCount == 1, + "%s: codec._invalidDataStreamCount == 1", + CURRENT_FUNCTION); + testOk(codec._closedCount == 0, + "%s: codec._closedCount == 0", CURRENT_FUNCTION); + testOk(codec._receivedControlMessages.size() == 0, + "%s: codec._receivedControlMessages.size() == 0 ", + CURRENT_FUNCTION); + testOk(codec._receivedAppMessages.size() == 0, + "%s: codec._receivedAppMessages.size() == 0", + CURRENT_FUNCTION); + } + + + void testInvalidHeaderSegmentedInNormal() + { + + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + + int8_t invalidFlagsValues[] = + {(int8_t)0x20, (int8_t)(0x30+0x80)}; + + std::size_t size=sizeof(invalidFlagsValues)/sizeof(int8_t); + + for (std::size_t i = 0; i < size; i++) + { + TestCodec codec(DEFAULT_BUFFER_SIZE,DEFAULT_BUFFER_SIZE); + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put(invalidFlagsValues[i]); + codec._readBuffer->put((int8_t)0x23); + codec._readBuffer->putInt(0); + codec._readBuffer->flip(); + + codec.processRead(); + + testOk(codec._invalidDataStreamCount == 1, + "%s: codec._invalidDataStreamCount == 1", + CURRENT_FUNCTION); + testOk(codec._closedCount == 0, + "%s: codec._closedCount == 0", CURRENT_FUNCTION); + testOk(codec._receivedControlMessages.size() == 0, + "%s: codec._receivedControlMessages.size() == 0 ", + CURRENT_FUNCTION); + testOk(codec._receivedAppMessages.size() == 0, + "%s: codec._receivedAppMessages.size() == 0", + CURRENT_FUNCTION); + } + } + + + void testInvalidHeaderPayloadNotRead() + { + + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + TestCodec codec(DEFAULT_BUFFER_SIZE,DEFAULT_BUFFER_SIZE); + + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0x80); + codec._readBuffer->put((int8_t)0x23); + codec._readBuffer->putInt(0x456789AB); + codec._readBuffer->flip(); + + codec.processRead(); + + testOk(codec._invalidDataStreamCount == 1, + "%s: codec._invalidDataStreamCount == 1", + CURRENT_FUNCTION); + testOk(codec._closedCount == 0, + "%s: codec._closedCount == 0", CURRENT_FUNCTION); + testOk(codec._receivedControlMessages.size() == 0, + "%s: codec._receivedControlMessages.size() == 0 ", + CURRENT_FUNCTION); + testOk(codec._receivedAppMessages.size() == 1, + "%s: codec._receivedAppMessages.size() == 1", + CURRENT_FUNCTION); + + } + + + void testHeaderSplitRead() + { + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + TestCodec codec(DEFAULT_BUFFER_SIZE,DEFAULT_BUFFER_SIZE); + + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0x01); + codec._readBuffer->flip(); + + codec.processRead(); + + testOk(codec._invalidDataStreamCount == 0, + "%s: codec._invalidDataStreamCount == 0", + CURRENT_FUNCTION); + testOk(codec._closedCount == 0, + "%s: codec._closedCount == 0", CURRENT_FUNCTION); + testOk(codec._receivedControlMessages.size() == 0, + "%s: codec._receivedControlMessages.size() == 0 ", + CURRENT_FUNCTION); + testOk(codec._receivedAppMessages.size() == 0, + "%s: codec._receivedAppMessages.size() == 0", + CURRENT_FUNCTION); + + codec._readBuffer->clear(); + + codec._readBuffer->put((int8_t)0x23); + codec._readBuffer->putInt(0x456789AB); + codec._readBuffer->flip(); + + codec.processRead(); + + testOk(codec._invalidDataStreamCount == 0, + "%s: codec._invalidDataStreamCount == 0", + CURRENT_FUNCTION); + testOk(codec._closedCount == 0, + "%s: codec._closedCount == 0", CURRENT_FUNCTION); + testOk(codec._receivedControlMessages.size() == 1, + "%s: codec._receivedControlMessages.size() == 1 ", + CURRENT_FUNCTION); + testOk(codec._receivedAppMessages.size() == 0, + "%s: codec._receivedAppMessages.size() == 0", + CURRENT_FUNCTION); + + + // app, no payload + PVAMessage header = codec._receivedControlMessages[0]; + + testOk(header._version == PVA_VERSION, + "%s: header._version == PVA_VERSION", CURRENT_FUNCTION); + testOk(header._flags == (int8_t)0x01, + "%s: header._flags == 0x01", CURRENT_FUNCTION); + testOk(header._command == (int8_t)0x23, + "%s: header._command == 0x23", CURRENT_FUNCTION); + testOk(header._payloadSize == 0x456789AB, + "%s: header._payloadSize == 0x456789AB", CURRENT_FUNCTION); + } + + + void testNonEmptyPayload() + { + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + TestCodec codec(DEFAULT_BUFFER_SIZE,DEFAULT_BUFFER_SIZE); + + // no misalignment + codec._readPayload = true; + + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0x80); + codec._readBuffer->put((int8_t)0x23); + codec._readBuffer->putInt(PVA_ALIGNMENT); + for (int i = 0; i < PVA_ALIGNMENT; i++) + codec._readBuffer->put((int8_t)i); + codec._readBuffer->flip(); + + codec.processRead(); + + testOk(codec._invalidDataStreamCount == 0, + "%s: codec._invalidDataStreamCount == 0", + CURRENT_FUNCTION); + testOk(codec._closedCount == 0, + "%s: codec._closedCount == 0", CURRENT_FUNCTION); + testOk(codec._receivedControlMessages.size() == 0, + "%s: codec._receivedControlMessages.size() == 0 ", + CURRENT_FUNCTION); + testOk(codec._receivedAppMessages.size() == 1, + "%s: codec._receivedAppMessages.size() == 1", + CURRENT_FUNCTION); + + // app, no payload + PVAMessage header = codec._receivedAppMessages[0]; + + testOk(header._payload.get() != 0, + "%s: header._payload.get() != 0", CURRENT_FUNCTION); + + header._payload->flip(); + + testOk( + (std::size_t)PVA_ALIGNMENT == header._payload->getLimit(), + "%s: PVA_ALIGNMENT == header._payload->getLimit()", + CURRENT_FUNCTION); + + } + + + void testNormalAlignment() + { + + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + TestCodec codec(DEFAULT_BUFFER_SIZE,DEFAULT_BUFFER_SIZE); + + codec._readPayload = true; + + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0x80); + codec._readBuffer->put((int8_t)0x23); + int32_t payloadSize1 = PVA_ALIGNMENT+1; + codec._readBuffer->putInt(payloadSize1); + + for (int32_t i = 0; i < payloadSize1; i++) + codec._readBuffer->put((int8_t)i); + // align + std::size_t aligned = + AbstractCodec::alignedValue(payloadSize1, PVA_ALIGNMENT); + + for (std::size_t i = payloadSize1; i < aligned; i++) + codec._readBuffer->put((int8_t)0xFF); + + + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0x80); + codec._readBuffer->put((int8_t)0x45); + + int32_t payloadSize2 = 2*PVA_ALIGNMENT-1; + codec._readBuffer->putInt(payloadSize2); + + for (int32_t i = 0; i < payloadSize2; i++) { + codec._readBuffer->put((int8_t)i); + } + + aligned = + AbstractCodec::alignedValue(payloadSize2, PVA_ALIGNMENT); + + for (std::size_t i = payloadSize2; i < aligned; i++) { + codec._readBuffer->put((int8_t)0xFF); + } + + codec._readBuffer->flip(); + codec.processRead(); + + testOk(codec._invalidDataStreamCount == 0, + "%s: codec._invalidDataStreamCount == 0", + CURRENT_FUNCTION); + testOk(codec._closedCount == 0, + "%s: codec._closedCount == 0", CURRENT_FUNCTION); + testOk(codec._receivedControlMessages.size() == 0, + "%s: codec._receivedControlMessages.size() == 0 ", + CURRENT_FUNCTION); + testOk(codec._receivedAppMessages.size() == 2, + "%s: codec._receivedAppMessages.size() == 2", + CURRENT_FUNCTION); + + PVAMessage msg = codec._receivedAppMessages[0]; + + testOk(msg._payloadSize == payloadSize1, + "%s: msg._payloadSize == payloadSize1", CURRENT_FUNCTION); + + testOk(msg._payload.get() != 0, + "%s: msg._payload.get() != 0", CURRENT_FUNCTION); + + + msg._payload->flip(); + + testOk((std::size_t)payloadSize1 == msg._payload->getLimit(), + "%s: payloadSize1, msg._payload->getLimit()", + CURRENT_FUNCTION); + + for (int32_t i = 0; i < msg._payloadSize; i++) { + testOk((int8_t)i == msg._payload->getByte(), + "%s: (int8_t)i == msg._payload->getByte()", + CURRENT_FUNCTION); + } + + msg = codec._receivedAppMessages[1]; + + testOk(msg._payloadSize == payloadSize2, + "%s: msg._payloadSize == payloadSize2", CURRENT_FUNCTION); + + testOk(msg._payload.get() != 0, + "%s: msg._payload.get() != 0", CURRENT_FUNCTION); + + msg._payload->flip(); + + testOk((std::size_t)payloadSize2 == msg._payload->getLimit(), + "%s: payloadSize2 == msg._payload->getLimit()", + CURRENT_FUNCTION); + + for (int32_t i = 0; i < msg._payloadSize; i++) { + testOk((int8_t)i == msg._payload->getByte(), + "%s: (int8_t)i == msg._payload->getByte()", + CURRENT_FUNCTION); + } + } + + + class ReadPollOneCallbackForTestSplitAlignment: + public ReadPollOneCallback { + + public: + + ReadPollOneCallbackForTestSplitAlignment( + TestCodec & codec, + int32_t payloadSize1, + int32_t payloadSize2): + _codec(codec), _payloadSize1(payloadSize1), + _payloadSize2(payloadSize2) {} + + + void readPollOne() { + + if (_codec._readPollOneCount == 1) + { + _codec._readBuffer->clear(); + for (int32_t i = _payloadSize1-2; + i < _payloadSize1; i++) { + _codec._readBuffer->put((int8_t)i); + } + + // align + std::size_t aligned = + AbstractCodec::alignedValue( + _payloadSize1, PVA_ALIGNMENT); + + for (std::size_t i = _payloadSize1; i < aligned; i++) + _codec._readBuffer->put((int8_t)0xFF); + + + _codec._readBuffer->put(PVA_MAGIC); + _codec._readBuffer->put(PVA_VERSION); + _codec._readBuffer->put((int8_t)0x80); + _codec._readBuffer->put((int8_t)0x45); + _codec._readBuffer->putInt(_payloadSize2); + + for (int32_t i = 0; i < _payloadSize2; i++) { + _codec._readBuffer->put((int8_t)i); + } + + _codec._readBuffer->flip(); + } + else if (_codec._readPollOneCount == 2) + { + _codec._readBuffer->clear(); + + std::size_t aligned = + AbstractCodec::alignedValue( + _payloadSize2, PVA_ALIGNMENT); + + for (std::size_t i = _payloadSize2; i < aligned; i++) { + _codec._readBuffer->put((int8_t)0xFF); + } + + _codec._readBuffer->flip(); + } + + else + throw std::logic_error("should not happen"); + } + + private: + TestCodec &_codec; + int8_t _payloadSize1; + int8_t _payloadSize2; + }; + + + void testSplitAlignment() + { + + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + TestCodec codec(DEFAULT_BUFFER_SIZE,DEFAULT_BUFFER_SIZE); + + // "<=" used instead of "==" to suppress compiler warning + if (PVA_ALIGNMENT <= 1) + return; + + codec._readPayload = true; + + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0x80); + codec._readBuffer->put((int8_t)0x23); + + int32_t payloadSize1 = PVA_ALIGNMENT+1; + codec._readBuffer->putInt(payloadSize1); + + for (int32_t i = 0; i < payloadSize1-2; i++) { + codec._readBuffer->put((int8_t)i); + } + + int32_t payloadSize2 = 2*PVA_ALIGNMENT-1; + + std::auto_ptr + readPollOneCallback( + new ReadPollOneCallbackForTestSplitAlignment + (codec, payloadSize1, payloadSize2)); + + codec._readPollOneCallback = readPollOneCallback; + + codec._readBuffer->flip(); + codec.processRead(); + + + testOk(codec._invalidDataStreamCount == 0, + "%s: codec._invalidDataStreamCount == 0", + CURRENT_FUNCTION); + testOk(codec._closedCount == 0, + "%s: codec._closedCount == 0", CURRENT_FUNCTION); + testOk(codec._receivedControlMessages.size() == 0, + "%s: codec._receivedControlMessages.size() == 0 ", + CURRENT_FUNCTION); + testOk(codec._receivedAppMessages.size() == 2, + "%s: codec._receivedAppMessages.size() == 2", + CURRENT_FUNCTION); + testOk(codec._readPollOneCount == 2, + "%s: codec._readPollOneCount == 2", CURRENT_FUNCTION); + + PVAMessage msg = codec._receivedAppMessages[0]; + + testOk(msg._payloadSize == payloadSize1, + "%s: msg._payloadSize == payloadSize1", CURRENT_FUNCTION); + testOk(msg._payload.get() != 0, + "%s: msg._payload.get() != 0", CURRENT_FUNCTION); + + msg._payload->flip(); + + testOk(payloadSize1 = msg._payload->getLimit(), + "%s: payloadSize1 = msg._payload->getLimit()", + CURRENT_FUNCTION); + + for (int32_t i = 0; i < msg._payloadSize; i++) { + testOk((int8_t)i == msg._payload->getByte(), + "%s: (int8_t)i == msg._payload->getByte()", + CURRENT_FUNCTION); + } + + msg = codec._receivedAppMessages[1]; + + testOk(msg._payloadSize == payloadSize2, + "%s: msg._payloadSize == payloadSize2", CURRENT_FUNCTION); + testOk(msg._payload.get() != 0, + "%s: msg._payload.get() != 0", CURRENT_FUNCTION); + + msg._payload->flip(); + + testOk((std::size_t)payloadSize2 == msg._payload->getLimit(), + "%s: payloadSize2 == msg._payload->getLimit()", + CURRENT_FUNCTION); + + for (int32_t i = 0; i < msg._payloadSize; i++) { + testOk((int8_t)i == msg._payload->getByte(), + "%s: (int8_t)i == msg._payload->getByte()", + CURRENT_FUNCTION); + } + } + + + void testSegmentedMessage() + { + + // no misalignment + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + TestCodec codec(DEFAULT_BUFFER_SIZE,DEFAULT_BUFFER_SIZE); + + codec._readPayload = true; + + // 1st + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0x90); + codec._readBuffer->put((int8_t)0x01); + + int32_t payloadSize1 = PVA_ALIGNMENT; + codec._readBuffer->putInt(payloadSize1); + + int32_t c = 0; + for (int32_t i = 0; i < payloadSize1; i++) + codec._readBuffer->put((int8_t)(c++)); + + // 2nd + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0xB0); + codec._readBuffer->put((int8_t)0x01); + + int32_t payloadSize2 = 2*PVA_ALIGNMENT; + codec._readBuffer->putInt(payloadSize2); + + for (int32_t i = 0; i < payloadSize2; i++) + codec._readBuffer->put((int8_t)(c++)); + + // control in between + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0x81); + codec._readBuffer->put((int8_t)0xEE); + codec._readBuffer->putInt(0xDDCCBBAA); + + // 3rd + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0xB0); + codec._readBuffer->put((int8_t)0x01); + + int32_t payloadSize3 = PVA_ALIGNMENT; + codec._readBuffer->putInt(payloadSize3); + + for (int32_t i = 0; i < payloadSize3; i++) + codec._readBuffer->put((int8_t)(c++)); + + // 4t (last) + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0xA0); + codec._readBuffer->put((int8_t)0x01); + + int32_t payloadSize4 = 2*PVA_ALIGNMENT; + codec._readBuffer->putInt(payloadSize4); + + for (int32_t i = 0; i < payloadSize4; i++) + codec._readBuffer->put((int8_t)(c++)); + + codec._readBuffer->flip(); + + int32_t payloadSizeSum = + payloadSize1+payloadSize2+payloadSize3+payloadSize4; + + codec._forcePayloadRead = payloadSizeSum; + + codec.processRead(); + + testOk(codec._invalidDataStreamCount == 0, + "%s: codec._invalidDataStreamCount == 0", + CURRENT_FUNCTION); + testOk(codec._closedCount == 0, + "%s: codec._closedCount == 0", CURRENT_FUNCTION); + testOk(codec._receivedControlMessages.size() == 1, + "%s: codec._receivedControlMessages.size() == 1 ", + CURRENT_FUNCTION); + testOk(codec._receivedAppMessages.size() == 1, + "%s: codec._receivedAppMessages.size() == 1", + CURRENT_FUNCTION); + testOk(codec._readPollOneCount == 0, + "%s: codec._readPollOneCount == 0", CURRENT_FUNCTION); + + + PVAMessage msg = codec._receivedAppMessages[0]; + + testOk(msg._payload.get() != 0, + "%s: msg._payload.get() != 0", CURRENT_FUNCTION); + + msg._payload->flip(); + + testOk( + (std::size_t)payloadSizeSum == msg._payload->getLimit(), + "%s: payloadSizeSum == msg._payload->getLimit()", + CURRENT_FUNCTION); + + for (int32_t i = 0; i < msg._payloadSize; i++) { + testOk((int8_t)i == msg._payload->getByte(), + "%s: (int8_t)i == msg._payload->getint8_t()", + CURRENT_FUNCTION); + } + + + msg = codec._receivedControlMessages[0]; + + testOk(msg._version == PVA_VERSION, + "%s: msg._version == PVA_VERSION", CURRENT_FUNCTION); + testOk(msg._flags == (int8_t)0x81, + "%s: msg._flags == 0x81", CURRENT_FUNCTION); + testOk(msg._command == (int8_t)0xEE, + "%s: msg._command == 0xEE", CURRENT_FUNCTION); + testOk(msg._payloadSize == (int32_t)0xDDCCBBAA, + "%s: msg._payloadSize == 0xDDCCBBAA", CURRENT_FUNCTION); + } + + + void testSegmentedInvalidInBetweenFlagsMessage() + { + // no misalignment + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + TestCodec codec(DEFAULT_BUFFER_SIZE,DEFAULT_BUFFER_SIZE); + + codec._readPayload = true; + + // 1st + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0x90); + codec._readBuffer->put((int8_t)0x01); + + int32_t payloadSize1 = PVA_ALIGNMENT; + codec._readBuffer->putInt(payloadSize1); + + int32_t c = 0; + for (int32_t i = 0; i < payloadSize1; i++) + codec._readBuffer->put((int8_t)(c++)); + + // 2nd + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + // invalid flag, should be 0xB0 + codec._readBuffer->put((int8_t)0x90); + codec._readBuffer->put((int8_t)0x01); + + int32_t payloadSize2 = 2*PVA_ALIGNMENT; + codec._readBuffer->putInt(payloadSize2); + + for (int32_t i = 0; i < payloadSize2; i++) + codec._readBuffer->put((int8_t)(c++)); + + // control in between + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0x81); + codec._readBuffer->put((int8_t)0xEE); + codec._readBuffer->putInt(0xDDCCBBAA); + + // 3rd + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0xB0); + codec._readBuffer->put((int8_t)0x01); + + int32_t payloadSize3 = PVA_ALIGNMENT; + codec._readBuffer->putInt(payloadSize3); + + for (int32_t i = 0; i < payloadSize3; i++) + codec._readBuffer->put((int8_t)(c++)); + + // 4t (last) + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0xA0); + codec._readBuffer->put((int8_t)0x01); + + int32_t payloadSize4 = 2*PVA_ALIGNMENT; + codec._readBuffer->putInt(payloadSize4); + + for (int32_t i = 0; i < payloadSize4; i++) + codec._readBuffer->put((int8_t)(c++)); + + codec._readBuffer->flip(); + + int32_t payloadSizeSum = + payloadSize1+payloadSize2+payloadSize3+payloadSize4; + codec._forcePayloadRead = payloadSizeSum; + + try { + codec.processRead(); + testFail( + "%s: invalid_data_stream_exception, but not reported", + CURRENT_FUNCTION); + } catch(invalid_data_stream_exception &) { + testOk(true, "%s: invalid_data_stream_exception reported", + CURRENT_FUNCTION); + } + + testOk(codec._invalidDataStreamCount == 1, + "%s: codec._invalidDataStreamCount == 1", + CURRENT_FUNCTION); + testOk(codec._closedCount == 0, + "%s: codec._closedCount == 0", CURRENT_FUNCTION); + testOk(codec._receivedControlMessages.size() == 0, + "%s: codec._receivedControlMessages.size() == 0 ", + CURRENT_FUNCTION); + testOk(codec._receivedAppMessages.size() == 0, + "%s: codec._receivedAppMessages.size() == 0", + CURRENT_FUNCTION); + } + + + void testSegmentedMessageAlignment() + { + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + TestCodec codec(DEFAULT_BUFFER_SIZE,DEFAULT_BUFFER_SIZE); + + codec._readPayload = true; + + // 1st + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0x90); + codec._readBuffer->put((int8_t)0x01); + + int32_t payloadSize1 = PVA_ALIGNMENT+1; + codec._readBuffer->putInt(payloadSize1); + + int32_t c = 0; + for (int32_t i = 0; i < payloadSize1; i++) + codec._readBuffer->put((int8_t)(c++)); + + std::size_t aligned = + AbstractCodec::alignedValue(payloadSize1, PVA_ALIGNMENT); + for (std::size_t i = payloadSize1; i < aligned; i++) + codec._readBuffer->put((int8_t)0xFF); + + + // 2nd + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0xB0); + codec._readBuffer->put((int8_t)0x01); + + int32_t payloadSize2 = 2*PVA_ALIGNMENT-1; + int32_t payloadSize2Real = + payloadSize2 + payloadSize1 % PVA_ALIGNMENT; + + codec._readBuffer->putInt(payloadSize2Real); + + // pre-message padding + for (int32_t i = 0; i < payloadSize1 % PVA_ALIGNMENT; i++) + codec._readBuffer->put((int8_t)0xEE); + + for (int32_t i = 0; i < payloadSize2; i++) + codec._readBuffer->put((int8_t)(c++)); + + aligned = + AbstractCodec::alignedValue( + payloadSize2Real, PVA_ALIGNMENT); + + for (std::size_t i = payloadSize2Real; i < aligned; i++) + codec._readBuffer->put((int8_t)0xFF); + + // 3rd + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0xB0); + codec._readBuffer->put((int8_t)0x01); + + int32_t payloadSize3 = PVA_ALIGNMENT+2; + int32_t payloadSize3Real = + payloadSize3 + payloadSize2Real % PVA_ALIGNMENT; + codec._readBuffer->putInt(payloadSize3Real); + + // pre-message padding required + for (int32_t i = 0; + i < payloadSize2Real % PVA_ALIGNMENT; i++) + codec._readBuffer->put((int8_t)0xEE); + + for (int32_t i = 0; i < payloadSize3; i++) + codec._readBuffer->put((int8_t)(c++)); + + aligned = + AbstractCodec::alignedValue( + payloadSize3Real, PVA_ALIGNMENT); + + for (std::size_t i = payloadSize3Real; i < aligned; i++) + codec._readBuffer->put((int8_t)0xFF); + + // 4t (last) + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0xA0); + codec._readBuffer->put((int8_t)0x01); + + int32_t payloadSize4 = 2*PVA_ALIGNMENT+3; + int32_t payloadSize4Real = + payloadSize4 + payloadSize3Real % PVA_ALIGNMENT; + + codec._readBuffer->putInt(payloadSize4Real); + + // pre-message padding required + for (int32_t i = 0; + i < payloadSize3Real % PVA_ALIGNMENT; i++) + codec._readBuffer->put((int8_t)0xEE); + + for (int32_t i = 0; i < payloadSize4; i++) + codec._readBuffer->put((int8_t)(c++)); + + aligned = + AbstractCodec::alignedValue( + payloadSize4Real, PVA_ALIGNMENT); + + for (std::size_t i = payloadSize4Real; i < aligned; i++) + codec._readBuffer->put((int8_t)0xFF); + + codec._readBuffer->flip(); + + int32_t payloadSizeSum = + payloadSize1+payloadSize2+payloadSize3+payloadSize4; + + codec._forcePayloadRead = payloadSizeSum; + + codec.processRead(); + + testOk(codec._invalidDataStreamCount == 0, + "%s: codec._invalidDataStreamCount == 0", + CURRENT_FUNCTION); + testOk(codec._closedCount == 0, + "%s: codec._closedCount == 0", CURRENT_FUNCTION); + testOk(codec._receivedControlMessages.size() == 0, + "%s: codec._receivedControlMessages.size() == 0 ", + CURRENT_FUNCTION); + testOk(codec._receivedAppMessages.size() == 1, + "%s: codec._receivedAppMessages.size() == 1", + CURRENT_FUNCTION); + testOk(codec._readPollOneCount == 0, + "%s: codec._readPollOneCount == 0", CURRENT_FUNCTION); + + PVAMessage msg = codec._receivedAppMessages[0]; + + testOk(msg._payload.get() != 0, + "%s: msg._payload.get() != 0", CURRENT_FUNCTION); + + msg._payload->flip(); + + testOk( + (std::size_t)payloadSizeSum == msg._payload->getLimit(), + "%s: payloadSizeSum == msg._payload->getLimit()", + CURRENT_FUNCTION); + + for (int32_t i = 0; i < msg._payloadSize; i++) + testOk((int8_t)i == msg._payload->getByte(), + "%s: (int8_t)i == msg._payload->getByte()", + CURRENT_FUNCTION); + } + + + class ReadPollOneCallbackForTestSegmentedSplitMessage: + public ReadPollOneCallback { + + public: + + ReadPollOneCallbackForTestSegmentedSplitMessage( + TestCodec & codec, + int32_t realReadBufferEnd): + _codec(codec), _realReadBufferEnd(realReadBufferEnd) {} + + + void readPollOne() { + if (_codec._readPollOneCount == 1) + { + _codec._readBuffer->setLimit(_realReadBufferEnd); + } + else + throw std::logic_error("should not happen"); + } + + private: + TestCodec &_codec; + std::size_t _realReadBufferEnd; + }; + + + void testSegmentedSplitMessage() + { + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + + for (int32_t firstMessagePayloadSize = 1; // cannot be zero + firstMessagePayloadSize <= 3*PVA_ALIGNMENT; + firstMessagePayloadSize++) + { + for (int32_t secondMessagePayloadSize = 0; + secondMessagePayloadSize <= 2*PVA_ALIGNMENT; + secondMessagePayloadSize++) + { + // cannot be zero + for (int32_t thirdMessagePayloadSize = 1; + thirdMessagePayloadSize <= 2*PVA_ALIGNMENT; + thirdMessagePayloadSize++) + { + std::size_t splitAt = 1; + while (true) + { + TestCodec codec(DEFAULT_BUFFER_SIZE, + DEFAULT_BUFFER_SIZE); + + codec._readPayload = true; + + // 1st + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0x90); + codec._readBuffer->put((int8_t)0x01); + + int32_t payloadSize1 = firstMessagePayloadSize; + codec._readBuffer->putInt(payloadSize1); + + int32_t c = 0; + for (int32_t i = 0; i < payloadSize1; i++) + codec._readBuffer->put((int8_t)(c++)); + + std::size_t aligned = + AbstractCodec::alignedValue( + payloadSize1, PVA_ALIGNMENT); + + for (std::size_t i = payloadSize1; i < aligned; i++) + codec._readBuffer->put((int8_t)0xFF); + + // 2nd + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0xB0); + codec._readBuffer->put((int8_t)0x01); + + int32_t payloadSize2 = secondMessagePayloadSize; + int payloadSize2Real = + payloadSize2 + payloadSize1 % PVA_ALIGNMENT; + + codec._readBuffer->putInt(payloadSize2Real); + + // pre-message padding + for (int32_t i = 0; + i < payloadSize1 % PVA_ALIGNMENT; i++) + codec._readBuffer->put((int8_t)0xEE); + + for (int32_t i = 0; i < payloadSize2; i++) + codec._readBuffer->put((int8_t)(c++)); + + aligned = + AbstractCodec::alignedValue( + payloadSize2Real, PVA_ALIGNMENT); + + for (std::size_t i = payloadSize2Real; + i < aligned; i++) + codec._readBuffer->put((int8_t)0xFF); + + // 3rd + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0xA0); + codec._readBuffer->put((int8_t)0x01); + + int32_t payloadSize3 = thirdMessagePayloadSize; + int32_t payloadSize3Real = + payloadSize3 + payloadSize2Real % PVA_ALIGNMENT; + + codec._readBuffer->putInt(payloadSize3Real); + + // pre-message padding required + for (int32_t i = 0; + i < payloadSize2Real % PVA_ALIGNMENT; i++) + codec._readBuffer->put((int8_t)0xEE); + + for (int32_t i = 0; i < payloadSize3; i++) + codec._readBuffer->put((int8_t)(c++)); + + aligned = + AbstractCodec::alignedValue( + payloadSize3Real, PVA_ALIGNMENT); + + for (std::size_t i = payloadSize3Real; + i < aligned; i++) + codec._readBuffer->put((int8_t)0xFF); + + codec._readBuffer->flip(); + + std::size_t realReadBufferEnd = + codec._readBuffer->getLimit(); + + if (splitAt++ == realReadBufferEnd) + break; + + codec._readBuffer->setLimit(splitAt); + + std::auto_ptr + readPollOneCallback( + new ReadPollOneCallbackForTestSegmentedSplitMessage + (codec, realReadBufferEnd)); + + codec._readPollOneCallback = readPollOneCallback; + + + int32_t payloadSizeSum = + payloadSize1+payloadSize2+payloadSize3; + + codec._forcePayloadRead = payloadSizeSum; + + codec.processRead(); + + while (codec._invalidDataStreamCount == 0 && + codec._readBuffer->getPosition() != + realReadBufferEnd) + { + codec._readPollOneCount++; + codec._readPollOneCallback->readPollOne(); + codec.processRead(); + } + + testOk(codec._invalidDataStreamCount == 0, + "%s: codec._invalidDataStreamCount == 0", + CURRENT_FUNCTION); + testOk(codec._closedCount == 0, + "%s: codec._closedCount == 0", CURRENT_FUNCTION); + testOk(codec._receivedControlMessages.size() == 0, + "%s: codec._receivedControlMessages.size() == 0 ", + CURRENT_FUNCTION); + testOk(codec._receivedAppMessages.size() == 1, + "%s: codec._receivedAppMessages.size() == 1", + CURRENT_FUNCTION); + + + if (splitAt == realReadBufferEnd) { + testOk(0 == codec._readPollOneCount, + "%s: 0 == codec._readPollOneCount", + CURRENT_FUNCTION); + } + else { + testOk(1 == codec._readPollOneCount, + "%s: 1 == codec._readPollOneCount", + CURRENT_FUNCTION); + } + + PVAMessage msg = codec._receivedAppMessages[0]; + + testOk(msg._payload.get() != 0, + "%s: msg._payload.get() != 0", CURRENT_FUNCTION); + + msg._payload->flip(); + + testOk((std::size_t)payloadSizeSum == + msg._payload->getLimit(), + "%s: payloadSizeSum == msg._payload->getLimit()", + CURRENT_FUNCTION); + + for (int32_t i = 0; i < msg._payloadSize; i++) { + testOk((int8_t)i == msg._payload->getByte(), + "%s: (int8_t)i == msg._payload->getByte()", + CURRENT_FUNCTION); + } + } + } + } + } + } + + + void testStartMessage() + { + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + TestCodec codec(DEFAULT_BUFFER_SIZE,DEFAULT_BUFFER_SIZE); + + codec.putControlMessage((int8_t)0x23, 0x456789AB); + + codec.transferToReadBuffer(); + + codec.processRead(); + + testOk(codec._invalidDataStreamCount == 0, + "%s: codec._invalidDataStreamCount == 0", + CURRENT_FUNCTION); + testOk(codec._closedCount == 0, + "%s: codec._closedCount == 0", CURRENT_FUNCTION); + testOk(codec._receivedControlMessages.size() == 1, + "%s: codec._receivedControlMessages.size() == 1 ", + CURRENT_FUNCTION); + testOk(codec._receivedAppMessages.size() == 0, + "%s: codec._receivedAppMessages.size() == 0", + CURRENT_FUNCTION); + + + PVAMessage header = codec._receivedControlMessages[0]; + + testOk(header._version == PVA_VERSION, + "%s: header._version == PVA_VERSION", CURRENT_FUNCTION); + testOk(header._flags == (int8_t)((EPICS_BYTE_ORDER == EPICS_ENDIAN_BIG ? 0x80 : 0x00) | 0x01), + "%s: header._flags == 0x(0|8)1", CURRENT_FUNCTION); + testOk(header._command == (int8_t)0x23, + "%s: header._command == 0x23", CURRENT_FUNCTION); + testOk(header._payloadSize == 0x456789AB, + "%s: header._payloadSize == 0x456789AB", CURRENT_FUNCTION); + + codec.reset(); + + // two at the time, app and control + codec.setByteOrder(EPICS_ENDIAN_LITTLE); + codec.startMessage((int8_t)0x20, 0x00000000); + codec.endMessage(); + + codec.setByteOrder(EPICS_ENDIAN_BIG); + codec.putControlMessage((int8_t)0xEE, 0xDDCCBBAA); + + codec.transferToReadBuffer(); + + codec.processRead(); + + testOk(codec._invalidDataStreamCount == 0, + "%s: codec._invalidDataStreamCount == 0", + CURRENT_FUNCTION); + testOk(codec._closedCount == 0, + "%s: codec._closedCount == 0", CURRENT_FUNCTION); + testOk(codec._receivedControlMessages.size() == 1, + "%s: codec._receivedControlMessages.size() == 1 ", + CURRENT_FUNCTION); + testOk(codec._receivedAppMessages.size() == 1, + "%s: codec._receivedAppMessages.size() == 1", + CURRENT_FUNCTION); + + // app, no payload + header = codec._receivedAppMessages[0]; + + testOk(header._version == PVA_VERSION, + "%s: header._version == PVA_VERSION", CURRENT_FUNCTION); + testOk(header._flags == (int8_t)0x00, + "%s: header._flags == 0x00", CURRENT_FUNCTION); + testOk(header._command == (int8_t)0x20, + "%s: header._command == 0x20", CURRENT_FUNCTION); + testOk(header._payloadSize == 0x00000000, + "%s: header._payloadSize == 0x00000000", CURRENT_FUNCTION); + + // control + header = codec._receivedControlMessages[0]; + + testOk(header._version == PVA_VERSION, + "%s: header._version == PVA_VERSION", CURRENT_FUNCTION); + testOk(header._flags == (int8_t)0x81, + "%s: header._flags == 0x81", CURRENT_FUNCTION); + testOk(header._command == (int8_t)0xEE, + "%s: header._command == 0xEE", CURRENT_FUNCTION); + testOk(header._payloadSize == (int32_t)0xDDCCBBAA, + "%s: header._payloadSize == 0xDDCCBBAA", CURRENT_FUNCTION); + } + + + void testStartMessageNonEmptyPayload() + { + // no misalignment + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + TestCodec codec(DEFAULT_BUFFER_SIZE,DEFAULT_BUFFER_SIZE); + + codec._readPayload = true; + codec.startMessage((int8_t)0x23, 0); + + codec.ensureBuffer((std::size_t)PVA_ALIGNMENT); + for (int32_t i = 0; i < PVA_ALIGNMENT; i++) + codec.getSendBuffer()->put((int8_t)i); + + codec.endMessage(); + + codec.transferToReadBuffer(); + + codec.processRead(); + + testOk(codec._invalidDataStreamCount == 0, + "%s: codec._invalidDataStreamCount == 0", + CURRENT_FUNCTION); + testOk(codec._closedCount == 0, + "%s: codec._closedCount == 0", CURRENT_FUNCTION); + testOk(codec._receivedControlMessages.size() == 0, + "%s: codec._receivedControlMessages.size() == 0 ", + CURRENT_FUNCTION); + testOk(codec._receivedAppMessages.size() == 1, + "%s: codec._receivedAppMessages.size() == 1", + CURRENT_FUNCTION); + + // app, no payload + PVAMessage header = codec._receivedAppMessages[0]; + + testOk(header._payload.get() != 0, + "%s: header._payload.get() != 0", CURRENT_FUNCTION); + + header._payload->flip(); + + testOk((std::size_t)PVA_ALIGNMENT == + header._payload->getLimit(), + "%s: PVA_ALIGNMENT == header._payload->getLimit()", + CURRENT_FUNCTION); + } + + + void testStartMessageNormalAlignment() + { + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + TestCodec codec(DEFAULT_BUFFER_SIZE,DEFAULT_BUFFER_SIZE); + + codec._readPayload = true; + + codec.startMessage((int8_t)0x23, 0); + int32_t payloadSize1 = PVA_ALIGNMENT+1; + codec.ensureBuffer(payloadSize1); + + for (int32_t i = 0; i < payloadSize1; i++) + codec.getSendBuffer()->put((int8_t)i); + + codec.endMessage(); + + codec.startMessage((int8_t)0x45, 0); + int32_t payloadSize2 = 2*PVA_ALIGNMENT-1; + codec.ensureBuffer(payloadSize2); + + for (int32_t i = 0; i < payloadSize2; i++) + codec.getSendBuffer()->put((int8_t)i); + + codec.endMessage(); + + codec.transferToReadBuffer(); + + codec.processRead(); + + testOk(codec._invalidDataStreamCount == 0, + "%s: codec._invalidDataStreamCount == 0", + CURRENT_FUNCTION); + testOk(codec._closedCount == 0, + "%s: codec._closedCount == 0", CURRENT_FUNCTION); + testOk(codec._receivedControlMessages.size() == 0, + "%s: codec._receivedControlMessages.size() == 0 ", + CURRENT_FUNCTION); + testOk(codec._receivedAppMessages.size() == 2, + "%s: codec._receivedAppMessages.size() == 2", + CURRENT_FUNCTION); + + PVAMessage msg = codec._receivedAppMessages[0]; + + testOk(msg._payloadSize == payloadSize1, + "%s: msg._payloadSize == payloadSize1", CURRENT_FUNCTION); + testOk(msg._payload.get() != 0, + "%s: msg._payload.get() != 0", CURRENT_FUNCTION); + + msg._payload->flip(); + + testOk((std::size_t)payloadSize1 == + msg._payload->getLimit(), + "%s: payloadSize1 == msg._payload->getLimit()", + CURRENT_FUNCTION); + + for (int32_t i = 0; i < msg._payloadSize; i++) + testOk((int8_t)i == msg._payload->getByte(), + "%s: (int8_t)i == msg._payload->getByte()", + CURRENT_FUNCTION); + + msg = codec._receivedAppMessages[1]; + + testOk(msg._payloadSize == payloadSize2, + "%s: msg._payloadSize == payloadSize2", CURRENT_FUNCTION); + testOk(msg._payload.get() != 0, + "%s: msg._payload.get() != 0", CURRENT_FUNCTION); + + msg._payload->flip(); + + testOk((std::size_t)payloadSize2 == + msg._payload->getLimit(), + "%s: payloadSize2 == msg._payload->getLimit()", + CURRENT_FUNCTION); + + for (int32_t i = 0; i < msg._payloadSize; i++) + testOk((int8_t)i == msg._payload->getByte(), + "%s: (int8_t)i == msg._payload->getByte()", + CURRENT_FUNCTION); + } + + + void testStartMessageSegmentedMessage() + { + // no misalignment + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + TestCodec codec(DEFAULT_BUFFER_SIZE,DEFAULT_BUFFER_SIZE); + + codec._readPayload = true; + + codec.startMessage((int8_t)0x01, 0); + + int32_t c = 0; + + int32_t payloadSize1 = PVA_ALIGNMENT; + for (int32_t i = 0; i < payloadSize1; i++) + codec.getSendBuffer()->put((int8_t)(c++)); + + codec.flush(false); + + int32_t payloadSize2 = 2*PVA_ALIGNMENT; + for (int32_t i = 0; i < payloadSize2; i++) + codec.getSendBuffer()->put((int8_t)(c++)); + + codec.flush(false); + + int32_t payloadSize3 = PVA_ALIGNMENT; + for (int32_t i = 0; i < payloadSize3; i++) + codec.getSendBuffer()->put((int8_t)(c++)); + + codec.flush(false); + + int32_t payloadSize4 = 2*PVA_ALIGNMENT; + for (int32_t i = 0; i < payloadSize4; i++) + codec.getSendBuffer()->put((int8_t)(c++)); + + codec.endMessage(); + + codec.transferToReadBuffer(); + + int32_t payloadSizeSum = + payloadSize1+payloadSize2+payloadSize3+payloadSize4; + + codec._forcePayloadRead = payloadSizeSum; + + codec.processRead(); + + testOk(codec._invalidDataStreamCount == 0, + "%s: codec._invalidDataStreamCount == 0", + CURRENT_FUNCTION); + testOk(codec._closedCount == 0, + "%s: codec._closedCount == 0", CURRENT_FUNCTION); + testOk(codec._receivedControlMessages.size() == 0, + "%s: codec._receivedControlMessages.size() == 0 ", + CURRENT_FUNCTION); + testOk(codec._receivedAppMessages.size() == 1, + "%s: codec._receivedAppMessages.size() == 1", + CURRENT_FUNCTION); + testOk(codec._readPollOneCount == 0, + "%s: codec._readPollOneCount == 0", CURRENT_FUNCTION); + + PVAMessage msg = codec._receivedAppMessages[0]; + + testOk(msg._payload.get() != 0, + "%s: msg._payload.get() != 0", CURRENT_FUNCTION); + + msg._payload->flip(); + + testOk((std::size_t)payloadSizeSum == + msg._payload->getLimit(), + "%s: payloadSizeSum == msg._payload->getLimit()", + CURRENT_FUNCTION); + + for (int32_t i = 0; i < msg._payloadSize; i++) + testOk((int8_t)i == msg._payload->getByte(), + "%s: (int8_t)i == msg._payload->getByte()", + CURRENT_FUNCTION); + } + + + void testStartMessageSegmentedMessageAlignment() + { + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + + for (int32_t firstMessagePayloadSize = 1; // cannot be zero + firstMessagePayloadSize <= 3*PVA_ALIGNMENT; + firstMessagePayloadSize++) + { + for (int32_t secondMessagePayloadSize = 0; + secondMessagePayloadSize <= 2*PVA_ALIGNMENT; + secondMessagePayloadSize++) + { + // cannot be zero + for (int32_t thirdMessagePayloadSize = 1; + thirdMessagePayloadSize <= 2*PVA_ALIGNMENT; + thirdMessagePayloadSize++) + { + // cannot be zero + for (int32_t fourthMessagePayloadSize = 1; + fourthMessagePayloadSize <= 2*PVA_ALIGNMENT; + fourthMessagePayloadSize++) + { + TestCodec codec(DEFAULT_BUFFER_SIZE, + DEFAULT_BUFFER_SIZE); + + codec._readPayload = true; + + codec.startMessage((int8_t)0x01, 0); + + int32_t c = 0; + + int32_t payloadSize1 = firstMessagePayloadSize; + for (int32_t i = 0; i < payloadSize1; i++) + codec.getSendBuffer()->put((int8_t)(c++)); + + codec.flush(false); + + int32_t payloadSize2 = secondMessagePayloadSize; + for (int32_t i = 0; i < payloadSize2; i++) + codec.getSendBuffer()->put((int8_t)(c++)); + + codec.flush(false); + + int32_t payloadSize3 = thirdMessagePayloadSize; + for (int32_t i = 0; i < payloadSize3; i++) + codec.getSendBuffer()->put((int8_t)(c++)); + + codec.flush(false); + + int32_t payloadSize4 = fourthMessagePayloadSize; + for (int32_t i = 0; i < payloadSize4; i++) + codec.getSendBuffer()->put((int8_t)(c++)); + + codec.endMessage(); + + codec.transferToReadBuffer(); + + int32_t payloadSizeSum = + payloadSize1+payloadSize2+payloadSize3 + +payloadSize4; + + codec._forcePayloadRead = payloadSizeSum; + + codec.processRead(); + + testOk(codec._invalidDataStreamCount == 0, + "%s: codec._invalidDataStreamCount == 0", + CURRENT_FUNCTION); + testOk(codec._closedCount == 0, + "%s: codec._closedCount == 0", CURRENT_FUNCTION); + testOk(codec._receivedControlMessages.size() == 0, + "%s: codec._receivedControlMessages.size() == 0 ", + CURRENT_FUNCTION); + testOk(codec._receivedAppMessages.size() == 1, + "%s: codec._receivedAppMessages.size() == 1", + CURRENT_FUNCTION); + testOk(codec._readPollOneCount == 0, + "%s: codec._readPollOneCount == 0", + CURRENT_FUNCTION); + + PVAMessage msg = codec._receivedAppMessages[0]; + + testOk(msg._payload.get() != 0, + "%s: msg._payload.get() != 0", CURRENT_FUNCTION); + + msg._payload->flip(); + + testOk((std::size_t)payloadSizeSum == + msg._payload->getLimit(), + "%s: payloadSizeSum == msg._payload->getLimit()", + CURRENT_FUNCTION); + + for (int32_t i = 0; i < msg._payloadSize; i++) { + if ((int8_t)i != msg._payload->getByte()) { + testFail( + "%s: (int8_t)%d == msg._payload->getByte()", + CURRENT_FUNCTION, (int8_t)i); + } + } + } + } + } + } + } + + + void testReadNormalConnectionLoss() + { + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + TestCodec codec(DEFAULT_BUFFER_SIZE,DEFAULT_BUFFER_SIZE); + + codec._readPayload = true; + codec._disconnected = true; + + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0x01); + codec._readBuffer->put((int8_t)0x23); + codec._readBuffer->putInt(0x456789AB); + + codec._readBuffer->flip(); + + codec.processRead(); + + testOk(codec._invalidDataStreamCount == 0, + "%s: codec._invalidDataStreamCount == 0", + CURRENT_FUNCTION); + testOk(codec._closedCount == 1, + "%s: codec._closedCount == 1", CURRENT_FUNCTION); + testOk(codec._receivedControlMessages.size() == 0, + "%s: codec._receivedControlMessages.size() == 0 ", + CURRENT_FUNCTION); + testOk(codec._receivedAppMessages.size() == 0, + "%s: codec._receivedAppMessages.size() == 0", + CURRENT_FUNCTION); + } + + + class ReadPollOneCallbackForTestSegmentedSplitConnectionLoss: + public ReadPollOneCallback { + + public: + + ReadPollOneCallbackForTestSegmentedSplitConnectionLoss( + TestCodec & codec): _codec(codec) {} + + + void readPollOne() { + if (_codec._readPollOneCount == 1) + { + _codec._disconnected = true; + } + else + throw std::logic_error("should not happen"); + } + + private: + TestCodec &_codec; + }; + + + void testSegmentedSplitConnectionLoss() + { + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + + + for (int32_t firstMessagePayloadSize = 1; // cannot be zero + firstMessagePayloadSize <= 3*PVA_ALIGNMENT; + firstMessagePayloadSize++) + { + for (int32_t secondMessagePayloadSize = 0; + secondMessagePayloadSize <= 2*PVA_ALIGNMENT; + secondMessagePayloadSize++) + { + // cannot be zero + for (int32_t thirdMessagePayloadSize = 1; + thirdMessagePayloadSize <= 2*PVA_ALIGNMENT; + thirdMessagePayloadSize++) + { + std::size_t splitAt = 1; + + while (true) + { + TestCodec codec(DEFAULT_BUFFER_SIZE, + DEFAULT_BUFFER_SIZE); + + codec._readPayload = true; + + // 1st + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0x90); + codec._readBuffer->put((int8_t)0x01); + + int32_t payloadSize1 = firstMessagePayloadSize; + codec._readBuffer->putInt(payloadSize1); + + int32_t c = 0; + for (int32_t i = 0; i < payloadSize1; i++) + codec._readBuffer->put((int8_t)(c++)); + + std::size_t aligned = + AbstractCodec::alignedValue( + payloadSize1, PVA_ALIGNMENT); + + for (std::size_t i = payloadSize1; i < aligned; i++) + codec._readBuffer->put((int8_t)0xFF); + + // 2nd + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0xB0); + codec._readBuffer->put((int8_t)0x01); + + int32_t payloadSize2 = secondMessagePayloadSize; + int32_t payloadSize2Real = + payloadSize2 + payloadSize1 % PVA_ALIGNMENT; + + codec._readBuffer->putInt(payloadSize2Real); + + // pre-message padding + for (int32_t i = 0; + i < payloadSize1 % PVA_ALIGNMENT; i++) + codec._readBuffer->put((int8_t)0xEE); + + for (int32_t i = 0; i < payloadSize2; i++) + codec._readBuffer->put((int8_t)(c++)); + + aligned = + AbstractCodec::alignedValue( + payloadSize2Real, PVA_ALIGNMENT); + + for (std::size_t i = payloadSize2Real; + i < aligned; i++) + codec._readBuffer->put((int8_t)0xFF); + + // 3rd + codec._readBuffer->put(PVA_MAGIC); + codec._readBuffer->put(PVA_VERSION); + codec._readBuffer->put((int8_t)0xA0); + codec._readBuffer->put((int8_t)0x01); + + int32_t payloadSize3 = thirdMessagePayloadSize; + int32_t payloadSize3Real = + payloadSize3 + payloadSize2Real % PVA_ALIGNMENT; + + codec._readBuffer->putInt(payloadSize3Real); + + // pre-message padding required + for (int32_t i = 0; + i < payloadSize2Real % PVA_ALIGNMENT; i++) + codec._readBuffer->put((int8_t)0xEE); + + for (int32_t i = 0; i < payloadSize3; i++) + codec._readBuffer->put((int8_t)(c++)); + + aligned = + AbstractCodec::alignedValue( + payloadSize3Real, PVA_ALIGNMENT); + + for (std::size_t i = payloadSize3Real; + i < aligned; i++) + codec._readBuffer->put((int8_t)0xFF); + + codec._readBuffer->flip(); + + std::size_t realReadBufferEnd = + codec._readBuffer->getLimit(); + + if (splitAt++ == realReadBufferEnd-1) + break; + + codec._readBuffer->setLimit(splitAt); + + std::auto_ptr + readPollOneCallback( new + ReadPollOneCallbackForTestSegmentedSplitConnectionLoss + (codec)); + + + codec._readPollOneCallback = readPollOneCallback; + + int32_t payloadSizeSum = + payloadSize1+payloadSize2+payloadSize3; + + codec._forcePayloadRead = payloadSizeSum; + + codec.processRead(); + + while (codec._closedCount == 0 && + codec._invalidDataStreamCount == 0 && + codec._readBuffer->getPosition() != + realReadBufferEnd) + { + codec._readPollOneCount++; + codec._readPollOneCallback->readPollOne(); + codec.processRead(); + } + + testOk(codec._invalidDataStreamCount == 0, + "%s: codec._invalidDataStreamCount == 0", + CURRENT_FUNCTION); + testOk(codec._closedCount == 1, + "%s: codec._closedCount == 1", CURRENT_FUNCTION); + } + } + } + } + } + + + void testSendConnectionLoss() + { + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + TestCodec codec(DEFAULT_BUFFER_SIZE,DEFAULT_BUFFER_SIZE); + + codec._readPayload = true; + codec._disconnected = true; + + codec.putControlMessage((int8_t)0x23, 0x456789AB); + + try + { + codec.transferToReadBuffer(); + testFail("%s: connection lost, but not reported", + CURRENT_FUNCTION); + } + catch (connection_closed_exception & ) { + testOk(true, "%s: connection closed exception expected", + CURRENT_FUNCTION); + } + + testOk(codec._closedCount == 1, + "%s: codec._closedCount == 1", CURRENT_FUNCTION); + } + + + class TransportSenderForTestEnqueueSendRequest: + public TransportSender { + public: + + TransportSenderForTestEnqueueSendRequest( + TestCodec & codec): _codec(codec) {} + + void unlock() { + } + + void lock() { + } + + void send(epics::pvData::ByteBuffer* buffer, + TransportSendControl* control) + { + _codec.startMessage((int8_t)0x20, 0x00000000); + _codec.endMessage(); + } + + private: + TestCodec &_codec; + }; + + + class TransportSender2ForTestEnqueueSendRequest: + public TransportSender { + public: + + TransportSender2ForTestEnqueueSendRequest( + TestCodec & codec): _codec(codec) {} + + void unlock() { + } + + void lock() { + } + + void send(epics::pvData::ByteBuffer* buffer, + TransportSendControl* control) + { + _codec.putControlMessage((int8_t)0xEE, 0xDDCCBBAA); + } + + private: + TestCodec &_codec; + }; + + + void testEnqueueSendRequest() + { + + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + TestCodec codec(DEFAULT_BUFFER_SIZE,DEFAULT_BUFFER_SIZE); + + std::tr1::shared_ptr sender = + std::tr1::shared_ptr( + new TransportSenderForTestEnqueueSendRequest(codec)); + + std::tr1::shared_ptr sender2 = + std::tr1::shared_ptr( + new TransportSender2ForTestEnqueueSendRequest(codec)); + + // process + codec.enqueueSendRequest(sender); + codec.enqueueSendRequest(sender2); + codec.processSendQueue(); + + codec.transferToReadBuffer(); + + codec.processRead(); + + testOk(codec._invalidDataStreamCount == 0, + "%s: codec._invalidDataStreamCount == 0", + CURRENT_FUNCTION); + testOk(codec._closedCount == 0, + "%s: codec._closedCount == 0", CURRENT_FUNCTION); + testOk(codec._receivedControlMessages.size() == 1, + "%s: codec._receivedControlMessages.size() == 1 ", + CURRENT_FUNCTION); + testOk(codec._receivedAppMessages.size() == 1, + "%s: codec._receivedAppMessages.size() == 1", + CURRENT_FUNCTION); + + + // app, no payload + PVAMessage header = codec._receivedAppMessages[0]; + + testOk(header._version == PVA_VERSION, + "%s: header._version == PVA_VERSION", CURRENT_FUNCTION); + testOk(header._flags == (int8_t)((EPICS_BYTE_ORDER == EPICS_ENDIAN_BIG ? 0x80 : 0x00) | 0x00), + "%s: header._flags == 0x(0|8)0", CURRENT_FUNCTION); + testOk(header._command == (int8_t)0x20, + "%s: header._command == 0x20", CURRENT_FUNCTION); + testOk(header._payloadSize == 0x00000000, + "%s: header._payloadSize == 0x00000000", CURRENT_FUNCTION); + + // control + header = codec._receivedControlMessages[0]; + + testOk(header._version == PVA_VERSION, + "%s: header._version == PVA_VERSION", CURRENT_FUNCTION); + testOk(header._flags == (int8_t)((EPICS_BYTE_ORDER == EPICS_ENDIAN_BIG ? 0x80 : 0x00) | 0x01), + "%s: header._flags == 0x(0|8)1", CURRENT_FUNCTION); + testOk(header._command == (int8_t)0xEE, + "%s: header._command == 0xEE", CURRENT_FUNCTION); + testOk(header._payloadSize == (int32_t)0xDDCCBBAA, + "%s: header._payloadSize == 0xDDCCBBAA", CURRENT_FUNCTION); + } + + + class TransportSenderForTestEnqueueSendDirectRequest: + public TransportSender { + public: + + TransportSenderForTestEnqueueSendDirectRequest( + TestCodec & codec): _codec(codec) {} + + void unlock() { + } + + void lock() { + } + + void send(epics::pvData::ByteBuffer* buffer, + TransportSendControl* control) + { + _codec.startMessage((int8_t)0x20, 0x00000000); + _codec.endMessage(); + } + + private: + TestCodec &_codec; + }; + + + class TransportSender2ForTestEnqueueSendDirectRequest: + public TransportSender { + public: + + TransportSender2ForTestEnqueueSendDirectRequest( + TestCodec & codec): _codec(codec) {} + + void unlock() { + } + + void lock() { + } + + void send(epics::pvData::ByteBuffer* buffer, + TransportSendControl* control) + { + _codec.putControlMessage((int8_t)0xEE, + 0xDDCCBBAA); + } + + private: + TestCodec &_codec; + }; + + + void testEnqueueSendDirectRequest() + { + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + TestCodec codec(DEFAULT_BUFFER_SIZE,DEFAULT_BUFFER_SIZE); + + std::tr1::shared_ptr sender = + std::tr1::shared_ptr( + new TransportSenderForTestEnqueueSendDirectRequest(codec)); + std::tr1::shared_ptr sender2 = + std::tr1::shared_ptr( + new TransportSender2ForTestEnqueueSendDirectRequest + (codec)); + + + // thread not right + codec.enqueueSendRequest(sender, PVA_MESSAGE_HEADER_SIZE); + + + testOk(1 == codec._scheduleSendCount, + "%s: 1 == codec._scheduleSendCount", CURRENT_FUNCTION); + testOk(0 == codec._receivedControlMessages.size(), + "%s: 0 == codec._receivedControlMessages.size()", + CURRENT_FUNCTION); + testOk(0 == codec._receivedAppMessages.size(), + "%s: 0 == codec._receivedAppMessages.size()", + CURRENT_FUNCTION); + + codec.setSenderThread(); + + // not empty queue + codec.enqueueSendRequest(sender2, PVA_MESSAGE_HEADER_SIZE); + + testOk(2 == codec._scheduleSendCount, + "%s: 2 == codec._scheduleSendCount", CURRENT_FUNCTION); + testOk(0 == codec._receivedControlMessages.size(), + "%s: 0 == codec._receivedControlMessages.size()", + CURRENT_FUNCTION); + testOk(0 == codec._receivedAppMessages.size(), + "%s: 0 == codec._receivedAppMessages.size()", + CURRENT_FUNCTION); + + // send will be triggered after last + //was processed + testOk(0 == codec._sendCompletedCount, + "%s: 0 == codec._sendCompletedCount", CURRENT_FUNCTION); + + codec.processSendQueue(); + + testOk(1 == codec._sendCompletedCount, + "%s: 1 == codec._sendCompletedCount", CURRENT_FUNCTION); + + codec.transferToReadBuffer(); + + codec.processRead(); + + + testOk(codec._invalidDataStreamCount == 0, + "%s: codec._invalidDataStreamCount == 0", + CURRENT_FUNCTION); + testOk(codec._closedCount == 0, + "%s: codec._closedCount == 0", CURRENT_FUNCTION); + testOk(codec._receivedControlMessages.size() == 1, + "%s: codec._receivedControlMessages.size() == 1 ", + CURRENT_FUNCTION); + testOk(codec._receivedAppMessages.size() == 1, + "%s: codec._receivedAppMessages.size() == 1", + CURRENT_FUNCTION); + + // app, no payload + PVAMessage header = codec._receivedAppMessages[0]; + + testOk(header._version == PVA_VERSION, + "%s: header._version == PVA_VERSION", CURRENT_FUNCTION); + testOk(header._flags == (int8_t)((EPICS_BYTE_ORDER == EPICS_ENDIAN_BIG ? 0x80 : 0x00) | 0x00), + "%s: header._flags == 0x(0|8)0", CURRENT_FUNCTION); + testOk(header._command == 0x20, + "%s: header._command == 0x20", CURRENT_FUNCTION); + testOk(header._payloadSize == 0x00000000, + "%s: header._payloadSize == 0x00000000", CURRENT_FUNCTION); + + + // control + header = codec._receivedControlMessages[0]; + + testOk(header._version == PVA_VERSION, + "%s: header._version == PVA_VERSION", CURRENT_FUNCTION); + testOk(header._flags == (int8_t)((EPICS_BYTE_ORDER == EPICS_ENDIAN_BIG ? 0x80 : 0x00) | 0x01), + "%s: header._flags == 0x(0|8)1", CURRENT_FUNCTION); + testOk(header._command == (int8_t)0xEE, + "%s: header._command == 0xEE", CURRENT_FUNCTION); + testOk(header._payloadSize == (int32_t)0xDDCCBBAA, + "%s: header._payloadSize == 0xDDCCBBAA", CURRENT_FUNCTION); + + + testOk(0 == codec.getSendBuffer()->getPosition(), + "%s: 0 == codec.getSendBuffer()->getPosition()", + CURRENT_FUNCTION); + + // now queue is empty and thread is right + codec.enqueueSendRequest(sender2, PVA_MESSAGE_HEADER_SIZE); + + testOk((std::size_t)PVA_MESSAGE_HEADER_SIZE == + codec.getSendBuffer()->getPosition(), + "%s: PVA_MESSAGE_HEADER_SIZE == " + "codec.getSendBuffer()->getPosition()", + CURRENT_FUNCTION); + testOk(3 == codec._scheduleSendCount, + "%s: 3 == codec._scheduleSendCount", CURRENT_FUNCTION); + testOk(1 == codec._sendCompletedCount, + "%s: 1 == codec._sendCompletedCount", CURRENT_FUNCTION); + + codec.processWrite(); + + testOk(2 == codec._sendCompletedCount, + "%s: 2 == codec._sendCompletedCount", CURRENT_FUNCTION); + + + codec.transferToReadBuffer(); + codec.processRead(); + + testOk(codec._receivedControlMessages.size() == 2, + "%s: codec._receivedControlMessages.size() == 2 ", + CURRENT_FUNCTION); + testOk(codec._receivedAppMessages.size() == 1, + "%s: codec._receivedAppMessages.size() == 1", + CURRENT_FUNCTION); + + header = codec._receivedControlMessages[1]; + + testOk(header._version == PVA_VERSION, + "%s: header._version == PVA_VERSION", CURRENT_FUNCTION); + testOk(header._flags == (int8_t)((EPICS_BYTE_ORDER == EPICS_ENDIAN_BIG ? 0x80 : 0x00) | 0x01), + "%s: header._flags == 0x(0|8)1", CURRENT_FUNCTION); + testOk(header._command == (int8_t)0xEE, + "%s: header._command == 0xEE", CURRENT_FUNCTION); + testOk(header._payloadSize == (int32_t)0xDDCCBBAA, + "%s: header._payloadSize == 0xDDCCBBAA", CURRENT_FUNCTION); + } + + + + class TransportSenderForTestSendPerPartes: + public TransportSender { + public: + + TransportSenderForTestSendPerPartes( + TestCodec & codec, std::size_t bytesToSend): + _codec(codec), _bytesToSent(bytesToSend) {} + + void unlock() { + } + + void lock() { + } + + void send(epics::pvData::ByteBuffer* buffer, + TransportSendControl* control) + { + _codec.startMessage((int8_t)0x12, _bytesToSent); + + for (std::size_t i = 0; i < _bytesToSent; i++) + _codec.getSendBuffer()->put((int8_t)i); + + _codec.endMessage(); + } + + private: + TestCodec &_codec; + std::size_t _bytesToSent; + }; + + + void testSendPerPartes() + { + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + + std::size_t bytesToSent = + DEFAULT_BUFFER_SIZE - 2*PVA_MESSAGE_HEADER_SIZE; + + TestCodec codec(DEFAULT_BUFFER_SIZE,DEFAULT_BUFFER_SIZE); + + std::tr1::shared_ptr sender = + std::tr1::shared_ptr( + new TransportSenderForTestSendPerPartes( + codec, bytesToSent)); + + codec._readPayload = true; + + // process + codec.enqueueSendRequest(sender); + codec.processSendQueue(); + + codec.transferToReadBuffer(); + + codec.processRead(); + + testOk(codec._invalidDataStreamCount == 0, + "%s: codec._invalidDataStreamCount == 0", + CURRENT_FUNCTION); + testOk(codec._closedCount == 0, + "%s: codec._closedCount == 0", CURRENT_FUNCTION); + testOk(codec._receivedControlMessages.size() == 0, + "%s: codec._receivedControlMessages.size() == 0 ", + CURRENT_FUNCTION); + testOk(codec._receivedAppMessages.size() == 1, + "%s: codec._receivedAppMessages.size() == 1", + CURRENT_FUNCTION); + + + // app + PVAMessage header = codec._receivedAppMessages[0]; + + testOk(header._version == PVA_VERSION, + "%s: header._version == PVA_VERSION", CURRENT_FUNCTION); + testOk(header._flags == (int8_t)0x80, + "%s: header._flags == 0x80", CURRENT_FUNCTION); + testOk(header._command == (int8_t)0x12, + "%s: header._command == 0x12", CURRENT_FUNCTION); + testOk(header._payloadSize == (int32_t)bytesToSent, + "%s: header._payloadSize == bytesToSent", + CURRENT_FUNCTION); + + + testOk(header._payload.get() != 0, + "%s: msg._payload.get() != 0", CURRENT_FUNCTION); + + header._payload->flip(); + + testOk(bytesToSent == header._payload->getLimit(), + "%s: bytesToSent == header._payload->getLimit()", + CURRENT_FUNCTION); + + for (int32_t i = 0; i < header._payloadSize; i++) { + if ((int8_t)i != header._payload->getByte()) { + testFail("%s: (int8_t)%d == header._payload->getByte()", + CURRENT_FUNCTION, i); + } + } + } + + + class TransportSenderForTestSendException: + public TransportSender { + public: + + TransportSenderForTestSendException( + TestCodec & codec): _codec(codec) {} + + void unlock() { + } + + void lock() { + } + + void send(epics::pvData::ByteBuffer* buffer, + TransportSendControl* control) + { + _codec.putControlMessage((int8_t)0x01, 0x00112233); + } + + private: + TestCodec &_codec; + }; + + + void testSendException() + { + + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + TestCodec codec(DEFAULT_BUFFER_SIZE,DEFAULT_BUFFER_SIZE); + + std::tr1::shared_ptr sender = + std::tr1::shared_ptr( + new TransportSenderForTestSendException(codec)); + + codec._throwExceptionOnSend = true; + + // process + codec.enqueueSendRequest(sender); + + try + { + codec.processSendQueue(); + testFail("%s: ConnectionClosedException expected", + CURRENT_FUNCTION); + } catch (connection_closed_exception &) { + // OK + } + + testOk(codec._invalidDataStreamCount == 0, + "%s: codec._invalidDataStreamCount == 0", + CURRENT_FUNCTION); + testOk(codec._closedCount == 1, + "%s: codec._closedCount == 1", CURRENT_FUNCTION); + testOk(codec._receivedControlMessages.size() == 0, + "%s: codec._receivedControlMessages.size() == 0 ", + CURRENT_FUNCTION); + testOk(codec._receivedAppMessages.size() == 0, + "%s: codec._receivedAppMessages.size() == 0", + CURRENT_FUNCTION); + } + + + class TransportSenderForTestSendHugeMessagePartes: + public TransportSender { + public: + + TransportSenderForTestSendHugeMessagePartes( + TestCodec & codec, std::size_t bytesToSend): + _codec(codec), _bytesToSent(bytesToSend) {} + + void unlock() { + } + + void lock() { + } + + void send(epics::pvData::ByteBuffer* buffer, + TransportSendControl* control) + { + _codec.startMessage((int8_t)0x12, 0); + std::size_t toSend = _bytesToSent; + int32_t c = 0; + while (toSend > 0) + { + std::size_t sendNow = std::min(toSend, + AbstractCodec::MAX_ENSURE_BUFFER_SIZE); + + _codec.ensureBuffer(sendNow); + for (std::size_t i = 0; i < sendNow; i++) + _codec.getSendBuffer()->put((int8_t)(c++)); + toSend -= sendNow; + } + _codec.endMessage(); + } + + private: + TestCodec &_codec; + std::size_t _bytesToSent; + }; + + + class WritePollOneCallbackForTestSendHugeMessagePartes: + public WritePollOneCallback { + public: + + WritePollOneCallbackForTestSendHugeMessagePartes( + TestCodec &codec): _codec(codec) {} + + void writePollOne() { + _codec.processWrite(); // this should return immediately + + // now we fake reading + _codec._writeBuffer.flip(); + + // in this test we made sure readBuffer is big enough + while(_codec._writeBuffer.getRemaining() > 0) + { + _codec._readBuffer->putByte( + _codec._writeBuffer.getByte()); + } + + _codec._writeBuffer.clear(); + } + private: + TestCodec & _codec; + }; + + + void testSendHugeMessagePartes() + { + + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + + std::size_t bytesToSent = 10*DEFAULT_BUFFER_SIZE+1; + TestCodec codec(DEFAULT_BUFFER_SIZE,DEFAULT_BUFFER_SIZE); + + codec._readPayload = true; + codec._readBuffer.reset( + new ByteBuffer(11*DEFAULT_BUFFER_SIZE)); + + std::auto_ptr + writePollOneCallback( + new WritePollOneCallbackForTestSendHugeMessagePartes + (codec)); + + std::tr1::shared_ptr sender = + std::tr1::shared_ptr( + new TransportSenderForTestSendHugeMessagePartes( + codec, bytesToSent)); + + codec._writePollOneCallback = writePollOneCallback; + + + // process + codec.enqueueSendRequest(sender); + codec.processSendQueue(); + + codec.addToReadBuffer(); + + codec._forcePayloadRead = bytesToSent; + + codec.processRead(); + + testOk(codec._invalidDataStreamCount == 0, + "%s: codec._invalidDataStreamCount == 0", + CURRENT_FUNCTION); + testOk(codec._closedCount == 0, + "%s: codec._closedCount == 0", CURRENT_FUNCTION); + testOk(codec._receivedControlMessages.size() == 0, + "%s: codec._receivedControlMessages.size() == 0 ", + CURRENT_FUNCTION); + testOk(codec._receivedAppMessages.size() == 1, + "%s: codec._receivedAppMessages.size() == 1", + CURRENT_FUNCTION); + + // app + PVAMessage header = codec._receivedAppMessages[0]; + + testOk(header._version == PVA_VERSION, + "%s: header._version == PVA_VERSION", CURRENT_FUNCTION); + testOk(header._flags == (int8_t)((EPICS_BYTE_ORDER == EPICS_ENDIAN_BIG ? 0x80 : 0x00) | 0x10), + "%s: header._flags == (int8_t)(0x(0|8)0 | 0x10)", + CURRENT_FUNCTION); + testOk(header._command == 0x12, + "%s: header._command == 0x12", CURRENT_FUNCTION); + + testOk(header._payload.get() != 0, + "%s: msg._payload.get() != 0", CURRENT_FUNCTION); + + header._payload->flip(); + + testOk(bytesToSent == header._payload->getLimit(), + "%s: bytesToSent == header._payload->getLimit()", + CURRENT_FUNCTION); + + + for (int32_t i = 0; i < header._payloadSize; i++) { + if ((int8_t)i != header._payload->getByte()) { + testFail("%s: (int8_t)%d == header._payload->getByte()", + CURRENT_FUNCTION, i); + } + } + + } + + + void testRecipient() + { + // nothing to test, depends on implementation + } + + + class TransportSenderForTestClearSendQueue: + public TransportSender { + public: + + TransportSenderForTestClearSendQueue( + TestCodec & codec): _codec(codec) {} + + void unlock() { + } + + void lock() { + } + + void send(epics::pvData::ByteBuffer* buffer, + TransportSendControl* control) + { + _codec.startMessage((int8_t)0x20, 0x00000000); + _codec.endMessage(); + } + + private: + TestCodec &_codec; + }; + + + class TransportSender2ForTestClearSendQueue: + public TransportSender { + public: + + TransportSender2ForTestClearSendQueue( + TestCodec & codec): _codec(codec) {} + + void unlock() { + } + + void lock() { + } + + void send(epics::pvData::ByteBuffer* buffer, + TransportSendControl* control) + { + _codec.putControlMessage((int8_t)0xEE, 0xDDCCBBAA); + } + + private: + TestCodec &_codec; + }; + + + void testClearSendQueue() + { + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + TestCodec codec(DEFAULT_BUFFER_SIZE,DEFAULT_BUFFER_SIZE); + + std::tr1::shared_ptr sender = + std::tr1::shared_ptr( + new TransportSenderForTestClearSendQueue(codec)); + + std::tr1::shared_ptr sender2 = + std::tr1::shared_ptr( + new TransportSender2ForTestClearSendQueue(codec)); + + + codec.enqueueSendRequest(sender); + codec.enqueueSendRequest(sender2); + + codec.clearSendQueue(); + + codec.processSendQueue(); + + testOk(0 == codec.getSendBuffer()->getPosition(), + "%s: 0 == codec.getSendBuffer()->getPosition()", + CURRENT_FUNCTION); + testOk(0 == codec._writeBuffer.getPosition(), + "%s: 0 == codec._writeBuffer.getPosition()", + CURRENT_FUNCTION); + } + + + void testInvalidArguments() + { + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + + try + { + // too small + TestCodec codec(1,DEFAULT_BUFFER_SIZE); + testFail("%s: too small buffer accepted", + CURRENT_FUNCTION); + } catch (std::exception &) { + // OK + } + + try + { + // too small + TestCodec codec(DEFAULT_BUFFER_SIZE,1); + testFail("%s: too small buffer accepted", + CURRENT_FUNCTION); + } catch (std::exception &) { + // OK + } + + if (PVA_ALIGNMENT > 1) + { + try + { + // non aligned + TestCodec codec(2*AbstractCodec::MAX_ENSURE_SIZE+1, + DEFAULT_BUFFER_SIZE); + + testFail("%s: non-aligned buffer size accepted", + CURRENT_FUNCTION); + + } catch (std::exception &) { + // OK + } + + try + { + // non aligned + TestCodec codec(DEFAULT_BUFFER_SIZE, + 2*AbstractCodec::MAX_ENSURE_SIZE+1); + + testFail("%s: non-aligned buffer size accepted", + CURRENT_FUNCTION); + + } catch (std::exception &) { + // OK + } + } + + TestCodec codec(DEFAULT_BUFFER_SIZE, + DEFAULT_BUFFER_SIZE); + + try + { + codec.ensureBuffer(DEFAULT_BUFFER_SIZE+1); + testFail("%s: too big size accepted", + CURRENT_FUNCTION); + } catch (std::exception &) { + // OK + } + + try + { + codec.ensureData(AbstractCodec::MAX_ENSURE_DATA_SIZE+1); + testFail("%s: too big size accepted", CURRENT_FUNCTION); + } catch (std::exception &) { + // OK + } + } + + + void testDefaultModes() + { + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + + TestCodec codec(DEFAULT_BUFFER_SIZE,DEFAULT_BUFFER_SIZE); + testOk(NORMAL== codec.getReadMode(), + "%s: NORMAL== codec.getReadMode()", CURRENT_FUNCTION); + testOk(PROCESS_SEND_QUEUE == codec.getWriteMode(), + "%s: PROCESS_SEND_QUEUE == codec.getWriteMode()", + CURRENT_FUNCTION); + } + + + class TransportSenderForTestEnqueueSendRequestExceptionThrown: + public TransportSender { + public: + + TransportSenderForTestEnqueueSendRequestExceptionThrown( + TestCodec & codec): _codec(codec) {} + + void unlock() { + } + + void lock() { + } + + void send(epics::pvData::ByteBuffer* buffer, + TransportSendControl* control) + { + // after connection_closed_exception is thrown codec is no longer valid, + // however we want to do some tests and in order for memory checkers not to complain + // the following step is needed + memset((void*)buffer->getBuffer(), 0, buffer->getSize()); + + throw connection_closed_exception( + "expected test exception"); + } + + private: + TestCodec &_codec; + }; + + + class TransportSender2ForTestEnqueueSendRequestExceptionThrown: + public TransportSender { + public: + + TransportSender2ForTestEnqueueSendRequestExceptionThrown( + TestCodec & codec): _codec(codec) {} + + void unlock() { + } + + void lock() { + } + + void send(epics::pvData::ByteBuffer* buffer, + TransportSendControl* control) + { + _codec.putControlMessage((int8_t)0xEE, 0xDDCCBBAA); + } + + private: + TestCodec &_codec; + }; + + + void testEnqueueSendRequestExceptionThrown() + { + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + + TestCodec codec(DEFAULT_BUFFER_SIZE,DEFAULT_BUFFER_SIZE); + + std::tr1::shared_ptr sender = + std::tr1::shared_ptr( + new + TransportSenderForTestEnqueueSendRequestExceptionThrown + (codec)); + + std::tr1::shared_ptr sender2 = + std::tr1::shared_ptr(new + TransportSender2ForTestEnqueueSendRequestExceptionThrown( + codec)); + + + // process + codec.enqueueSendRequest(sender); + codec.enqueueSendRequest(sender2); + + try + { + codec.processSendQueue(); + testFail("%s: ConnectionClosedException expected", + CURRENT_FUNCTION); + + } catch (connection_closed_exception &) { + // OK + } + + codec.transferToReadBuffer(); + + codec.processRead(); + + testOk(codec._invalidDataStreamCount == 0, + "%s: codec._invalidDataStreamCount == 0", + CURRENT_FUNCTION); + testOk(codec._closedCount == 1, + "%s: codec._closedCount == 1", CURRENT_FUNCTION); + testOk(codec._receivedControlMessages.size() == 0, + "%s: codec._receivedControlMessages.size() == 0 ", + CURRENT_FUNCTION); + testOk(codec._receivedAppMessages.size() == 0, + "%s: codec._receivedAppMessages.size() == 0", + CURRENT_FUNCTION); + } + + + class TransportSenderForTestBlockingProcessQueueTest: + public TransportSender { + public: + + TransportSenderForTestBlockingProcessQueueTest( + TestCodec & codec): _codec(codec) {} + + void unlock() { + } + + void lock() { + } + + void send(epics::pvData::ByteBuffer* buffer, + TransportSendControl* control) + { + _codec.putControlMessage((int8_t)0x01, 0x00112233); + } + + private: + TestCodec &_codec; + }; + + + class ValueHolder { + public: + ValueHolder( + TestCodec &testCodec, + AtomicValue &processTreadExited): + _testCodec(testCodec), + _processTreadExited(processTreadExited) {} + + TestCodec &_testCodec; + AtomicValue & _processTreadExited; + }; + + + void testBlockingProcessQueueTest() + { + testDiag("BEGIN TEST %s:", CURRENT_FUNCTION); + + TestCodec codec(DEFAULT_BUFFER_SIZE, + DEFAULT_BUFFER_SIZE, true); + + _processTreadExited.getAndSet(false); + std::tr1::shared_ptr sender = + std::tr1::shared_ptr( + new TransportSenderForTestBlockingProcessQueueTest(codec)); + + ValueHolder valueHolder(codec, _processTreadExited); + + epicsThreadCreate( + "testBlockingProcessQueueTest-processThread", + epicsThreadPriorityMedium, + epicsThreadGetStackSize( + epicsThreadStackMedium), + CodecTest::blockingProcessQueueThread, + &valueHolder); + + epicsThreadSleep(3); + + testOk(_processTreadExited.get() == false, + "%s: _processTreadExited.get() == false", + CURRENT_FUNCTION); + + // let's put something into it + + codec.enqueueSendRequest(sender); + + epicsThreadSleep(1); + + testOk((std::size_t)PVA_MESSAGE_HEADER_SIZE == + codec._writeBuffer.getPosition(), + "%s: PVA_MESSAGE_HEADER_SIZE == " + "codec._writeBuffer.getPosition()", + CURRENT_FUNCTION); + + codec.endBlockedProcessSendQueue(); + + epicsThreadSleep(1); + + testOk(_processTreadExited.get() == true, + "%s: _processTreadExited.get() == true", CURRENT_FUNCTION); + } + + private: + + void static blockingProcessQueueThread(void *param) { + ValueHolder *valueHolder = static_cast(param); + // this should block + valueHolder->_testCodec.processSendQueue(); + valueHolder->_processTreadExited.getAndSet(true); + } + + AtomicValue _processTreadExited; + }; + } +} + + +using namespace epics::pvAccess; + +MAIN(testCodec) +{ + CodecTest codecTest; + return codecTest.runAllTest(); +} diff --git a/testApp/remote/testServer.cpp b/testApp/remote/testServer.cpp index 3d10b55..75ab021 100644 --- a/testApp/remote/testServer.cpp +++ b/testApp/remote/testServer.cpp @@ -1541,21 +1541,45 @@ public: } else if (m_channelName == "testSum") { - int a = pvArgument->getIntField("a")->get(); - int b = pvArgument->getIntField("b")->get(); + PVStructure::shared_pointer args( + (pvArgument->getStructure()->getID() == "uri:ev4:nt/2012/pwd:NTURI") ? + pvArgument->getStructureField("query") : + pvArgument + ); - FieldCreatePtr fieldCreate = getFieldCreate(); + const String helpText = + "Calculates a sum of two integer values.\n" + "Arguments:\n" + "\tint a\tfirst integer number\n" + "\tint b\tsecond integer number\n"; + if (handleHelp(args, m_channelRPCRequester, helpText)) + return; - StringArray fieldNames; - fieldNames.push_back("c"); - FieldConstPtrArray fields; - fields.push_back(fieldCreate->createScalar(pvInt)); - StructureConstPtr resultStructure = fieldCreate->createStructure(fieldNames, fields); + PVInt::shared_pointer pa = args->getSubField("a"); + PVInt::shared_pointer pb = args->getSubField("b"); + if (!pa || !pb) + { + PVStructure::shared_pointer nullPtr; + Status errorStatus(Status::STATUSTYPE_ERROR, "int a and int b arguments are required"); + m_channelRPCRequester->requestDone(errorStatus, nullPtr); + return; + } - PVStructure::shared_pointer result = getPVDataCreate()->createPVStructure(resultStructure); - result->getIntField("c")->put(a+b); + int a = pa->get(); + int b = pb->get(); - m_channelRPCRequester->requestDone(Status::Ok, result); + FieldCreatePtr fieldCreate = getFieldCreate(); + + StringArray fieldNames; + fieldNames.push_back("c"); + FieldConstPtrArray fields; + fields.push_back(fieldCreate->createScalar(pvInt)); + StructureConstPtr resultStructure = fieldCreate->createStructure(fieldNames, fields); + + PVStructure::shared_pointer result = getPVDataCreate()->createPVStructure(resultStructure); + result->getIntField("c")->put(a+b); + + m_channelRPCRequester->requestDone(Status::Ok, result); } else if (m_channelName.find("testServerShutdown") == 0)