first working WebSocketClient

add LGPLv2.1
This commit is contained in:
Markus Sattler
2015-05-24 15:40:47 +02:00
parent 6d46f22be2
commit e1e6280e82
12 changed files with 753 additions and 322 deletions

View File

@ -38,7 +38,7 @@ extern "C" {
* @param reasonLen
*/
void WebSockets::clientDisconnect(WSclient_t * client, uint16_t code, char * reason, size_t reasonLen) {
DEBUG_WEBSOCKETS("[WS-Server][%d][handleWebsocket] clientDisconnect code: %u\n", client->num, code);
DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] clientDisconnect code: %u\n", client->num, code);
if(client->status == WSC_CONNECTED && code) {
if(reason) {
sendFrame(client, WSop_close, (uint8_t *) reason, reasonLen);
@ -59,22 +59,36 @@ void WebSockets::clientDisconnect(WSclient_t * client, uint16_t code, char * rea
* @param payload uint8_t *
* @param length size_t
*/
void WebSockets::sendFrame(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length) {
void WebSockets::sendFrame(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool mask) {
DEBUG_WEBSOCKETS("[WS][%d][sendFrame] ------- send massage frame -------\n", client->num);
DEBUG_WEBSOCKETS("[WS][%d][sendFrame] opCode: %u mask: %u length: %u\n", client->num, opcode, mask, length);
if(opcode == WSop_text) {
DEBUG_WEBSOCKETS("[WS][%d][sendFrame] text: %s\n", client->num, payload);
}
if(!client->tcp.connected()) {
DEBUG_WEBSOCKETS("[WS][%d][sendFrame] not Connected!?\n", client->num);
return;
}
uint8_t maskKey[4] = { 0 };
uint8_t buffer[16] = { 0 };
uint8_t i = 0;
//create header
buffer[i] = bit(7); // set Fin
buffer[i++] |= opcode; // set opcode
buffer[i] = bit(7); // set Fin
buffer[i++] |= opcode; // set opcode
buffer[i] = 0x00;
if(mask) {
buffer[i] |= bit(7); // set mask
}
if(length < 126) {
buffer[i++] = length;
buffer[i++] |= length;
} else if(length < 0xFFFF) {
buffer[i++] = 126;
@ -93,6 +107,20 @@ void WebSockets::sendFrame(WSclient_t * client, WSopcode_t opcode, uint8_t * pay
buffer[i++] = (length & 0xFF);
}
if(mask) {
// todo generate random mask key
for(uint8_t x = 0; x < sizeof(maskKey); x++) {
// maskKey[x] = random(0xFF);
maskKey[x] = 0x00; // fake xor (0x00 0x00 0x00 0x00)
buffer[i++] = maskKey[x];
}
// todo encode XOR
//for(size_t x = 0; x < length; x++) {
// payload[x] = (payload[x] ^ maskKey[x % 4]);
//}
}
// send header
client->tcp.write(&buffer[0], i);
@ -123,7 +151,7 @@ void WebSockets::handleWebsocket(WSclient_t * client) {
uint8_t * payload = NULL;
DEBUG_WEBSOCKETS("[WS-Server][%d][handleWebsocket] ------- read massage frame -------\n", client->num);
DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] ------- read massage frame -------\n", client->num);
if(!readWait(client, buffer, 2)) {
//timeout
@ -164,11 +192,11 @@ void WebSockets::handleWebsocket(WSclient_t * client) {
}
}
DEBUG_WEBSOCKETS("[WS-Server][%d][handleWebsocket] fin: %u rsv1: %u rsv2: %u rsv3 %u opCode: %u\n", client->num, fin, rsv1, rsv2, rsv3, opCode);
DEBUG_WEBSOCKETS("[WS-Server][%d][handleWebsocket] mask: %u payloadLen: %u\n", client->num, mask, payloadLen);
DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] fin: %u rsv1: %u rsv2: %u rsv3 %u opCode: %u\n", client->num, fin, rsv1, rsv2, rsv3, opCode);
DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] mask: %u payloadLen: %u\n", client->num, mask, payloadLen);
if(payloadLen > WEBSOCKETS_MAX_DATA_SIZE) {
DEBUG_WEBSOCKETS("[WS-Server][%d][handleWebsocket] payload to big! (%u)\n", client->num, payloadLen);
DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] payload to big! (%u)\n", client->num, payloadLen);
clientDisconnect(client, 1009);
return;
}
@ -182,13 +210,13 @@ void WebSockets::handleWebsocket(WSclient_t * client) {
payload = (uint8_t *) malloc(payloadLen + 1);
if(!payload) {
DEBUG_WEBSOCKETS("[WS-Server][%d][handleWebsocket] to less memory to handle payload %d!\n", client->num, payloadLen);
DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] to less memory to handle payload %d!\n", client->num, payloadLen);
clientDisconnect(client, 1011);
return;
}
if(!readWait(client, payload, payloadLen)) {
DEBUG_WEBSOCKETS("[WS-Server][%d][handleWebsocket] missing data!\n", client->num);
DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] missing data!\n", client->num);
free(payload);
clientDisconnect(client, 1002);
return;
@ -206,7 +234,7 @@ void WebSockets::handleWebsocket(WSclient_t * client) {
switch(opCode) {
case WSop_text:
DEBUG_WEBSOCKETS("[WS-Server][%d][handleWebsocket] text: %s\n", client->num, payload);
DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] text: %s\n", client->num, payload);
// no break here!
case WSop_binary:
messageRecived(client, opCode, payload, payloadLen);
@ -216,7 +244,7 @@ void WebSockets::handleWebsocket(WSclient_t * client) {
sendFrame(client, WSop_pong, payload, payloadLen);
break;
case WSop_pong:
DEBUG_WEBSOCKETS("[WS-Server][%d][handleWebsocket] get pong from Client (%s)\n", client->num, payload);
DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] get pong (%s)\n", client->num, payload);
break;
case WSop_close:
{
@ -225,9 +253,9 @@ void WebSockets::handleWebsocket(WSclient_t * client) {
reasonCode = payload[0] << 8 | payload[1];
}
DEBUG_WEBSOCKETS("[WS-Server][%d][handleWebsocket] client ask for close. Code: %d", client->num, reasonCode);
DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] get ask for close. Code: %d", client->num, reasonCode);
if(payloadLen > 2) {
DEBUG_WEBSOCKETS("(%s)\n", (payload+2));
DEBUG_WEBSOCKETS(" (%s)\n", (payload+2));
} else {
DEBUG_WEBSOCKETS("\n");
}
@ -271,8 +299,8 @@ String WebSockets::acceptKey(String clientKey) {
* @return base64 encoded String
*/
String WebSockets::base64_encode(uint8_t * data, size_t length) {
char * buffer = (char *) malloc((length*1.4)+1);
size_t size = ((length*1.6f)+1);
char * buffer = (char *) malloc(size);
if(buffer) {
base64_encodestate _state;
base64_init_encodestate(&_state);
@ -283,7 +311,7 @@ String WebSockets::base64_encode(uint8_t * data, size_t length) {
free(buffer);
return base64;
}
return "-FAIL-";
return String("-FAIL-");
}
/**

View File

@ -37,7 +37,7 @@
#endif
#endif
#define DEBUG_WEBSOCKETS(...) Serial1.printf( __VA_ARGS__ );
//#define DEBUG_WEBSOCKETS(...) Serial1.printf( __VA_ARGS__ )
#ifndef DEBUG_WEBSOCKETS
#define DEBUG_WEBSOCKETS(...)
@ -85,14 +85,16 @@ typedef struct {
#endif
#endif
String cUrl; ///< http url
uint16_t cCode; ///< http code
bool cIsUpgrade; ///< Connection == Upgrade
bool cIsWebsocket; ///< Upgrade == websocket
String cKey; ///< client Sec-WebSocket-Key
String cAccept; ///< client Sec-WebSocket-Accept
String cProtocol; ///< client Sec-WebSocket-Protocol
String cExtensions; ///< client Sec-WebSocket-Extensions
int cVersion; ///< client Sec-WebSocket-Version
uint16_t cVersion; ///< client Sec-WebSocket-Version
} WSclient_t;
@ -104,7 +106,7 @@ class WebSockets {
virtual void messageRecived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length);
void clientDisconnect(WSclient_t * client, uint16_t code, char * reason = NULL, size_t reasonLen = 0);
void sendFrame(WSclient_t * client, WSopcode_t opcode, uint8_t * payload = NULL, size_t length = 0);
void sendFrame(WSclient_t * client, WSopcode_t opcode, uint8_t * payload = NULL, size_t length = 0, bool mask = false);
void handleWebsocket(WSclient_t * client);
@ -113,6 +115,7 @@ class WebSockets {
String acceptKey(String clientKey);
String base64_encode(uint8_t * data, size_t length);
};
#endif /* WEBSOCKETS_H_ */

View File

@ -44,12 +44,17 @@ void WebSocketsClient::begin(const char *host, uint16_t port, const char * url)
_client.status = WSC_NOT_CONNECTED;
_client.cUrl = url;
_client.cCode = 0;
_client.cIsUpgrade = false;
_client.cIsWebsocket = true;
_client.cKey = "";
_client.cAccept = "";
_client.cProtocol = "";
_client.cExtensions = "";
_client.cVersion = 0;
// todo find better seed
randomSeed(millis());
}
void WebSocketsClient::begin(String host, uint16_t port, String url) {
@ -62,12 +67,20 @@ void WebSocketsClient::begin(String host, uint16_t port, String url) {
void WebSocketsClient::loop(void) {
if(!clientIsConnected(&_client)) {
if(_client.tcp.connect(_host.c_str(), _port)) {
DEBUG_WEBSOCKETS("[WS-Client] connected to %s:%u\n", _host.c_str(), _port);
DEBUG_WEBSOCKETS("[WS-Client] connected to %s:%u.\n", _host.c_str(), _port);
_client.status = WSC_HEADER;
_client.tcp.setNoDelay(true);
// set Timeout for readBytesUntil and readStringUntil
_client.tcp.setTimeout(WEBSOCKETS_TCP_TIMEOUT);
_client.status = WSC_HEADER;
#ifdef ESP8266
_client.tcp.setNoDelay(true);
#endif
// send Header to Server
sendHeader(&_client);
} else {
DEBUG_WEBSOCKETS("[WS-Client] connection to %s:%u Faild\n", _host.c_str(), _port);
delay(10); //some litle delay to not flood the server
@ -96,7 +109,7 @@ void WebSocketsClient::sendTXT(uint8_t * payload, size_t length) {
length = strlen((const char *) payload);
}
if(clientIsConnected(&_client)) {
sendFrame(&_client, WSop_text, payload, length);
sendFrame(&_client, WSop_text, payload, length, true);
}
}
@ -124,7 +137,7 @@ void WebSocketsClient::sendTXT(String payload) {
*/
void WebSocketsClient::sendBIN(uint8_t * payload, size_t length) {
if(clientIsConnected(&_client)) {
sendFrame(&_client, WSop_binary, payload, length);
sendFrame(&_client, WSop_binary, payload, length, true);
}
}
@ -181,7 +194,9 @@ void WebSocketsClient::clientDisconnect(WSclient_t * client) {
client->tcp.stop();
}
client->cCode = 0;
client->cKey = "";
client->cAccept = "";
client->cProtocol = "";
client->cVersion = 0;
client->cIsUpgrade = false;
@ -232,7 +247,59 @@ void WebSocketsClient::handleClientData(void) {
break;
}
}
#ifdef ESP8266
delay(0);
#endif
}
/**
* send the WebSocket header to Server
* @param client WSclient_t * ptr to the client struct
*/
void WebSocketsClient::sendHeader(WSclient_t * client) {
DEBUG_WEBSOCKETS("[WS-Client][sendHeader] sending header...\n");
uint8_t randomKey[16] = { 0 };
for(uint8_t i = 0; i < sizeof(randomKey); i++) {
randomKey[i] = random(0xFF);
}
client->cKey = base64_encode(&randomKey[0], 16);
unsigned long start = micros();
//todo use tcp.write only once
client->tcp.write("GET ");
client->tcp.write(client->cUrl.c_str(), client->cUrl.length());
client->tcp.write(" HTTP/1.1\r\n"
"Host: ");
client->tcp.write(_host.c_str(), _host.length());
client->tcp.write("\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"User-Agent: arduino-WebSocket-Client\r\n"
"Sec-WebSocket-Version: 13\r\n"
"Sec-WebSocket-Protocol: arduino\r\n" // todo add api to set Protocol of Server
"Sec-WebSocket-Key: ");
client->tcp.write(client->cKey.c_str(), client->cKey.length());
client->tcp.write("\r\n");
if(client->cExtensions.length() > 0) {
client->tcp.write("Sec-WebSocket-Extensions: ");
client->tcp.write(client->cExtensions.c_str(), client->cExtensions.length());
client->tcp.write("\r\n");
}
// Header end wait for Server response
client->tcp.write("\r\n");
DEBUG_WEBSOCKETS("[WS-Client][sendHeader] sending header... Done (%uus).\n", (micros() - start));
}
/**
@ -247,36 +314,40 @@ void WebSocketsClient::handleHeader(WSclient_t * client) {
if(headerLine.length() > 0) {
DEBUG_WEBSOCKETS("[WS-Client][handleHeader] RX: %s\n", headerLine.c_str());
// websocket request starts allways with GET see rfc6455
if(headerLine.startsWith("GET ")) {
// cut URL out
client->cUrl = headerLine.substring(4, headerLine.indexOf(' ', 4));
if(headerLine.startsWith("HTTP/1.")) {
// "HTTP/1.1 101 Switching Protocols"
client->cCode = headerLine.substring(9, headerLine.indexOf(' ', 9)).toInt();
} else if(headerLine == "Connection: Upgrade") {
client->cIsUpgrade = true;
} else if(headerLine == "Upgrade: websocket") {
client->cIsWebsocket = true;
} else if(headerLine.startsWith("Sec-WebSocket-Version: ")) {
// 23 = lenght of "Sec-WebSocket-Version: "
client->cVersion = headerLine.substring(23).toInt();
} else if(headerLine.startsWith("Sec-WebSocket-Key: ")) {
// 19 = lenght of "Sec-WebSocket-Key: "
client->cKey = headerLine.substring(19);
client->cKey.trim(); // see rfc6455
} else if(headerLine.startsWith("Sec-WebSocket-Accept: ")) {
// 22 = lenght of "Sec-WebSocket-Accept: "
client->cAccept = headerLine.substring(22);
client->cAccept.trim(); // see rfc6455
} else if(headerLine.startsWith("Sec-WebSocket-Protocol: ")) {
// 24 = lenght of "Sec-WebSocket-Protocol: "
client->cProtocol = headerLine.substring(24);
} else if(headerLine.startsWith("Sec-WebSocket-Extensions: ")) {
// 26 = lenght of "Sec-WebSocket-Extensions: "
client->cExtensions = headerLine.substring(26);
} else if(headerLine.startsWith("Sec-WebSocket-Version: ")) {
// 23 = lenght of "Sec-WebSocket-Version: "
client->cVersion = headerLine.substring(23).toInt();
}
} else {
DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Header read fin.\n");
DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Client settings:\n");
DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cURL: %s\n", client->cUrl.c_str());
DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cKey: %s\n", client->cKey.c_str());
DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Server header:\n");
DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cCode: %d\n", client->cCode);
DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cIsUpgrade: %d\n", client->cIsUpgrade);
DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cIsWebsocket: %d\n", client->cIsWebsocket);
DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cKey: %s\n", client->cKey.c_str());
DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cAccept: %s\n", client->cAccept.c_str());
DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cProtocol: %s\n", client->cProtocol.c_str());
DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cExtensions: %s\n", client->cExtensions.c_str());
DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cVersion: %d\n", client->cVersion);
@ -284,62 +355,46 @@ void WebSocketsClient::handleHeader(WSclient_t * client) {
bool ok = (client->cIsUpgrade && client->cIsWebsocket);
if(ok) {
if(client->cUrl.length() == 0) {
ok = false;
switch(client->cCode) {
case 101: ///< Switching Protocols
break;
case 403: ///< Forbidden
// todo handle login
default: ///< Server dont unterstand requrst
ok = false;
DEBUG_WEBSOCKETS("[WS-Client][handleHeader] serverCode is not 101 (%d)\n", client->cCode);
clientDisconnect(&_client);
break;
}
if(client->cKey.length() == 0) {
ok = false;
}
if(client->cVersion != 13) {
}
if(ok) {
if(client->cAccept.length() == 0) {
ok = false;
} else {
// generate Sec-WebSocket-Accept key for check
String sKey = acceptKey(client->cKey);
if(sKey != client->cAccept) {
DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Sec-WebSocket-Accept is wrong\n");
ok = false;
}
}
}
if(ok) {
DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Websocket connection incomming.\n");
// generate Sec-WebSocket-Accept key
String sKey = acceptKey(client->cKey);
DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - sKey: %s\n", sKey.c_str());
DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Websocket connection init done.\n");
client->status = WSC_CONNECTED;
client->tcp.write("HTTP/1.1 101 Switching Protocols\r\n"
"Server: ESP8266-WebSocketsClient\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Version: 13\r\n"
"Sec-WebSocket-Accept: ");
client->tcp.write(sKey.c_str(), sKey.length());
client->tcp.write("\r\n");
if(client->cProtocol.length() > 0) {
// todo add api to set Protocol of Server
client->tcp.write("Sec-WebSocket-Protocol: esp8266\r\n");
}
// header end
client->tcp.write("\r\n");
// send ping
WebSockets::sendFrame(client, WSop_ping);
if(_cbEvent) {
_cbEvent(WStype_CONNECTED, (uint8_t *) client->cUrl.c_str(), client->cUrl.length());
}
} else {
DEBUG_WEBSOCKETS("[WS-Client][handleHeader] no Websocket connection close.\n");
client->tcp.write("HTTP/1.1 400 Bad Request\r\n"
"Server: ESP8266-WebSocketsClient\r\n"
"Content-Type: text/plain\r\n"
"Content-Length: 32\r\n"
"Connection: close\r\n"
"Sec-WebSocket-Version: 13\r\n"
"\r\n"
"This is a Websocket server only!");
client->tcp.write("This is a webSocket client!");
clientDisconnect(client);
}
}

View File

@ -47,8 +47,8 @@ class WebSocketsClient: private WebSockets {
WebSocketsClient(void);
~WebSocketsClient(void);
void begin(const char *host, uint16_t port, const char * url);
void begin(String host, uint16_t port, String url);
void begin(const char *host, uint16_t port, const char * url = "/");
void begin(String host, uint16_t port, String url = "/");
void loop(void);
@ -81,6 +81,7 @@ class WebSocketsClient: private WebSockets {
void handleNewClients(void);
void handleClientData(void);
void sendHeader(WSclient_t * client);
void handleHeader(WSclient_t * client);
};

View File

@ -58,6 +58,9 @@ void WebSocketsServer::begin(void) {
client->status = WSC_NOT_CONNECTED;
}
// todo find better seed
randomSeed(millis());
_server->begin();
}
@ -128,7 +131,9 @@ void WebSocketsServer::broadcastTXT(uint8_t * payload, size_t length) {
if(clientIsConnected(client)) {
sendFrame(client, WSop_text, payload, length);
}
#ifdef ESP8266
delay(0);
#endif
}
}
@ -180,7 +185,9 @@ void WebSocketsServer::broadcastBIN(uint8_t * payload, size_t length) {
if(clientIsConnected(client)) {
sendFrame(client, WSop_binary, payload, length);
}
#ifdef ESP8266
delay(0);
#endif
}
}
@ -320,7 +327,9 @@ void WebSocketsServer::handleNewClients(void) {
// store new connection
client->tcp = _server->available();
#ifdef ESP8266
client->tcp.setNoDelay(true);
#endif
// set Timeout for readBytesUntil and readStringUntil
client->tcp.setTimeout(WEBSOCKETS_TCP_TIMEOUT);
client->status = WSC_HEADER;
@ -340,7 +349,9 @@ void WebSocketsServer::handleNewClients(void) {
tcpClient.stop();
}
#ifdef ESP8266
delay(0);
#endif
}
}
@ -369,7 +380,9 @@ void WebSocketsServer::handleClientData(void) {
}
}
}
#ifdef ESP8266
delay(0);
#endif
}
}
@ -455,7 +468,7 @@ void WebSocketsServer::handleHeader(WSclient_t * client) {
if(client->cProtocol.length() > 0) {
// todo add api to set Protocol of Server
client->tcp.write("Sec-WebSocket-Protocol: esp8266\r\n");
client->tcp.write("Sec-WebSocket-Protocol: arduino\r\n");
}
// header end
@ -471,7 +484,7 @@ void WebSocketsServer::handleHeader(WSclient_t * client) {
} else {
DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] no Websocket connection close.\n", client->num);
client->tcp.write("HTTP/1.1 400 Bad Request\r\n"
"Server: ESP8266-WebSocketsServer\r\n"
"Server: arduino-WebSocket-Server\r\n"
"Content-Type: text/plain\r\n"
"Content-Length: 32\r\n"
"Connection: close\r\n"