unittest on appveyor

This commit is contained in:
Benjamin Sergeant
2019-01-04 17:28:13 -08:00
parent 9bfba28d01
commit 3eef8fba27
40 changed files with 5441 additions and 246 deletions

View File

@ -6,9 +6,18 @@
#include "IXDNSLookup.h"
#ifdef _WIN32
# include <basetsd.h>
# include <WinSock2.h>
# include <ws2def.h>
# include <WS2tcpip.h>
# include <io.h>
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#endif
#include <string.h>
#include <chrono>

View File

@ -125,7 +125,7 @@ namespace ix
return (int) ::recv(_sockfd, (char*) buffer, length, flags);
}
int Socket::getErrno() const
int Socket::getErrno()
{
#ifdef _WIN32
return WSAGetLastError();

View File

@ -47,7 +47,7 @@ namespace ix
const CancellationRequest& isCancellationRequested);
std::pair<bool, std::string> readLine(const CancellationRequest& isCancellationRequested);
int getErrno() const;
static int getErrno();
static bool init(); // Required on Windows to initialize WinSocket
static void cleanup(); // Required on Windows to cleanup WinSocket

View File

@ -104,7 +104,11 @@ namespace ix
if (!FD_ISSET(fd, &wfds)) continue;
// Something was written to the socket
#ifdef _WIN32
char optval = -1;
#else
int optval = -1;
#endif
socklen_t optlen = sizeof(optval);
// getsockopt() puts the errno value for connect into optval so 0
@ -173,7 +177,7 @@ namespace ix
// 2. make socket non blocking
#ifdef _WIN32
unsigned long nonblocking = 1;
ioctlsocket(_sockfd, FIONBIO, &nonblocking);
ioctlsocket(sockfd, FIONBIO, &nonblocking);
#else
fcntl(sockfd, F_SETFL, O_NONBLOCK); // make socket non blocking
#endif

View File

@ -44,8 +44,8 @@ namespace ix
std::string& errMsg);
bool checkHost(const std::string& host, const char *pattern);
SSL_CTX* _ssl_context;
SSL* _ssl_connection;
SSL_CTX* _ssl_context;
const SSL_METHOD* _ssl_method;
mutable std::mutex _mutex; // OpenSSL routines are not thread-safe
};

View File

