diff --git a/include/echo/Network/Connection.h b/include/echo/Network/Connection.h --- a/include/echo/Network/Connection.h +++ b/include/echo/Network/Connection.h @@ -1,507 +1,587 @@ #ifndef _CONNECTION_H_ #define _CONNECTION_H_ #include #include #include #include #include #include #include #include #include namespace Echo { class DataPacket; class DataPacketHeader; class Connection; class ConnectionOwner; class NetworkManager; class Connection : public enable_shared_from_this { public: struct States { enum _ { DISCONNECTED, CONNECTING, CONNECTED }; }; typedef States::_ State; /** * PacketCallback * @param first parameter is the connection. * @param second parameter is the incoming DataPacket. */ typedef function, shared_ptr)> PacketCallback; /** * LabelledPacketCallback * @param first parameter is the connection. * @param second parameter is the data packet that contained the labelled data. * @param third is a pointer to the data in the data packet starting at the first byte after the label. The memory * is owned by the DataPacket. This is read only and should not be assumed to exist after your callback returns * unless you also make a copy of the DataPacket shared_ptr. * @param fourth the length of the data in bytes from the (the DataPacket data size minus the label+meta info size). */ typedef function, shared_ptr, const u8*, Size)> LabelledPacketCallback; /** * DisconnectCallback * @param first parameter is the connection. */ typedef function)> DisconnectCallback; /** * ConnectCallback * @param first parameter is the connection. */ typedef function)> ConnectCallback; - //These packets are used internally for control + //These packets are used internally for struct PacketTypes { enum Value { LABELLED_PACKET =0xFFFFFFFF, REMOTE_DETAILS =0xFEFEFEFE, // Remote details packet, sent internally to determine // communication details with the remote host, such as // protocol version and endian format. The ID needs to // read the same in big and little endian. + LABELLED_RESPONSE_PACKET =0xF0000002, // Labelled response packets are given this type so + // there isn't confusion when ID conflicts occur. + RESPONSE_PACKET =0xF0000001, // Response packets are given this type so there isn't + // confusion when ID conflicts occur. MINIMUM_RESERVED_PACKET_ID =0xF0000000 // Packets IDs after this value are reserved }; }; typedef PacketTypes::Value PacketType; struct PacketPoolResizeActions { enum Value{ RESIZE_ENTIRE_POOL=1, //Re-allocates enough memory for the //pool size. AFFECT_PACKET_COUNT_ONLY=2, //Attempts to avoid a reallocation //and instead just affect the number //of available packets. }; }; typedef PacketPoolResizeActions::Value PacketPoolResizeAction; static void SetPlatformBigEndian(bool isBigEndian) {mPlatformBigEndian=isBigEndian;} static bool IsPlatformBigEndian() {return mPlatformBigEndian;} NetworkManager& GetNetworkManager() {return mNetworkManager;} bool Connect(); bool Disconnect(); void SetOwner(ConnectionOwner* owner); ConnectionOwner* GetOwner() const {return mOwner;} std::string GetFriendlyIdentifier(); std::string GetLocalFriendlyIdentifier(); shared_ptr NewDataPacket(); shared_ptr NewDataPacket(u32 packetTypeID, u32 size); shared_ptr NewDataPacket(std::string label, u32 size); //Returns true if there are still packets queued to notify bool NotifyAnyReceivedPackets(); size_t GetNumReceviedPackets() const {return mReceviedPackets.size();} /** * Send a DataPacket * @param packet the packet * @param prioritise If true the packet will be sent before of any packets currently queued. */ void SendDataPacket(shared_ptr packet, PacketCallback responseCallback = PacketCallback(), bool prioritise = false, bool disconnectAfterSend = false, bool isResponsePacket = false); /** * Helper method to send data. * This method builds a DataPacket for you. * @param data The data * @param dataSize The number of bytes to send * @param packetTypeID The packet ID to send the data as. * @param prioritise If true the packet will be sent before of any packets currently queued. */ void SendData(const u8* data, u32 dataSize, u32 packetTypeID, PacketCallback responseCallback = PacketCallback(), bool prioritise = false); /** * Helper method to send a string. * @param message the message to send. * @param packetTypeID the packet ID to send the message as. * @param prioritise If true the packet will be sent before of any packets currently queued. */ void SendMessage(const std::string& message, u32 packetTypeID, PacketCallback responseCallback = PacketCallback(), bool prioritise = false); /** * Send a control packet. - * A control packet has an ID and no data. Control packets are designed to be - * used for notification. + * A control packet has an ID and no data. Control packets are designed to be used for notification. + * @note You cannot send a control packet as response because the ID will be set to the response ID. * @param packetTypeID the ID of the packet you want to send. * @param prioritise If true the packet will be sent before of any packets currently queued. */ void SendControlPacket(u32 packetTypeID, PacketCallback responseCallback = PacketCallback(), bool prioritise = false); /** * Helper method to send a labelled DataPacket. * @param prioritise If true the packet will be sent before of any packets currently queued. */ void SendLabelledPacket(const std::string& label, const u8* data, u32 dataSize, PacketCallback responseCallback = PacketCallback(), bool prioritise = false); /** * Helper method to send a labeled DataPacket. * @param prioritise If true the packet will be sent before of any packets currently queued. */ template< typename T> void SendLabelledPacket(const std::string& label, const T& content, PacketCallback responseCallback = PacketCallback(), bool prioritise = false) { //TODO: try and catch. std::string contentAsString=boost::lexical_cast(content); SendLabelledPacket(label,reinterpret_cast(contentAsString.c_str()),contentAsString.length(),responseCallback,prioritise); } /** * Helper method to send a labeled DataPacket with a string as the content. * @param prioritise If true the packet will be sent before of any packets currently queued. */ void SendLabelledPacket(const std::string& label, const std::string& content, PacketCallback responseCallback = PacketCallback(), bool prioritise = false); /** * Helper method to send a labeled DataPacket with multiple strings as the content. * @param prioritise If true the packet will be sent before of any packets currently queued. */ void SendLabelledPacket(const std::string& label, const std::vector& content, PacketCallback responseCallback = PacketCallback(), bool prioritise = false); + /** + * Send a response DataPacket + * @note The PacketType is overridden to allow the to be tracked as a response correctly. + * @param packetRespondingTo The packet you are responding to. + * @param packet The packet you want to send + * @param prioritise If true the packet will be sent before of any packets currently queued. + * @param disconnectAfterSend If true disconnect the Connection after the packet is sent. + */ void SendDataPacketResponse(shared_ptr packetRespondingTo, shared_ptr packet, bool prioritise = false, bool disconnectAfterSend = false); - void SendDataResponse(shared_ptr packetRespondingTo, const u8* data, u32 dataSize, u32 packetTypeID, bool prioritise = false); - void SendMessageResponse(shared_ptr packetRespondingTo, const std::string& message, u32 packetTypeID, bool prioritise = false); - void SendControlPacketResponse(shared_ptr packetRespondingTo, u32 packetTypeID, bool prioritise = false); - void SendLabelledPacketResponse(shared_ptr packetRespondingTo, const std::string& label, const u8* data, u32 dataSize, bool prioritise = false); + + /** + * Send data as a response to a DataPacket. + * @param packetRespondingTo The packet you are responding to. + * @param data The data + * @param dataSize The number of bytes to send + * @param prioritise If true the packet will be sent before of any packets currently queued. + * @param disconnectAfterSend If true disconnect the Connection after the packet is sent. + */ + void SendDataResponse(shared_ptr packetRespondingTo, const u8* data, u32 dataSize, bool prioritise = false, bool disconnectAfterSend = false); + + /** + * Send a message as a response to a DataPacket + * @param packetRespondingTo The packet you are responding to. + * @param message The message to send + * @param prioritise If true the packet will be sent before of any packets currently queued. + * @param disconnectAfterSend If true disconnect the Connection after the packet is sent. + */ + void SendMessageResponse(shared_ptr packetRespondingTo, const std::string& message, bool prioritise = false, bool disconnectAfterSend = false); + + /** + * Build and send a labelled packet as a response to a DataPacket. + * @param packetRespondingTo The packet you are responding to. + * @param label The label of the packet to send. + * @param data The data + * @param dataSize The number of bytes to send + * @param prioritise If true the packet will be sent before of any packets currently queued. + * @param disconnectAfterSend If true disconnect the Connection after the packet is sent. + */ + void SendLabelledPacketResponse(shared_ptr packetRespondingTo, const std::string& label, const u8* data, u32 dataSize, bool prioritise = false, bool disconnectAfterSend = false); + + /** + * Build and send a labelled packet with content converted to a string as a response to a DataPacket. + * @param packetRespondingTo The packet you are responding to. + * @param label The label of the packet to send. + * @param content The content to convert to a string then send. + * @param prioritise If true the packet will be sent before of any packets currently queued. + * @param disconnectAfterSend If true disconnect the Connection after the packet is sent. + * @return true if the content could be converted to a string and it was queued to send. false indicates the conversion failed. + */ template< typename T> - void SendLabelledPacketResponse(shared_ptr packetRespondingTo, const std::string& label, const T& content, bool prioritise = false) + bool SendLabelledPacketResponse(shared_ptr packetRespondingTo, const std::string& label, const T& content, bool prioritise = false, bool disconnectAfterSend = false) { - //TODO: try and catch. - std::string contentAsString=boost::lexical_cast(content); - SendLabelledPacketResponse(packetRespondingTo, label,reinterpret_cast(contentAsString.c_str()),contentAsString.length(),prioritise); + try + { + std::string contentAsString=boost::lexical_cast(content); + SendLabelledPacketResponse(packetRespondingTo, label,reinterpret_cast(contentAsString.c_str()),contentAsString.length(),prioritise,disconnectAfterSend); + return true; + }catch(...) + { + } + return false; } - void SendLabelledPacketResponse(shared_ptr packetRespondingTo, const std::string& label, const std::string& content, bool prioritise = false); - void SendLabelledPacketResponse(shared_ptr packetRespondingTo, const std::string& label, const std::vector& content, bool prioritise = false); + + /** + * Build and send a labelled packet with a string included in the data as a response to a DataPacket. + * This is similar to responding with a message, except the packet is sent as a labelled packet response so + * the label is preserved. + * @param packetRespondingTo The packet you are responding to. + * @param label The label of the packet to send. + * @param content The string content to send. + * @param prioritise If true the packet will be sent before of any packets currently queued. + * @param disconnectAfterSend If true disconnect the Connection after the packet is sent. + */ + void SendLabelledPacketResponse(shared_ptr packetRespondingTo, const std::string& label, const std::string& content, bool prioritise = false, bool disconnectAfterSend = false); + + /** + * Build and send a labelled packet with multiple strings included in the data as a response to a DataPacket. + * @param packetRespondingTo The packet you are responding to. + * @param label The label of the packet to send. + * @param content The strings to send. + * @param prioritise If true the packet will be sent before of any packets currently queued. + * @param disconnectAfterSend If true disconnect the Connection after the packet is sent. + */ + void SendLabelledPacketResponse(shared_ptr packetRespondingTo, const std::string& label, const std::vector& content, bool prioritise = false, bool disconnectAfterSend = false); /** * Attempts to receive data. * Data received will be used to build DataPacket objects. When a full packet * is received it will be added to the list of received packets. The number of * packets available will be returned. * This method will call _recv() then pass the return value into * _HandleRecvError(). If _HandleRecvError() returns false and the _recv() * return value is less than 0 then that value will be returned from this method, * otherwise -1 will used to indicate an error. Positive return values from this * method indicate the number of packets available. * @note You do not normally need to call this method manually. The NetworkSystem * managing the connection will call it and deal with firing callbacks. * @note Error codes are system specific, which is why the handle methods are used. * @return less than 0 on error, otherwise the number of complete packets available. */ int Receive(); /** * Attempts to send data. * This method will call _send() then pass the return value into _HandleWriteError(). * @note You do not normally need to call this method manually. * @note Error codes are system specific, which is why the handle methods are used. */ void Write(bool reenable); /** * Used to change the state of the connection flag. * @note This is used by network systems and should not be used in attempt to connect * or disconnect. Use the dedicated methods for that which will perform the desired * action. * @parm state The new state, if it is not connected the can send flag is set to false. */ void SetState(State state); bool IsConnected() const {return (mState==States::CONNECTED);} /** * Get whether the connection is in a connecting state or not. */ bool GetConnecting() const {return (mState==States::CONNECTING);} const ConnectionDetails& GetConnectionDetails() const { return mConnectionDetails; } void SetConnectionDetails(const ConnectionDetails& val) { mConnectionDetails = val; } const ConnectionDetails& GetLocalConnectionDetails() const { return mLocalConnectionDetails; } void SetLocalConnectionDetails(const ConnectionDetails& val) { mLocalConnectionDetails = val; } /** * Register a Packet Callback method for a labelled packet. * Callbacks can be registered for incoming packets which will be called when the * incoming packet is identified as a labelled packet. * @param label The label in which to match a labelled incoming packet. * @param callback The callback to call if the label matches the packet label. */ void RegisterLabelledPacketCallback(const std::string& label, LabelledPacketCallback callback); /** * Register a Packet Callback method for a packet. * Callbacks can be registered for incoming packets which will be called when the * incoming packet has the specified ID. This allows you to register callbacks for * packets with different ids and not have to handle the use of the packet in * ReceivedPacket. * @note Some packet IDs are reserved, see PacketTypes for more information. * @param packetTypeID The id in which to match with the id of an incoming packet. * @param callback The callback to call if the id matches the packet id. */ void RegisterPacketCallback(u32 packetTypeID, PacketCallback callback); /** * Register a "Connect" Callback method with an identifier. * @note The callback will only be called if the callback is registered before the connection event. * @note To remove the callback you need to reference the identifier. This is because boost functions cannot be compared. * @param identifier The identifier in which to reference the registration. * @param callback The callback to call when the connection connects. */ void RegisterConnectCallback(const std::string& identifier, ConnectCallback callback); /** * Register a "Disconnect" Callback method with an identifier. * @note To remove the callback you need to reference the identifier. This is because boost functions cannot be compared. * @param identifier The identifier in which to reference the registration. * @param callback The callback to call when the connection connects. */ void RegisterDisconnectCallback(const std::string& identifier, DisconnectCallback callback); /** * Clear all labelled packet callbacks with corresponding label. * @param label */ void ClearLabelledPacketCallbacks(const std::string& label); /** * Clear all labelled packet callbacks. */ void ClearAllLabelledPacketCallbacks(); /** * Clear all packet callbacks for corresponding packet ID. * @param packetTypeID */ void ClearPacketIDCallbacks(u32 packetTypeID); /** * Clear all packet id callbacks that were registered with RegisterPacketCallback(u32,PacketCallback) */ void ClearAllPacketIDCallbacks(); /** * Clear all labelled and packet ID callbacks. */ void ClearAllPacketCallbacks(); /** * Clear connect callbacks corresponding to identifier. * @param identifier */ void ClearConnectCallbacks(const std::string& identifier); /** * Clear all of the connect callbacks. */ void ClearAllConnectCallbacks(); /** * Clear disconnect callbacks corresponding to identifier. * @param identifier */ void ClearDisconnectCallbacks(const std::string& identifier); /** * Clear all of the disconnect callbacks. * @param attemptReconnet */ void ClearAllDisconnectCallbacks(); /** * Clear all callbacks. */ inline void ClearAllCallbacks() { ClearAllPacketCallbacks(); ClearAllConnectCallbacks(); ClearAllDisconnectCallbacks(); } + /** + * Set whether this connection should automatically reconnect. + * @see SetAutoAttemptReconnectTime() for information on making this work for non-regular Kernel updates. + * @param attemptReconnet true if this Connection should attempt to reconnect after the reconnect time. + */ void SetAutoAttemptReconnect(bool attemptReconnet){mAutoAttemptReconnect=attemptReconnet;} bool GetAutoAttemptReconnect() const {return mAutoAttemptReconnect;} + /** * Set the auto reconnect time. * @note The Connection will configure a CountDownTimer to process the reconnect. CountDownTimers * are Tasks so they are updated during Kernel updates. This means that if an ExecutionModel that * does not update regularly is in use (such as the NetworkExecutionModel) the auto reconnect time * may turn out to be longer than what is configured in the Connection. * @param seconds how long to wait before attempting to reconnect if the connection has disconnected. */ void SetAutoAttemptReconnectTime(Seconds seconds); /** * This method will be called internally or by a network system to disconnect the connection. * If the connection is connected then this method at the very minimum should: * - Set the connection into the disconnected state using SetConnected(false) then; * - call mManager.UpdateConnect(shared_from_this()) to notify the manager to process * the dropped connection. * @return true if the connection changed state to disconnect otherwise false the connection * is already disconnected or if something prevents the connection from disconnecting. */ virtual bool _Disconnect()=0; /** * Check whether or not the remote host is big endian. * @return true if the remote host is big endian, otherwise false to indicate little endian. */ bool IsRemoteBigEndian() const {return mIsRemoteBigEndian;} /** * Set whether or not to discard the data packet queue upon disconnect. * If false (default), any packets queued will be sent when the connection reestablishes. * If true, upon disconnect the data packet queue will be discarded. * If true, packets are discarded after the disconnect callbacks are called. */ void SetDiscardDataPacketQueueOnDisconnect(bool discard){mDiscardDataPacketQueueOnDisconnect=discard;} /** * Get whether or not to discard the data packet queue upon disconnect. */ bool GetDiscardDataPacketQueueOnDisconnect() const {return mDiscardDataPacketQueueOnDisconnect;} /** * Set whether or not to queue DataPackets if the connection is not connected. * By default, DataPackets are added to the send queue and are sent when the connection is established. * If you disable this, calling one of the Send* methods will silently discard the data. */ void SetQueueDataPacketsIfNotConnected(bool queue){mQueueDataPacketsIfNotConnected=queue;} /** * Get whether or not to queue DataPackets if the connection isn't connected. */ bool GetQueueDataPacketsIfNotConnected() const {return mQueueDataPacketsIfNotConnected;} /** * Set the temporary buffer size. * This method reallocates the temporary buffer to the specified size. * When a connection is created the temporary buffer takes the size determined by * NetworkManager::GetNewConnectionBufferSize(). In some situations the temporary buffer * size might need to be resized. The temp buffer size doesn't restrict the DataPacket * size, instead it determines how much data can be processed in one block. * @param size The new size in bytes. */ void SetTempBufferSize(Size sizeInBytes); protected: static bool mPlatformBigEndian; NetworkManager& mNetworkManager; bool mDiscardDataPacketQueueOnDisconnect; bool mQueueDataPacketsIfNotConnected; u32 mNextPacketID; //Only the NetworkManager can clean up a connection friend class NetworkManager; Connection(NetworkManager& manager); virtual ~Connection(); /** * Called by NetworkManager after receiving remote details. */ void SetRemoteBigEndian(bool isBigEndian) {mIsRemoteBigEndian = isBigEndian;} /** * Process the remote details packet after initial connection. */ void OnRemoteDetails(shared_ptr connection, shared_ptr dataPacket); void SendHostDetails(); shared_ptr mHeaderPacket; shared_ptr mCurrentPacket; ConnectionOwner* mOwner; unique_ptr mTempBuffer; Size mTempBufferSize; State mState; bool mCanSend; bool mAutoAttemptReconnect; /// If true will attempt to automatically reconnect if the connection drops. bool mManualDisconnect; /// Used to determine whether we should automatically reconnect. Mutex mQueuedPacketsMutex; Mutex mReceviedPacketsMutex; std::list< std::pair< shared_ptr, bool > > mQueuedPackets; std::list< shared_ptr > mReceviedPackets; s32 mQueuedData; //Count of queued Data only packets bool mHeaderSent; //Flag: concerned with mQueuedPackets bool mIsRemoteBigEndian; /** * Normally the implementation is just a wrapper around a send method. * The method should be non-blocking. * @return the number of bytes that have been sent or if there was an error, * an error code less than 0. */ virtual int _send(const u8 * buf, int len, int flags)=0; /** * Normally the implementation is just a wrapper around a receive method. * The method should be non-blocking. * @return the number of bytes that have been recevied or if there was an error, * an error code less than 0. */ virtual int _recv(u8 * buf, int len, int flags)=0; /** * Handle errors from the call to _recv(). * The implementation should call _Disconnect() if there was an error that results * in the connection becoming disconnected. */ virtual bool _HandleRecvError(int code)=0; /** * Handle errors from the call to _send(). * The implementation should call _Disconnect() if there was an error that results * in the connection becoming disconnected. */ virtual bool _HandleWriteError(int code)=0; virtual void _notifyOwner(shared_ptr packet); /** * Called to process dropped connections and notify the connection owner. * @note This method is called by the NetworkManager. */ virtual void _dropped(); /** * Called to process dropped connections and notify the connection owner. * @note This method is called by the NetworkManager when a connection is established. */ virtual void _established(); /** * Called to start a connection attempt. * @return false if there were problems, otherwise true. */ virtual bool _Connect()=0; ConnectionDetails mConnectionDetails; ConnectionDetails mLocalConnectionDetails; CountDownTimer mReconnectTimer; static const u32 mUnreasonableDataSize; //Data size we consider to be unreasonable. private: std::map< u32, PacketCallback > mResponseCallbacks; std::map< u32, std::vector > mPacketCallbacks; std::map< std::string, std::vector > mLabelledPacketCallbacks; std::map< std::string, std::vector > mConnectCallbacks; std::map< std::string, std::vector > mDisconnectCallbacks; void ProcessReceivedPacket(shared_ptr packet); void ProcessLabelledPacket(shared_ptr connection, shared_ptr packet); }; } #endif diff --git a/include/echo/Network/DataPacket.h b/include/echo/Network/DataPacket.h --- a/include/echo/Network/DataPacket.h +++ b/include/echo/Network/DataPacket.h @@ -1,253 +1,269 @@ #ifndef _DATAPACKET_H_ #define _DATAPACKET_H_ #include #include #include #include namespace Echo { class Connection; class DataPacket; class DataPacketHeader { private: friend class Connection; // Data is sent/received by two hosts as the header. //[0] - Packet Type ID Custom field for packet type //[1] - Packet ID //[2] - Length of data to come u32 mData[3]; bool mIsBigEndian; void Reorder(); //Reorders the header data from one endian to the other public: DataPacketHeader(); bool BuildFromPacketData(const DataPacket& packet); void BuildForPacket(const DataPacket& packet); const bool IsBigEndian() const {return mIsBigEndian;} inline u32* GetHeaderData() {return mData;} inline u32 GetHeaderDataSizeInBytes() {return SizeOfArray(mData)*sizeof(u32);} inline u32 GetPacketID() const {return mData[1];} inline u32 GetPacketTypeID() const {return mData[0];} inline void SetPacketTypeID(u32 id) {mData[0]=id;} inline u32 GetDataLength() const {return mData[2];} }; class DataPacket { protected: friend class Connection; u32 mPacketTypeID; //Four bytes to specify the packet type - this is a custom field u32 mPacketID; //Four bytes for the ID of the packet, this is assigned internally //and used for response callback processing. u32 mSize; //Size of data u32 mReceived; //Record of number of bytes received bool mIsBigEndian; u8* mData; //The data public: const static size_t NUMBYTES_FOR_STRING_HEADER; class SizeExceedsMaximumAllowedException{}; DataPacket(); DataPacket(u32 packetTypeID, u32 size); DataPacket(const std::vector< std::string >& content); DataPacket(const std::string& content); DataPacket(const DataPacketHeader& header); - DataPacket(DataPacket& packet); + DataPacket(const DataPacket& packet); virtual ~DataPacket(); /** * Ensure the order of the given data. * This method will compare the packet endian format against the platform format. * The returned result is either the data passed in or the reordered value. */ inline u32 EnsureOrder(u32 data) const { #ifdef ECHO_LITTLE_ENDIAN if(mIsBigEndian) { u32 b1,b2,b3,b4; b1=data & 0xFF000000; b2=data & 0x00FF0000; b3=data & 0x0000FF00; b4=data & 0x000000FF; u32 reordered=(b1>>24) | (b2>>8) | (b3<<8) | (b4<<24); return reordered; } #else if(!mIsBigEndian) { u32 b1,b2,b3,b4; b1=data & 0xFF000000; b2=data & 0x00FF0000; b3=data & 0x0000FF00; b4=data & 0x000000FF; u32 reordered=(b1>>24) | (b2>>8) | (b3<<8) | (b4<<24); return reordered; } #endif return data; } inline u16 EnsureOrder(u16 data) { #ifdef ECHO_LITTLE_ENDIAN if(mIsBigEndian) { u16 b1,b2; b1=data & 0xFF00; b2=data & 0x00FF; u32 reordered=(b1>>8) | (b2<<8); return reordered; } #else if(!mIsBigEndian) { u16 b1,b2; b1=data & 0xFF00; b2=data & 0x00FF; u32 reordered=(b1>>8) | (b2<<8); return reordered; } #endif return data; } //Alternative method for setting up a DataPacket is to create //with the default constructor then call Configure. This is //designed to be used by Connection. void Configure(u32 packetTypeID, u32 size); void Configure(const std::string& content); void Configure(const std::vector< std::string >& content); void Configure(const std::string& label, const std::vector< std::string >& content); void Configure(const std::string& label, const std::string& content); void Configure(const std::string& label, u32 size); void Configure(const DataPacketHeader& header, bool hasData=true); /** * Configure the data packet with a data buffer and size. * @note Use SetPacketTypeID() to set the packet type ID. * @note If you want your packet to be a labelled packet the labelled needs to exist at * the start of the buffer. Use AppendString(std::string,u8*,u32,u32) first. * @param dataBuffer The data packet that the packet will taken ownership of * @param size the size of the DataPacket. This has to be less than or equal to the size of * the passed buffer. The size is the amount of data that will be sent. */ void ConfigureAndTakeData(u8* dataBuffer, u32 size, u32 emptySpaceAvailable); void SetBigEndian(bool isBigEndian) {mIsBigEndian=isBigEndian;} bool IsBigEndian() const {return mIsBigEndian;} void SetPacketTypeID(u32 packetTypeID) {mPacketTypeID=packetTypeID;} u32 GetPacketTypeID() const {return mPacketTypeID;} - u32 GetPacketID() const {return mPacketID;} + + /** + * The packet ID is is used by Connection to manage packet responses. + * Connection will assign a packetID to a DataPacket which the remote end can use and assign + * to another packet which is then used to identify that packet as the response. Typically + * client code will not modify packet id's unless you are forwarding a packet to another + * destination, for which you would need to save then restore the packet ID of the packet + * being forwarded before you send a response to the originating connection. + */ + void SetPacketID(u32 packetD) {mPacketID=packetD;} + u32 GetPacketID() const {return mPacketID;} u32 AppendData(const void* data, u32 size); //Returns the number of bytes appended inline bool HasReceivedAllData() const {return (mSize==mReceived);} inline u32 GetReceivedDataSize() const {return mReceived;} inline u32 GetRemainingDataSize() const {return (mSize-mReceived);} inline u32 GetDataSize() const {return mSize;} inline u8* GetData() {return mData;} /** * Get a value by accessing the packet data as though it is an array of the specified type. * @note It is very important that you check that you are not going to try and access past * the end of the data. * @param index The index of the data as though the data was an array of the specified type, * this is NOT a byte offset. To access a value at a given byte use GetAtByte() * @param defaultValue The default value if there isn't enough data to retreive the type. * @param skipLabel If the packet is a labelled packet, whether or not to skip the label. * @return The data at the index as though the data was a single array of T. */ template inline T Get(Size index, T defaultValue, bool skipLabel=true) const { if(skipLabel && IsLabelledPacket()) { return GetAtByte(index * sizeof(T), skipLabel); } if(index < GetDataSize()/sizeof(T)) { return reinterpret_cast(mData)[index]; } return defaultValue; } /** * Get a value at the specified byte. * @note It is very important that you check that you are not going to try and access past * the end of the data. * @return The data at the specified byte interpreted as T. */ template inline T GetAtByte(Size byteOffset, T defaultValue, bool skipLabel=true) const { if(skipLabel && IsLabelledPacket()) { Size labelEnd = 0; GetLabel(&labelEnd); byteOffset+=labelEnd; } if(byteOffset < (GetDataSize() - sizeof(T))) { return *reinterpret_cast(&mData[byteOffset]); } return defaultValue; } inline u8* GetCurrentWritePointer() {return &mData[mReceived];} inline bool SendHeaderOnly() const {return (mData==0);} //Store a string from an std::string. Format will be bytesPerChar(2)length(2)content(length). //If there is not enough space remaining to append the string it is not appended at all and //false is returned. Otherwise true. bool AppendString(const std::string& s); /** * Append a string to a buffer using the internal send format. * @param s the string to append. * @param buffer The buffer to append the string to. * @param offset The position in the buffer to put the string * @param totalSize The total size of the buffer. * @return false if there is not enough space available to append the string, otherwise true. */ static bool AppendString(const std::string& s, u8* buffer, u32 offset, u32 totalSize); /** * Get a string from a data packet using the built in format * @param outString A string to hold the value on success, it will be empty on failure. * @param dataOffset An offset in the DataPacket to read from. If the packet is a labelled packet * the offset is past the label. * @param outDataStartOffset If set and the method succeeds, will contain the offset in the DataPacket * that points to after the string. * @param skipLabel If true and the packet is PacketTypes::LABELLED_PACKET the label will be skipped. * @return true on success, false if there was a problem processing the data (e.g. incorrect format) */ bool GetStringFromDataPacket(std::string& outString, Size dataOffset=0, Size* outDataStartOffset=nullptr, bool skipLabel=true) const; /** * Get std::strings from a data packet using the built in format * If the packet is PacketTypes::LABELLED_PACKET the label will be skipped. * @param outStrings The vector to write to, the container has all strings added to it but is not cleared before * the method begins. Strings may be added to the vector even if the method returns false, if there is an error * processing one of the strings in a multi-string packet. * @param dataOffset The offset in the packet. * @param skipLabel If true and the packet is PacketTypes::LABELLED_PACKET the label will be skipped. * @return false if there were problems processing the content or there weren't any strings. */ bool GetStringFromDataPacket(std::vector& outStrings, Size dataOffset=0, bool skipLabel=true) const; /** * Determine whether the DataPacket a labelled packet or not. */ bool IsLabelledPacket() const; + + /** + * Determine whether the DataPacket a response packet. + * Response packets are assigned a response PacketType and not processed through registered callbacks. + */ + bool IsResponsePacket() const; /** * Get the packet label. * @param labelEnd if not null will have the position of the next byte after the label. * @return the label or if the packet is not a labelled type the string will be empty. */ std::string GetLabel(Size* labelEnd = nullptr) const; }; } #endif diff --git a/src/Network/Connection.cpp b/src/Network/Connection.cpp --- a/src/Network/Connection.cpp +++ b/src/Network/Connection.cpp @@ -1,745 +1,742 @@ #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Echo { #ifdef ECHO_BIG_ENDIAN bool Connection::mPlatformBigEndian=true; #else bool Connection::mPlatformBigEndian=false; #endif const u32 Connection::mUnreasonableDataSize=0x00A00000; //10MiB for a packet HUGE really. Connection::Connection(NetworkManager& manager) : mNetworkManager(manager) { mState=States::DISCONNECTED; mQueuedData=0; mCanSend=false; mAutoAttemptReconnect = false; mQueueDataPacketsIfNotConnected = true; mDiscardDataPacketQueueOnDisconnect = false; SetAutoAttemptReconnectTime(Seconds(5.)); mReconnectTimer.AddTimeoutFunction(bind(&Connection::Connect,this),"Connection::Connect"); mOwner=0; mHeaderSent=false; // This is updated when a PacketTypes::REMOTE_DETAILS is received, which is queued to send first when a connection is established. mIsRemoteBigEndian = false; mTempBuffer = nullptr; mTempBufferSize = 0; DataPacketHeader header; mHeaderPacket=shared_ptr(new DataPacket(0,header.GetHeaderDataSizeInBytes())); RegisterPacketCallback(PacketTypes::LABELLED_PACKET,bind(&Connection::ProcessLabelledPacket,this,_1,_2)); RegisterPacketCallback(PacketTypes::REMOTE_DETAILS, bind(&Connection::OnRemoteDetails,this,_1,_2)); SetTempBufferSize(manager.GetNewConnectionBufferSize()); } Connection::~Connection() { } void Connection::SetTempBufferSize(Size sizeInBytes) { ECHO_ASSERT_NOT_NULL(sizeInBytes); mTempBuffer.reset(new u8[sizeInBytes]); mTempBufferSize = sizeInBytes; } void Connection::SetOwner(ConnectionOwner* receiver) { mOwner=receiver; } void Connection::SetState(State state) { mState=state; //Disable sending if we aren't connected. if(mState!=States::CONNECTED) { mCanSend = false; }else { SendHostDetails(); Write(true); } } shared_ptr Connection::NewDataPacket() { return mNetworkManager.NewDataPacket(); } shared_ptr Connection::NewDataPacket(u32 packetTypeID, u32 size) { return mNetworkManager.NewDataPacket(packetTypeID,size); } shared_ptr Connection::NewDataPacket(std::string label, u32 size) { return mNetworkManager.NewDataPacket(label,size); } void Connection::_notifyOwner(shared_ptr packet) { if(mOwner) { mOwner->ReceivedPacket(shared_from_this(), packet); } } void Connection::_dropped() { SetState(States::DISCONNECTED); if(mOwner) { mOwner->ConnectionDrop(shared_from_this()); } shared_ptr connection = shared_from_this(); //Make a copy of the callbacks because they might want to remove from mDisconnectCallbacks. std::map< std::string, std::vector > disconnectCallbacks = mDisconnectCallbacks; typedef std::pair< const std::string, std::vector > IdentifierDisconnectPair; BOOST_FOREACH(IdentifierDisconnectPair& idp, disconnectCallbacks) { BOOST_FOREACH(DisconnectCallback& callback, idp.second) { callback(connection); } } if(mDiscardDataPacketQueueOnDisconnect) { ScopedLock lock(mQueuedPacketsMutex); mQueuedPackets.clear(); }else { ScopedLock lock(mQueuedPacketsMutex); // Make sure the head packet is reset for resending if(!mQueuedPackets.empty()) { mQueuedPackets.front().first->mReceived = mQueuedPackets.front().first->mSize; } } //Attempt to reconnect if(mAutoAttemptReconnect) { if(!mNetworkManager.HasTask(mReconnectTimer)) { mNetworkManager.AddTask(mReconnectTimer); } mReconnectTimer.Reset(); } } void Connection::_established() { if(mOwner) { mOwner->ConnectionEstablished(shared_from_this()); } shared_ptr connection = shared_from_this(); typedef std::pair< const std::string, std::vector > IdentifierConnectPair; BOOST_FOREACH(IdentifierConnectPair& icp, mConnectCallbacks) { BOOST_FOREACH(ConnectCallback& callback, icp.second) { callback(connection); } } } bool Connection::Connect() { if(!IsConnected()) { mReconnectTimer.Pause(); mNetworkManager.RemoveTask(mReconnectTimer); mManualDisconnect=false; //Reset this to allow auto connect to work. return _Connect(); } return false; } bool Connection::Disconnect() { if(IsConnected()) { mManualDisconnect=true; //This was a deliberate disconnect, this stops allow auto connecting. return _Disconnect(); } return false; } int Connection::Receive() { assert(mHeaderPacket); s32 bytesReceived=0; //If there isn't a current packet we need to extract header data bytesReceived=_recv(mTempBuffer.get(),mTempBufferSize,0); if(!_HandleRecvError(bytesReceived)) { if(bytesReceived<0) { return bytesReceived; } //Revert to the default error code. return -1; } mNetworkManager.ReportReceivedData(bytesReceived); //ECHO_LOG_DEBUG("0x" << std::hex << this << std::dec << "Recv: " << mTempBuffer << ":" << bytesReceived); u8* bufferStart=mTempBuffer.get(); while(bytesReceived>0) { //ECHO_LOG_DEBUG("bufferStart: " << bufferStart << ":" << bytesReceived); u32 headerBytes=0; //ECHO_LOG_DEBUG(bytesReceived << " bytes"); if(!(mHeaderPacket->HasReceivedAllData())) //We have a valid header { //ECHO_LOG_DEBUG("I'll try and make a header for you"); //Add the received data to the header packet u32 remainingBytes=mHeaderPacket->GetRemainingDataSize(); if(remainingBytes>(u32)bytesReceived) headerBytes=bytesReceived; else headerBytes=remainingBytes; //ECHO_LOG_DEBUG("Appending " << headerBytes << " bytes"); mHeaderPacket->AppendData(bufferStart,headerBytes); if(mHeaderPacket->HasReceivedAllData()) { //ECHO_LOG_DEBUG("Found enough data for a full header..."; DataPacketHeader header; if(!header.BuildFromPacketData(*mHeaderPacket)) { ECHO_LOG_ERROR("Failed to build packet header from incoming data"); mHeaderPacket->mReceived=0; Disconnect(); return GetNumReceviedPackets(); } //Determine if the packet size is reasonable. //If it is then get rid of that incoming data if(header.GetDataLength()<=mUnreasonableDataSize) { mCurrentPacket=NewDataPacket(); mCurrentPacket->Configure(header); }else { ECHO_LOG_ERROR("DataPacket length was too large: " << header.GetDataLength() << " when maximum is " << mUnreasonableDataSize); mHeaderPacket->mReceived=0; Disconnect(); return GetNumReceviedPackets(); } } bytesReceived-=headerBytes; //For next part bufferStart+=headerBytes; } if(mCurrentPacket) { ///ECHO_LOG_DEBUG("Constructing Packet..."); if(bytesReceived>0) { //ECHO_LOG_DEBUG("Appending " << headerBytes << " bytes"); u32 bytesAppended=0; bytesAppended=mCurrentPacket->AppendData(bufferStart, bytesReceived); u32 remaining=bytesReceived-bytesAppended; if(remaining>0) { bufferStart+=bytesAppended; //There is another packet waiting } bytesReceived-=bytesAppended; } if(mCurrentPacket->HasReceivedAllData()) { //ECHO_LOG_DEBUG("Packet Constructed!"); //ECHO_LOG_DEBUG("Notifying Owner..."); ProcessReceivedPacket(mCurrentPacket); mCurrentPacket.reset(); mHeaderPacket->mReceived=0; } } } return GetNumReceviedPackets(); } void Connection::Write(bool reenable) { // reenable is true when the NetworkSystem changes the connections state appropriately // or calls Write(true) explicitly to update the write state. if(!reenable) { // If we can't send or acquire a lock, we need to bail. The latter means something else is writing. if(!mCanSend || !mQueuedPacketsMutex.AttemptLock()) { return; } }else { mQueuedPacketsMutex.Lock(); } BOOST_SCOPE_EXIT(&mQueuedPacketsMutex) { mQueuedPacketsMutex.Unlock(); } BOOST_SCOPE_EXIT_END mCanSend=false; //ECHO_LOG_DEBUG(mQueuedPackets.size()); //if(!mSendingData) s32 totalDataSent=0; while(!(mQueuedPackets.empty())) { shared_ptr packet=mQueuedPackets.front().first; if(packet) { //ECHO_LOG_DEBUG(packet->GetData()); //ECHO_LOG_DEBUG("Connection::Write() - mCanSend==true"); //ECHO_LOG_DEBUG("Connection::Write():Packet:0x" << std::hex << packet->GetPacketTypeID() << std::dec); if(packet->HasReceivedAllData() && !mHeaderSent) { DataPacketHeader header; header.BuildForPacket(*packet); //ECHO_LOG_DEBUG("0x" << std::hex << this << std::dec << ": Send Header"); s32 bytesSent=_send((const u8*)header.GetHeaderData(),header.GetHeaderDataSizeInBytes(),0); //ECHO_LOG_DEBUG("0x" << std::hex << socketNum << std::dec); if(!_HandleWriteError(bytesSent)) { //ECHO_LOG_DEBUG("mCanSend=false; for header"); return; } totalDataSent+=bytesSent; mNetworkManager.ReportSentData(bytesSent); //ECHO_LOG_DEBUG("Sent: Header"); mHeaderSent=true; //See if this is just a control packet - If the data packet is not the same as the header if(packet->SendHeaderOnly()) { packet.reset(); mHeaderSent=false; if(mQueuedPackets.front().second) { Disconnect(); mQueuedPackets.pop_front(); return; } mQueuedPackets.pop_front(); continue; } } u8* data=&(packet->mData[packet->mSize-packet->mReceived]); //ECHO_LOG_DEBUG("0x" << std::hex << this << std::dec << ": Send Data"); s32 bytesSent=_send((const u8*)data,packet->mReceived,0); if(_HandleWriteError(bytesSent)) { packet->mReceived-=bytesSent; //ECHO_LOG_DEBUG("Sent: " << packet->mSize-packet->mReceived << " of " << packet->mSize << " bytes"); if(packet->mReceived==0) //All our data was sent { packet.reset(); mHeaderSent=false; if(mQueuedPackets.front().second) { Disconnect(); mQueuedPackets.pop_front(); return; } mQueuedPackets.pop_front(); } totalDataSent+=bytesSent; mNetworkManager.ReportSentData(bytesSent); }else { //ECHO_LOG_DEBUG("mCanSend=false; for packet"); return; } } } mCanSend=true; } void Connection::SendDataPacket(shared_ptr packet, PacketCallback responseCallback, bool prioritise, bool disconnectAfterSend, bool isResponsePacket) { // In the CONNECTING or CONNECTED states we should treat it as connected. // If we're not connected, should we silently discard? if(mState==States::DISCONNECTED && !mQueueDataPacketsIfNotConnected) { return; } //ECHO_LOG_DEBUG("SendDataPacket: " << mQueuedPackets.size()); if(packet) { if(packet->mReceived!=packet->mSize) { packet->mReceived=packet->mSize; } if(!isResponsePacket) { // All packets are sent through this method. packet->mPacketID = mNextPacketID++; } mQueuedPacketsMutex.Lock(); if(prioritise) { mQueuedPackets.push_front( std::pair< shared_ptr, bool >(packet, disconnectAfterSend) ); }else { mQueuedPackets.push_back( std::pair< shared_ptr, bool >(packet, disconnectAfterSend) ); } if(responseCallback) { mResponseCallbacks[packet->mPacketID] = responseCallback; } mQueuedPacketsMutex.Unlock(); //ECHO_LOG_DEBUG("SendDataPacket2: " << mQueuedPackets.size()); Write(false); } } void Connection::SendData(const u8* data, u32 dataSize, u32 packetTypeID, PacketCallback responseCallback, bool prioritise) { shared_ptr packet=NewDataPacket(); packet->Configure(packetTypeID,dataSize); //Copy the data packet->AppendData(data,dataSize); SendDataPacket(packet,responseCallback,prioritise); } void Connection::SendMessage(const std::string& message, u32 packetTypeID, PacketCallback responseCallback, bool prioritise) { shared_ptr packet=NewDataPacket(); packet->Configure(message); packet->SetPacketTypeID(packetTypeID); packet->AppendString(message); SendDataPacket(packet,responseCallback,prioritise); } void Connection::SendControlPacket(u32 packetTypeID, PacketCallback responseCallback, bool prioritise) { shared_ptr packet=NewDataPacket(); packet->Configure(packetTypeID,0); SendDataPacket(packet,responseCallback,prioritise); } void Connection::SendLabelledPacket(const std::string& label, const u8* data, u32 dataSize, PacketCallback responseCallback, bool prioritise) { shared_ptr packet=NewDataPacket(); packet->Configure(PacketTypes::LABELLED_PACKET,label.length()+DataPacket::NUMBYTES_FOR_STRING_HEADER+dataSize); packet->AppendString(label); packet->AppendData(data,dataSize); SendDataPacket(packet,responseCallback,prioritise); } void Connection::SendLabelledPacket(const std::string& label, const std::string& content, PacketCallback responseCallback, bool prioritise) { shared_ptr packet=NewDataPacket(); packet->Configure(label,content); SendDataPacket(packet,responseCallback,prioritise); } void Connection::SendLabelledPacket(const std::string& label, const std::vector& content, PacketCallback responseCallback, bool prioritise) { shared_ptr packet=NewDataPacket(); packet->Configure(label,content); SendDataPacket(packet,responseCallback,prioritise); } void Connection::SendDataPacketResponse(shared_ptr packetRespondingTo, shared_ptr packet, bool prioritise, bool disconnectAfterSend) { - packet->mPacketID = packetRespondingTo->mPacketID; + packet->SetPacketID(packetRespondingTo->GetPacketID()); + packet->SetPacketTypeID(PacketTypes::RESPONSE_PACKET); SendDataPacket(packet,PacketCallback(),prioritise,disconnectAfterSend,true); } - void Connection::SendDataResponse(shared_ptr packetRespondingTo, const u8* data, u32 dataSize, u32 packetTypeID, bool prioritise) + void Connection::SendDataResponse(shared_ptr packetRespondingTo, const u8* data, u32 dataSize, bool prioritise, bool disconnectAfterSend) { shared_ptr packet=NewDataPacket(); - packet->Configure(packetTypeID,dataSize); + packet->Configure(PacketTypes::RESPONSE_PACKET,dataSize); //Copy the data packet->AppendData(data,dataSize); - packet->mPacketID = packetRespondingTo->mPacketID; - SendDataPacket(packet,PacketCallback(),prioritise,false,true); + packet->SetPacketID(packetRespondingTo->GetPacketID()); + SendDataPacket(packet,PacketCallback(),prioritise,disconnectAfterSend,true); } - void Connection::SendMessageResponse(shared_ptr packetRespondingTo, const std::string& message, u32 packetTypeID, bool prioritise) + void Connection::SendMessageResponse(shared_ptr packetRespondingTo, const std::string& message, bool prioritise, bool disconnectAfterSend) { shared_ptr packet=NewDataPacket(); packet->Configure(message); - packet->SetPacketTypeID(packetTypeID); - packet->AppendString(message); - packet->mPacketID = packetRespondingTo->mPacketID; - SendDataPacket(packet,PacketCallback(),prioritise,false,true); + packet->SetPacketID(packetRespondingTo->GetPacketID()); + packet->SetPacketTypeID(PacketTypes::RESPONSE_PACKET); + SendDataPacket(packet,PacketCallback(),prioritise,disconnectAfterSend,true); } - void Connection::SendControlPacketResponse(shared_ptr packetRespondingTo, u32 packetTypeID, bool prioritise) + void Connection::SendLabelledPacketResponse(shared_ptr packetRespondingTo, const std::string& label, const u8* data, u32 dataSize, bool prioritise, bool disconnectAfterSend) { shared_ptr packet=NewDataPacket(); - packet->Configure(packetTypeID,0); - packet->mPacketID = packetRespondingTo->mPacketID; - SendDataPacket(packet,PacketCallback(),prioritise,false,true); - } - - void Connection::SendLabelledPacketResponse(shared_ptr packetRespondingTo, const std::string& label, const u8* data, u32 dataSize, bool prioritise) - { - shared_ptr packet=NewDataPacket(); - packet->Configure(PacketTypes::LABELLED_PACKET,label.length()+DataPacket::NUMBYTES_FOR_STRING_HEADER+dataSize); + packet->Configure(PacketTypes::LABELLED_RESPONSE_PACKET,label.length()+DataPacket::NUMBYTES_FOR_STRING_HEADER+dataSize); packet->AppendString(label); packet->AppendData(data,dataSize); - packet->mPacketID = packetRespondingTo->mPacketID; - SendDataPacket(packet,PacketCallback(),prioritise,false,true); + packet->SetPacketID(packetRespondingTo->GetPacketID()); + SendDataPacket(packet,PacketCallback(),prioritise,disconnectAfterSend,true); } - void Connection::SendLabelledPacketResponse(shared_ptr packetRespondingTo, const std::string& label, const std::string& content, bool prioritise) + void Connection::SendLabelledPacketResponse(shared_ptr packetRespondingTo, const std::string& label, const std::string& content, bool prioritise, bool disconnectAfterSend) { shared_ptr packet=NewDataPacket(); packet->Configure(label,content); - packet->mPacketID = packetRespondingTo->mPacketID; - SendDataPacket(packet,PacketCallback(),prioritise,false,true); + packet->SetPacketID(packetRespondingTo->GetPacketID()); + packet->SetPacketTypeID(PacketTypes::LABELLED_RESPONSE_PACKET); + SendDataPacket(packet,PacketCallback(),prioritise,disconnectAfterSend,true); } - void Connection::SendLabelledPacketResponse(shared_ptr packetRespondingTo, const std::string& label, const std::vector& content, bool prioritise) + void Connection::SendLabelledPacketResponse(shared_ptr packetRespondingTo, const std::string& label, const std::vector& content, bool prioritise, bool disconnectAfterSend) { shared_ptr packet=NewDataPacket(); packet->Configure(label,content); - packet->mPacketID = packetRespondingTo->mPacketID; - SendDataPacket(packet,PacketCallback(),prioritise,false,true); + packet->SetPacketID(packetRespondingTo->GetPacketID()); + packet->SetPacketTypeID(PacketTypes::LABELLED_RESPONSE_PACKET); + SendDataPacket(packet,PacketCallback(),prioritise,disconnectAfterSend,true); } void Connection::SendHostDetails() { #ifdef ECHO_LITTLE_ENDIAN std::string hostDetails="1.0:little"; #elif defined(ECHO_BIG_ENDIAN) std::string hostDetails="1.0:big"; #else #error "Unable to determine host endianess" #endif ECHO_LOG_DEBUG("Sending host details " << hostDetails); SendMessage(hostDetails,PacketTypes::REMOTE_DETAILS, PacketCallback(), true); } void Connection::OnRemoteDetails(shared_ptr connection, shared_ptr dataPacket) { std::string remoteDetails; if(!dataPacket->GetStringFromDataPacket(remoteDetails)) { connection->Disconnect(); return; } //Remote details needs to be "protocolVersion:endian" // protocolVersion - 1.0 // endian - "big" or "little" ECHO_LOG_DEBUG("Remote details: " << remoteDetails); std::vector parameters; Utils::String::Split(remoteDetails,":",parameters); if(parameters.size()<2) { ECHO_LOG_WARNING("Received remote details without enough parameters - " << remoteDetails << ". Diconnecting."); connection->Disconnect(); return; } if(parameters[0]!="1.0") { ECHO_LOG_WARNING("Received remote details specifying an unsupported protocol version - " << parameters[0] << ". Diconnecting."); connection->Disconnect(); return; } bool bigEndian = true; if(parameters[1]=="little") { bigEndian = false; }else if(parameters[1]!="big") { ECHO_LOG_WARNING("Received remote details specifying an unsupported endian format - " << parameters[1] << ". Diconnecting."); connection->Disconnect(); return; } connection->SetRemoteBigEndian(bigEndian); } bool Connection::NotifyAnyReceivedPackets() { //Only process as many packets as we received in the last frame to avoid getting stuck //in here if we have a constant stream of packets. //Don't worry about locking the mutex, if we're off by one (less) then the remaining //packet will be in the next frame, and probably should have been anyway. mReceviedPacketsMutex.Lock(); std::list< shared_ptr > packetsToProcess = std::move(mReceviedPackets); mReceviedPacketsMutex.Unlock(); while(!packetsToProcess.empty()) { shared_ptr packet=packetsToProcess.front(); packetsToProcess.pop_front(); - std::map< u32, PacketCallback >::iterator rit=mResponseCallbacks.find(packet->GetPacketID()); - if(rit!=mResponseCallbacks.end()) + if(packet->IsResponsePacket()) { - rit->second(shared_from_this(),packet); - mResponseCallbacks.erase(rit); - } - - std::map< u32, std::vector >::iterator it=mPacketCallbacks.find(packet->GetPacketTypeID()); - if(it!=mPacketCallbacks.end()) + std::map< u32, PacketCallback >::iterator rit=mResponseCallbacks.find(packet->GetPacketID()); + if(rit!=mResponseCallbacks.end()) + { + rit->second(shared_from_this(),packet); + mResponseCallbacks.erase(rit); + } + }else { - BOOST_FOREACH(PacketCallback& callback, it->second) + std::map< u32, std::vector >::iterator it=mPacketCallbacks.find(packet->GetPacketTypeID()); + if(it!=mPacketCallbacks.end()) { - callback(shared_from_this(),packet); + BOOST_FOREACH(PacketCallback& callback, it->second) + { + callback(shared_from_this(),packet); + } } } - _notifyOwner(packet); } return !mReceviedPackets.empty(); } void Connection::ProcessReceivedPacket(shared_ptr packet) { mReceviedPacketsMutex.Lock(); mReceviedPackets.push_back(packet); mReceviedPacketsMutex.Unlock(); } void Connection::ProcessLabelledPacket(shared_ptr connection, shared_ptr packet) { Size dataOffset = 0; std::string label = packet->GetLabel(&dataOffset); if(label.empty()) { ECHO_LOG_WARNING("Packet with ID LABELLED_PACKET is invalid or does not contain a label."); return; } std::map< std::string, std::vector >::iterator it=mLabelledPacketCallbacks.find(label); if(it!=mLabelledPacketCallbacks.end()) { u8* dataStart = &(packet->GetData()[dataOffset]); Size numberOfBytes = (packet->GetDataSize()-dataOffset); BOOST_FOREACH(LabelledPacketCallback& callback, it->second) { callback(connection,packet,dataStart,numberOfBytes); } } } std::string Connection::GetFriendlyIdentifier() { return mConnectionDetails.ToString(); } std::string Connection::GetLocalFriendlyIdentifier() { return mLocalConnectionDetails.ToString(); } void Connection::RegisterLabelledPacketCallback(const std::string& label, LabelledPacketCallback callback) { if(callback) { mLabelledPacketCallbacks[label].push_back(callback); } } void Connection::RegisterPacketCallback(u32 packetTypeID, PacketCallback callback) { if(callback) { mPacketCallbacks[packetTypeID].push_back(callback); } } void Connection::RegisterConnectCallback(const std::string& identifier, ConnectCallback callback) { if(callback) { mConnectCallbacks[identifier].push_back(callback); } } void Connection::RegisterDisconnectCallback(const std::string& identifier, DisconnectCallback callback) { if(callback) { mDisconnectCallbacks[identifier].push_back(callback); } } void Connection::ClearLabelledPacketCallbacks(const std::string& label) { mLabelledPacketCallbacks.erase(label); } void Connection::ClearAllLabelledPacketCallbacks() { mLabelledPacketCallbacks.clear(); } void Connection::ClearPacketIDCallbacks(u32 packetTypeID) { mPacketCallbacks.erase(packetTypeID); } void Connection::ClearAllPacketIDCallbacks() { mPacketCallbacks.clear(); } void Connection::ClearAllPacketCallbacks() { ClearAllPacketIDCallbacks(); ClearAllLabelledPacketCallbacks(); } void Connection::ClearConnectCallbacks(const std::string& identifier) { mConnectCallbacks.erase(identifier); } void Connection::ClearAllConnectCallbacks() { mConnectCallbacks.clear(); } void Connection::ClearDisconnectCallbacks(const std::string& identifier) { mDisconnectCallbacks.erase(identifier); } void Connection::ClearAllDisconnectCallbacks() { mDisconnectCallbacks.clear(); } void Connection::SetAutoAttemptReconnectTime(Seconds seconds) { mReconnectTimer.SetTimeout(seconds); } } diff --git a/src/Network/DataPacket.cpp b/src/Network/DataPacket.cpp --- a/src/Network/DataPacket.cpp +++ b/src/Network/DataPacket.cpp @@ -1,479 +1,484 @@ #include #include #include #include #include #include #include namespace Echo { DataPacketHeader::DataPacketHeader() { mData[0]=0; mData[1]=0; mData[2]=0; #ifdef ECHO_LITTLE_ENDIAN mIsBigEndian = false; #else mIsBigEndian = true; #endif } bool DataPacketHeader::BuildFromPacketData(const DataPacket& packet) { if(packet.GetDataSize()!=GetHeaderDataSizeInBytes()) { ECHO_LOG_ERROR("Invalid packet data size when trying to build header from data. The packet size should be " << GetHeaderDataSizeInBytes() << " bytes."); return false; } mData[0]=packet.Get(0,0); mData[1]=packet.Get(1,0); mData[2]=packet.Get(2,0); mIsBigEndian = packet.IsBigEndian(); if(mIsBigEndian) { Reorder(); } return true; } void DataPacketHeader::BuildForPacket(const DataPacket& packet) { mData[0]=packet.GetPacketTypeID(); mData[1]=packet.GetPacketID(); mData[2]=packet.GetDataSize(); } void DataPacketHeader::Reorder() { //The header is fine u32 b1,b2,b3,b4; b1=mData[0] & 0xFF000000; b2=mData[0] & 0x00FF0000; b3=mData[0] & 0x0000FF00; b4=mData[0] & 0x000000FF; mData[0]=(b1>>24) | (b2>>8) | (b3<<8) | (b4<<24); b1=mData[1] & 0xFF000000; b2=mData[1] & 0x00FF0000; b3=mData[1] & 0x0000FF00; b4=mData[1] & 0x000000FF; mData[1]=(b1>>24) | (b2>>8) | (b3<<8) | (b4<<24); b1=mData[2] & 0xFF000000; b2=mData[2] & 0x00FF0000; b3=mData[2] & 0x0000FF00; b4=mData[2] & 0x000000FF; mData[2]=(b1>>24) | (b2>>8) | (b3<<8) | (b4<<24); } DataPacket::DataPacket() { mPacketTypeID=0; mPacketID=0; mSize=0; mReceived=0; mIsBigEndian=Connection::IsPlatformBigEndian(); mData=0; } DataPacket::DataPacket(const std::string& content) { mSize=(u32)(content.length() + NUMBYTES_FOR_STRING_HEADER); mReceived=0; mPacketTypeID=0; mPacketID=0; mData=0; mIsBigEndian=Connection::IsPlatformBigEndian(); mData=new u8[mSize]; AppendString(content); } DataPacket::DataPacket(const std::vector< std::string >& content) { u32 requiredSize=0; for(Size i=0;i& content) { u32 requiredSize=label.length() + NUMBYTES_FOR_STRING_HEADER; for(Size i=0;i& content) { u32 requiredSize=0; for(Size i=0;imSize) { size=mSize-mReceived; } u32 remaining=mSize-mReceived; if(size>remaining) { size=remaining; } if(size==0 || !mData) return 0; //Copy the data over to our buffer const u8* byteData = reinterpret_cast(data); std::copy(byteData,byteData+size,&(mData[mReceived])); mReceived+=size; assert(mReceived<=mSize); return size; } const size_t DataPacket::NUMBYTES_FOR_STRING_HEADER = sizeof(u32)*2; //Store a string from an std::string. Format will be bytesPerChar(4)length(4)content(length). //If there is not enough space remaining to append the string it is not appended at all and //false is returned. Otherwise true. bool DataPacket::AppendString(const std::string& s) { u32 l=(u32)s.length(); //+NUMBYTES_FOR_STRING_HEADER for [bytes per char][length] if(l+NUMBYTES_FOR_STRING_HEADER>GetRemainingDataSize()) return false; u32 one=1; AppendData(reinterpret_cast(&one),sizeof(u32)); AppendData(reinterpret_cast(&l),sizeof(u32)); AppendData(reinterpret_cast(s.c_str()),l); return true; } bool DataPacket::AppendString(const std::string& s, u8* buffer, u32 offset, u32 totalSize) { assert(offset < totalSize && "Cannot have an offset greater than the total buffer size"); if(offset >= totalSize) { return false; } u32 l=(u32)s.length(); //+NUMBYTES_FOR_STRING_HEADER for [bytes per char][length] u32 availableBytes = totalSize-offset; if(l+NUMBYTES_FOR_STRING_HEADER>availableBytes) { return false; } buffer+=offset; u32 one=1; const u8* onePtr = reinterpret_cast(&one); const u8* lPtr = reinterpret_cast(&l); const u8* sPtr = reinterpret_cast(s.c_str()); std::copy(onePtr,onePtr+sizeof(u32),buffer); buffer+=sizeof(u32); std::copy(lPtr,lPtr+sizeof(u32),buffer); buffer+=sizeof(u32); std::copy(sPtr,sPtr+l,buffer); return true; } bool DataPacket::GetStringFromDataPacket(std::string& outString, Size dataOffset, Size* outDataStartOffset, bool skipLabel) const { u32 dataSize=GetDataSize(); if(dataSize<=sizeof(u32)*2) { //Bad packet return false; } Size position=dataOffset; if(skipLabel && IsLabelledPacket()) { Size labelEnd = 0; GetLabel(&labelEnd); position += labelEnd; } // Find out the bytes per character. //Internal format is as follows: bytesPerChar(4)length(4)content(length) u32 bytesPerChar=*reinterpret_cast(&(mData[position])); bytesPerChar = EnsureOrder(bytesPerChar); // We only permit 1 byte per character at the moment. if(bytesPerChar>1) { return false; } position+=sizeof(u32); //Get the length u32 length=*reinterpret_cast(&(mData[position])); length = EnsureOrder(length); // Check the length position+=sizeof(u32); if(length<=0 || length>(dataSize-position)) { return false; } // Copy the string data. const char* data=reinterpret_cast(&(mData[position])); outString = std::string(data,length*bytesPerChar); if(outDataStartOffset) { *outDataStartOffset = position+length*bytesPerChar; } return true; } bool DataPacket::GetStringFromDataPacket(std::vector& outStrings, Size dataOffset, bool skipLabel) const { u32 ps=GetDataSize(); if(ps<=sizeof(u32)) { //Bad packet return false; } bool isLabelled = IsLabelledPacket(); bool skippedLabel = false; u32 position=dataOffset; while(position(&(mData[position])); bytesPerChar = EnsureOrder(bytesPerChar); //Reorder if(bytesPerChar>1) { return false; } position+=sizeof(u32); u32 length=*reinterpret_cast(&(mData[position])); length = EnsureOrder(length); position+=sizeof(u32); if(length<=0 || length>(mSize-position)) { return false; } const char* data=reinterpret_cast(&(mData[position])); std::string currentString; for(int b=0;b