Add binary frames support and tests, to close feature request #38.

This commit is contained in:
David Baird
2014-12-06 23:38:35 -05:00
parent 4fe25a880f
commit e1e50d092d
6 changed files with 139 additions and 57 deletions
+43 -13
View File
@@ -74,6 +74,9 @@
#include "easywsclient.hpp"
using easywsclient::Callback_Imp;
using easywsclient::BytesCallback_Imp;
namespace { // private module-only namespace
socket_t hostname_connect(const std::string& hostname, int port) {
@@ -112,14 +115,16 @@ class _DummyWebSocket : public easywsclient::WebSocket
public:
void poll(int timeout) { }
void send(const std::string& message) { }
void sendBinary(const std::string& message) { }
void sendBinary(const std::vector<uint8_t>& message) { }
void sendPing() { }
void close() { }
void _dispatch(Callback & callable) { }
readyStateValues getReadyState() const { return CLOSED; }
void _dispatch(Callback_Imp & callable) { }
void _dispatchBinary(BytesCallback_Imp& callable) { }
};
class _RealWebSocket : public easywsclient::WebSocket
{
public:
@@ -242,7 +247,22 @@ class _RealWebSocket : public easywsclient::WebSocket
// lambda:
//template<class Callable>
//void dispatch(Callable callable)
virtual void _dispatch(WebSocket::Callback & callable) {
virtual void _dispatch(Callback_Imp & callable) {
struct CallbackAdapter : public BytesCallback_Imp
// Adapt void(const std::string<uint8_t>&) to void(const std::string&)
{
Callback_Imp& callable;
CallbackAdapter(Callback_Imp& callable) : callable(callable) { }
void operator()(const std::vector<uint8_t>& message) {
std::string stringMessage(message.begin(), message.end());
callable(stringMessage);
}
};
CallbackAdapter bytesCallback(callable);
_dispatchBinary(bytesCallback);
}
virtual void _dispatchBinary(BytesCallback_Imp & callable) {
// TODO: consider acquiring a lock on rxbuf...
while (true) {
wsheader_type ws;
@@ -293,14 +313,15 @@ class _RealWebSocket : public easywsclient::WebSocket
// We got a whole message, now do something with it:
if (false) { }
else if (ws.opcode == wsheader_type::TEXT_FRAME
else if (
ws.opcode == wsheader_type::TEXT_FRAME
|| ws.opcode == wsheader_type::BINARY_FRAME
|| ws.opcode == wsheader_type::CONTINUATION
) {
if (ws.mask) { for (size_t i = 0; i != ws.N; ++i) { rxbuf[i+ws.header_size] ^= ws.masking_key[i&0x3]; } }
receivedData.insert(receivedData.end(), rxbuf.begin()+ws.header_size, rxbuf.begin()+ws.header_size+(size_t)ws.N);// just feed
if (ws.fin) {
std::string data(receivedData.begin(), receivedData.end());
callable((const std::string) data);
callable((const std::vector<uint8_t>) receivedData);
receivedData.erase(receivedData.begin(), receivedData.end());
std::vector<uint8_t> ().swap(receivedData);// free memory
}
@@ -308,7 +329,7 @@ class _RealWebSocket : public easywsclient::WebSocket
else if (ws.opcode == wsheader_type::PING) {
if (ws.mask) { for (size_t i = 0; i != ws.N; ++i) { rxbuf[i+ws.header_size] ^= ws.masking_key[i&0x3]; } }
std::string data(rxbuf.begin()+ws.header_size, rxbuf.begin()+ws.header_size+(size_t)ws.N);
sendData(wsheader_type::PONG, data);
sendData(wsheader_type::PONG, data.size(), data.begin(), data.end());
}
else if (ws.opcode == wsheader_type::PONG) { }
else if (ws.opcode == wsheader_type::CLOSE) { close(); }
@@ -319,14 +340,24 @@ class _RealWebSocket : public easywsclient::WebSocket
}
void sendPing() {
sendData(wsheader_type::PING, std::string());
std::string empty;
sendData(wsheader_type::PING, empty.size(), empty.begin(), empty.end());
}
void send(const std::string& message) {
sendData(wsheader_type::TEXT_FRAME, message);
sendData(wsheader_type::TEXT_FRAME, message.size(), message.begin(), message.end());
}
void sendData(wsheader_type::opcode_type type, const std::string& message) {
void sendBinary(const std::string& message) {
sendData(wsheader_type::BINARY_FRAME, message.size(), message.begin(), message.end());
}
void sendBinary(const std::vector<uint8_t>& message) {
sendData(wsheader_type::BINARY_FRAME, message.size(), message.begin(), message.end());
}
template<class Iterator>
void sendData(wsheader_type::opcode_type type, uint64_t message_size, Iterator message_begin, Iterator message_end) {
// TODO:
// Masking key should (must) be derived from a high quality random
// number generator, to mitigate attacks on non-WebSocket friendly
@@ -335,7 +366,6 @@ class _RealWebSocket : public easywsclient::WebSocket
// TODO: consider acquiring a lock on txbuf...
if (readyState == CLOSING || readyState == CLOSED) { return; }
std::vector<uint8_t> header;
uint64_t message_size = message.size();
header.assign(2 + (message_size >= 126 ? 2 : 0) + (message_size >= 65536 ? 6 : 0) + (useMask ? 4 : 0), 0);
header[0] = 0x80 | type;
if (false) { }
@@ -378,9 +408,9 @@ class _RealWebSocket : public easywsclient::WebSocket
}
// N.B. - txbuf will keep growing until it can be transmitted over the socket:
txbuf.insert(txbuf.end(), header.begin(), header.end());
txbuf.insert(txbuf.end(), message.begin(), message.end());
txbuf.insert(txbuf.end(), message_begin, message_end);
if (useMask) {
for (size_t i = 0; i != message.size(); ++i) { *(txbuf.end() - message.size() + i) ^= masking_key[i&0x3]; }
for (size_t i = 0; i != message_size; ++i) { *(txbuf.end() - message_size + i) ^= masking_key[i&0x3]; }
}
}