Compare commits

...

52 Commits

Author SHA1 Message Date
fa7ef06f4d heartbeat correct 2019-01-25 16:11:39 -08:00
3c9ec0aed0 close server socket on exit 2019-01-24 21:16:32 -08:00
c665d65cba unittest fix 2019-01-24 19:54:10 -08:00
5d4e897cc4 add an heartbeat test 2019-01-24 18:50:07 -08:00
05033714bf hearbeat 2019-01-24 12:42:49 -08:00
fdbd213fa2 check and validate the Connection: Upgrade header in client/server 2019-01-15 09:31:37 -08:00
da64d349c8 Merge pull request #10 from tonylin0826/master
Fix missing "Upgrade" header error
2019-01-15 09:22:11 -08:00
17b01a8c66 Fix missing upgrade header error 2019-01-15 15:35:37 +08:00
79dd766fab C++14 + use make_unique and make_shared to make shared pointers 2019-01-11 21:25:06 -08:00
8375b28747 add travis badge 2019-01-08 10:13:23 -08:00
e12551f309 travis -> osx 2019-01-08 10:04:47 -08:00
6102f81710 Revert "Revert "try asan on Linux"" [Back to asan on Linux]
This reverts commit 02a704a8c7.
2019-01-07 21:13:48 -08:00
9f678e5962 travis-ci: try to use clang on Linux 2019-01-07 20:49:03 -08:00
02a704a8c7 Revert "try asan on Linux"
This reverts commit dd2360ed70.
2019-01-07 20:47:25 -08:00
dd2360ed70 try asan on Linux 2019-01-07 18:29:44 -08:00
c4ab996470 build with osx on travis 2019-01-07 18:16:29 -08:00
6c54b07d92 fix simple compile error in test/IXTest.h 2019-01-07 18:08:11 -08:00
7f9bef3b8d add a travis file for real 2019-01-07 18:05:55 -08:00
12d1c5d956 add a travis file 2019-01-07 18:04:28 -08:00
e9a4bd5617 update test remote ws url 2019-01-07 11:28:53 -08:00
f34ccbfdb5 remove cmake sanitizer submodule 2019-01-07 11:26:23 -08:00
1fa75d7fb2 check select errors better 2019-01-07 11:18:00 -08:00
39140ef98c sanitizer cmake stuff 2019-01-06 18:54:16 -08:00
e30ef4a87c DNSLookup _id member does not need to be an atomic 2019-01-06 18:32:19 -08:00
9fc94f0487 DNSLookup: fix #8 2019-01-06 18:27:26 -08:00
121acdab6f DNSLookup: copy hostname and port instead of accessing member 2019-01-06 18:17:12 -08:00
6deaa03114 return false -> return -1 2019-01-06 18:10:39 -08:00
f4f30686c5 add new unittest 2019-01-06 15:14:13 -08:00
a21aae521f remove dead file 2019-01-06 14:26:11 -08:00
aed2356fc1 remove openssl testing bits for apple build 2019-01-06 14:21:49 -08:00
a478f734f6 gcc linux compile fix 2019-01-06 12:12:39 -08:00
98c579da03 make a class hierarchy for server code (IXWebSocketServer <- IXSocketServer) 2019-01-06 12:09:31 -08:00
e80def0cd0 add log 2019-01-05 21:16:13 -08:00
cc8a9e883e unittest + compiler warnings 2019-01-05 21:10:08 -08:00
4d587e35d8 windows compile fix 2019-01-05 21:02:55 -08:00
50f4fd1115 int -> ssize_t for socker recv and send 2019-01-05 20:53:50 -08:00
06d2b68696 header refactoring 2019-01-05 20:38:43 -08:00
bf6f057777 windows connect (compile fix) 2019-01-05 17:35:50 -08:00
b57c1d69f2 windows connect potential fix 2019-01-05 17:32:21 -08:00
ff265d83f9 more accurate description of errors 2019-01-05 17:18:43 -08:00
5b1c97b774 SocketTest / more debug info 2019-01-05 17:10:01 -08:00
c8c81366f7 windows (compile) fix 2019-01-05 17:04:09 -08:00
9a37fd56d1 windows fix 2019-01-05 17:02:39 -08:00
7ecaff8c5d test failure is not noticed 2019-01-05 16:30:22 -08:00
e4b0286a25 fix gcc warning 2019-01-05 16:26:11 -08:00
7ae6972306 makefile tweak 2019-01-05 14:43:21 -08:00
59cea0372b add dns lookup test 2019-01-05 14:40:17 -08:00
78d88a8520 openssl cleanup 2019-01-05 11:42:25 -08:00
273af25d57 Merge pull request #7 from bsergean/user/bsergeant/appveyor_first
unittest on appveyor
2019-01-04 17:29:23 -08:00
46d00360a8 unittest on appveyor 2019-01-04 17:28:13 -08:00
3f5935a284 windows fixes 2019-01-04 15:23:57 -08:00
c236ff66e9 Merge pull request #6 from machinezone/user/bsergeant/server
Add support for writing websocket servers (IXWebSocketServer)
2019-01-03 18:47:30 -08:00
84 changed files with 7393 additions and 704 deletions

0
.gitmodules vendored Normal file
View File

9
.travis.yml Normal file
View File

@ -0,0 +1,9 @@
language: cpp
dist: xenial
compiler:
- clang
# - gcc
os: osx
script: make test

View File

