diff --git a/src/SocketIOclient.cpp b/src/SocketIOclient.cpp new file mode 100644 index 0000000..1ce8d0c --- /dev/null +++ b/src/SocketIOclient.cpp @@ -0,0 +1,168 @@ +/* + * SocketIOclient.cpp + * + * Created on: May 12, 2018 + * Author: links + */ + +#include "WebSockets.h" +#include "WebSocketsClient.h" +#include "SocketIOclient.h" + +SocketIOclient::SocketIOclient() { + +} + +SocketIOclient::~SocketIOclient() { + +} + +void SocketIOclient::begin(const char *host, uint16_t port, const char * url, const char * protocol) { + WebSocketsClient::beginSocketIO(host, port, url, protocol); +} + +void SocketIOclient::begin(String host, uint16_t port, String url, String protocol) { + WebSocketsClient::beginSocketIO(host, port, url, protocol); +} + +bool SocketIOclient::isConnected(void) { + return WebSocketsClient::isConnected(); +} + +/** + * send text data to client + * @param num uint8_t client id + * @param payload uint8_t * + * @param length size_t + * @param headerToPayload bool (see sendFrame for more details) + * @return true if ok + */ +bool SocketIOclient::sendEVENT(uint8_t * payload, size_t length, bool headerToPayload) { + bool ret = false; + if(length == 0) { + length = strlen((const char *) payload); + } + if(clientIsConnected(&_client)) { + + if(!headerToPayload) { + // webSocket Header + ret = WebSocketsClient::sendFrameHeader(&_client, WSop_text, length + 2, true); + // Engine.IO / Socket.IO Header + if(ret) { + uint8_t buf[3] = { eIOtype_MESSAGE, sIOtype_EVENT, 0x00 }; + ret = WebSocketsClient::write(&_client, buf, 2); + } + if(ret) { + ret = WebSocketsClient::write(&_client, payload, length ); + } + return ret; + } else { + // TODO implement + } + + // return WebSocketsClient::sendFrame(&_client, WSop_text, payload, length, true, true, headerToPayload); + } + return false; +} + +bool SocketIOclient::sendEVENT(const uint8_t * payload, size_t length) { + return sendEVENT((uint8_t *) payload, length); +} + +bool SocketIOclient::sendEVENT(char * payload, size_t length, bool headerToPayload) { + return sendEVENT((uint8_t *) payload, length, headerToPayload); +} + +bool SocketIOclient::sendEVENT(const char * payload, size_t length) { + return sendEVENT((uint8_t *) payload, length); +} + +bool SocketIOclient::sendEVENT(String & payload) { + return sendEVENT((uint8_t *) payload.c_str(), payload.length()); +} + +void SocketIOclient::loop(void) { + WebSocketsClient::loop(); + unsigned long t = millis(); + if((t - _lastConnectionFail) > EIO_HEARTBEAT_INTERVAL) { + _lastConnectionFail = t; + //WebSocketsClient::sendTXT(eIOtype_PING); + } +} + +void SocketIOclient::runCbEvent(WStype_t type, uint8_t * payload, size_t length) { + switch(type) { + case WStype_DISCONNECTED: + DEBUG_WEBSOCKETS("[wsIOc] Disconnected!\n"); + break; + case WStype_CONNECTED: { + DEBUG_WEBSOCKETS("[wsIOc] Connected to url: %s\n", payload); + // send message to server when Connected + // Engine.io upgrade confirmation message (required) + WebSocketsClient::sendTXT(eIOtype_UPGRADE); + } + break; + case WStype_TEXT: { + + if(length < 1) { + break; + } + + engineIOmessageType_t eType = (engineIOmessageType_t) payload[0]; + switch(eType) { + case eIOtype_PING: + payload[0] = eIOtype_PONG; + DEBUG_WEBSOCKETS("[wsIOc] get ping send pong (%s)\n", payload); + WebSocketsClient::sendTXT(payload, length, false); + break; + case eIOtype_PONG: + DEBUG_WEBSOCKETS("[wsIOc] get pong\n"); + break; + case eIOtype_MESSAGE: { + if(length < 2) { + break; + } + socketIOmessageType_t ioType = (socketIOmessageType_t) payload[1]; + uint8_t * data = &payload[2]; + size_t lData = length - 2; + switch(ioType) { + case sIOtype_EVENT: + DEBUG_WEBSOCKETS("[wsIOc] get event (%d): %s\n", lData, data); + break; + case sIOtype_CONNECT: + case sIOtype_DISCONNECT: + case sIOtype_ACK: + case sIOtype_ERROR: + case sIOtype_BINARY_EVENT: + case sIOtype_BINARY_ACK: + default: + DEBUG_WEBSOCKETS("[wsIOc] Socket.IO Message Type %c (%02X) is not implemented\n", ioType, ioType); + DEBUG_WEBSOCKETS("[wsIOc] get text: %s\n", payload); + break; + } + + } + break; + case eIOtype_OPEN: + case eIOtype_CLOSE: + case eIOtype_UPGRADE: + case eIOtype_NOOP: + default: + DEBUG_WEBSOCKETS("[wsIOc] Engine.IO Message Type %c (%02X) is not implemented\n", eType, eType); + DEBUG_WEBSOCKETS("[wsIOc] get text: %s\n", payload); + break; + } + + // send message to server + // webSocket.sendTXT("message here"); + } + break; + case WStype_BIN: + DEBUG_WEBSOCKETS("[wsIOc] get binary length: %u\n", length); + // hexdump(payload, length); + + // send data to server + // webSocket.sendBIN(payload, length); + break; + } +} diff --git a/src/SocketIOclient.h b/src/SocketIOclient.h new file mode 100644 index 0000000..b179954 --- /dev/null +++ b/src/SocketIOclient.h @@ -0,0 +1,69 @@ +/* + * SocketIOclient.h + * + * Created on: May 12, 2018 + * Author: links + */ + +#ifndef SOCKETIOCLIENT_H_ +#define SOCKETIOCLIENT_H_ + +#include "WebSockets.h" + +#define EIO_HEARTBEAT_INTERVAL 10000 + +#define EIO_MAX_HEADER_SIZE (WEBSOCKETS_MAX_HEADER_SIZE + 1) +#define SIO_MAX_HEADER_SIZE (EIO_MAX_HEADER_SIZE + 1) + +typedef enum { + eIOtype_OPEN = '0', ///< Sent from the server when a new transport is opened (recheck) + eIOtype_CLOSE = '1', ///< Request the close of this transport but does not shutdown the connection itself. + eIOtype_PING = '2', ///< Sent by the client. Server should answer with a pong packet containing the same data + eIOtype_PONG = '3', ///< Sent by the server to respond to ping packets. + eIOtype_MESSAGE = '4', ///< actual message, client and server should call their callbacks with the data + eIOtype_UPGRADE = '5', ///< Before engine.io switches a transport, it tests, if server and client can communicate over this transport. If this test succeed, the client sends an upgrade packets which requests the server to flush its cache on the old transport and switch to the new transport. + eIOtype_NOOP = '6', ///< A noop packet. Used primarily to force a poll cycle when an incoming websocket connection is received. +} engineIOmessageType_t; + + +typedef enum { + sIOtype_CONNECT = '0', + sIOtype_DISCONNECT = '1', + sIOtype_EVENT = '2', + sIOtype_ACK = '3', + sIOtype_ERROR = '4', + sIOtype_BINARY_EVENT = '5', + sIOtype_BINARY_ACK = '6', +} socketIOmessageType_t; + +class SocketIOclient: protected WebSocketsClient { + + public: +#ifdef __AVR__ + typedef void (*SocketIOclientEvent)(WStype_t type, uint8_t * payload, size_t length); +#else + typedef std::function SocketIOclientEvent; +#endif + + SocketIOclient(void); + virtual ~SocketIOclient(void); + + void begin(const char *host, uint16_t port, const char * url = "/socket.io/?EIO=3", const char * protocol = "arduino"); + void begin(String host, uint16_t port, String url = "/socket.io/?EIO=3", String protocol = "arduino"); + + bool isConnected(void); + + bool sendEVENT(uint8_t * payload, size_t length = 0, bool headerToPayload = false); + bool sendEVENT(const uint8_t * payload, size_t length = 0); + bool sendEVENT(char * payload, size_t length = 0, bool headerToPayload = false); + bool sendEVENT(const char * payload, size_t length = 0); + bool sendEVENT(String & payload); + + void loop(void); + + protected: + void runCbEvent(WStype_t type, uint8_t * payload, size_t length); + uint64_t _lastHeartbeat = 0; +}; + +#endif /* SOCKETIOCLIENT_H_ */ diff --git a/src/WebSockets.cpp b/src/WebSockets.cpp index 608c045..a3ada67 100644 --- a/src/WebSockets.cpp +++ b/src/WebSockets.cpp @@ -70,6 +70,112 @@ void WebSockets::clientDisconnect(WSclient_t * client, uint16_t code, char * rea clientDisconnect(client); } +/** + * + * @param buf uint8_t * ptr to the buffer for writing + * @param opcode WSopcode_t + * @param length size_t length of the payload + * @param mask bool add dummy mask to the frame (needed for web browser) + * @param maskkey uint8_t[4] key used for payload + * @param fin bool can be used to send data in more then one frame (set fin on the last frame) + */ +uint8_t WebSockets::createHeader(uint8_t * headerPtr, WSopcode_t opcode, size_t length, bool mask, uint8_t maskKey[4], bool fin) { + uint8_t headerSize; + // calculate header Size + if(length < 126) { + headerSize = 2; + } else if(length < 0xFFFF) { + headerSize = 4; + } else { + headerSize = 10; + } + + if(mask) { + headerSize += 4; + } + + // create header + + // byte 0 + *headerPtr = 0x00; + if(fin) { + *headerPtr |= bit(7); ///< set Fin + } + *headerPtr |= opcode; ///< set opcode + headerPtr++; + + // byte 1 + *headerPtr = 0x00; + if(mask) { + *headerPtr |= bit(7); ///< set mask + } + + if(length < 126) { + *headerPtr |= length; + headerPtr++; + } else if(length < 0xFFFF) { + *headerPtr |= 126; + headerPtr++; + *headerPtr = ((length >> 8) & 0xFF); + headerPtr++; + *headerPtr = (length & 0xFF); + headerPtr++; + } else { + // Normally we never get here (to less memory) + *headerPtr |= 127; + headerPtr++; + *headerPtr = 0x00; + headerPtr++; + *headerPtr = 0x00; + headerPtr++; + *headerPtr = 0x00; + headerPtr++; + *headerPtr = 0x00; + headerPtr++; + *headerPtr = ((length >> 24) & 0xFF); + headerPtr++; + *headerPtr = ((length >> 16) & 0xFF); + headerPtr++; + *headerPtr = ((length >> 8) & 0xFF); + headerPtr++; + *headerPtr = (length & 0xFF); + headerPtr++; + } + + if(mask) { + *headerPtr = maskKey[0]; + headerPtr++; + *headerPtr = maskKey[1]; + headerPtr++; + *headerPtr = maskKey[2]; + headerPtr++; + *headerPtr = maskKey[3]; + headerPtr++; + } + return headerSize; +} + +/** + * + * @param client WSclient_t * ptr to the client struct + * @param opcode WSopcode_t + * @param length size_t length of the payload + * @param fin bool can be used to send data in more then one frame (set fin on the last frame) + * @return true if ok + */ +bool WebSockets::sendFrameHeader(WSclient_t * client, WSopcode_t opcode, size_t length, bool fin) { + uint8_t maskKey[4] = { 0x00, 0x00, 0x00, 0x00 }; + uint8_t buffer[WEBSOCKETS_MAX_HEADER_SIZE] = { 0 }; + + uint8_t headerSize = createHeader(&buffer[0], opcode, length, client->cIsClient, maskKey, fin); + + if(write(client, &buffer[0], headerSize) != headerSize) { + return false; + } + + return true; +} + /** * * @param client WSclient_t * ptr to the client struct @@ -143,85 +249,27 @@ bool WebSockets::sendFrame(WSclient_t * client, WSopcode_t opcode, uint8_t * pay headerPtr = &buffer[0]; } - // create header - - // byte 0 - *headerPtr = 0x00; - if(fin) { - *headerPtr |= bit(7); ///< set Fin - } - *headerPtr |= opcode; ///< set opcode - headerPtr++; - - // byte 1 - *headerPtr = 0x00; - if(client->cIsClient) { - *headerPtr |= bit(7); ///< set mask + if(client->cIsClient && useInternBuffer) { + // if we use a Intern Buffer we can modify the data + // by this fact its possible the do the masking + for(uint8_t x = 0; x < sizeof(maskKey); x++) { + maskKey[x] = random(0xFF); + } } - if(length < 126) { - *headerPtr |= length; - headerPtr++; - } else if(length < 0xFFFF) { - *headerPtr |= 126; - headerPtr++; - *headerPtr = ((length >> 8) & 0xFF); - headerPtr++; - *headerPtr = (length & 0xFF); - headerPtr++; - } else { - // Normally we never get here (to less memory) - *headerPtr |= 127; - headerPtr++; - *headerPtr = 0x00; - headerPtr++; - *headerPtr = 0x00; - headerPtr++; - *headerPtr = 0x00; - headerPtr++; - *headerPtr = 0x00; - headerPtr++; - *headerPtr = ((length >> 24) & 0xFF); - headerPtr++; - *headerPtr = ((length >> 16) & 0xFF); - headerPtr++; - *headerPtr = ((length >> 8) & 0xFF); - headerPtr++; - *headerPtr = (length & 0xFF); - headerPtr++; - } + createHeader(headerPtr, opcode, length, client->cIsClient, maskKey, fin); - if(client->cIsClient) { - if(useInternBuffer) { - // if we use a Intern Buffer we can modify the data - // by this fact its possible the do the masking - for(uint8_t x = 0; x < sizeof(maskKey); x++) { - maskKey[x] = random(0xFF); - *headerPtr = maskKey[x]; - headerPtr++; - } - - uint8_t * dataMaskPtr; - - if(headerToPayload) { - dataMaskPtr = (payloadPtr + WEBSOCKETS_MAX_HEADER_SIZE); - } else { - dataMaskPtr = payloadPtr; - } - - for(size_t x = 0; x < length; x++) { - dataMaskPtr[x] = (dataMaskPtr[x] ^ maskKey[x % 4]); - } + if(client->cIsClient && useInternBuffer) { + uint8_t * dataMaskPtr; + if(headerToPayload) { + dataMaskPtr = (payloadPtr + WEBSOCKETS_MAX_HEADER_SIZE); } else { - *headerPtr = maskKey[0]; - headerPtr++; - *headerPtr = maskKey[1]; - headerPtr++; - *headerPtr = maskKey[2]; - headerPtr++; - *headerPtr = maskKey[3]; - headerPtr++; + dataMaskPtr = payloadPtr; + } + + for(size_t x = 0; x < length; x++) { + dataMaskPtr[x] = (dataMaskPtr[x] ^ maskKey[x % 4]); } } diff --git a/src/WebSockets.h b/src/WebSockets.h index 6a749fa..592dda8 100644 --- a/src/WebSockets.h +++ b/src/WebSockets.h @@ -298,9 +298,12 @@ class WebSockets { virtual void clientDisconnect(WSclient_t * client) = 0; virtual bool clientIsConnected(WSclient_t * client) = 0; + void clientDisconnect(WSclient_t * client, uint16_t code, char * reason = NULL, size_t reasonLen = 0); + virtual void messageReceived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin) = 0; - void clientDisconnect(WSclient_t * client, uint16_t code, char * reason = NULL, size_t reasonLen = 0); + uint8_t createHeader(uint8_t * buf, WSopcode_t opcode, size_t length, bool mask, uint8_t maskKey[4], bool fin); + bool sendFrameHeader(WSclient_t * client, WSopcode_t opcode, size_t length = 0, bool fin = true); bool sendFrame(WSclient_t * client, WSopcode_t opcode, uint8_t * payload = NULL, size_t length = 0, bool fin = true, bool headerToPayload = false); void headerDone(WSclient_t * client); diff --git a/src/WebSocketsClient.cpp b/src/WebSocketsClient.cpp index 8d48ccd..9f8e195 100644 --- a/src/WebSocketsClient.cpp +++ b/src/WebSocketsClient.cpp @@ -251,6 +251,12 @@ bool WebSocketsClient::sendTXT(String & payload) { return sendTXT((uint8_t *)payload.c_str(), payload.length()); } +bool WebSocketsClient::sendTXT(char payload) { + uint8_t buf[WEBSOCKETS_MAX_HEADER_SIZE + 2] = { 0x00 }; + buf[WEBSOCKETS_MAX_HEADER_SIZE] = payload; + return sendTXT(buf, 1, true); +} + /** * send binary data to client * @param num uint8_t client id @@ -343,6 +349,10 @@ void WebSocketsClient::setReconnectInterval(unsigned long time) { _reconnectInterval = time; } +bool WebSocketsClient::isConnected(void) { + return (_client.status == WSC_CONNECTED); +} + //################################################################################# //################################################################################# //################################################################################# @@ -694,6 +704,13 @@ void WebSocketsClient::handleHeader(WSclient_t * client, String * headerLine) { runCbEvent(WStype_CONNECTED, (uint8_t *)client->cUrl.c_str(), client->cUrl.length()); } else if(clientIsConnected(client) && client->isSocketIO && client->cSessionId.length() > 0) { + if(_client.tcp->available()) { + // read not needed data + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] still data in buffer (%d), clean up.\n", _client.tcp->available()); + while(_client.tcp->available() > 0) { + _client.tcp->read(); + } + } sendHeader(client); } else { DEBUG_WEBSOCKETS("[WS-Client][handleHeader] no Websocket connection close.\n"); diff --git a/src/WebSocketsClient.h b/src/WebSocketsClient.h index 557a6d7..ed3f630 100644 --- a/src/WebSocketsClient.h +++ b/src/WebSocketsClient.h @@ -27,7 +27,7 @@ #include "WebSockets.h" -class WebSocketsClient : private WebSockets { +class WebSocketsClient : protected WebSockets { public: #ifdef __AVR__ typedef void (*WebSocketClientEvent)(WStype_t type, uint8_t * payload, size_t length); @@ -71,6 +71,7 @@ class WebSocketsClient : private WebSockets { bool sendTXT(char * payload, size_t length = 0, bool headerToPayload = false); bool sendTXT(const char * payload, size_t length = 0); bool sendTXT(String & payload); + bool sendTXT(char payload); bool sendBIN(uint8_t * payload, size_t length, bool headerToPayload = false); bool sendBIN(const uint8_t * payload, size_t length); @@ -94,6 +95,8 @@ class WebSocketsClient : private WebSockets { String _host; uint16_t _port; + bool isConnected(void); + #if defined(HAS_SSL) String _fingerprint; const char * _CA_cert;