@ -47,201 +47,10 @@
#include "IXWebSocketPerMessageDeflate.h"
#include "IXWebSocketPerMessageDeflateOptions.h"
#include <iostream>
#include <cassert>
#include <string.h>
namespace
{
// The passed in size (4) is important, without it the string litteral
// is treated as a char* and the null termination (\x00) makes it
// look like an empty string.
const std::string kEmptyUncompressedBlock = std::string("\x00\x00\xff\xff", 4);
const int kBufferSize = 1 << 14;
}
#include "IXWebSocketPerMessageDeflateCodec.h"
namespace ix
{
//
// Compressor
//
WebSocketPerMessageDeflateCompressor::WebSocketPerMessageDeflateCompressor()
: _compressBufferSize(kBufferSize)
{
memset(&_deflateState, 0, sizeof(_deflateState));
_deflateState.zalloc = Z_NULL;
_deflateState.zfree = Z_NULL;
_deflateState.opaque = Z_NULL;
}
WebSocketPerMessageDeflateCompressor::~WebSocketPerMessageDeflateCompressor()
{
deflateEnd(&_deflateState);
}
bool WebSocketPerMessageDeflateCompressor::init(uint8_t deflateBits,
bool clientNoContextTakeOver)
{
int ret = deflateInit2(
&_deflateState,
Z_DEFAULT_COMPRESSION,
Z_DEFLATED,
-1*deflateBits,
4, // memory level 1-9
Z_DEFAULT_STRATEGY
);
if (ret != Z_OK) return false;
_compressBuffer.reset(new unsigned char[_compressBufferSize]);
_flush = (clientNoContextTakeOver)
? Z_FULL_FLUSH
: Z_SYNC_FLUSH;
return true;
}
bool WebSocketPerMessageDeflateCompressor::endsWith(const std::string& value,
const std::string& ending)
{
if (ending.size() > value.size()) return false;
return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
}
bool WebSocketPerMessageDeflateCompressor::compress(const std::string& in,
std::string& out)
{
//
// 7.2.1. Compression
//
// An endpoint uses the following algorithm to compress a message.
//
// 1. Compress all the octets of the payload of the message using
// DEFLATE.
//
// 2. If the resulting data does not end with an empty DEFLATE block
// with no compression (the "BTYPE" bits are set to 00), append an
// empty DEFLATE block with no compression to the tail end.
//
// 3. Remove 4 octets (that are 0x00 0x00 0xff 0xff) from the tail end.
// After this step, the last octet of the compressed data contains
// (possibly part of) the DEFLATE header bits with the "BTYPE" bits
// set to 00.
//
size_t output;
if (in.empty())
{
uint8_t buf[6] = {0x02, 0x00, 0x00, 0x00, 0xff, 0xff};
out.append((char *)(buf), 6);
return true;
}
_deflateState.avail_in = (uInt) in.size();
_deflateState.next_in = (Bytef*) in.data();
do
{
// Output to local buffer
_deflateState.avail_out = (uInt) _compressBufferSize;
_deflateState.next_out = _compressBuffer.get();
deflate(&_deflateState, _flush);
output = _compressBufferSize - _deflateState.avail_out;
out.append((char *)(_compressBuffer.get()),output);
} while (_deflateState.avail_out == 0);
if (endsWith(out, kEmptyUncompressedBlock))
{
out.resize(out.size() - 4);
}
return true;
}
//
// Decompressor
//
WebSocketPerMessageDeflateDecompressor::WebSocketPerMessageDeflateDecompressor()
: _compressBufferSize(kBufferSize)
{
memset(&_inflateState, 0, sizeof(_inflateState));
_inflateState.zalloc = Z_NULL;
_inflateState.zfree = Z_NULL;
_inflateState.opaque = Z_NULL;
_inflateState.avail_in = 0;
_inflateState.next_in = Z_NULL;
}
WebSocketPerMessageDeflateDecompressor::~WebSocketPerMessageDeflateDecompressor()
{
inflateEnd(&_inflateState);
}
bool WebSocketPerMessageDeflateDecompressor::init(uint8_t inflateBits,
bool clientNoContextTakeOver)
{
int ret = inflateInit2(
&_inflateState,
-1*inflateBits
);
if (ret != Z_OK) return false;
_compressBuffer.reset(new unsigned char[_compressBufferSize]);
_flush = (clientNoContextTakeOver)
? Z_FULL_FLUSH
: Z_SYNC_FLUSH;
return true;
}
bool WebSocketPerMessageDeflateDecompressor::decompress(const std::string& in,
std::string& out)
{
//
// 7.2.2. Decompression
//
// An endpoint uses the following algorithm to decompress a message.
//
// 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the
// payload of the message.
//
// 2. Decompress the resulting data using DEFLATE.
//
std::string inFixed(in);
inFixed += kEmptyUncompressedBlock;
_inflateState.avail_in = (uInt) inFixed.size();
_inflateState.next_in = (unsigned char *)(const_cast<char *>(inFixed.data()));
do
{
_inflateState.avail_out = (uInt) _compressBufferSize;
_inflateState.next_out = _compressBuffer.get();
int ret = inflate(&_inflateState, Z_SYNC_FLUSH);
if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
{
return false; // zlib error
}
out.append(
reinterpret_cast<char *>(_compressBuffer.get()),
_compressBufferSize - _inflateState.avail_out
);
} while (_inflateState.avail_out == 0);
return true;
}
WebSocketPerMessageDeflate::WebSocketPerMessageDeflate()
{
_compressor.reset(new WebSocketPerMessageDeflateCompressor());

View File

@ -34,47 +34,14 @@
#pragma once
#include "zlib.h"
#include <string>
#include <memory>
namespace ix
{
class WebSocketPerMessageDeflateOptions;
class WebSocketPerMessageDeflateCompressor
{
public:
WebSocketPerMessageDeflateCompressor();
~WebSocketPerMessageDeflateCompressor();
bool init(uint8_t deflateBits, bool clientNoContextTakeOver);
bool compress(const std::string& in, std::string& out);
private:
static bool endsWith(const std::string& value, const std::string& ending);
int _flush;
size_t _compressBufferSize;
std::unique_ptr<unsigned char[]> _compressBuffer;
z_stream _deflateState;
};
class WebSocketPerMessageDeflateDecompressor
{
public:
WebSocketPerMessageDeflateDecompressor();
~WebSocketPerMessageDeflateDecompressor();
bool init(uint8_t inflateBits, bool clientNoContextTakeOver);
bool decompress(const std::string& in, std::string& out);
private:
int _flush;
size_t _compressBufferSize;
std::unique_ptr<unsigned char[]> _compressBuffer;
z_stream _inflateState;
};
class WebSocketPerMessageDeflateCompressor;
class WebSocketPerMessageDeflateDecompressor;
class WebSocketPerMessageDeflate
{

View File

@ -0,0 +1,204 @@
/*
* IXWebSocketPerMessageDeflateCodec.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXWebSocketPerMessageDeflateCodec.h"
#include "IXWebSocketPerMessageDeflateOptions.h"
#include <iostream>
#include <cassert>
#include <string.h>
namespace
{
// The passed in size (4) is important, without it the string litteral
// is treated as a char* and the null termination (\x00) makes it
// look like an empty string.
const std::string kEmptyUncompressedBlock = std::string("\x00\x00\xff\xff", 4);
const int kBufferSize = 1 << 14;
}
namespace ix
{
//
// Compressor
//
WebSocketPerMessageDeflateCompressor::WebSocketPerMessageDeflateCompressor()
: _compressBufferSize(kBufferSize)
{
memset(&_deflateState, 0, sizeof(_deflateState));
_deflateState.zalloc = Z_NULL;
_deflateState.zfree = Z_NULL;
_deflateState.opaque = Z_NULL;
}
WebSocketPerMessageDeflateCompressor::~WebSocketPerMessageDeflateCompressor()
{
deflateEnd(&_deflateState);
}
bool WebSocketPerMessageDeflateCompressor::init(uint8_t deflateBits,
bool clientNoContextTakeOver)
{
int ret = deflateInit2(
&_deflateState,
Z_DEFAULT_COMPRESSION,
Z_DEFLATED,
-1*deflateBits,
4, // memory level 1-9
Z_DEFAULT_STRATEGY
);
if (ret != Z_OK) return false;
_compressBuffer.reset(new unsigned char[_compressBufferSize]);
_flush = (clientNoContextTakeOver)
? Z_FULL_FLUSH
: Z_SYNC_FLUSH;
return true;
}
bool WebSocketPerMessageDeflateCompressor::endsWith(const std::string& value,
const std::string& ending)
{
if (ending.size() > value.size()) return false;
return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
}
bool WebSocketPerMessageDeflateCompressor::compress(const std::string& in,
std::string& out)
{
//
// 7.2.1. Compression
//
// An endpoint uses the following algorithm to compress a message.
//
// 1. Compress all the octets of the payload of the message using
// DEFLATE.
//
// 2. If the resulting data does not end with an empty DEFLATE block
// with no compression (the "BTYPE" bits are set to 00), append an
// empty DEFLATE block with no compression to the tail end.
//
// 3. Remove 4 octets (that are 0x00 0x00 0xff 0xff) from the tail end.
// After this step, the last octet of the compressed data contains
// (possibly part of) the DEFLATE header bits with the "BTYPE" bits
// set to 00.
//
size_t output;
if (in.empty())
{
uint8_t buf[6] = {0x02, 0x00, 0x00, 0x00, 0xff, 0xff};
out.append((char *)(buf), 6);
return true;
}
_deflateState.avail_in = (uInt) in.size();
_deflateState.next_in = (Bytef*) in.data();
do
{
// Output to local buffer
_deflateState.avail_out = (uInt) _compressBufferSize;
_deflateState.next_out = _compressBuffer.get();
deflate(&_deflateState, _flush);
output = _compressBufferSize - _deflateState.avail_out;
out.append((char *)(_compressBuffer.get()),output);
} while (_deflateState.avail_out == 0);
if (endsWith(out, kEmptyUncompressedBlock))
{
out.resize(out.size() - 4);
}
return true;
}
//
// Decompressor
//
WebSocketPerMessageDeflateDecompressor::WebSocketPerMessageDeflateDecompressor()
: _compressBufferSize(kBufferSize)
{
memset(&_inflateState, 0, sizeof(_inflateState));
_inflateState.zalloc = Z_NULL;
_inflateState.zfree = Z_NULL;
_inflateState.opaque = Z_NULL;
_inflateState.avail_in = 0;
_inflateState.next_in = Z_NULL;
}
WebSocketPerMessageDeflateDecompressor::~WebSocketPerMessageDeflateDecompressor()
{
inflateEnd(&_inflateState);
}
bool WebSocketPerMessageDeflateDecompressor::init(uint8_t inflateBits,
bool clientNoContextTakeOver)
{
int ret = inflateInit2(
&_inflateState,
-1*inflateBits
);
if (ret != Z_OK) return false;
_compressBuffer.reset(new unsigned char[_compressBufferSize]);
_flush = (clientNoContextTakeOver)
? Z_FULL_FLUSH
: Z_SYNC_FLUSH;
return true;
}
bool WebSocketPerMessageDeflateDecompressor::decompress(const std::string& in,
std::string& out)
{
//
// 7.2.2. Decompression
//
// An endpoint uses the following algorithm to decompress a message.
//
// 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the
// payload of the message.
//
// 2. Decompress the resulting data using DEFLATE.
//
std::string inFixed(in);
inFixed += kEmptyUncompressedBlock;
_inflateState.avail_in = (uInt) inFixed.size();
_inflateState.next_in = (unsigned char *)(const_cast<char *>(inFixed.data()));
do
{
_inflateState.avail_out = (uInt) _compressBufferSize;
_inflateState.next_out = _compressBuffer.get();
int ret = inflate(&_inflateState, Z_SYNC_FLUSH);
if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
{
return false; // zlib error
}
out.append(
reinterpret_cast<char *>(_compressBuffer.get()),
_compressBufferSize - _inflateState.avail_out
);
} while (_inflateState.avail_out == 0);
return true;
}
}

View File

@ -0,0 +1,50 @@
/*
* IXWebSocketPerMessageDeflateCodec.h
* Author: Benjamin Sergeant
* Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "zlib.h"
#include <string>
#include <memory>
namespace ix
{
class WebSocketPerMessageDeflateCompressor
{
public:
WebSocketPerMessageDeflateCompressor();
~WebSocketPerMessageDeflateCompressor();
bool init(uint8_t deflateBits, bool clientNoContextTakeOver);
bool compress(const std::string& in, std::string& out);
private:
static bool endsWith(const std::string& value, const std::string& ending);
int _flush;
size_t _compressBufferSize;
std::unique_ptr<unsigned char[]> _compressBuffer;
z_stream _deflateState;
};
class WebSocketPerMessageDeflateDecompressor
{
public:
WebSocketPerMessageDeflateDecompressor();
~WebSocketPerMessageDeflateDecompressor();
bool init(uint8_t inflateBits, bool clientNoContextTakeOver);
bool decompress(const std::string& in, std::string& out);
private:
int _flush;
size_t _compressBufferSize;
std::unique_ptr<unsigned char[]> _compressBuffer;
z_stream _inflateState;
};
}

View File

@ -9,6 +9,7 @@
#include <sstream>
#include <iostream>
#include <algorithm>
#include <cctype>
namespace ix
{

View File

@ -12,10 +12,20 @@
#include <sstream>
#include <future>
#include <netdb.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#ifdef _WIN32
# include <basetsd.h>
# include <WinSock2.h>
# include <ws2def.h>
# include <WS2tcpip.h>
# include <io.h>
#else
# include <unistd.h>
# include <netdb.h>
# include <stdio.h>
# include <arpa/inet.h>
# include <sys/socket.h>
#endif
#include <string.h>
namespace ix
@ -72,14 +82,15 @@ namespace ix
{
std::stringstream ss;
ss << "WebSocketServer::listen() error creating socket): "
<< strerror(errno);
<< strerror(Socket::getErrno());
return std::make_pair(false, ss.str());
}
// Make that socket reusable. (allow restarting this server at will)
int enable = 1;
if (setsockopt(_serverFd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0)
if (setsockopt(_serverFd, SOL_SOCKET, SO_REUSEADDR,
(char*) &enable, sizeof(enable)) < 0)
{
std::stringstream ss;
ss << "WebSocketServer::listen() error calling setsockopt(SO_REUSEADDR): "
@ -105,7 +116,7 @@ namespace ix
{
std::stringstream ss;
ss << "WebSocketServer::listen() error calling bind: "
<< strerror(errno);
<< strerror(Socket::getErrno());
return std::make_pair(false, ss.str());
}
@ -117,7 +128,7 @@ namespace ix
{
std::stringstream ss;
ss << "WebSocketServer::listen() error calling listen: "
<< strerror(errno);
<< strerror(Socket::getErrno());
return std::make_pair(false, ss.str());
}
@ -191,12 +202,12 @@ namespace ix
if ((clientFd = accept(_serverFd, (struct sockaddr *)&client, &addressLen)) < 0)
{
if (errno != EWOULDBLOCK)
if (Socket::getErrno() != EWOULDBLOCK)
{
// FIXME: that error should be propagated
std::stringstream ss;
ss << "WebSocketServer::run() error accepting connection: "
<< strerror(errno);
<< strerror(Socket::getErrno());
logError(ss.str());
}
continue;

View File

@ -409,7 +409,7 @@ namespace ix
{
std::string decompressedMessage;
bool success = _perMessageDeflate.decompress(message, decompressedMessage);
onMessageCallback(decompressedMessage, wireSize, not success, messageKind);
onMessageCallback(decompressedMessage, wireSize, !success, messageKind);
}
else
{

View File

@ -0,0 +1,16 @@
/*
* IXSetThreadName_windows.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "../IXSetThreadName.h"
#include <iostream>
namespace ix
{
void setThreadName(const std::string& name)
{
// FIXME
std::cerr << "setThreadName not implemented on Windows yet" << std::endl;
}
}