@ -6,15 +6,18 @@
cmake_minimum_required(VERSION 3.4.1)
project(ixwebsocket C CXX)
set (CMAKE_CXX_STANDARD 11)
set (CMAKE_CXX_STANDARD 14)
set (CXX_STANDARD_REQUIRED ON)
set (CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wshorten-64-to-32")
if (NOT WIN32)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic")
endif()
set( IXWEBSOCKET_SOURCES
ixwebsocket/IXEventFd.cpp
ixwebsocket/IXSocket.cpp
ixwebsocket/IXSocketServer.cpp
ixwebsocket/IXSocketConnect.cpp
ixwebsocket/IXDNSLookup.cpp
ixwebsocket/IXCancellationRequest.cpp
@ -23,12 +26,14 @@ set( IXWEBSOCKET_SOURCES
ixwebsocket/IXWebSocketTransport.cpp
ixwebsocket/IXWebSocketHandshake.cpp
ixwebsocket/IXWebSocketPerMessageDeflate.cpp
ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp
ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp
)
set( IXWEBSOCKET_HEADERS
ixwebsocket/IXEventFd.h
ixwebsocket/IXSocket.h
ixwebsocket/IXSocketServer.h
ixwebsocket/IXSocketConnect.h
ixwebsocket/IXSetThreadName.h
ixwebsocket/IXDNSLookup.h
@ -40,18 +45,21 @@ set( IXWEBSOCKET_HEADERS
ixwebsocket/IXWebSocketSendInfo.h
ixwebsocket/IXWebSocketErrorInfo.h
ixwebsocket/IXWebSocketPerMessageDeflate.h
ixwebsocket/IXWebSocketPerMessageDeflateCodec.h
ixwebsocket/IXWebSocketPerMessageDeflateOptions.h
ixwebsocket/IXWebSocketHttpHeaders.h
ixwebsocket/libwshandshake.hpp
)
# Platform specific code
if (APPLE)
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/apple/IXSetThreadName_apple.cpp)
elseif (WIN32)
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/windows/IXSetThreadName_windows.cpp)
else()
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/linux/IXSetThreadName_linux.cpp)
endif()
if (USE_TLS)
add_definitions(-DIXWEBSOCKET_USE_TLS)
@ -72,7 +80,33 @@ add_library( ixwebsocket STATIC
${IXWEBSOCKET_HEADERS}
)
target_link_libraries(ixwebsocket "z")
# gcc/Linux needs -pthread
find_package(Threads)
if(UNIX AND NOT APPLE)
find_package(OpenSSL REQUIRED)
add_definitions(${OPENSSL_DEFINITIONS})
message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
include_directories(${OPENSSL_INCLUDE_DIR})
endif()
if (WIN32)
get_filename_component(libz_path
${PROJECT_SOURCE_DIR}/third_party/ZLIB-Windows/zlib-1.2.11_deploy_v140/release_dynamic/x64/lib/zlib.lib
ABSOLUTE)
add_library(libz STATIC IMPORTED)
set_target_properties(libz PROPERTIES IMPORTED_LOCATION
${libz_path})
include_directories(${PROJECT_SOURCE_DIR}/third_party/ZLIB-Windows/zlib-1.2.11_deploy_v140/include)
target_link_libraries(ixwebsocket libz wsock32 ws2_32)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
else()
target_link_libraries(ixwebsocket
z ${OPENSSL_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
endif()
set( IXWEBSOCKET_INCLUDE_DIRS
.

View File

@ -1,5 +1,7 @@
# General
![Alt text](https://travis-ci.org/machinezone/IXWebSocket.svg?branch=master)
## Introduction
[*WebSocket*](https://en.wikipedia.org/wiki/WebSocket) is a computer communications protocol, providing full-duplex
@ -21,7 +23,11 @@ Here is what the client API looks like.
ix::WebSocket webSocket;
std::string url("ws://localhost:8080/");
webSocket.configure(url);
webSocket.setUrl(url);
// Optional heart beat, sent every 45 seconds when there isn't any traffic
// to make sure that load balancers do not kill an idle connection.
webSocket.setHeartBeatPeriod(45);
// Setup a callback to be fired when a message or an event (open, close, error) is received
webSocket.setOnMessageCallback(
@ -303,4 +309,13 @@ A ping message can be sent to the server, with an optional data string.
```
websocket.ping("ping data, optional (empty string is ok): limited to 125 bytes long");
### Heartbeat.
You can configure an optional heart beat / keep-alive, sent every 45 seconds
when there isn't any traffic to make sure that load balancers do not kill an
idle connection.
```
webSocket.setHeartBeatPeriod(45);
```

10
appveyor.yml Normal file
View File

@ -0,0 +1,10 @@
image:
- Visual Studio 2017
- Ubuntu
install:
- ls -al
- cmd: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat"
- python test/run.py
build: off

View File

@ -11,7 +11,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wshorten-64-to-
set (OPENSSL_PREFIX /usr/local/opt/openssl) # Homebrew openssl
set (CMAKE_CXX_STANDARD 11)
set (CMAKE_CXX_STANDARD 14)
option(USE_TLS "Add TLS support" ON)

View File

@ -7,7 +7,7 @@
cmake_minimum_required (VERSION 3.4.1)
project (cmd_websocket_chat)
set (CMAKE_CXX_STANDARD 11)
set (CMAKE_CXX_STANDARD 14)
option(USE_TLS "Add TLS support" ON)

View File

@ -11,7 +11,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wshorten-64-to-
set (OPENSSL_PREFIX /usr/local/opt/openssl) # Homebrew openssl
set (CMAKE_CXX_STANDARD 11)
set (CMAKE_CXX_STANDARD 14)
option(USE_TLS "Add TLS support" ON)

View File

@ -11,7 +11,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wshorten-64-to-
set (OPENSSL_PREFIX /usr/local/opt/openssl) # Homebrew openssl
set (CMAKE_CXX_STANDARD 11)
set (CMAKE_CXX_STANDARD 14)
option(USE_TLS "Add TLS support" ON)

View File

@ -6,7 +6,7 @@
cmake_minimum_required (VERSION 3.4.1)
project (ping_pong)
set (CMAKE_CXX_STANDARD 11)
set (CMAKE_CXX_STANDARD 14)
option(USE_TLS "Add TLS support" ON)

View File

@ -6,7 +6,7 @@
cmake_minimum_required (VERSION 3.4.1)
project (ws_connect)
set (CMAKE_CXX_STANDARD 11)
set (CMAKE_CXX_STANDARD 14)
option(USE_TLS "Add TLS support" ON)

View File

@ -5,10 +5,8 @@
*/
#include "IXDNSLookup.h"
#include "IXNetSystem.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <string.h>
#include <chrono>
@ -107,7 +105,7 @@ namespace ix
// Good resource on thread forced termination
// https://www.bo-yang.net/2017/11/19/cpp-kill-detached-thread
//
_thread = std::thread(&DNSLookup::run, this);
_thread = std::thread(&DNSLookup::run, this, _id, _hostname, _port);
_thread.detach();
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
@ -140,11 +138,13 @@ namespace ix
return _res;
}
void DNSLookup::run()
void DNSLookup::run(uint64_t id, const std::string& hostname, int port) // thread runner
{
uint64_t id = _id;
// We don't want to read or write into members variables of an object that could be
// gone, so we use temporary variables (res) or we pass in by copy everything that
// getAddrInfo needs to work.
std::string errMsg;
_res = getAddrInfo(_hostname, _port, errMsg);
struct addrinfo* res = getAddrInfo(hostname, port, errMsg);
// if this isn't an active job, and the control thread is gone
// there is not thing to do, and we don't want to touch the defunct
@ -155,9 +155,10 @@ namespace ix
return;
}
// Copy result into the member variables
_res = res;
_errMsg = errMsg;
_condition.notify_one();
_done = true;
}
}

View File

@ -43,7 +43,7 @@ namespace ix
int port,
std::string& errMsg);
void run(); // thread runner
void run(uint64_t id, const std::string& hostname, int port); // thread runner
std::string _hostname;
int _port;
@ -56,7 +56,7 @@ namespace ix
std::condition_variable _condition;
std::mutex _conditionVariableMutex;
std::atomic<uint64_t> _id;
uint64_t _id;
static std::atomic<uint64_t> _nextId;
static std::set<uint64_t> _activeJobs;
static std::mutex _activeJobsMutex;

25
ixwebsocket/IXNetSystem.h Normal file
View File

@ -0,0 +1,25 @@
/*
* IXNetSystem.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
#pragma once
#ifdef _WIN32
# include <WS2tcpip.h>
# include <WinSock2.h>
# include <basetsd.h>
# include <io.h>
# include <ws2def.h>
#else
# include <arpa/inet.h>
# include <errno.h>
# include <netdb.h>
# include <netinet/tcp.h>
# include <sys/select.h>
# include <sys/socket.h>
# include <sys/stat.h>
# include <sys/time.h>
# include <unistd.h>
#endif

View File

@ -1,31 +0,0 @@
/*
* IXSetThreadName.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#include "IXSetThreadName.h"
#include <pthread.h>
namespace ix
{
void setThreadName(const std::string& name)
{
#if defined(__linux__)
//
// Linux only reserve 16 bytes for its thread names
// See prctl and PR_SET_NAME property in
// http://man7.org/linux/man-pages/man2/prctl.2.html
//
pthread_setname_np(pthread_self(),
name.substr(0, 15).c_str());
#elif defined(__APPLE__)
//
// Apple is more generous with 64 chars.
// notice how the Apple version does not take a pthread_t argument
//
pthread_setname_np(name.substr(0, 63).c_str());
#elif
#error("Unsupported platform");
#endif
}
}

View File

@ -6,23 +6,7 @@
#include "IXSocket.h"
#include "IXSocketConnect.h"
#ifdef _WIN32
# include <basetsd.h>
# include <WinSock2.h>
# include <ws2def.h>
# include <WS2tcpip.h>
# include <io.h>
#else
# include <unistd.h>
# include <errno.h>
# include <netdb.h>
# include <netinet/tcp.h>
# include <sys/socket.h>
# include <sys/time.h>
# include <sys/select.h>
# include <sys/stat.h>
#endif
#include "IXNetSystem.h"
#include <stdio.h>
#include <stdlib.h>
@ -37,6 +21,9 @@
namespace ix
{
const int Socket::kDefaultPollNoTimeout = -1; // No poll timeout by default
const int Socket::kDefaultPollTimeout = kDefaultPollNoTimeout;
Socket::Socket(int fd) :
_sockfd(fd)
{
@ -48,14 +35,8 @@ namespace ix
close();
}
void Socket::poll(const OnPollCallback& onPollCallback)
void Socket::poll(const OnPollCallback& onPollCallback, int timeoutSecs)
{
if (_sockfd == -1)
{
onPollCallback();
return;
}
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(_sockfd, &rfds);
@ -64,11 +45,26 @@ namespace ix
FD_SET(_eventfd.getFd(), &rfds);
#endif
struct timeval timeout;
timeout.tv_sec = timeoutSecs;
timeout.tv_usec = 0;
int sockfd = _sockfd;
int nfds = (std::max)(sockfd, _eventfd.getFd());
select(nfds + 1, &rfds, nullptr, nullptr, nullptr);
int ret = select(nfds + 1, &rfds, nullptr, nullptr,
(timeoutSecs == kDefaultPollNoTimeout) ? nullptr : &timeout);
onPollCallback();
PollResultType pollResult = PollResultType_ReadyForRead;
if (ret < 0)
{
pollResult = PollResultType_Error;
}
else if (ret == 0)
{
pollResult = PollResultType_Timeout;
}
onPollCallback(pollResult);
}
void Socket::wakeUpFromPoll()
@ -100,32 +96,32 @@ namespace ix
_sockfd = -1;
}
int Socket::send(char* buffer, size_t length)
ssize_t Socket::send(char* buffer, size_t length)
{
int flags = 0;
#ifdef MSG_NOSIGNAL
flags = MSG_NOSIGNAL;
#endif
return (int) ::send(_sockfd, buffer, length, flags);
return ::send(_sockfd, buffer, length, flags);
}
int Socket::send(const std::string& buffer)
ssize_t Socket::send(const std::string& buffer)
{
return send((char*)&buffer[0], buffer.size());
}
int Socket::recv(void* buffer, size_t length)
ssize_t Socket::recv(void* buffer, size_t length)
{
int flags = 0;
#ifdef MSG_NOSIGNAL
flags = MSG_NOSIGNAL;
#endif
return (int) ::recv(_sockfd, (char*) buffer, length, flags);
return ::recv(_sockfd, (char*) buffer, length, flags);
}
int Socket::getErrno() const
int Socket::getErrno()
{
#ifdef _WIN32
return WSAGetLastError();
@ -170,7 +166,7 @@ namespace ix
{
if (isCancellationRequested()) return false;
int ret;
ssize_t ret;
ret = recv(buffer, 1);
// We read one byte, as needed, all good.
@ -187,11 +183,16 @@ namespace ix
fd_set rfds;
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 1 * 1000; // 1ms
timeout.tv_usec = 1 * 1000; // 1ms timeout
FD_ZERO(&rfds);
FD_SET(_sockfd, &rfds);
select(_sockfd + 1, &rfds, nullptr, nullptr, &timeout);
if (select(_sockfd + 1, &rfds, nullptr, nullptr, &timeout) < 0 &&
(errno == EBADF || errno == EINVAL))
{
return false;
}
continue;
}
@ -213,7 +214,7 @@ namespace ix
char* buffer = const_cast<char*>(str.c_str());
int len = (int) str.size();
int ret = send(buffer, len);
ssize_t ret = send(buffer, len);
// We wrote some bytes, as needed, all good.
if (ret > 0)

View File

@ -11,21 +11,34 @@
#include <mutex>
#include <atomic>
#ifdef _WIN32
#include <BaseTsd.h>
typedef SSIZE_T ssize_t;
#endif
#include "IXEventFd.h"
#include "IXCancellationRequest.h"
namespace ix
{
enum PollResultType
{
PollResultType_ReadyForRead = 0,
PollResultType_Timeout = 1,
PollResultType_Error = 2
};
class Socket {
public:
using OnPollCallback = std::function<void()>;
using OnPollCallback = std::function<void(PollResultType)>;
Socket(int fd = -1);
virtual ~Socket();
void configure();
virtual void poll(const OnPollCallback& onPollCallback);
virtual void poll(const OnPollCallback& onPollCallback,
int timeoutSecs = kDefaultPollTimeout);
virtual void wakeUpFromPoll();
// Virtual methods
@ -35,9 +48,9 @@ namespace ix
const CancellationRequest& isCancellationRequested);
virtual void close();
virtual int send(char* buffer, size_t length);
virtual int send(const std::string& buffer);
virtual int recv(void* buffer, size_t length);
virtual ssize_t send(char* buffer, size_t length);
virtual ssize_t send(const std::string& buffer);
virtual ssize_t recv(void* buffer, size_t length);
// Blocking and cancellable versions, working with socket that can be set
// to non blocking mode. Used during HTTP upgrade.
@ -47,7 +60,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
@ -57,5 +70,9 @@ namespace ix
std::atomic<int> _sockfd;
std::mutex _socketMutex;
EventFd _eventfd;
private:
static const int kDefaultPollTimeout;
static const int kDefaultPollNoTimeout;
};
}

View File

@ -203,7 +203,7 @@ namespace ix
Socket::close();
}
int SocketAppleSSL::send(char* buf, size_t nbyte)
ssize_t SocketAppleSSL::send(char* buf, size_t nbyte)
{
ssize_t ret = 0;
OSStatus status;
@ -218,16 +218,16 @@ namespace ix
if (ret == 0 && errSSLClosedAbort != status)
ret = -1;
return (int) ret;
return ret;
}
int SocketAppleSSL::send(const std::string& buffer)
ssize_t SocketAppleSSL::send(const std::string& buffer)
{
return send((char*)&buffer[0], buffer.size());
}
// No wait support
int SocketAppleSSL::recv(void* buf, size_t nbyte)
ssize_t SocketAppleSSL::recv(void* buf, size_t nbyte)
{
OSStatus status = errSSLWouldBlock;
while (errSSLWouldBlock == status)
@ -237,7 +237,7 @@ namespace ix
status = SSLRead(_sslContext, buf, nbyte, &processed);
if (processed > 0)
return (int) processed;
return (ssize_t) processed;
// The connection was reset, inform the caller that this
// Socket should close

View File

@ -28,9 +28,9 @@ namespace ix
const CancellationRequest& isCancellationRequested) final;
virtual void close() final;
virtual int send(char* buffer, size_t length) final;
virtual int send(const std::string& buffer) final;
virtual int recv(void* buffer, size_t length) final;
virtual ssize_t send(char* buffer, size_t length) final;
virtual ssize_t send(const std::string& buffer) final;
virtual ssize_t recv(void* buffer, size_t length) final;
private:
SSLContextRef _sslContext;

View File

@ -6,23 +6,7 @@
#include "IXSocketConnect.h"
#include "IXDNSLookup.h"
#ifdef _WIN32
# include <basetsd.h>
# include <WinSock2.h>
# include <ws2def.h>
# include <WS2tcpip.h>
# include <io.h>
#else
# include <unistd.h>
# include <errno.h>
# include <netdb.h>
# include <netinet/tcp.h>
# include <sys/socket.h>
# include <sys/time.h>
# include <sys/select.h>
# include <sys/stat.h>
#endif
#include "IXNetSystem.h"
#include <string.h>
#include <fcntl.h>
@ -86,31 +70,45 @@ namespace ix
{
closeSocket(fd);
errMsg = "Cancelled";
return false;
return -1;
}
fd_set wfds;
FD_ZERO(&wfds);
FD_SET(fd, &wfds);
// 50ms select timeout
// Use select to check the status of the new connection
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 50 * 1000;
timeout.tv_usec = 10 * 1000; // 10ms timeout
fd_set wfds;
fd_set efds;
select(fd + 1, nullptr, &wfds, nullptr, &timeout);
FD_ZERO(&wfds);
FD_SET(fd, &wfds);
FD_ZERO(&efds);
FD_SET(fd, &efds);
if (select(fd + 1, nullptr, &wfds, &efds, &timeout) < 0 &&
(errno == EBADF || errno == EINVAL))
{
closeSocket(fd);
errMsg = std::string("Connect error, select error: ") + strerror(errno);
return -1;
}
// Nothing was written to the socket, wait again.
if (!FD_ISSET(fd, &wfds)) continue;
// Something was written to the socket
// Something was written to the socket. Check for errors.
int optval = -1;
socklen_t optlen = sizeof(optval);
#ifdef _WIN32
// On connect error, in async mode, windows will write to the exceptions fds
if (FD_ISSET(fd, &efds))
#else
// getsockopt() puts the errno value for connect into optval so 0
// means no-error.
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1 ||
optval != 0)
#endif
{
closeSocket(fd);
errMsg = strerror(optval);
@ -173,7 +171,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

@ -18,67 +18,15 @@
#include <errno.h>
#define socketerrno errno
namespace {
std::mutex initMutex;
bool openSSLInitialized = false;
bool openSSLInitializationSuccessful = false;
bool openSSLInitialize(std::string& errMsg)
{
std::lock_guard<std::mutex> lock(initMutex);
if (openSSLInitialized)
{
return openSSLInitializationSuccessful;
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
if (!OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, nullptr))
{
errMsg = "OPENSSL_init_ssl failure";
openSSLInitializationSuccessful = false;
openSSLInitialized = true;
return false;
}
#else
(void) OPENSSL_config(nullptr);
#endif
(void) OpenSSL_add_ssl_algorithms();
(void) SSL_load_error_strings();
openSSLInitializationSuccessful = true;
return true;
}
int openssl_verify_callback(int preverify, X509_STORE_CTX *x509_ctx)
{
return preverify;
}
/* create new SSL connection state object */
SSL *openssl_create_connection(SSL_CTX *ctx, int socket)
{
assert(ctx != nullptr);
assert(socket > 0);
SSL *ssl = SSL_new(ctx);
if (ssl)
SSL_set_fd(ssl, socket);
return ssl;
}
} // anonymous namespace
namespace ix
{
std::atomic<bool> SocketOpenSSL::_openSSLInitializationSuccessful(false);
SocketOpenSSL::SocketOpenSSL(int fd) : Socket(fd),
_ssl_connection(nullptr),
_ssl_context(nullptr)
{
;
std::call_once(_openSSLInitFlag, &SocketOpenSSL::openSSLInitialize, this);
}
SocketOpenSSL::~SocketOpenSSL()
@ -86,6 +34,20 @@ namespace ix
SocketOpenSSL::close();
}
void SocketOpenSSL::openSSLInitialize()
{
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
if (!OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, nullptr)) return;
#else
(void) OPENSSL_config(nullptr);
#endif
(void) OpenSSL_add_ssl_algorithms();
(void) SSL_load_error_strings();
_openSSLInitializationSuccessful = true;
}
std::string SocketOpenSSL::getSSLError(int ret)
{
unsigned long e;
@ -153,7 +115,12 @@ namespace ix
if (ctx)
{
// To skip verification, pass in SSL_VERIFY_NONE
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, openssl_verify_callback);
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER,
[](int preverify, X509_STORE_CTX*) -> int
{
return preverify;
});
SSL_CTX_set_verify_depth(ctx, 4);
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
}
@ -283,8 +250,9 @@ namespace ix
{
std::lock_guard<std::mutex> lock(_mutex);
if (!openSSLInitialize(errMsg))
if (!_openSSLInitializationSuccessful)
{
errMsg = "OPENSSL_init_ssl failure";
return false;
}
@ -306,14 +274,15 @@ namespace ix
errMsg += ERR_error_string(ssl_err, nullptr);
}
_ssl_connection = openssl_create_connection(_ssl_context, _sockfd);
if (nullptr == _ssl_connection)
_ssl_connection = SSL_new(_ssl_context);
if (_ssl_connection == nullptr)
{
errMsg = "OpenSSL failed to connect";
SSL_CTX_free(_ssl_context);
_ssl_context = nullptr;
return false;
}
SSL_set_fd(_ssl_connection, _sockfd);
// SNI support
SSL_set_tlsext_host_name(_ssl_connection, host.c_str());
@ -357,7 +326,7 @@ namespace ix
Socket::close();
}
int SocketOpenSSL::send(char* buf, size_t nbyte)
ssize_t SocketOpenSSL::send(char* buf, size_t nbyte)
{
ssize_t sent = 0;
@ -371,7 +340,7 @@ namespace ix
}
ERR_clear_error();
int write_result = SSL_write(_ssl_connection, buf + sent, (int) nbyte);
ssize_t write_result = SSL_write(_ssl_connection, buf + sent, (int) nbyte);
int reason = SSL_get_error(_ssl_connection, write_result);
if (reason == SSL_ERROR_NONE) {
@ -384,16 +353,16 @@ namespace ix
return -1;
}
}
return (int) sent;
return sent;
}
int SocketOpenSSL::send(const std::string& buffer)
ssize_t SocketOpenSSL::send(const std::string& buffer)
{
return send((char*)&buffer[0], buffer.size());
}
// No wait support
int SocketOpenSSL::recv(void* buf, size_t nbyte)
ssize_t SocketOpenSSL::recv(void* buf, size_t nbyte)
{
while (true)
{
@ -405,7 +374,7 @@ namespace ix
}
ERR_clear_error();
int read_result = SSL_read(_ssl_connection, buf, (int) nbyte);
ssize_t read_result = SSL_read(_ssl_connection, buf, (int) nbyte);
if (read_result > 0)
{

View File

@ -31,11 +31,12 @@ namespace ix
const CancellationRequest& isCancellationRequested) final;
virtual void close() final;
virtual int send(char* buffer, size_t length) final;
virtual int send(const std::string& buffer) final;
virtual int recv(void* buffer, size_t length) final;
virtual ssize_t send(char* buffer, size_t length) final;
virtual ssize_t send(const std::string& buffer) final;
virtual ssize_t recv(void* buffer, size_t length) final;
private:
void openSSLInitialize();
std::string getSSLError(int ret);
SSL_CTX* openSSLCreateContext(std::string& errMsg);
bool openSSLHandshake(const std::string& hostname, std::string& errMsg);
@ -44,10 +45,13 @@ 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
std::once_flag _openSSLInitFlag;
static std::atomic<bool> _openSSLInitializationSuccessful;
};
}

View File

@ -0,0 +1,228 @@
/*
* IXSocketServer.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#include "IXSocketServer.h"
#include "IXSocket.h"
#include "IXSocketConnect.h"
#include "IXNetSystem.h"
#include <iostream>
#include <sstream>
#include <future>
#include <string.h>
namespace ix
{
const int SocketServer::kDefaultPort(8080);
const std::string SocketServer::kDefaultHost("127.0.0.1");
const int SocketServer::kDefaultTcpBacklog(5);
const size_t SocketServer::kDefaultMaxConnections(32);
SocketServer::SocketServer(int port,
const std::string& host,
int backlog,
size_t maxConnections) :
_port(port),
_host(host),
_backlog(backlog),
_maxConnections(maxConnections),
_stop(false)
{
}
SocketServer::~SocketServer()
{
stop();
}
void SocketServer::logError(const std::string& str)
{
std::lock_guard<std::mutex> lock(_logMutex);
std::cerr << str << std::endl;
}
void SocketServer::logInfo(const std::string& str)
{
std::lock_guard<std::mutex> lock(_logMutex);
std::cout << str << std::endl;
}
std::pair<bool, std::string> SocketServer::listen()
{
struct sockaddr_in server; // server address information
// Get a socket for accepting connections.
if ((_serverFd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
std::stringstream ss;
ss << "SocketServer::listen() error creating socket): "
<< 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,
(char*) &enable, sizeof(enable)) < 0)
{
std::stringstream ss;
ss << "SocketServer::listen() error calling setsockopt(SO_REUSEADDR) "
<< "at address " << _host << ":" << _port
<< " : " << strerror(Socket::getErrno());
::close(_serverFd);
return std::make_pair(false, ss.str());
}
// Bind the socket to the server address.
server.sin_family = AF_INET;
server.sin_port = htons(_port);
// Using INADDR_ANY trigger a pop-up box as binding to any address is detected
// by the osx firewall. We need to codesign the binary with a self-signed cert
// to allow that, but this is a bit of a pain. (this is what node or python would do).
//
// Using INADDR_LOOPBACK also does not work ... while it should.
// We default to 127.0.0.1 (localhost)
//
server.sin_addr.s_addr = inet_addr(_host.c_str());
if (bind(_serverFd, (struct sockaddr *)&server, sizeof(server)) < 0)
{
std::stringstream ss;
ss << "SocketServer::listen() error calling bind "
<< "at address " << _host << ":" << _port
<< " : " << strerror(Socket::getErrno());
::close(_serverFd);
return std::make_pair(false, ss.str());
}
//
// Listen for connections. Specify the tcp backlog.
//
if (::listen(_serverFd, _backlog) < 0)
{
std::stringstream ss;
ss << "SocketServer::listen() error calling listen "
<< "at address " << _host << ":" << _port
<< " : " << strerror(Socket::getErrno());
::close(_serverFd);
return std::make_pair(false, ss.str());
}
return std::make_pair(true, "");
}
void SocketServer::start()
{
if (_thread.joinable()) return; // we've already been started
_thread = std::thread(&SocketServer::run, this);
}
void SocketServer::wait()
{
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
_conditionVariable.wait(lock);
}
void SocketServer::stop()
{
if (!_thread.joinable()) return; // nothing to do
_stop = true;
_thread.join();
_stop = false;
_conditionVariable.notify_one();
::close(_serverFd);
}
void SocketServer::run()
{
// Set the socket to non blocking mode, so that accept calls are not blocking
SocketConnect::configure(_serverFd);
// Return value of std::async, ignored
std::future<void> f;
for (;;)
{
if (_stop) return;
// Use select to check whether a new connection is in progress
fd_set rfds;
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 10 * 1000; // 10ms timeout
FD_ZERO(&rfds);
FD_SET(_serverFd, &rfds);
if (select(_serverFd + 1, &rfds, nullptr, nullptr, &timeout) < 0 &&
(errno == EBADF || errno == EINVAL))
{
std::stringstream ss;
ss << "SocketServer::run() error in select: "
<< strerror(Socket::getErrno());
logError(ss.str());
continue;
}
if (!FD_ISSET(_serverFd, &rfds))
{
// We reached the select timeout, and no new connections are pending
continue;
}
// Accept a connection.
struct sockaddr_in client; // client address information
int clientFd; // socket connected to client
socklen_t addressLen = sizeof(socklen_t);
memset(&client, 0, sizeof(client));
if ((clientFd = accept(_serverFd, (struct sockaddr *)&client, &addressLen)) < 0)
{
if (Socket::getErrno() != EWOULDBLOCK)
{
// FIXME: that error should be propagated
std::stringstream ss;
ss << "SocketServer::run() error accepting connection: "
<< strerror(Socket::getErrno());
logError(ss.str());
}
continue;
}
if (getConnectedClientsCount() >= _maxConnections)
{
std::stringstream ss;
ss << "SocketServer::run() reached max connections = "
<< _maxConnections << ". "
<< "Not accepting connection";
logError(ss.str());
::close(clientFd);
continue;
}
// Launch the handleConnection work asynchronously in its own thread.
//
// the destructor of a future returned by std::async blocks,
// so we need to declare it outside of this loop
f = std::async(std::launch::async,
&SocketServer::handleConnection,
this,
clientFd);
}
}
}

View File

@ -0,0 +1,68 @@
/*
* IXSocketServer.h
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <utility> // pair
#include <string>
#include <set>
#include <thread>
#include <mutex>
#include <functional>
#include <memory>
#include <atomic>
#include <condition_variable>
namespace ix
{
class SocketServer {
public:
SocketServer(int port = SocketServer::kDefaultPort,
const std::string& host = SocketServer::kDefaultHost,
int backlog = SocketServer::kDefaultTcpBacklog,
size_t maxConnections = SocketServer::kDefaultMaxConnections);
virtual ~SocketServer();
virtual void stop();
const static int kDefaultPort;
const static std::string kDefaultHost;
const static int kDefaultTcpBacklog;
const static size_t kDefaultMaxConnections;
void start();
std::pair<bool, std::string> listen();
void wait();
protected:
// Logging
void logError(const std::string& str);
void logInfo(const std::string& str);
private:
// Member variables
int _port;
std::string _host;
int _backlog;
size_t _maxConnections;
// socket for accepting connections
int _serverFd;
std::mutex _logMutex;
std::atomic<bool> _stop;
std::thread _thread;
std::condition_variable _conditionVariable;
std::mutex _conditionVariableMutex;
// Methods
void run();
virtual void handleConnection(int fd) = 0;
virtual size_t getConnectedClientsCount() = 0;
};
}

View File

@ -31,12 +31,14 @@ namespace ix
{
OnTrafficTrackerCallback WebSocket::_onTrafficTrackerCallback = nullptr;
const int WebSocket::kDefaultHandShakeTimeoutSecs(60);
const int WebSocket::kDefaultHeartBeatPeriod(-1);
WebSocket::WebSocket() :
_onMessageCallback(OnMessageCallback()),
_stop(false),
_automaticReconnection(true),
_handshakeTimeoutSecs(kDefaultHandShakeTimeoutSecs)
_handshakeTimeoutSecs(kDefaultHandShakeTimeoutSecs),
_heartBeatPeriod(kDefaultHeartBeatPeriod)
{
_ws.setOnCloseCallback(
[this](uint16_t code, const std::string& reason, size_t wireSize)
@ -77,6 +79,18 @@ namespace ix
return _perMessageDeflateOptions;
}
void WebSocket::setHeartBeatPeriod(int hearBeatPeriod)
{
std::lock_guard<std::mutex> lock(_configMutex);
_heartBeatPeriod = hearBeatPeriod;
}
int WebSocket::getHeartBeatPeriod() const
{
std::lock_guard<std::mutex> lock(_configMutex);
return _heartBeatPeriod;
}
void WebSocket::start()
{
if (_thread.joinable()) return; // we've already been started
@ -110,7 +124,8 @@ namespace ix
{
{
std::lock_guard<std::mutex> lock(_configMutex);
_ws.configure(_perMessageDeflateOptions);
_ws.configure(_perMessageDeflateOptions,
_heartBeatPeriod);
}
WebSocketInitResult status = _ws.connectToUrl(_url, timeoutSecs);
@ -130,7 +145,7 @@ namespace ix
{
{
std::lock_guard<std::mutex> lock(_configMutex);
_ws.configure(_perMessageDeflateOptions);
_ws.configure(_perMessageDeflateOptions, _heartBeatPeriod);
}
WebSocketInitResult status = _ws.connectToSocket(fd, timeoutSecs);
@ -331,6 +346,7 @@ namespace ix
case ix::WebSocketTransport::CONNECTING: return WebSocket_ReadyState_Connecting;
case ix::WebSocketTransport::CLOSING: return WebSocket_ReadyState_Closing;
case ix::WebSocketTransport::CLOSED: return WebSocket_ReadyState_Closed;
default: return WebSocket_ReadyState_Closed;
}
}
@ -342,6 +358,7 @@ namespace ix
case WebSocket_ReadyState_Connecting: return "CONNECTING";
case WebSocket_ReadyState_Closing: return "CLOSING";
case WebSocket_ReadyState_Closed: return "CLOSED";
default: return "CLOSED";
}
}

View File

@ -60,7 +60,7 @@ namespace ix
uint16_t code;
std::string reason;
WebSocketCloseInfo(uint64_t c = 0,
WebSocketCloseInfo(uint16_t c = 0,
const std::string& r = std::string())
: code(c)
, reason(r)
@ -86,7 +86,8 @@ namespace ix
void setUrl(const std::string& url);
void setPerMessageDeflateOptions(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
void setHandshakeTimeout(int _handshakeTimeoutSecs);
void setHandshakeTimeout(int handshakeTimeoutSecs);
void setHeartBeatPeriod(int hearBeatPeriod);
// Run asynchronously, by calling start and stop.
void start();
@ -107,6 +108,7 @@ namespace ix
ReadyState getReadyState() const;
const std::string& getUrl() const;
const WebSocketPerMessageDeflateOptions& getPerMessageDeflateOptions() const;
int getHeartBeatPeriod() const;
void enableAutomaticReconnection();
void disableAutomaticReconnection();
@ -142,6 +144,10 @@ namespace ix
std::atomic<int> _handshakeTimeoutSecs;
static const int kDefaultHandShakeTimeoutSecs;
// Optional Heartbeat
int _heartBeatPeriod;
static const int kDefaultHeartBeatPeriod;
friend class WebSocketServer;
};
}

View File

@ -116,7 +116,7 @@ namespace ix
std::cout << "-------------------------------" << std::endl;
}
std::string trim(const std::string& str)
std::string WebSocketHandshake::trim(const std::string& str)
{
std::string out(str);
out.erase(std::remove(out.begin(), out.end(), ' '), out.end());
@ -354,6 +354,14 @@ namespace ix
return WebSocketInitResult(false, status, "Error parsing HTTP headers");
}
// Check the presence of the Upgrade field
if (headers.find("connection") == headers.end() ||
headers["connection"] != "Upgrade")
{
std::string errorMsg("Invalid or missing connection value");
return WebSocketInitResult(false, status, errorMsg);
}
char output[29] = {};
WebSocketHandshakeKeyGen::generate(secWebSocketKey.c_str(), output);
if (std::string(output) != headers["sec-websocket-accept"])
@ -466,6 +474,8 @@ namespace ix
std::stringstream ss;
ss << "HTTP/1.1 101\r\n";
ss << "Sec-WebSocket-Accept: " << std::string(output) << "\r\n";
ss << "Upgrade: websocket\r\n";
ss << "Connection: Upgrade\r\n";
// Parse the client headers. Does it support deflate ?
std::string header = headers["sec-websocket-extensions"];

View File

@ -75,6 +75,7 @@ namespace ix
WebSocketInitResult sendErrorResponse(int code, const std::string& reason);
std::tuple<std::string, std::string, std::string> parseRequestLine(const std::string& line);
std::string trim(const std::string& str);
std::atomic<bool>& _requestInitCancellation;
std::shared_ptr<Socket> _socket;

View File

@ -47,211 +47,20 @@
#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)
WebSocketPerMessageDeflate::WebSocketPerMessageDeflate() :
_compressor(std::make_unique<WebSocketPerMessageDeflateCompressor>()),
_decompressor(std::make_unique<WebSocketPerMessageDeflateDecompressor>())
{
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());
_decompressor.reset(new WebSocketPerMessageDeflateDecompressor());
;
}
WebSocketPerMessageDeflate::~WebSocketPerMessageDeflate()
{
_compressor.reset();
_decompressor.reset();
;
}
bool WebSocketPerMessageDeflate::init(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions)

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
{
@ -87,7 +54,7 @@ namespace ix
bool decompress(const std::string& in, std::string& out);
private:
std::shared_ptr<WebSocketPerMessageDeflateCompressor> _compressor;
std::shared_ptr<WebSocketPerMessageDeflateDecompressor> _decompressor;
std::unique_ptr<WebSocketPerMessageDeflateCompressor> _compressor;
std::unique_ptr<WebSocketPerMessageDeflateDecompressor> _decompressor;
};
}

View File

@ -0,0 +1,206 @@
/*
* 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 = std::make_unique<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 = std::make_unique<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

@ -8,35 +8,22 @@
#include "IXWebSocketTransport.h"
#include "IXWebSocket.h"
#include "IXSocketConnect.h"
#include "IXNetSystem.h"
#include <sstream>
#include <future>
#include <netdb.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string.h>
namespace ix
{
const int WebSocketServer::kDefaultPort(8080);
const std::string WebSocketServer::kDefaultHost("127.0.0.1");
const int WebSocketServer::kDefaultTcpBacklog(5);
const size_t WebSocketServer::kDefaultMaxConnections(32);
const int WebSocketServer::kDefaultHandShakeTimeoutSecs(3); // 3 seconds
WebSocketServer::WebSocketServer(int port,
const std::string& host,
int backlog,
size_t maxConnections,
int handshakeTimeoutSecs) :
_port(port),
_host(host),
_backlog(backlog),
_maxConnections(maxConnections),
_handshakeTimeoutSecs(handshakeTimeoutSecs),
_stop(false)
int handshakeTimeoutSecs) : SocketServer(port, host, backlog, maxConnections),
_handshakeTimeoutSecs(handshakeTimeoutSecs)
{
}
@ -46,189 +33,25 @@ namespace ix
stop();
}
void WebSocketServer::setOnConnectionCallback(const OnConnectionCallback& callback)
{
_onConnectionCallback = callback;
}
void WebSocketServer::logError(const std::string& str)
{
std::lock_guard<std::mutex> lock(_logMutex);
std::cerr << str << std::endl;
}
void WebSocketServer::logInfo(const std::string& str)
{
std::lock_guard<std::mutex> lock(_logMutex);
std::cout << str << std::endl;
}
std::pair<bool, std::string> WebSocketServer::listen()
{
struct sockaddr_in server; // server address information
// Get a socket for accepting connections.
if ((_serverFd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
std::stringstream ss;
ss << "WebSocketServer::listen() error creating socket): "
<< strerror(errno);
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)
{
std::stringstream ss;
ss << "WebSocketServer::listen() error calling setsockopt(SO_REUSEADDR): "
<< strerror(errno);
return std::make_pair(false, ss.str());
}
// Bind the socket to the server address.
server.sin_family = AF_INET;
server.sin_port = htons(_port);
// Using INADDR_ANY trigger a pop-up box as binding to any address is detected
// by the osx firewall. We need to codesign the binary with a self-signed cert
// to allow that, but this is a bit of a pain. (this is what node or python would do).
//
// Using INADDR_LOOPBACK also does not work ... while it should.
// We default to 127.0.0.1 (localhost)
//
server.sin_addr.s_addr = inet_addr(_host.c_str());
if (bind(_serverFd, (struct sockaddr *)&server, sizeof(server)) < 0)
{
std::stringstream ss;
ss << "WebSocketServer::listen() error calling bind: "
<< strerror(errno);
return std::make_pair(false, ss.str());
}
/*
* Listen for connections. Specify the tcp backlog.
*/
if (::listen(_serverFd, _backlog) != 0)
{
std::stringstream ss;
ss << "WebSocketServer::listen() error calling listen: "
<< strerror(errno);
return std::make_pair(false, ss.str());
}
return std::make_pair(true, "");
}
void WebSocketServer::start()
{
if (_thread.joinable()) return; // we've already been started
_thread = std::thread(&WebSocketServer::run, this);
}
void WebSocketServer::wait()
{
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
_conditionVariable.wait(lock);
}
void WebSocketServer::stop()
{
if (!_thread.joinable()) return; // nothing to do
auto clients = getClients();
for (auto client : clients)
{
client->close();
}
_stop = true;
_thread.join();
_stop = false;
_conditionVariable.notify_one();
SocketServer::stop();
}
void WebSocketServer::run()
void WebSocketServer::setOnConnectionCallback(const OnConnectionCallback& callback)
{
// Set the socket to non blocking mode, so that accept calls are not blocking
SocketConnect::configure(_serverFd);
// Return value of std::async, ignored
std::future<void> f;
// Select arguments
fd_set rfds;
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 10 * 1000; // 10ms
for (;;)
{
if (_stop) return;
FD_ZERO(&rfds);
FD_SET(_serverFd, &rfds);
select(_serverFd + 1, &rfds, nullptr, nullptr, &timeout);
if (!FD_ISSET(_serverFd, &rfds))
{
// We reached the select timeout, and no new connections are pending
continue;
}
// Accept a connection.
struct sockaddr_in client; // client address information
int clientFd; // socket connected to client
socklen_t addressLen = sizeof(socklen_t);
memset(&client, 0, sizeof(client));
if ((clientFd = accept(_serverFd, (struct sockaddr *)&client, &addressLen)) < 0)
{
if (errno != EWOULDBLOCK)
{
// FIXME: that error should be propagated
std::stringstream ss;
ss << "WebSocketServer::run() error accepting connection: "
<< strerror(errno);
logError(ss.str());
}
continue;
}
if (getConnectedClientsCount() >= _maxConnections)
{
std::stringstream ss;
ss << "WebSocketServer::run() reached max connections = "
<< _maxConnections << ". "
<< "Not accepting connection";
logError(ss.str());
::close(clientFd);
continue;
}
// Launch the handleConnection work asynchronously in its own thread.
//
// the destructor of a future returned by std::async blocks,
// so we need to declare it outside of this loop
f = std::async(std::launch::async,
&WebSocketServer::handleConnection,
this,
clientFd);
}
_onConnectionCallback = callback;
}
void WebSocketServer::handleConnection(int fd)
{
std::shared_ptr<WebSocket> webSocket(new WebSocket);
auto webSocket = std::make_shared<WebSocket>();
_onConnectionCallback(webSocket);
webSocket->disableAutomaticReconnection();
@ -276,6 +99,7 @@ namespace ix
size_t WebSocketServer::getConnectedClientsCount()
{
return getClients().size();
std::lock_guard<std::mutex> lock(_clientsMutex);
return _clients.size();
}
}

View File

@ -16,67 +16,40 @@
#include <condition_variable>
#include "IXWebSocket.h"
#include "IXSocketServer.h"
namespace ix
{
using OnConnectionCallback = std::function<void(std::shared_ptr<WebSocket>)>;
class WebSocketServer {
class WebSocketServer : public SocketServer {
public:
WebSocketServer(int port = WebSocketServer::kDefaultPort,
const std::string& host = WebSocketServer::kDefaultHost,
int backlog = WebSocketServer::kDefaultTcpBacklog,
size_t maxConnections = WebSocketServer::kDefaultMaxConnections,
WebSocketServer(int port = SocketServer::kDefaultPort,
const std::string& host = SocketServer::kDefaultHost,
int backlog = SocketServer::kDefaultTcpBacklog,
size_t maxConnections = SocketServer::kDefaultMaxConnections,
int handshakeTimeoutSecs = WebSocketServer::kDefaultHandShakeTimeoutSecs);
virtual ~WebSocketServer();
virtual void stop() final;
void setOnConnectionCallback(const OnConnectionCallback& callback);
void start();
void wait();
void stop();
std::pair<bool, std::string> listen();
// Get all the connected clients
std::set<std::shared_ptr<WebSocket>> getClients();
private:
// Member variables
int _port;
std::string _host;
int _backlog;
size_t _maxConnections;
int _handshakeTimeoutSecs;
OnConnectionCallback _onConnectionCallback;
// socket for accepting connections
int _serverFd;
std::mutex _clientsMutex;
std::set<std::shared_ptr<WebSocket>> _clients;
std::mutex _logMutex;
std::atomic<bool> _stop;
std::thread _thread;
std::condition_variable _conditionVariable;
std::mutex _conditionVariableMutex;
const static int kDefaultPort;
const static std::string kDefaultHost;
const static int kDefaultTcpBacklog;
const static size_t kDefaultMaxConnections;
const static int kDefaultHandShakeTimeoutSecs;
// Methods
void run();
void handleConnection(int fd);
size_t getConnectedClientsCount();
// Logging
void logError(const std::string& str);
void logInfo(const std::string& str);
virtual void handleConnection(int fd) final;
virtual size_t getConnectedClientsCount() final;
};
}

View File

@ -33,12 +33,17 @@
namespace ix
{
const std::string WebSocketTransport::kHeartBeatPingMessage("ixwebsocket::hearbeat");
const int WebSocketTransport::kDefaultHeartBeatPeriod(-1);
WebSocketTransport::WebSocketTransport() :
_readyState(CLOSED),
_closeCode(0),
_closeWireSize(0),
_enablePerMessageDeflate(false),
_requestInitCancellation(false)
_requestInitCancellation(false),
_heartBeatPeriod(kDefaultHeartBeatPeriod),
_lastSendTimePoint(std::chrono::steady_clock::now())
{
}
@ -48,10 +53,12 @@ namespace ix
;
}
void WebSocketTransport::configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions)
void WebSocketTransport::configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
int hearBeatPeriod)
{
_perMessageDeflateOptions = perMessageDeflateOptions;
_enablePerMessageDeflate = _perMessageDeflateOptions.enabled();
_heartBeatPeriod = hearBeatPeriod;
}
// Client
@ -149,18 +156,36 @@ namespace ix
_onCloseCallback = onCloseCallback;
}
bool WebSocketTransport::exceedSendHeartBeatTimeOut()
{
std::lock_guard<std::mutex> lock(_lastSendTimePointMutex);
auto now = std::chrono::steady_clock::now();
return now - _lastSendTimePoint > std::chrono::seconds(_heartBeatPeriod);
}
void WebSocketTransport::poll()
{
_socket->poll(
[this]()
[this](PollResultType pollResult)
{
// If (1) heartbeat is enabled, and (2) no data was received or
// send for a duration exceeding our heart-beat period, send a
// ping to the server.
if (pollResult == PollResultType_Timeout &&
exceedSendHeartBeatTimeOut())
{
std::stringstream ss;
ss << kHeartBeatPingMessage << "::" << _heartBeatPeriod << "s";
sendPing(ss.str());
return;
}
while (true)
{
int N = (int) _rxbuf.size();
int ret;
_rxbuf.resize(N + 1500);
ret = _socket->recv((char*)&_rxbuf[0] + N, 1500);
ssize_t ret = _socket->recv((char*)&_rxbuf[0] + N, 1500);
if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK ||
_socket->getErrno() == EAGAIN)) {
@ -186,7 +211,8 @@ namespace ix
_socket->close();
setReadyState(CLOSED);
}
});
},
_heartBeatPeriod);
}
bool WebSocketTransport::isSendBufferEmpty() const
@ -409,7 +435,7 @@ namespace ix
{
std::string decompressedMessage;
bool success = _perMessageDeflate.decompress(message, decompressedMessage);
onMessageCallback(decompressedMessage, wireSize, not success, messageKind);
onMessageCallback(decompressedMessage, wireSize, !success, messageKind);
}
else
{
@ -539,7 +565,7 @@ namespace ix
while (_txbuf.size())
{
int ret = _socket->send((char*)&_txbuf[0], _txbuf.size());
ssize_t ret = _socket->send((char*)&_txbuf[0], _txbuf.size());
if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK ||
_socket->getErrno() == EAGAIN))
@ -558,6 +584,9 @@ namespace ix
_txbuf.erase(_txbuf.begin(), _txbuf.begin() + ret);
}
}
std::lock_guard<std::mutex> lck(_lastSendTimePointMutex);
_lastSendTimePoint = std::chrono::steady_clock::now();
}
void WebSocketTransport::close()

View File

@ -57,7 +57,8 @@ namespace ix
WebSocketTransport();
~WebSocketTransport();
void configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
void configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
int hearBeatPeriod);
WebSocketInitResult connectToUrl(const std::string& url, // Client
int timeoutSecs);
@ -116,6 +117,16 @@ namespace ix
// Used to cancel dns lookup + socket connect + http upgrade
std::atomic<bool> _requestInitCancellation;
// Optional Heartbeat
int _heartBeatPeriod;
static const int kDefaultHeartBeatPeriod;
const static std::string kHeartBeatPingMessage;
mutable std::mutex _lastSendTimePointMutex;
std::chrono::time_point<std::chrono::steady_clock> _lastSendTimePoint;
// No data was send through the socket for longer that the hearbeat period
bool exceedSendHeartBeatTimeOut();
void sendOnSocket();
WebSocketSendInfo sendData(wsheader_type::opcode_type type,

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;
}
}

View File

@ -24,8 +24,17 @@ test_server:
(cd test && npm i ws && node broadcast-server.js)
# env TEST=Websocket_server make test
# env TEST=websocket_server make test
# env TEST=heartbeat make test
test:
(cd test && sh run.sh)
python test/run.py
# For the fork that is configured with appveyor
rebase_upstream:
git fetch upstream
git checkout master
git reset --hard upstream/master
git push origin master --force
.PHONY: test
.PHONY: build

View File

@ -2,13 +2,17 @@
# Author: Benjamin Sergeant
# Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
#
cmake_minimum_required (VERSION 3.4.1)
project (ixwebsocket_unittest)
set (CMAKE_CXX_STANDARD 11)
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../third_party/sanitizers-cmake/cmake" ${CMAKE_MODULE_PATH})
find_package(Sanitizers)
option(USE_TLS "Add TLS support" ON)
set (CMAKE_CXX_STANDARD 14)
if (NOT WIN32)
option(USE_TLS "Add TLS support" ON)
endif()
add_subdirectory(${PROJECT_SOURCE_DIR}/.. ixwebsocket)
@ -16,14 +20,29 @@ include_directories(
${PROJECT_SOURCE_DIR}/Catch2/single_include
)
add_executable(ixwebsocket_unittest
# Shared sources
set (SOURCES
test_runner.cpp
cmd_websocket_chat.cpp
IXWebSocketServerTest.cpp
IXTest.cpp
msgpack11.cpp
IXDNSLookupTest.cpp
IXSocketTest.cpp
)
# Some unittest don't work on windows yet
if (NOT WIN32)
list(APPEND SOURCES
IXWebSocketServerTest.cpp
IXWebSocketHeartBeatTest.cpp
cmd_websocket_chat.cpp
IXWebSocketTestConnectionDisconnection.cpp
)
endif()
add_executable(ixwebsocket_unittest ${SOURCES})
add_sanitizers(ixwebsocket_unittest)
if (APPLE AND USE_TLS)
target_link_libraries(ixwebsocket_unittest "-framework foundation" "-framework security")
endif()

50
test/IXDNSLookupTest.cpp Normal file
View File

@ -0,0 +1,50 @@
/*
* IXDNSLookupTest.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone. All rights reserved.
*/
#include "catch.hpp"
#include "IXTest.h"
#include <ixwebsocket/IXDNSLookup.h>
#include <iostream>
using namespace ix;
TEST_CASE("dns", "[net]")
{
SECTION("Test resolving a known hostname")
{
DNSLookup dnsLookup("www.google.com", 80);
std::string errMsg;
struct addrinfo* res;
res = dnsLookup.resolve(errMsg, [] { return false; });
std::cerr << "Error message: " << errMsg << std::endl;
REQUIRE(res != nullptr);
}
SECTION("Test resolving a non-existing hostname")
{
DNSLookup dnsLookup("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", 80);
std::string errMsg;
struct addrinfo* res = dnsLookup.resolve(errMsg, [] { return false; });
std::cerr << "Error message: " << errMsg << std::endl;
REQUIRE(res == nullptr);
}
SECTION("Test resolving a good hostname, with cancellation")
{
DNSLookup dnsLookup("www.google.com", 80, 1);
std::string errMsg;
// The callback returning true means we are requesting cancellation
struct addrinfo* res = dnsLookup.resolve(errMsg, [] { return true; });
std::cerr << "Error message: " << errMsg << std::endl;
REQUIRE(res == nullptr);
}
}

92
test/IXSocketTest.cpp Normal file
View File

@ -0,0 +1,92 @@
/*
* IXSocketTest.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
#include <iostream>
#include <ixwebsocket/IXSocket.h>
#include <ixwebsocket/IXCancellationRequest.h>
#if defined(__APPLE__) or defined(__linux__)
# ifdef __APPLE__
# include <ixwebsocket/IXSocketAppleSSL.h>
# else
# include <ixwebsocket/IXSocketOpenSSL.h>
# endif
#endif
#include "IXTest.h"
#include "catch.hpp"
using namespace ix;
namespace ix
{
void testSocket(const std::string& host,
int port,
const std::string& request,
std::shared_ptr<Socket> socket,
int expectedStatus,
int timeoutSecs)
{
std::string errMsg;
static std::atomic<bool> requestInitCancellation(false);
auto isCancellationRequested =
makeCancellationRequestWithTimeout(timeoutSecs, requestInitCancellation);
bool success = socket->connect(host, port, errMsg, isCancellationRequested);
Logger() << "errMsg: " << errMsg;
REQUIRE(success);
std::cout << "Sending request: " << request
<< "to " << host << ":" << port
<< std::endl;
REQUIRE(socket->writeBytes(request, isCancellationRequested));
auto lineResult = socket->readLine(isCancellationRequested);
auto lineValid = lineResult.first;
auto line = lineResult.second;
std::cout << "read error: " << strerror(Socket::getErrno()) << std::endl;
REQUIRE(lineValid);
int status = -1;
REQUIRE(sscanf(line.c_str(), "HTTP/1.1 %d", &status) == 1);
REQUIRE(status == expectedStatus);
}
}
TEST_CASE("socket", "[socket]")
{
SECTION("Connect to google HTTP server. Send GET request without header. Should return 200")
{
std::shared_ptr<Socket> socket(new Socket);
std::string host("www.google.com");
int port = 80;
std::string request("GET / HTTP/1.1\r\n\r\n");
int expectedStatus = 200;
int timeoutSecs = 3;
testSocket(host, port, request, socket, expectedStatus, timeoutSecs);
}
#if defined(__APPLE__) or defined(__linux__)
SECTION("Connect to google HTTPS server. Send GET request without header. Should return 200")
{
# ifdef __APPLE__
std::shared_ptr<Socket> socket = std::make_shared<SocketAppleSSL>();
# else
std::shared_ptr<Socket> socket = std::make_shared<SocketOpenSSL>();
# endif
std::string host("www.google.com");
int port = 443;
std::string request("GET / HTTP/1.1\r\n\r\n");
int expectedStatus = 200;
int timeoutSecs = 3;
testSocket(host, port, request, socket, expectedStatus, timeoutSecs);
}
#endif
}

View File

@ -9,6 +9,7 @@
#include <chrono>
#include <thread>
#include <mutex>
#include <string>
#include <fstream>
#include <iostream>
@ -18,6 +19,7 @@ namespace ix
{
std::atomic<size_t> incomingBytes(0);
std::atomic<size_t> outgoingBytes(0);
std::mutex Logger::_mutex;
void setupWebSocketTrafficTrackerCallback()
{
@ -38,9 +40,9 @@ namespace ix
void reportWebSocketTraffic()
{
std::cout << incomingBytes << std::endl;
std::cout << "Incoming bytes: " << incomingBytes << std::endl;
std::cout << "Outgoing bytes: " << outgoingBytes << std::endl;
Logger() << incomingBytes;
Logger() << "Incoming bytes: " << incomingBytes;
Logger() << "Outgoing bytes: " << outgoingBytes;
}
void msleep(int ms)
@ -58,4 +60,10 @@ namespace ix
return std::to_string(seconds);
}
void log(const std::string& msg)
{
Logger() << msg;
}
}

View File

@ -9,6 +9,8 @@
#include <string>
#include <vector>
#include <sstream>
#include <iostream>
#include <mutex>
namespace ix
{
@ -21,4 +23,32 @@ namespace ix
// Record and report websocket traffic
void setupWebSocketTrafficTrackerCallback();
void reportWebSocketTraffic();
struct Logger
{
public:
Logger& operator<<(const std::string& msg)
{
std::lock_guard<std::mutex> lock(_mutex);
std::cerr << msg;
std::cerr << std::endl;
return *this;
}
template <typename T>
Logger& operator<<(T const& obj)
{
std::lock_guard<std::mutex> lock(_mutex);
std::cerr << obj;
std::cerr << std::endl;
return *this;
}
private:
static std::mutex _mutex;
};
void log(const std::string& msg);
}

View File

@ -0,0 +1,222 @@
/*
* IXWebSocketHeartBeatTest.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
#include <iostream>
#include <sstream>
#include <queue>
#include <ixwebsocket/IXWebSocket.h>
#include <ixwebsocket/IXWebSocketServer.h>
#include "IXTest.h"
#include "catch.hpp"
using namespace ix;
namespace
{
class WebSocketClient
{
public:
WebSocketClient(int port);
void subscribe(const std::string& channel);
void start();
void stop();
bool isReady() const;
void sendMessage(const std::string& text);
private:
ix::WebSocket _webSocket;
int _port;
};
WebSocketClient::WebSocketClient(int port)
: _port(port)
{
;
}
bool WebSocketClient::isReady() const
{
return _webSocket.getReadyState() == ix::WebSocket_ReadyState_Open;
}
void WebSocketClient::stop()
{
_webSocket.stop();
}
void WebSocketClient::start()
{
std::string url;
{
std::stringstream ss;
ss << "ws://localhost:"
<< _port
<< "/";
url = ss.str();
}
_webSocket.setUrl(url);
// The important bit for this test.
// Set a 1 second hearbeat ; if no traffic is present on the connection for 1 second
// a ping message will be sent by the client.
_webSocket.setHeartBeatPeriod(1);
std::stringstream ss;
log(std::string("Connecting to url: ") + url);
_webSocket.setOnMessageCallback(
[](ix::WebSocketMessageType messageType,
const std::string& str,
size_t wireSize,
const ix::WebSocketErrorInfo& error,
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
{
std::stringstream ss;
if (messageType == ix::WebSocket_MessageType_Open)
{
log("client connected");
}
else if (messageType == ix::WebSocket_MessageType_Close)
{
log("client disconnected");
}
else if (messageType == ix::WebSocket_MessageType_Error)
{
ss << "Error ! " << error.reason;
log(ss.str());
}
else if (messageType == ix::WebSocket_MessageType_Pong)
{
ss << "Received pong message " << str;
log(ss.str());
}
else if (messageType == ix::WebSocket_MessageType_Ping)
{
ss << "Received ping message " << str;
log(ss.str());
}
else if (messageType == ix::WebSocket_MessageType_Message)
{
ss << "Received message " << str;
log(ss.str());
}
else
{
ss << "Invalid ix::WebSocketMessageType";
log(ss.str());
}
});
_webSocket.start();
}
void WebSocketClient::sendMessage(const std::string& text)
{
_webSocket.send(text);
}
bool startServer(ix::WebSocketServer& server, std::atomic<int>& receivedPingMessages)
{
// A dev/null server
server.setOnConnectionCallback(
[&server, &receivedPingMessages](std::shared_ptr<ix::WebSocket> webSocket)
{
webSocket->setOnMessageCallback(
[webSocket, &server, &receivedPingMessages](ix::WebSocketMessageType messageType,
const std::string& str,
size_t wireSize,
const ix::WebSocketErrorInfo& error,
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
{
if (messageType == ix::WebSocket_MessageType_Open)
{
Logger() << "New server connection";
Logger() << "Uri: " << openInfo.uri;
Logger() << "Headers:";
for (auto it : openInfo.headers)
{
Logger() << it.first << ": " << it.second;
}
}
else if (messageType == ix::WebSocket_MessageType_Close)
{
log("Server closed connection");
}
else if (messageType == ix::WebSocket_MessageType_Ping)
{
log("Server received a ping");
receivedPingMessages++;
}
}
);
}
);
auto res = server.listen();
if (!res.first)
{
log(res.second);
return false;
}
server.start();
return true;
}
}
TEST_CASE("Websocket_heartbeat", "[heartbeat]")
{
SECTION("Make sure that ping messages are sent during heartbeat.")
{
ix::setupWebSocketTrafficTrackerCallback();
int port = 8093;
ix::WebSocketServer server(port);
std::atomic<int> serverReceivedPingMessages(0);
REQUIRE(startServer(server, serverReceivedPingMessages));
std::string session = ix::generateSessionId();
WebSocketClient webSocketClientA(port);
WebSocketClient webSocketClientB(port);
webSocketClientA.start();
webSocketClientB.start();
// Wait for all chat instance to be ready
while (true)
{
if (webSocketClientA.isReady() && webSocketClientB.isReady()) break;
ix::msleep(10);
}
REQUIRE(server.getClients().size() == 2);
ix::msleep(900);
webSocketClientB.sendMessage("hello world");
ix::msleep(900);
webSocketClientB.sendMessage("hello world");
ix::msleep(900);
webSocketClientA.stop();
webSocketClientB.stop();
REQUIRE(serverReceivedPingMessages >= 2);
REQUIRE(serverReceivedPingMessages <= 4);
// Give us 500ms for the server to notice that clients went away
ix::msleep(500);
REQUIRE(server.getClients().size() == 0);
ix::reportWebSocketTraffic();
}
}

View File

@ -32,17 +32,17 @@ namespace ix
{
if (messageType == ix::WebSocket_MessageType_Open)
{
std::cerr << "New connection" << std::endl;
std::cerr << "Uri: " << openInfo.uri << std::endl;
std::cerr << "Headers:" << std::endl;
Logger() << "New connection";
Logger() << "Uri: " << openInfo.uri;
Logger() << "Headers:";
for (auto it : openInfo.headers)
{
std::cerr << it.first << ": " << it.second << std::endl;
Logger() << it.first << ": " << it.second;
}
}
else if (messageType == ix::WebSocket_MessageType_Close)
{
std::cerr << "Closed connection" << std::endl;
Logger() << "Closed connection";
}
else if (messageType == ix::WebSocket_MessageType_Message)
{
@ -62,7 +62,7 @@ namespace ix
auto res = server.listen();
if (!res.first)
{
std::cerr << res.second << std::endl;
Logger() << res.second;
return false;
}
@ -121,7 +121,7 @@ TEST_CASE("Websocket_server", "[websocket_server]")
bool success = socket.connect(host, port, errMsg, isCancellationRequested);
REQUIRE(success);
std::cout << "writeBytes" << std::endl;
Logger() << "writeBytes";
socket.writeBytes("GET /\r\n", isCancellationRequested);
auto lineResult = socket.readLine(isCancellationRequested);

View File

@ -0,0 +1,128 @@
/*
* IXWebSocketTestConnectionDisconnection.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2017 Machine Zone. All rights reserved.
*/
#include <iostream>
#include <sstream>
#include <set>
#include <ixwebsocket/IXWebSocket.h>
#include "IXTest.h"
#include "catch.hpp"
using namespace ix;
namespace
{
const std::string WEBSOCKET_DOT_ORG_URL("wss://echo.websocket.org");
const std::string GOOGLE_URL("wss://google.com");
const std::string UNKNOWN_URL("wss://asdcasdcaasdcasdcasdcasdcasdcasdcasassdd.com");
}
namespace
{
class IXWebSocketTestConnectionDisconnection
{
public:
IXWebSocketTestConnectionDisconnection();
void start(const std::string& url);
void stop();
private:
ix::WebSocket _webSocket;
};
IXWebSocketTestConnectionDisconnection::IXWebSocketTestConnectionDisconnection()
{
;
}
void IXWebSocketTestConnectionDisconnection::stop()
{
_webSocket.stop();
}
void IXWebSocketTestConnectionDisconnection::start(const std::string& url)
{
_webSocket.setUrl(url);
std::stringstream ss;
log(std::string("Connecting to url: ") + url);
_webSocket.setOnMessageCallback(
[](ix::WebSocketMessageType messageType,
const std::string& str,
size_t wireSize,
const ix::WebSocketErrorInfo& error,
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
{
std::stringstream ss;
if (messageType == ix::WebSocket_MessageType_Open)
{
log("cmd_websocket_satori_chat: connected !");
}
else if (messageType == ix::WebSocket_MessageType_Close)
{
log("cmd_websocket_satori_chat: disconnected !");
}
else if (messageType == ix::WebSocket_MessageType_Error)
{
log("cmd_websocket_satori_chat: Error!");
}
else if (messageType == ix::WebSocket_MessageType_Message)
{
log("cmd_websocket_satori_chat: received message.!");
}
else if (messageType == ix::WebSocket_MessageType_Ping)
{
log("cmd_websocket_satori_chat: received ping message.!");
}
else if (messageType == ix::WebSocket_MessageType_Pong)
{
log("cmd_websocket_satori_chat: received pong message.!");
}
else
{
log("Invalid ix::WebSocketMessageType");
}
});
// Start the connection
_webSocket.start();
}
}
//
// We try to connect to different servers, and make sure there are no crashes.
// FIXME: We could do more checks (make sure that we were not able to connect to unknown servers, etc...)
//
TEST_CASE("websocket_connections", "[websocket]")
{
SECTION("Try to connect to invalid servers.")
{
IXWebSocketTestConnectionDisconnection chatA;
chatA.start(GOOGLE_URL);
ix::msleep(1000);
chatA.stop();
chatA.start(UNKNOWN_URL);
ix::msleep(1000);
chatA.stop();
}
SECTION("Try to connect and disconnect with different timing.")
{
IXWebSocketTestConnectionDisconnection chatA;
for (int i = 0; i < 50; ++i)
{
log(std::string("Run: ") + std::to_string(i));
chatA.start(WEBSOCKET_DOT_ORG_URL);
ix::msleep(i);
chatA.stop();
}
}
}

View File

@ -4,11 +4,6 @@
* Copyright (c) 2017 Machine Zone. All rights reserved.
*/
//
// Simple chat program that talks to the node.js server at
// websocket_chat_server/broacast-server.js
//
#include <iostream>
#include <sstream>
#include <queue>
@ -43,8 +38,6 @@ namespace
std::pair<std::string, std::string> decodeMessage(const std::string& str);
private:
void log(const std::string& msg);
std::string _user;
std::string _session;
@ -76,11 +69,6 @@ namespace
_webSocket.stop();
}
void WebSocketChat::log(const std::string& msg)
{
std::cerr << msg << std::endl;
}
void WebSocketChat::start()
{
std::string url("ws://localhost:8090/");
@ -188,17 +176,17 @@ namespace
{
if (messageType == ix::WebSocket_MessageType_Open)
{
std::cerr << "New connection" << std::endl;
std::cerr << "Uri: " << openInfo.uri << std::endl;
std::cerr << "Headers:" << std::endl;
Logger() << "New connection";
Logger() << "Uri: " << openInfo.uri;
Logger() << "Headers:";
for (auto it : openInfo.headers)
{
std::cerr << it.first << ": " << it.second << std::endl;
Logger() << it.first << ": " << it.second;
}
}
else if (messageType == ix::WebSocket_MessageType_Close)
{
std::cerr << "Closed connection" << std::endl;
log("Closed connection");
}
else if (messageType == ix::WebSocket_MessageType_Message)
{
@ -218,7 +206,7 @@ namespace
auto res = server.listen();
if (!res.first)
{
std::cerr << res.second << std::endl;
log(res.second);
return false;
}

78
test/run.py Normal file
View File

@ -0,0 +1,78 @@
import os
import platform
import shutil
osName = platform.system()
print('os name = {}'.format(osName))
root = os.path.dirname(os.path.realpath(__file__))
buildDir = os.path.join(root, 'build')
if not os.path.exists(buildDir):
os.mkdir(buildDir)
os.chdir(buildDir)
if osName == 'Windows':
generator = '-G"NMake Makefiles"'
make = 'nmake'
testBinary ='ixwebsocket_unittest.exe'
else:
generator = ''
make = 'make -j6'
testBinary ='./ixwebsocket_unittest'
sanitizersFlags = {
'asan': '-DSANITIZE_ADDRESS=On',
'ubsan': '-DSANITIZE_UNDEFINED=On',
'tsan': '-DSANITIZE_THREAD=On',
'none': ''
}
sanitizer = 'tsan'
if osName == 'Linux':
sanitizer = 'asan'
sanitizerFlags = sanitizersFlags[sanitizer]
cmakeCmd = 'cmake -DCMAKE_BUILD_TYPE=Debug {} {} ..'.format(generator, sanitizerFlags)
print(cmakeCmd)
ret = os.system(cmakeCmd)
assert ret == 0, 'CMake failed, exiting'
ret = os.system(make)
assert ret == 0, 'Make failed, exiting'
def findFiles(prefix):
'''Find all files under a given directory'''
paths = []
for root, _, files in os.walk(prefix):
for path in files:
fullPath = os.path.join(root, path)
if os.path.islink(fullPath):
continue
paths.append(fullPath)
return paths
#for path in findFiles('.'):
# print(path)
# We need to copy the zlib DLL in the current work directory
shutil.copy(os.path.join(
'..',
'..',
'third_party',
'ZLIB-Windows',
'zlib-1.2.11_deploy_v140',
'release_dynamic',
'x64',
'bin',
'zlib.dll'), '.')
testCommand = '{} {}'.format(testBinary, os.getenv('TEST', ''))
ret = os.system(testCommand)
assert ret == 0, 'Test command failed'

View File

@ -7,8 +7,14 @@
#define CATCH_CONFIG_RUNNER
#include "catch.hpp"
#include <ixwebsocket/IXSocket.h>
int main(int argc, char* argv[])
{
ix::Socket::init(); // for Windows
int result = Catch::Session().run(argc, argv);
ix::Socket::cleanup(); // for Windows
return result;
}

115
third_party/ZLIB-Windows/README.md vendored Normal file
View File

@ -0,0 +1,115 @@
ZLIB DATA COMPRESSION LIBRARY
zlib 1.2.11 is a general purpose data compression library. All the code is
thread safe. The data format used by the zlib library is described by RFCs
(Request for Comments) 1950 to 1952 in the files
http://tools.ietf.org/html/rfc1950 (zlib format), rfc1951 (deflate format) and
rfc1952 (gzip format).
All functions of the compression library are documented in the file zlib.h
(volunteer to write man pages welcome, contact zlib@gzip.org). A usage example
of the library is given in the file test/example.c which also tests that
the library is working correctly. Another example is given in the file
test/minigzip.c. The compression library itself is composed of all source
files in the root directory.
To compile all files and run the test program, follow the instructions given at
the top of Makefile.in. In short "./configure; make test", and if that goes
well, "make install" should work for most flavors of Unix. For Windows, use
one of the special makefiles in win32/ or contrib/vstudio/ . For VMS, use
make_vms.com.
Questions about zlib should be sent to <zlib@gzip.org>, or to Gilles Vollant
<info@winimage.com> for the Windows DLL version. The zlib home page is
http://zlib.net/ . Before reporting a problem, please check this site to
verify that you have the latest version of zlib; otherwise get the latest
version and check whether the problem still exists or not.
PLEASE read the zlib FAQ http://zlib.net/zlib_faq.html before asking for help.
Mark Nelson <markn@ieee.org> wrote an article about zlib for the Jan. 1997
issue of Dr. Dobb's Journal; a copy of the article is available at
http://marknelson.us/1997/01/01/zlib-engine/ .
The changes made in version 1.2.11 are documented in the file ChangeLog.
Unsupported third party contributions are provided in directory contrib/ .
zlib is available in Java using the java.util.zip package, documented at
http://java.sun.com/developer/technicalArticles/Programming/compression/ .
A Perl interface to zlib written by Paul Marquess <pmqs@cpan.org> is available
at CPAN (Comprehensive Perl Archive Network) sites, including
http://search.cpan.org/~pmqs/IO-Compress-Zlib/ .
A Python interface to zlib written by A.M. Kuchling <amk@amk.ca> is
available in Python 1.5 and later versions, see
http://docs.python.org/library/zlib.html .
zlib is built into tcl: http://wiki.tcl.tk/4610 .
An experimental package to read and write files in .zip format, written on top
of zlib by Gilles Vollant <info@winimage.com>, is available in the
contrib/minizip directory of zlib.
Notes for some targets:
- For Windows DLL versions, please see win32/DLL_FAQ.txt
- For 64-bit Irix, deflate.c must be compiled without any optimization. With
-O, one libpng test fails. The test works in 32 bit mode (with the -n32
compiler flag). The compiler bug has been reported to SGI.
- zlib doesn't work with gcc 2.6.3 on a DEC 3000/300LX under OSF/1 2.1 it works
when compiled with cc.
- On Digital Unix 4.0D (formely OSF/1) on AlphaServer, the cc option -std1 is
necessary to get gzprintf working correctly. This is done by configure.
- zlib doesn't work on HP-UX 9.05 with some versions of /bin/cc. It works with
other compilers. Use "make test" to check your compiler.
- gzdopen is not supported on RISCOS or BEOS.
- For PalmOs, see http://palmzlib.sourceforge.net/
Acknowledgments:
The deflate format used by zlib was defined by Phil Katz. The deflate and
zlib specifications were written by L. Peter Deutsch. Thanks to all the
people who reported problems and suggested various improvements in zlib; they
are too numerous to cite here.
Copyright notice:
(C) 1995-2017 Jean-loup Gailly and Mark Adler
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Jean-loup Gailly Mark Adler
jloup@gzip.org madler@alumni.caltech.edu
If you use the zlib library in a product, we would appreciate *not* receiving
lengthy legal documents to sign. The sources are provided for free but without
warranty of any kind. The library has been entirely written by Jean-loup
Gailly and Mark Adler; it does not include third-party code.
If you redistribute modified sources, we would appreciate that you include in
the file ChangeLog history information documenting your changes. Please read
the FAQ for more information on the distribution of modified source versions.

View File

@ -0,0 +1,536 @@
/* zconf.h -- configuration of the zlib compression library
* Copyright (C) 1995-2016 Jean-loup Gailly, Mark Adler
* For conditions of distribution and use, see copyright notice in zlib.h
*/
/* @(#) $Id$ */
#ifndef ZCONF_H
#define ZCONF_H
/* #undef Z_PREFIX */
/* #undef Z_HAVE_UNISTD_H */
/*
* If you *really* need a unique prefix for all types and library functions,
* compile with -DZ_PREFIX. The "standard" zlib should be compiled without it.
* Even better than compiling with -DZ_PREFIX would be to use configure to set
* this permanently in zconf.h using "./configure --zprefix".
*/
#ifdef Z_PREFIX /* may be set to #if 1 by ./configure */
# define Z_PREFIX_SET
/* all linked symbols and init macros */
# define _dist_code z__dist_code
# define _length_code z__length_code
# define _tr_align z__tr_align
# define _tr_flush_bits z__tr_flush_bits
# define _tr_flush_block z__tr_flush_block
# define _tr_init z__tr_init
# define _tr_stored_block z__tr_stored_block
# define _tr_tally z__tr_tally
# define adler32 z_adler32
# define adler32_combine z_adler32_combine
# define adler32_combine64 z_adler32_combine64
# define adler32_z z_adler32_z
# ifndef Z_SOLO
# define compress z_compress
# define compress2 z_compress2
# define compressBound z_compressBound
# endif
# define crc32 z_crc32
# define crc32_combine z_crc32_combine
# define crc32_combine64 z_crc32_combine64
# define crc32_z z_crc32_z
# define deflate z_deflate
# define deflateBound z_deflateBound
# define deflateCopy z_deflateCopy
# define deflateEnd z_deflateEnd
# define deflateGetDictionary z_deflateGetDictionary
# define deflateInit z_deflateInit
# define deflateInit2 z_deflateInit2
# define deflateInit2_ z_deflateInit2_
# define deflateInit_ z_deflateInit_
# define deflateParams z_deflateParams
# define deflatePending z_deflatePending
# define deflatePrime z_deflatePrime
# define deflateReset z_deflateReset
# define deflateResetKeep z_deflateResetKeep
# define deflateSetDictionary z_deflateSetDictionary
# define deflateSetHeader z_deflateSetHeader
# define deflateTune z_deflateTune
# define deflate_copyright z_deflate_copyright
# define get_crc_table z_get_crc_table
# ifndef Z_SOLO
# define gz_error z_gz_error
# define gz_intmax z_gz_intmax
# define gz_strwinerror z_gz_strwinerror
# define gzbuffer z_gzbuffer
# define gzclearerr z_gzclearerr
# define gzclose z_gzclose
# define gzclose_r z_gzclose_r
# define gzclose_w z_gzclose_w
# define gzdirect z_gzdirect
# define gzdopen z_gzdopen
# define gzeof z_gzeof
# define gzerror z_gzerror
# define gzflush z_gzflush
# define gzfread z_gzfread
# define gzfwrite z_gzfwrite
# define gzgetc z_gzgetc
# define gzgetc_ z_gzgetc_
# define gzgets z_gzgets
# define gzoffset z_gzoffset
# define gzoffset64 z_gzoffset64
# define gzopen z_gzopen
# define gzopen64 z_gzopen64
# ifdef _WIN32
# define gzopen_w z_gzopen_w
# endif
# define gzprintf z_gzprintf
# define gzputc z_gzputc
# define gzputs z_gzputs
# define gzread z_gzread
# define gzrewind z_gzrewind
# define gzseek z_gzseek
# define gzseek64 z_gzseek64
# define gzsetparams z_gzsetparams
# define gztell z_gztell
# define gztell64 z_gztell64
# define gzungetc z_gzungetc
# define gzvprintf z_gzvprintf
# define gzwrite z_gzwrite
# endif
# define inflate z_inflate
# define inflateBack z_inflateBack
# define inflateBackEnd z_inflateBackEnd
# define inflateBackInit z_inflateBackInit
# define inflateBackInit_ z_inflateBackInit_
# define inflateCodesUsed z_inflateCodesUsed
# define inflateCopy z_inflateCopy
# define inflateEnd z_inflateEnd
# define inflateGetDictionary z_inflateGetDictionary
# define inflateGetHeader z_inflateGetHeader
# define inflateInit z_inflateInit
# define inflateInit2 z_inflateInit2
# define inflateInit2_ z_inflateInit2_
# define inflateInit_ z_inflateInit_
# define inflateMark z_inflateMark
# define inflatePrime z_inflatePrime
# define inflateReset z_inflateReset
# define inflateReset2 z_inflateReset2
# define inflateResetKeep z_inflateResetKeep
# define inflateSetDictionary z_inflateSetDictionary
# define inflateSync z_inflateSync
# define inflateSyncPoint z_inflateSyncPoint
# define inflateUndermine z_inflateUndermine
# define inflateValidate z_inflateValidate
# define inflate_copyright z_inflate_copyright
# define inflate_fast z_inflate_fast
# define inflate_table z_inflate_table
# ifndef Z_SOLO
# define uncompress z_uncompress
# define uncompress2 z_uncompress2
# endif
# define zError z_zError
# ifndef Z_SOLO
# define zcalloc z_zcalloc
# define zcfree z_zcfree
# endif
# define zlibCompileFlags z_zlibCompileFlags
# define zlibVersion z_zlibVersion
/* all zlib typedefs in zlib.h and zconf.h */
# define Byte z_Byte
# define Bytef z_Bytef
# define alloc_func z_alloc_func
# define charf z_charf
# define free_func z_free_func
# ifndef Z_SOLO
# define gzFile z_gzFile
# endif
# define gz_header z_gz_header
# define gz_headerp z_gz_headerp
# define in_func z_in_func
# define intf z_intf
# define out_func z_out_func
# define uInt z_uInt
# define uIntf z_uIntf
# define uLong z_uLong
# define uLongf z_uLongf
# define voidp z_voidp
# define voidpc z_voidpc
# define voidpf z_voidpf
/* all zlib structs in zlib.h and zconf.h */
# define gz_header_s z_gz_header_s
# define internal_state z_internal_state
#endif
#if defined(__MSDOS__) && !defined(MSDOS)
# define MSDOS
#endif
#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2)
# define OS2
#endif
#if defined(_WINDOWS) && !defined(WINDOWS)
# define WINDOWS
#endif
#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__)
# ifndef WIN32
# define WIN32
# endif
#endif
#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32)
# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__)
# ifndef SYS16BIT
# define SYS16BIT
# endif
# endif
#endif
/*
* Compile with -DMAXSEG_64K if the alloc function cannot allocate more
* than 64k bytes at a time (needed on systems with 16-bit int).
*/
#ifdef SYS16BIT
# define MAXSEG_64K
#endif
#ifdef MSDOS
# define UNALIGNED_OK
#endif
#ifdef __STDC_VERSION__
# ifndef STDC
# define STDC
# endif
# if __STDC_VERSION__ >= 199901L
# ifndef STDC99
# define STDC99
# endif
# endif
#endif
#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus))
# define STDC
#endif
#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__))
# define STDC
#endif
#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32))
# define STDC
#endif
#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__))
# define STDC
#endif
#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */
# define STDC
#endif
#ifndef STDC
# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */
# define const /* note: need a more gentle solution here */
# endif
#endif
#if defined(ZLIB_CONST) && !defined(z_const)
# define z_const const
#else
# define z_const
#endif
#ifdef Z_SOLO
typedef unsigned long z_size_t;
#else
# define z_longlong long long
# if defined(NO_SIZE_T)
typedef unsigned NO_SIZE_T z_size_t;
# elif defined(STDC)
# include <stddef.h>
typedef size_t z_size_t;
# else
typedef unsigned long z_size_t;
# endif
# undef z_longlong
#endif
/* Maximum value for memLevel in deflateInit2 */
#ifndef MAX_MEM_LEVEL
# ifdef MAXSEG_64K
# define MAX_MEM_LEVEL 8
# else
# define MAX_MEM_LEVEL 9
# endif
#endif
/* Maximum value for windowBits in deflateInit2 and inflateInit2.
* WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files
* created by gzip. (Files created by minigzip can still be extracted by
* gzip.)
*/
#ifndef MAX_WBITS
# define MAX_WBITS 15 /* 32K LZ77 window */
#endif
/* The memory requirements for deflate are (in bytes):
(1 << (windowBits+2)) + (1 << (memLevel+9))
that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values)
plus a few kilobytes for small objects. For example, if you want to reduce
the default memory requirements from 256K to 128K, compile with
make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7"
Of course this will generally degrade compression (there's no free lunch).
The memory requirements for inflate are (in bytes) 1 << windowBits
that is, 32K for windowBits=15 (default value) plus about 7 kilobytes
for small objects.
*/
/* Type declarations */
#ifndef OF /* function prototypes */
# ifdef STDC
# define OF(args) args
# else
# define OF(args) ()
# endif
#endif
#ifndef Z_ARG /* function prototypes for stdarg */
# if defined(STDC) || defined(Z_HAVE_STDARG_H)
# define Z_ARG(args) args
# else
# define Z_ARG(args) ()
# endif
#endif
/* The following definitions for FAR are needed only for MSDOS mixed
* model programming (small or medium model with some far allocations).
* This was tested only with MSC; for other MSDOS compilers you may have
* to define NO_MEMCPY in zutil.h. If you don't need the mixed model,
* just define FAR to be empty.
*/
#ifdef SYS16BIT
# if defined(M_I86SM) || defined(M_I86MM)
/* MSC small or medium model */
# define SMALL_MEDIUM
# ifdef _MSC_VER
# define FAR _far
# else
# define FAR far
# endif
# endif
# if (defined(__SMALL__) || defined(__MEDIUM__))
/* Turbo C small or medium model */
# define SMALL_MEDIUM
# ifdef __BORLANDC__
# define FAR _far
# else
# define FAR far
# endif
# endif
#endif
#if defined(WINDOWS) || defined(WIN32)
/* If building or using zlib as a DLL, define ZLIB_DLL.
* This is not mandatory, but it offers a little performance increase.
*/
# ifdef ZLIB_DLL
# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500))
# ifdef ZLIB_INTERNAL
# define ZEXTERN extern __declspec(dllexport)
# else
# define ZEXTERN extern __declspec(dllimport)
# endif
# endif
# endif /* ZLIB_DLL */
/* If building or using zlib with the WINAPI/WINAPIV calling convention,
* define ZLIB_WINAPI.
* Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI.
*/
# ifdef ZLIB_WINAPI
# ifdef FAR
# undef FAR
# endif
# include <windows.h>
/* No need for _export, use ZLIB.DEF instead. */
/* For complete Windows compatibility, use WINAPI, not __stdcall. */
# define ZEXPORT WINAPI
# ifdef WIN32
# define ZEXPORTVA WINAPIV
# else
# define ZEXPORTVA FAR CDECL
# endif
# endif
#endif
#if defined (__BEOS__)
# ifdef ZLIB_DLL
# ifdef ZLIB_INTERNAL
# define ZEXPORT __declspec(dllexport)
# define ZEXPORTVA __declspec(dllexport)
# else
# define ZEXPORT __declspec(dllimport)
# define ZEXPORTVA __declspec(dllimport)
# endif
# endif
#endif
#ifndef ZEXTERN
# define ZEXTERN extern
#endif
#ifndef ZEXPORT
# define ZEXPORT
#endif
#ifndef ZEXPORTVA
# define ZEXPORTVA
#endif
#ifndef FAR
# define FAR
#endif
#if !defined(__MACTYPES__)
typedef unsigned char Byte; /* 8 bits */
#endif
typedef unsigned int uInt; /* 16 bits or more */
typedef unsigned long uLong; /* 32 bits or more */
#ifdef SMALL_MEDIUM
/* Borland C/C++ and some old MSC versions ignore FAR inside typedef */
# define Bytef Byte FAR
#else
typedef Byte FAR Bytef;
#endif
typedef char FAR charf;
typedef int FAR intf;
typedef uInt FAR uIntf;
typedef uLong FAR uLongf;
#ifdef STDC
typedef void const *voidpc;
typedef void FAR *voidpf;
typedef void *voidp;
#else
typedef Byte const *voidpc;
typedef Byte FAR *voidpf;
typedef Byte *voidp;
#endif
#if !defined(Z_U4) && !defined(Z_SOLO) && defined(STDC)
# include <limits.h>
# if (UINT_MAX == 0xffffffffUL)
# define Z_U4 unsigned
# elif (ULONG_MAX == 0xffffffffUL)
# define Z_U4 unsigned long
# elif (USHRT_MAX == 0xffffffffUL)
# define Z_U4 unsigned short
# endif
#endif
#ifdef Z_U4
typedef Z_U4 z_crc_t;
#else
typedef unsigned long z_crc_t;
#endif
#ifdef HAVE_UNISTD_H /* may be set to #if 1 by ./configure */
# define Z_HAVE_UNISTD_H
#endif
#ifdef HAVE_STDARG_H /* may be set to #if 1 by ./configure */
# define Z_HAVE_STDARG_H
#endif
#ifdef STDC
# ifndef Z_SOLO
# include <sys/types.h> /* for off_t */
# endif
#endif
#if defined(STDC) || defined(Z_HAVE_STDARG_H)
# ifndef Z_SOLO
# include <stdarg.h> /* for va_list */
# endif
#endif
#ifdef _WIN32
# ifndef Z_SOLO
# include <stddef.h> /* for wchar_t */
# endif
#endif
/* a little trick to accommodate both "#define _LARGEFILE64_SOURCE" and
* "#define _LARGEFILE64_SOURCE 1" as requesting 64-bit operations, (even
* though the former does not conform to the LFS document), but considering
* both "#undef _LARGEFILE64_SOURCE" and "#define _LARGEFILE64_SOURCE 0" as
* equivalently requesting no 64-bit operations
*/
#if defined(_LARGEFILE64_SOURCE) && -_LARGEFILE64_SOURCE - -1 == 1
# undef _LARGEFILE64_SOURCE
#endif
#if defined(__WATCOMC__) && !defined(Z_HAVE_UNISTD_H)
# define Z_HAVE_UNISTD_H
#endif
#ifndef Z_SOLO
# if defined(Z_HAVE_UNISTD_H) || defined(_LARGEFILE64_SOURCE)
# include <unistd.h> /* for SEEK_*, off_t, and _LFS64_LARGEFILE */
# ifdef VMS
# include <unixio.h> /* for off_t */
# endif
# ifndef z_off_t
# define z_off_t off_t
# endif
# endif
#endif
#if defined(_LFS64_LARGEFILE) && _LFS64_LARGEFILE-0
# define Z_LFS64
#endif
#if defined(_LARGEFILE64_SOURCE) && defined(Z_LFS64)
# define Z_LARGE64
#endif
#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS-0 == 64 && defined(Z_LFS64)
# define Z_WANT64
#endif
#if !defined(SEEK_SET) && !defined(Z_SOLO)
# define SEEK_SET 0 /* Seek from beginning of file. */
# define SEEK_CUR 1 /* Seek from current position. */
# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */
#endif
#ifndef z_off_t
# define z_off_t long
#endif
#if !defined(_WIN32) && defined(Z_LARGE64)
# define z_off64_t off64_t
#else
# if defined(_WIN32) && !defined(__GNUC__) && !defined(Z_SOLO)
# define z_off64_t __int64
# else
# define z_off64_t z_off_t
# endif
#endif
/* MVS linker does not support external names larger than 8 bytes */
#if defined(__MVS__)
#pragma map(deflateInit_,"DEIN")
#pragma map(deflateInit2_,"DEIN2")
#pragma map(deflateEnd,"DEEND")
#pragma map(deflateBound,"DEBND")
#pragma map(inflateInit_,"ININ")
#pragma map(inflateInit2_,"ININ2")
#pragma map(inflateEnd,"INEND")
#pragma map(inflateSync,"INSY")
#pragma map(inflateSetDictionary,"INSEDI")
#pragma map(compressBound,"CMBND")
#pragma map(inflate_table,"INTABL")
#pragma map(inflate_fast,"INFA")
#pragma map(inflate_copyright,"INCOPY")
#endif
#endif /* ZCONF_H */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,536 @@
/* zconf.h -- configuration of the zlib compression library
* Copyright (C) 1995-2016 Jean-loup Gailly, Mark Adler
* For conditions of distribution and use, see copyright notice in zlib.h
*/
/* @(#) $Id$ */
#ifndef ZCONF_H
#define ZCONF_H
/* #undef Z_PREFIX */
/* #undef Z_HAVE_UNISTD_H */
/*
* If you *really* need a unique prefix for all types and library functions,
* compile with -DZ_PREFIX. The "standard" zlib should be compiled without it.
* Even better than compiling with -DZ_PREFIX would be to use configure to set
* this permanently in zconf.h using "./configure --zprefix".
*/
#ifdef Z_PREFIX /* may be set to #if 1 by ./configure */
# define Z_PREFIX_SET
/* all linked symbols and init macros */
# define _dist_code z__dist_code
# define _length_code z__length_code
# define _tr_align z__tr_align
# define _tr_flush_bits z__tr_flush_bits
# define _tr_flush_block z__tr_flush_block
# define _tr_init z__tr_init
# define _tr_stored_block z__tr_stored_block
# define _tr_tally z__tr_tally
# define adler32 z_adler32
# define adler32_combine z_adler32_combine
# define adler32_combine64 z_adler32_combine64
# define adler32_z z_adler32_z
# ifndef Z_SOLO
# define compress z_compress
# define compress2 z_compress2
# define compressBound z_compressBound
# endif
# define crc32 z_crc32
# define crc32_combine z_crc32_combine
# define crc32_combine64 z_crc32_combine64
# define crc32_z z_crc32_z
# define deflate z_deflate
# define deflateBound z_deflateBound
# define deflateCopy z_deflateCopy
# define deflateEnd z_deflateEnd
# define deflateGetDictionary z_deflateGetDictionary
# define deflateInit z_deflateInit
# define deflateInit2 z_deflateInit2
# define deflateInit2_ z_deflateInit2_
# define deflateInit_ z_deflateInit_
# define deflateParams z_deflateParams
# define deflatePending z_deflatePending
# define deflatePrime z_deflatePrime
# define deflateReset z_deflateReset
# define deflateResetKeep z_deflateResetKeep
# define deflateSetDictionary z_deflateSetDictionary
# define deflateSetHeader z_deflateSetHeader
# define deflateTune z_deflateTune
# define deflate_copyright z_deflate_copyright
# define get_crc_table z_get_crc_table
# ifndef Z_SOLO
# define gz_error z_gz_error
# define gz_intmax z_gz_intmax
# define gz_strwinerror z_gz_strwinerror
# define gzbuffer z_gzbuffer
# define gzclearerr z_gzclearerr
# define gzclose z_gzclose
# define gzclose_r z_gzclose_r
# define gzclose_w z_gzclose_w
# define gzdirect z_gzdirect
# define gzdopen z_gzdopen
# define gzeof z_gzeof
# define gzerror z_gzerror
# define gzflush z_gzflush
# define gzfread z_gzfread
# define gzfwrite z_gzfwrite
# define gzgetc z_gzgetc
# define gzgetc_ z_gzgetc_
# define gzgets z_gzgets
# define gzoffset z_gzoffset
# define gzoffset64 z_gzoffset64
# define gzopen z_gzopen
# define gzopen64 z_gzopen64
# ifdef _WIN32
# define gzopen_w z_gzopen_w
# endif
# define gzprintf z_gzprintf
# define gzputc z_gzputc
# define gzputs z_gzputs
# define gzread z_gzread
# define gzrewind z_gzrewind
# define gzseek z_gzseek
# define gzseek64 z_gzseek64
# define gzsetparams z_gzsetparams
# define gztell z_gztell
# define gztell64 z_gztell64
# define gzungetc z_gzungetc
# define gzvprintf z_gzvprintf
# define gzwrite z_gzwrite
# endif
# define inflate z_inflate
# define inflateBack z_inflateBack
# define inflateBackEnd z_inflateBackEnd
# define inflateBackInit z_inflateBackInit
# define inflateBackInit_ z_inflateBackInit_
# define inflateCodesUsed z_inflateCodesUsed
# define inflateCopy z_inflateCopy
# define inflateEnd z_inflateEnd
# define inflateGetDictionary z_inflateGetDictionary
# define inflateGetHeader z_inflateGetHeader
# define inflateInit z_inflateInit
# define inflateInit2 z_inflateInit2
# define inflateInit2_ z_inflateInit2_
# define inflateInit_ z_inflateInit_
# define inflateMark z_inflateMark
# define inflatePrime z_inflatePrime
# define inflateReset z_inflateReset
# define inflateReset2 z_inflateReset2
# define inflateResetKeep z_inflateResetKeep
# define inflateSetDictionary z_inflateSetDictionary
# define inflateSync z_inflateSync
# define inflateSyncPoint z_inflateSyncPoint
# define inflateUndermine z_inflateUndermine
# define inflateValidate z_inflateValidate
# define inflate_copyright z_inflate_copyright
# define inflate_fast z_inflate_fast
# define inflate_table z_inflate_table
# ifndef Z_SOLO
# define uncompress z_uncompress
# define uncompress2 z_uncompress2
# endif
# define zError z_zError
# ifndef Z_SOLO
# define zcalloc z_zcalloc
# define zcfree z_zcfree
# endif
# define zlibCompileFlags z_zlibCompileFlags
# define zlibVersion z_zlibVersion
/* all zlib typedefs in zlib.h and zconf.h */
# define Byte z_Byte
# define Bytef z_Bytef
# define alloc_func z_alloc_func
# define charf z_charf
# define free_func z_free_func
# ifndef Z_SOLO
# define gzFile z_gzFile
# endif
# define gz_header z_gz_header
# define gz_headerp z_gz_headerp
# define in_func z_in_func
# define intf z_intf
# define out_func z_out_func
# define uInt z_uInt
# define uIntf z_uIntf
# define uLong z_uLong
# define uLongf z_uLongf
# define voidp z_voidp
# define voidpc z_voidpc
# define voidpf z_voidpf
/* all zlib structs in zlib.h and zconf.h */
# define gz_header_s z_gz_header_s
# define internal_state z_internal_state
#endif
#if defined(__MSDOS__) && !defined(MSDOS)
# define MSDOS
#endif
#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2)
# define OS2
#endif
#if defined(_WINDOWS) && !defined(WINDOWS)
# define WINDOWS
#endif
#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__)
# ifndef WIN32
# define WIN32
# endif
#endif
#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32)
# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__)
# ifndef SYS16BIT
# define SYS16BIT
# endif
# endif
#endif
/*
* Compile with -DMAXSEG_64K if the alloc function cannot allocate more
* than 64k bytes at a time (needed on systems with 16-bit int).
*/
#ifdef SYS16BIT
# define MAXSEG_64K
#endif
#ifdef MSDOS
# define UNALIGNED_OK
#endif
#ifdef __STDC_VERSION__
# ifndef STDC
# define STDC
# endif
# if __STDC_VERSION__ >= 199901L
# ifndef STDC99
# define STDC99
# endif
# endif
#endif
#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus))
# define STDC
#endif
#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__))
# define STDC
#endif
#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32))
# define STDC
#endif
#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__))
# define STDC
#endif
#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */
# define STDC
#endif
#ifndef STDC
# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */
# define const /* note: need a more gentle solution here */
# endif
#endif
#if defined(ZLIB_CONST) && !defined(z_const)
# define z_const const
#else
# define z_const
#endif
#ifdef Z_SOLO
typedef unsigned long z_size_t;
#else
# define z_longlong long long
# if defined(NO_SIZE_T)
typedef unsigned NO_SIZE_T z_size_t;
# elif defined(STDC)
# include <stddef.h>
typedef size_t z_size_t;
# else
typedef unsigned long z_size_t;
# endif
# undef z_longlong
#endif
/* Maximum value for memLevel in deflateInit2 */
#ifndef MAX_MEM_LEVEL
# ifdef MAXSEG_64K
# define MAX_MEM_LEVEL 8
# else
# define MAX_MEM_LEVEL 9
# endif
#endif
/* Maximum value for windowBits in deflateInit2 and inflateInit2.
* WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files
* created by gzip. (Files created by minigzip can still be extracted by
* gzip.)
*/
#ifndef MAX_WBITS
# define MAX_WBITS 15 /* 32K LZ77 window */
#endif
/* The memory requirements for deflate are (in bytes):
(1 << (windowBits+2)) + (1 << (memLevel+9))
that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values)
plus a few kilobytes for small objects. For example, if you want to reduce
the default memory requirements from 256K to 128K, compile with
make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7"
Of course this will generally degrade compression (there's no free lunch).
The memory requirements for inflate are (in bytes) 1 << windowBits
that is, 32K for windowBits=15 (default value) plus about 7 kilobytes
for small objects.
*/
/* Type declarations */
#ifndef OF /* function prototypes */
# ifdef STDC
# define OF(args) args
# else
# define OF(args) ()
# endif
#endif
#ifndef Z_ARG /* function prototypes for stdarg */
# if defined(STDC) || defined(Z_HAVE_STDARG_H)
# define Z_ARG(args) args
# else
# define Z_ARG(args) ()
# endif
#endif
/* The following definitions for FAR are needed only for MSDOS mixed
* model programming (small or medium model with some far allocations).
* This was tested only with MSC; for other MSDOS compilers you may have
* to define NO_MEMCPY in zutil.h. If you don't need the mixed model,
* just define FAR to be empty.
*/
#ifdef SYS16BIT
# if defined(M_I86SM) || defined(M_I86MM)
/* MSC small or medium model */
# define SMALL_MEDIUM
# ifdef _MSC_VER
# define FAR _far
# else
# define FAR far
# endif
# endif
# if (defined(__SMALL__) || defined(__MEDIUM__))
/* Turbo C small or medium model */
# define SMALL_MEDIUM
# ifdef __BORLANDC__
# define FAR _far
# else
# define FAR far
# endif
# endif
#endif
#if defined(WINDOWS) || defined(WIN32)
/* If building or using zlib as a DLL, define ZLIB_DLL.
* This is not mandatory, but it offers a little performance increase.
*/
# ifdef ZLIB_DLL
# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500))
# ifdef ZLIB_INTERNAL
# define ZEXTERN extern __declspec(dllexport)
# else
# define ZEXTERN extern __declspec(dllimport)
# endif
# endif
# endif /* ZLIB_DLL */
/* If building or using zlib with the WINAPI/WINAPIV calling convention,
* define ZLIB_WINAPI.
* Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI.
*/
# ifdef ZLIB_WINAPI
# ifdef FAR
# undef FAR
# endif
# include <windows.h>
/* No need for _export, use ZLIB.DEF instead. */
/* For complete Windows compatibility, use WINAPI, not __stdcall. */
# define ZEXPORT WINAPI
# ifdef WIN32
# define ZEXPORTVA WINAPIV
# else
# define ZEXPORTVA FAR CDECL
# endif
# endif
#endif
#if defined (__BEOS__)
# ifdef ZLIB_DLL
# ifdef ZLIB_INTERNAL
# define ZEXPORT __declspec(dllexport)
# define ZEXPORTVA __declspec(dllexport)
# else
# define ZEXPORT __declspec(dllimport)
# define ZEXPORTVA __declspec(dllimport)
# endif
# endif
#endif
#ifndef ZEXTERN
# define ZEXTERN extern
#endif
#ifndef ZEXPORT
# define ZEXPORT
#endif
#ifndef ZEXPORTVA
# define ZEXPORTVA
#endif
#ifndef FAR
# define FAR
#endif
#if !defined(__MACTYPES__)
typedef unsigned char Byte; /* 8 bits */
#endif
typedef unsigned int uInt; /* 16 bits or more */
typedef unsigned long uLong; /* 32 bits or more */
#ifdef SMALL_MEDIUM
/* Borland C/C++ and some old MSC versions ignore FAR inside typedef */
# define Bytef Byte FAR
#else
typedef Byte FAR Bytef;
#endif
typedef char FAR charf;
typedef int FAR intf;
typedef uInt FAR uIntf;
typedef uLong FAR uLongf;
#ifdef STDC
typedef void const *voidpc;
typedef void FAR *voidpf;
typedef void *voidp;
#else
typedef Byte const *voidpc;
typedef Byte FAR *voidpf;
typedef Byte *voidp;
#endif
#if !defined(Z_U4) && !defined(Z_SOLO) && defined(STDC)
# include <limits.h>
# if (UINT_MAX == 0xffffffffUL)
# define Z_U4 unsigned
# elif (ULONG_MAX == 0xffffffffUL)
# define Z_U4 unsigned long
# elif (USHRT_MAX == 0xffffffffUL)
# define Z_U4 unsigned short
# endif
#endif
#ifdef Z_U4
typedef Z_U4 z_crc_t;
#else
typedef unsigned long z_crc_t;
#endif
#ifdef HAVE_UNISTD_H /* may be set to #if 1 by ./configure */
# define Z_HAVE_UNISTD_H
#endif
#ifdef HAVE_STDARG_H /* may be set to #if 1 by ./configure */
# define Z_HAVE_STDARG_H
#endif
#ifdef STDC
# ifndef Z_SOLO
# include <sys/types.h> /* for off_t */
# endif
#endif
#if defined(STDC) || defined(Z_HAVE_STDARG_H)
# ifndef Z_SOLO
# include <stdarg.h> /* for va_list */
# endif
#endif
#ifdef _WIN32
# ifndef Z_SOLO
# include <stddef.h> /* for wchar_t */
# endif
#endif
/* a little trick to accommodate both "#define _LARGEFILE64_SOURCE" and
* "#define _LARGEFILE64_SOURCE 1" as requesting 64-bit operations, (even
* though the former does not conform to the LFS document), but considering
* both "#undef _LARGEFILE64_SOURCE" and "#define _LARGEFILE64_SOURCE 0" as
* equivalently requesting no 64-bit operations
*/
#if defined(_LARGEFILE64_SOURCE) && -_LARGEFILE64_SOURCE - -1 == 1
# undef _LARGEFILE64_SOURCE
#endif
#if defined(__WATCOMC__) && !defined(Z_HAVE_UNISTD_H)
# define Z_HAVE_UNISTD_H
#endif
#ifndef Z_SOLO
# if defined(Z_HAVE_UNISTD_H) || defined(_LARGEFILE64_SOURCE)
# include <unistd.h> /* for SEEK_*, off_t, and _LFS64_LARGEFILE */
# ifdef VMS
# include <unixio.h> /* for off_t */
# endif
# ifndef z_off_t
# define z_off_t off_t
# endif
# endif
#endif
#if defined(_LFS64_LARGEFILE) && _LFS64_LARGEFILE-0
# define Z_LFS64
#endif
#if defined(_LARGEFILE64_SOURCE) && defined(Z_LFS64)
# define Z_LARGE64
#endif
#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS-0 == 64 && defined(Z_LFS64)
# define Z_WANT64
#endif
#if !defined(SEEK_SET) && !defined(Z_SOLO)
# define SEEK_SET 0 /* Seek from beginning of file. */
# define SEEK_CUR 1 /* Seek from current position. */
# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */
#endif
#ifndef z_off_t
# define z_off_t long
#endif
#if !defined(_WIN32) && defined(Z_LARGE64)
# define z_off64_t off64_t
#else
# if defined(_WIN32) && !defined(__GNUC__) && !defined(Z_SOLO)
# define z_off64_t __int64
# else
# define z_off64_t z_off_t
# endif
#endif
/* MVS linker does not support external names larger than 8 bytes */
#if defined(__MVS__)
#pragma map(deflateInit_,"DEIN")
#pragma map(deflateInit2_,"DEIN2")
#pragma map(deflateEnd,"DEEND")
#pragma map(deflateBound,"DEBND")
#pragma map(inflateInit_,"ININ")
#pragma map(inflateInit2_,"ININ2")
#pragma map(inflateEnd,"INEND")
#pragma map(inflateSync,"INSY")
#pragma map(inflateSetDictionary,"INSEDI")
#pragma map(compressBound,"CMBND")
#pragma map(inflate_table,"INTABL")
#pragma map(inflate_fast,"INFA")
#pragma map(inflate_copyright,"INCOPY")
#endif
#endif /* ZCONF_H */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,3 @@
# out-of-source build top-level folders.
build/
_build/

View File

@ -0,0 +1,51 @@
# This file is part of CMake-sanitizers.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
#
# Copyright (c)
# 2013-2015 Matt Arsenault
# 2015 RWTH Aachen University, Federal Republic of Germany
#
#
# project information
#
# minimum required cmake version
cmake_minimum_required(VERSION 2.8)
# project name
project("CMake-sanitizers")
#
# cmake configuration
#
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
#
# add tests
#
enable_testing()
add_subdirectory(tests)

22
third_party/sanitizers-cmake/LICENSE vendored Normal file
View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c)
2013 Matthew Arsenault
2015-2016 RWTH Aachen University, Federal Republic of Germany
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

73
third_party/sanitizers-cmake/README.md vendored Normal file
View File

@ -0,0 +1,73 @@
# sanitizers-cmake
[![](https://img.shields.io/github/issues-raw/arsenm/sanitizers-cmake.svg?style=flat-square)](https://github.com/arsenm/sanitizers-cmake/issues)
[![MIT](http://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](LICENSE)
CMake module to enable sanitizers for binary targets.
## Include into your project
To use [FindSanitizers.cmake](cmake/FindSanitizers.cmake), simply add this repository as git submodule into your own repository
```Shell
mkdir externals
git submodule add git://github.com/arsenm/sanitizers-cmake.git externals/sanitizers-cmake
```
and adding ```externals/sanitizers-cmake/cmake``` to your ```CMAKE_MODULE_PATH```
```CMake
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/externals/sanitizers-cmake/cmake" ${CMAKE_MODULE_PATH})
```
If you don't use git or dislike submodules you can copy the files in [cmake directory](cmake) into your repository. *Be careful and keep updates in mind!*
Now you can simply run ```find_package``` in your CMake files:
```CMake
find_package(Sanitizers)
```
## Usage
You can enable the sanitizers with ``SANITIZE_ADDRESS``, ``SANITIZE_MEMORY``, ``SANITIZE_THREAD`` or ``SANITIZE_UNDEFINED`` options in your CMake configuration. You can do this by passing e.g. ``-DSANITIZE_ADDRESS=On`` on your command line or with your graphical interface.
If sanitizers are supported by your compiler, the specified targets will be build with sanitizer support. If your compiler has no sanitizing capabilities (I asume intel compiler doesn't) you'll get a warning but CMake will continue processing and sanitizing will simply just be ignored.
#### Compiler issues
Different compilers may be using different implementations for sanitizers. If you'll try to sanitize targets with C and Fortran code but don't use gcc & gfortran but clang & gfortran, this will cause linking problems. To avoid this, such problems will be detected and sanitizing will be disabled for these targets.
Even C only targets may cause problems in certain situations. Some problems have been seen with AddressSanitizer for preloading or dynamic linking. In such cases you may try the ``SANITIZE_LINK_STATIC`` to link sanitizers for gcc static.
## Build targets with sanitizer support
To enable sanitizer support you simply have to add ``add_sanitizers(<TARGET>)`` after defining your target. To provide a sanitizer blacklist file you can use the ``sanitizer_add_blacklist_file(<FILE>)`` function:
```CMake
find_package(Sanitizers)
sanitizer_add_blacklist_file("blacklist.txt")
add_executable(some_exe foo.c bar.c)
add_sanitizers(some_exe)
add_library(some_lib foo.c bar.c)
add_sanitizers(some_lib)
```
## Run your application
The sanitizers check your program, while it's running. In some situations (e.g. LD_PRELOAD your target) it might be required to preload the used AddressSanitizer library first. In this case you may use the ``asan-wrapper`` script defined in ``ASan_WRAPPER`` variable to execute your application with ``${ASan_WRAPPER} myexe arg1 ...``.
## Contribute
Anyone is welcome to contribute. Simply fork this repository, make your changes **in an own branch** and create a pull-request for your change. Please do only one change per pull-request.
You found a bug? Please fill out an [issue](https://github.com/arsenm/sanitizers-cmake/issues) and include any data to reproduce the bug.
#### Contributors
* [Matt Arsenault](https://github.com/arsenm)
* [Alexander Haase](https://github.com/alehaa)

View File

@ -0,0 +1,59 @@
# The MIT License (MIT)
#
# Copyright (c)
# 2013 Matthew Arsenault
# 2015-2016 RWTH Aachen University, Federal Republic of Germany
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
option(SANITIZE_ADDRESS "Enable AddressSanitizer for sanitized targets." Off)
set(FLAG_CANDIDATES
# Clang 3.2+ use this version. The no-omit-frame-pointer option is optional.
"-g -fsanitize=address -fno-omit-frame-pointer"
"-g -fsanitize=address"
# Older deprecated flag for ASan
"-g -faddress-sanitizer"
)
if (SANITIZE_ADDRESS AND (SANITIZE_THREAD OR SANITIZE_MEMORY))
message(FATAL_ERROR "AddressSanitizer is not compatible with "
"ThreadSanitizer or MemorySanitizer.")
endif ()
include(sanitize-helpers)
if (SANITIZE_ADDRESS)
sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" "AddressSanitizer"
"ASan")
find_program(ASan_WRAPPER "asan-wrapper" PATHS ${CMAKE_MODULE_PATH})
mark_as_advanced(ASan_WRAPPER)
endif ()
function (add_sanitize_address TARGET)
if (NOT SANITIZE_ADDRESS)
return()
endif ()
sanitizer_add_flags(${TARGET} "AddressSanitizer" "ASan")
endfunction ()

View File

@ -0,0 +1,57 @@
# The MIT License (MIT)
#
# Copyright (c)
# 2013 Matthew Arsenault
# 2015-2016 RWTH Aachen University, Federal Republic of Germany
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
option(SANITIZE_MEMORY "Enable MemorySanitizer for sanitized targets." Off)
set(FLAG_CANDIDATES
"-g -fsanitize=memory"
)
include(sanitize-helpers)
if (SANITIZE_MEMORY)
if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
message(WARNING "MemorySanitizer disabled for target ${TARGET} because "
"MemorySanitizer is supported for Linux systems only.")
set(SANITIZE_MEMORY Off CACHE BOOL
"Enable MemorySanitizer for sanitized targets." FORCE)
elseif (NOT ${CMAKE_SIZEOF_VOID_P} EQUAL 8)
message(WARNING "MemorySanitizer disabled for target ${TARGET} because "
"MemorySanitizer is supported for 64bit systems only.")
set(SANITIZE_MEMORY Off CACHE BOOL
"Enable MemorySanitizer for sanitized targets." FORCE)
else ()
sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" "MemorySanitizer"
"MSan")
endif ()
endif ()
function (add_sanitize_memory TARGET)
if (NOT SANITIZE_MEMORY)
return()
endif ()
sanitizer_add_flags(${TARGET} "MemorySanitizer" "MSan")
endfunction ()

View File

@ -0,0 +1,87 @@
# The MIT License (MIT)
#
# Copyright (c)
# 2013 Matthew Arsenault
# 2015-2016 RWTH Aachen University, Federal Republic of Germany
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# If any of the used compiler is a GNU compiler, add a second option to static
# link against the sanitizers.
option(SANITIZE_LINK_STATIC "Try to link static against sanitizers." Off)
set(FIND_QUIETLY_FLAG "")
if (DEFINED Sanitizers_FIND_QUIETLY)
set(FIND_QUIETLY_FLAG "QUIET")
endif ()
find_package(ASan ${FIND_QUIETLY_FLAG})
find_package(TSan ${FIND_QUIETLY_FLAG})
find_package(MSan ${FIND_QUIETLY_FLAG})
find_package(UBSan ${FIND_QUIETLY_FLAG})
function(sanitizer_add_blacklist_file FILE)
if(NOT IS_ABSOLUTE ${FILE})
set(FILE "${CMAKE_CURRENT_SOURCE_DIR}/${FILE}")
endif()
get_filename_component(FILE "${FILE}" REALPATH)
sanitizer_check_compiler_flags("-fsanitize-blacklist=${FILE}"
"SanitizerBlacklist" "SanBlist")
endfunction()
function(add_sanitizers ...)
# If no sanitizer is enabled, return immediately.
if (NOT (SANITIZE_ADDRESS OR SANITIZE_MEMORY OR SANITIZE_THREAD OR
SANITIZE_UNDEFINED))
return()
endif ()
foreach (TARGET ${ARGV})
# Check if this target will be compiled by exactly one compiler. Other-
# wise sanitizers can't be used and a warning should be printed once.
sanitizer_target_compilers(${TARGET} TARGET_COMPILER)
list(LENGTH TARGET_COMPILER NUM_COMPILERS)
if (NUM_COMPILERS GREATER 1)
message(WARNING "Can't use any sanitizers for target ${TARGET}, "
"because it will be compiled by incompatible compilers. "
"Target will be compiled without sanitizers.")
return()
# If the target is compiled by no known compiler, ignore it.
elseif (NUM_COMPILERS EQUAL 0)
message(WARNING "Can't use any sanitizers for target ${TARGET}, "
"because it uses an unknown compiler. Target will be "
"compiled without sanitizers.")
return()
endif ()
# Add sanitizers for target.
add_sanitize_address(${TARGET})
add_sanitize_thread(${TARGET})
add_sanitize_memory(${TARGET})
add_sanitize_undefined(${TARGET})
endforeach ()
endfunction(add_sanitizers)

View File

@ -0,0 +1,65 @@
# The MIT License (MIT)
#
# Copyright (c)
# 2013 Matthew Arsenault
# 2015-2016 RWTH Aachen University, Federal Republic of Germany
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
option(SANITIZE_THREAD "Enable ThreadSanitizer for sanitized targets." Off)
set(FLAG_CANDIDATES
"-g -fsanitize=thread"
)
# ThreadSanitizer is not compatible with MemorySanitizer.
if (SANITIZE_THREAD AND SANITIZE_MEMORY)
message(FATAL_ERROR "ThreadSanitizer is not compatible with "
"MemorySanitizer.")
endif ()
include(sanitize-helpers)
if (SANITIZE_THREAD)
if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" AND
NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
message(WARNING "ThreadSanitizer disabled for target ${TARGET} because "
"ThreadSanitizer is supported for Linux systems and macOS only.")
set(SANITIZE_THREAD Off CACHE BOOL
"Enable ThreadSanitizer for sanitized targets." FORCE)
elseif (NOT ${CMAKE_SIZEOF_VOID_P} EQUAL 8)
message(WARNING "ThreadSanitizer disabled for target ${TARGET} because "
"ThreadSanitizer is supported for 64bit systems only.")
set(SANITIZE_THREAD Off CACHE BOOL
"Enable ThreadSanitizer for sanitized targets." FORCE)
else ()
sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" "ThreadSanitizer"
"TSan")
endif ()
endif ()
function (add_sanitize_thread TARGET)
if (NOT SANITIZE_THREAD)
return()
endif ()
sanitizer_add_flags(${TARGET} "ThreadSanitizer" "TSan")
endfunction ()

View File

@ -0,0 +1,46 @@
# The MIT License (MIT)
#
# Copyright (c)
# 2013 Matthew Arsenault
# 2015-2016 RWTH Aachen University, Federal Republic of Germany
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
option(SANITIZE_UNDEFINED
"Enable UndefinedBehaviorSanitizer for sanitized targets." Off)
set(FLAG_CANDIDATES
"-g -fsanitize=undefined"
)
include(sanitize-helpers)
if (SANITIZE_UNDEFINED)
sanitizer_check_compiler_flags("${FLAG_CANDIDATES}"
"UndefinedBehaviorSanitizer" "UBSan")
endif ()
function (add_sanitize_undefined TARGET)
if (NOT SANITIZE_UNDEFINED)
return()
endif ()
sanitizer_add_flags(${TARGET} "UndefinedBehaviorSanitizer" "UBSan")
endfunction ()

View File

@ -0,0 +1,55 @@
#!/bin/sh
# The MIT License (MIT)
#
# Copyright (c)
# 2013 Matthew Arsenault
# 2015-2016 RWTH Aachen University, Federal Republic of Germany
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# This script is a wrapper for AddressSanitizer. In some special cases you need
# to preload AddressSanitizer to avoid error messages - e.g. if you're
# preloading another library to your application. At the moment this script will
# only do something, if we're running on a Linux platform. OSX might not be
# affected.
# Exit immediately, if platform is not Linux.
if [ "$(uname)" != "Linux" ]
then
exec $@
fi
# Get the used libasan of the application ($1). If a libasan was found, it will
# be prepended to LD_PRELOAD.
libasan=$(ldd $1 | grep libasan | sed "s/^[[:space:]]//" | cut -d' ' -f1)
if [ -n "$libasan" ]
then
if [ -n "$LD_PRELOAD" ]
then
export LD_PRELOAD="$libasan:$LD_PRELOAD"
else
export LD_PRELOAD="$libasan"
fi
fi
# Execute the application.
exec $@

View File

@ -0,0 +1,170 @@
# The MIT License (MIT)
#
# Copyright (c)
# 2013 Matthew Arsenault
# 2015-2016 RWTH Aachen University, Federal Republic of Germany
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# Helper function to get the language of a source file.
function (sanitizer_lang_of_source FILE RETURN_VAR)
get_filename_component(FILE_EXT "${FILE}" EXT)
string(TOLOWER "${FILE_EXT}" FILE_EXT)
string(SUBSTRING "${FILE_EXT}" 1 -1 FILE_EXT)
get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES)
foreach (LANG ${ENABLED_LANGUAGES})
list(FIND CMAKE_${LANG}_SOURCE_FILE_EXTENSIONS "${FILE_EXT}" TEMP)
if (NOT ${TEMP} EQUAL -1)
set(${RETURN_VAR} "${LANG}" PARENT_SCOPE)
return()
endif ()
endforeach()
set(${RETURN_VAR} "" PARENT_SCOPE)
endfunction ()
# Helper function to get compilers used by a target.
function (sanitizer_target_compilers TARGET RETURN_VAR)
# Check if all sources for target use the same compiler. If a target uses
# e.g. C and Fortran mixed and uses different compilers (e.g. clang and
# gfortran) this can trigger huge problems, because different compilers may
# use different implementations for sanitizers.
set(BUFFER "")
get_target_property(TSOURCES ${TARGET} SOURCES)
foreach (FILE ${TSOURCES})
# If expression was found, FILE is a generator-expression for an object
# library. Object libraries will be ignored.
string(REGEX MATCH "TARGET_OBJECTS:([^ >]+)" _file ${FILE})
if ("${_file}" STREQUAL "")
sanitizer_lang_of_source(${FILE} LANG)
if (LANG)
list(APPEND BUFFER ${CMAKE_${LANG}_COMPILER_ID})
endif ()
endif ()
endforeach ()
list(REMOVE_DUPLICATES BUFFER)
set(${RETURN_VAR} "${BUFFER}" PARENT_SCOPE)
endfunction ()
# Helper function to check compiler flags for language compiler.
function (sanitizer_check_compiler_flag FLAG LANG VARIABLE)
if (${LANG} STREQUAL "C")
include(CheckCCompilerFlag)
check_c_compiler_flag("${FLAG}" ${VARIABLE})
elseif (${LANG} STREQUAL "CXX")
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag("${FLAG}" ${VARIABLE})
elseif (${LANG} STREQUAL "Fortran")
# CheckFortranCompilerFlag was introduced in CMake 3.x. To be compatible
# with older Cmake versions, we will check if this module is present
# before we use it. Otherwise we will define Fortran coverage support as
# not available.
include(CheckFortranCompilerFlag OPTIONAL RESULT_VARIABLE INCLUDED)
if (INCLUDED)
check_fortran_compiler_flag("${FLAG}" ${VARIABLE})
elseif (NOT CMAKE_REQUIRED_QUIET)
message(STATUS "Performing Test ${VARIABLE}")
message(STATUS "Performing Test ${VARIABLE}"
" - Failed (Check not supported)")
endif ()
endif()
endfunction ()
# Helper function to test compiler flags.
function (sanitizer_check_compiler_flags FLAG_CANDIDATES NAME PREFIX)
set(CMAKE_REQUIRED_QUIET ${${PREFIX}_FIND_QUIETLY})
get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES)
foreach (LANG ${ENABLED_LANGUAGES})
# Sanitizer flags are not dependend on language, but the used compiler.
# So instead of searching flags foreach language, search flags foreach
# compiler used.
set(COMPILER ${CMAKE_${LANG}_COMPILER_ID})
if (NOT DEFINED ${PREFIX}_${COMPILER}_FLAGS)
foreach (FLAG ${FLAG_CANDIDATES})
if(NOT CMAKE_REQUIRED_QUIET)
message(STATUS "Try ${COMPILER} ${NAME} flag = [${FLAG}]")
endif()
set(CMAKE_REQUIRED_FLAGS "${FLAG}")
unset(${PREFIX}_FLAG_DETECTED CACHE)
sanitizer_check_compiler_flag("${FLAG}" ${LANG}
${PREFIX}_FLAG_DETECTED)
if (${PREFIX}_FLAG_DETECTED)
# If compiler is a GNU compiler, search for static flag, if
# SANITIZE_LINK_STATIC is enabled.
if (SANITIZE_LINK_STATIC AND (${COMPILER} STREQUAL "GNU"))
string(TOLOWER ${PREFIX} PREFIX_lower)
sanitizer_check_compiler_flag(
"-static-lib${PREFIX_lower}" ${LANG}
${PREFIX}_STATIC_FLAG_DETECTED)
if (${PREFIX}_STATIC_FLAG_DETECTED)
set(FLAG "-static-lib${PREFIX_lower} ${FLAG}")
endif ()
endif ()
set(${PREFIX}_${COMPILER}_FLAGS "${FLAG}" CACHE STRING
"${NAME} flags for ${COMPILER} compiler.")
mark_as_advanced(${PREFIX}_${COMPILER}_FLAGS)
break()
endif ()
endforeach ()
if (NOT ${PREFIX}_FLAG_DETECTED)
set(${PREFIX}_${COMPILER}_FLAGS "" CACHE STRING
"${NAME} flags for ${COMPILER} compiler.")
mark_as_advanced(${PREFIX}_${COMPILER}_FLAGS)
message(WARNING "${NAME} is not available for ${COMPILER} "
"compiler. Targets using this compiler will be "
"compiled without ${NAME}.")
endif ()
endif ()
endforeach ()
endfunction ()
# Helper to assign sanitizer flags for TARGET.
function (sanitizer_add_flags TARGET NAME PREFIX)
# Get list of compilers used by target and check, if sanitizer is available
# for this target. Other compiler checks like check for conflicting
# compilers will be done in add_sanitizers function.
sanitizer_target_compilers(${TARGET} TARGET_COMPILER)
list(LENGTH TARGET_COMPILER NUM_COMPILERS)
if ("${${PREFIX}_${TARGET_COMPILER}_FLAGS}" STREQUAL "")
return()
endif()
# Set compile- and link-flags for target.
set_property(TARGET ${TARGET} APPEND_STRING
PROPERTY COMPILE_FLAGS " ${${PREFIX}_${TARGET_COMPILER}_FLAGS}")
set_property(TARGET ${TARGET} APPEND_STRING
PROPERTY COMPILE_FLAGS " ${SanBlist_${TARGET_COMPILER}_FLAGS}")
set_property(TARGET ${TARGET} APPEND_STRING
PROPERTY LINK_FLAGS " ${${PREFIX}_${TARGET_COMPILER}_FLAGS}")
endfunction ()

View File

@ -0,0 +1,52 @@
# This file is part of CMake-sanitizers.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
#
# Copyright (c)
# 2013-2015 Matt Arsenault
# 2015 RWTH Aachen University, Federal Republic of Germany
#
# Function to add testcases.
function(add_testcase TESTNAME SOURCEFILES)
# remove ${TESTNAME} from ${ARGV} to use ${ARGV} as ${SOURCEFILES}
list(REMOVE_AT ARGV 0)
# add a new executable
add_executable(${TESTNAME} ${ARGV})
add_sanitizers(${TESTNAME})
# add a testcase for executable
add_test(${TESTNAME} ${TESTNAME})
endfunction(add_testcase)
#
# search for sanitizers
#
find_package(Sanitizers)
#
# add testcases
#
add_testcase("asan_test_cpp" asan_test.cpp)

View File

@ -0,0 +1,39 @@
/* This file is part of CMake-sanitizers.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*
* Copyright (c)
* 2013-2015 Matt Arsenault
* 2015 RWTH Aachen University, Federal Republic of Germany
*/
int
main(int argc, char **argv)
{
// Allocate a new array and delete it.
int *array = new int[argc];
delete[] array;
/* Access element of the deleted array. This will cause an memory error with
* address sanitizer.
*/
return array[argc];
}