Compare commits

...

21 Commits

Author SHA1 Message Date
Benjamin Sergeant
a6199f1009 ping / pong support / fix bug in dispatching received message type 2018-10-25 14:40:58 -07:00
Benjamin Sergeant
69093953da Better ping/pong support 2018-10-25 12:01:47 -07:00
Benjamin Sergeant
64e88d617b more examples 2018-10-09 17:45:28 -07:00
Benjamin Sergeant
5b4ca7f9df more headers 2018-10-09 16:54:17 -07:00
Benjamin Sergeant
71bac1ea7d missing header 2018-10-09 16:20:08 -07:00
Benjamin Sergeant
9c9e7f3206 missing header 2018-10-09 16:11:12 -07:00
Benjamin Sergeant
e691ac3704 missing endif 2018-10-09 16:07:50 -07:00
Benjamin Sergeant
6fcecb84eb missing files in cmake 2018-10-09 16:02:00 -07:00
Benjamin Sergeant
e26cd1faba implement secureSocket 2018-10-09 15:59:40 -07:00
Benjamin Sergeant
1f659c34bd add stub for windows tls socket class 2018-10-09 15:55:44 -07:00
Benjamin Sergeant
93f16018f7 typo 2018-10-09 15:25:17 -07:00
Benjamin Sergeant
a3cfd6810b linking directives for winsock 2018-10-09 15:17:40 -07:00
Benjamin Sergeant
f7b87be65b win tls wip 2018-10-09 14:43:25 -07:00
Benjamin Sergeant
739a43988c New ws_connect example. Close to wscat node.js tool. 2018-10-09 14:35:08 -07:00
Benjamin Sergeant
16805759d3 Windows support (no TLS yet) 2018-10-08 21:44:54 -07:00
Benjamin Sergeant
88c2e1f6de make TLS support optional 2018-10-08 15:24:36 -07:00
Benjamin Sergeant
1dc9b559e9 move examples around 2018-10-08 15:24:36 -07:00
Benjamin Sergeant
d31ecfc64e
Update IXWebSocket.h
Remove dead code
2018-10-07 15:49:07 -07:00
Benjamin Sergeant
4813a40f2a
Update README.md
Advanced usage -> API
2018-10-07 15:47:38 -07:00
Benjamin Sergeant
ea81470f4a more ssl peer validation stuff 2018-10-05 18:45:44 -07:00
Benjamin Sergeant
2a6b1d5f15
Update README.md 2018-10-05 14:35:09 -07:00
27 changed files with 887 additions and 115 deletions

49
CMakeLists.txt Normal file
View File

@ -0,0 +1,49 @@
#
# cmd_websocket_chat.cpp
# Author: Benjamin Sergeant
# Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
#
cmake_minimum_required(VERSION 3.4.1)
project(ixwebsocket C CXX)
set (CMAKE_CXX_STANDARD 11)
set (CXX_STANDARD_REQUIRED ON)
set (CMAKE_CXX_EXTENSIONS OFF)
set( IXWEBSOCKET_SOURCES
ixwebsocket/IXSocket.cpp
ixwebsocket/IXWebSocket.cpp
ixwebsocket/IXWebSocketTransport.cpp
)
set( IXWEBSOCKET_HEADERS
ixwebsocket/IXSocket.h
ixwebsocket/IXWebSocket.h
ixwebsocket/IXWebSocketTransport.h
)
if (USE_TLS)
add_definitions(-DIXWEBSOCKET_USE_TLS)
if (APPLE)
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketAppleSSL.h)
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketAppleSSL.cpp)
elseif (WIN32)
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketSChannel.h)
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketSChannel.cpp)
else()
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketOpenSSL.h)
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketOpenSSL.cpp)
endif()
endif()
add_library( ixwebsocket STATIC
${IXWEBSOCKET_SOURCES}
${IXWEBSOCKET_HEADERS}
)
set( IXWEBSOCKET_INCLUDE_DIRS
.
../../shared/OpenSSL/include)
target_include_directories( ixwebsocket PUBLIC ${IXWEBSOCKET_INCLUDE_DIRS} )

View File

@ -36,14 +36,18 @@ webSocket.send("hello world");
// ... finally ... // ... finally ...
// Stop the connection // Stop the connection
webSocket:stop() webSocket.stop()
``` ```
## Build
CMakefiles for the library and the examples are available. This library has few dependencies, so it is possible to just add the source files into your project.
## Implementation details ## Implementation details
### TLS/SSL ### TLS/SSL
Connections can be optionally secured and encrypted with TLS/SSL when using a wss:// endpoint, or using normal un-encrypted socket with ws:// endpoints. AppleSSL is used on iOS and OpenSSL is used on Android. Connections can be optionally secured and encrypted with TLS/SSL when using a wss:// endpoint, or using normal un-encrypted socket with ws:// endpoints. AppleSSL is used on iOS and macOS, and OpenSSL is used on Android and Linux.
### Polling and background thread work ### Polling and background thread work
@ -65,8 +69,8 @@ If the remote end (server) breaks the connection, the code will try to perpetual
2. Compile the example C++ code. `sh build.sh` 2. Compile the example C++ code. `sh build.sh`
3. Install node.js from [here](https://nodejs.org/en/download/). 3. Install node.js from [here](https://nodejs.org/en/download/).
4. Type `npm install` to install the node.js dependencies. Then `node broadcast-server.js` to run the server. 4. Type `npm install` to install the node.js dependencies. Then `node broadcast-server.js` to run the server.
5. Bring up a second terminal. `env USER=bob ./cmd_websocket_chat` 5. Bring up a second terminal. `./cmd_websocket_chat bob`
6. Bring up a third terminal. `env USER=bill ./cmd_websocket_chat` 6. Bring up a third terminal. `./cmd_websocket_chat bill`
7. Start typing things in any of those terminals. Hopefully you should see your message being received on the other end. 7. Start typing things in any of those terminals. Hopefully you should see your message being received on the other end.
## C++ code organization ## C++ code organization
@ -91,7 +95,7 @@ Here's a simplistic diagram which explains how the code is structured in term of
+-----------------------+ +-----------------------+
``` ```
## Advanced usage ## API
### Sending messages ### Sending messages

View File

@ -0,0 +1,28 @@
#
# cmd_websocket_chat.cpp
# Author: Benjamin Sergeant
# Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
#
cmake_minimum_required (VERSION 3.4.1)
project (cmd_websocket_chat)
set (CMAKE_CXX_STANDARD 11)
option(USE_TLS "Add TLS support" ON)
add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket)
add_executable(cmd_websocket_chat cmd_websocket_chat.cpp)
if (APPLE AND USE_TLS)
target_link_libraries(cmd_websocket_chat "-framework foundation" "-framework security")
endif()
if (WIN32)
target_link_libraries(cmd_websocket_chat wsock32 ws2_32)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
endif()
target_link_libraries(cmd_websocket_chat ixwebsocket)
install(TARGETS cmd_websocket_chat DESTINATION bin)

39
examples/chat/README.md Normal file
View File

@ -0,0 +1,39 @@
# Building
1. cmake -G .
2. make
## Disable TLS
chat$ cmake -DUSE_TLS=OFF .
-- Configuring done
-- Generating done
-- Build files have been written to: /Users/bsergeant/src/foss/ixwebsocket/examples/chat
chat$ make
Scanning dependencies of target ixwebsocket
[ 16%] Building CXX object ixwebsocket/CMakeFiles/ixwebsocket.dir/ixwebsocket/IXSocket.cpp.o
[ 33%] Building CXX object ixwebsocket/CMakeFiles/ixwebsocket.dir/ixwebsocket/IXWebSocket.cpp.o
[ 50%] Building CXX object ixwebsocket/CMakeFiles/ixwebsocket.dir/ixwebsocket/IXWebSocketTransport.cpp.o
[ 66%] Linking CXX static library libixwebsocket.a
[ 66%] Built target ixwebsocket
[ 83%] Linking CXX executable cmd_websocket_chat
[100%] Built target cmd_websocket_chat
## Enable TLS (default)
```
chat$ cmake -DUSE_TLS=ON .
-- Configuring done
-- Generating done
-- Build files have been written to: /Users/bsergeant/src/foss/ixwebsocket/examples/chat
(venv) chat$ make
Scanning dependencies of target ixwebsocket
[ 14%] Building CXX object ixwebsocket/CMakeFiles/ixwebsocket.dir/ixwebsocket/IXSocket.cpp.o
[ 28%] Building CXX object ixwebsocket/CMakeFiles/ixwebsocket.dir/ixwebsocket/IXWebSocket.cpp.o
[ 42%] Building CXX object ixwebsocket/CMakeFiles/ixwebsocket.dir/ixwebsocket/IXWebSocketTransport.cpp.o
[ 57%] Building CXX object ixwebsocket/CMakeFiles/ixwebsocket.dir/ixwebsocket/IXSocketAppleSSL.cpp.o
[ 71%] Linking CXX static library libixwebsocket.a
[ 71%] Built target ixwebsocket
[ 85%] Linking CXX executable cmd_websocket_chat
[100%] Built target cmd_websocket_chat
```

View File

@ -4,11 +4,13 @@
# Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved. # Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
# #
# 'manual' way of building. You can also use cmake.
clang++ --std=c++11 --stdlib=libc++ \ clang++ --std=c++11 --stdlib=libc++ \
../ixwebsocket/IXSocket.cpp \ ../../ixwebsocket/IXSocket.cpp \
../ixwebsocket/IXWebSocketTransport.cpp \ ../../ixwebsocket/IXWebSocketTransport.cpp \
../ixwebsocket/IXSocketAppleSSL.cpp \ ../../ixwebsocket/IXSocketAppleSSL.cpp \
../ixwebsocket/IXWebSocket.cpp \ ../../ixwebsocket/IXWebSocket.cpp \
cmd_websocket_chat.cpp \ cmd_websocket_chat.cpp \
-o cmd_websocket_chat \ -o cmd_websocket_chat \
-framework Security \ -framework Security \

View File

@ -12,7 +12,8 @@
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <queue> #include <queue>
#include "../ixwebsocket/IXWebSocket.h" #include <ixwebsocket/IXWebSocket.h>
#include <ixwebsocket/IXSocket.h>
#include "nlohmann/json.hpp" #include "nlohmann/json.hpp"
@ -158,13 +159,10 @@ namespace
_webSocket.send(encodeMessage(text)); _webSocket.send(encodeMessage(text));
} }
void interactiveMain() void interactiveMain(const std::string& user)
{ {
std::string user(getenv("USER"));
WebSocketChat webSocketChat(user);
std::cout << "Type Ctrl-D to exit prompt..." << std::endl; std::cout << "Type Ctrl-D to exit prompt..." << std::endl;
WebSocketChat webSocketChat(user);
webSocketChat.start(); webSocketChat.start();
while (true) while (true)
@ -186,8 +184,15 @@ namespace
} }
} }
int main() int main(int argc, char** argv)
{ {
interactiveMain(); std::string user("user");
if (argc == 2)
{
user = argv[1];
}
Socket::init();
interactiveMain(user);
return 0; return 0;
} }

1
examples/ping_pong/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
venv

View File

@ -0,0 +1,27 @@
#
# Author: Benjamin Sergeant
# Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
#
cmake_minimum_required (VERSION 3.4.1)
project (ping_pong)
set (CMAKE_CXX_STANDARD 11)
option(USE_TLS "Add TLS support" ON)
add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket)
add_executable(ping_pong ping_pong.cpp)
if (APPLE AND USE_TLS)
target_link_libraries(ping_pong "-framework foundation" "-framework security")
endif()
if (WIN32)
target_link_libraries(ping_pong wsock32 ws2_32)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
endif()
target_link_libraries(ping_pong ixwebsocket)
install(TARGETS ping_pong DESTINATION bin)

View File

@ -0,0 +1,17 @@
#!/usr/bin/env python
import asyncio
import websockets
async def hello(uri):
async with websockets.connect(uri) as websocket:
await websocket.send("Hello world!")
response = await websocket.recv()
print(response)
pong_waiter = await websocket.ping('coucou')
ret = await pong_waiter # only if you want to wait for the pong
print(ret)
asyncio.get_event_loop().run_until_complete(
hello('ws://localhost:5678'))

View File

@ -0,0 +1,145 @@
/*
* ws_connect.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
*/
#include <iostream>
#include <sstream>
#include <ixwebsocket/IXWebSocket.h>
#include <ixwebsocket/IXSocket.h>
using namespace ix;
namespace
{
void log(const std::string& msg)
{
std::cout << msg << std::endl;
}
class WebSocketPingPong
{
public:
WebSocketPingPong(const std::string& _url);
void subscribe(const std::string& channel);
void start();
void stop();
void ping(const std::string& text);
private:
std::string _url;
ix::WebSocket _webSocket;
};
WebSocketPingPong::WebSocketPingPong(const std::string& url) :
_url(url)
{
;
}
void WebSocketPingPong::stop()
{
_webSocket.stop();
}
void WebSocketPingPong::start()
{
_webSocket.configure(_url);
std::stringstream ss;
log(std::string("Connecting to url: ") + _url);
_webSocket.setOnMessageCallback(
[this](ix::WebSocketMessageType messageType, const std::string& str, ix::WebSocketErrorInfo error)
{
std::stringstream ss;
if (messageType == ix::WebSocket_MessageType_Open)
{
log("ws_connect: connected");
}
else if (messageType == ix::WebSocket_MessageType_Close)
{
log("ws_connect: disconnected");
}
else if (messageType == ix::WebSocket_MessageType_Message)
{
ss << "ws_connect: received message: "
<< str;
log(ss.str());
}
else if (messageType == ix::WebSocket_MessageType_Ping)
{
ss << "ws_connect: received ping message: "
<< str;
log(ss.str());
}
else if (messageType == ix::WebSocket_MessageType_Pong)
{
ss << "ws_connect: received pong message: "
<< str;
log(ss.str());
}
else if (messageType == ix::WebSocket_MessageType_Error)
{
ss << "Connection error: " << error.reason << std::endl;
ss << "#retries: " << error.retries << std::endl;
ss << "Wait time(ms): " << error.wait_time << std::endl;
ss << "HTTP Status: " << error.http_status << std::endl;
log(ss.str());
}
else
{
ss << "Invalid ix::WebSocketMessageType";
log(ss.str());
}
});
_webSocket.start();
}
void WebSocketPingPong::ping(const std::string& text)
{
_webSocket.ping(text);
}
void interactiveMain(const std::string& url)
{
std::cout << "Type Ctrl-D to exit prompt..." << std::endl;
WebSocketPingPong webSocketPingPong(url);
webSocketPingPong.start();
while (true)
{
std::string text;
std::cout << "> " << std::flush;
std::getline(std::cin, text);
if (!std::cin)
{
break;
}
webSocketPingPong.ping(text);
}
std::cout << std::endl;
webSocketPingPong.stop();
}
}
int main(int argc, char** argv)
{
if (argc != 2)
{
std::cerr << "Usage: ping_pong <url>" << std::endl;
return 1;
}
std::string url = argv[1];
Socket::init();
interactiveMain(url);
return 0;
}

View File

@ -0,0 +1,13 @@
#!/usr/bin/env python
import asyncio
import websockets
async def echo(websocket, path):
async for message in websocket:
print(message)
await websocket.send(message)
asyncio.get_event_loop().run_until_complete(
websockets.serve(echo, 'localhost', 5678))
asyncio.get_event_loop().run_forever()

View File

@ -0,0 +1,9 @@
#!/bin/sh
test -d build || {
mkdir -p build
cd build
cmake ..
}
(cd build ; make)
./build/ping_pong ws://localhost:5678

View File

@ -0,0 +1,27 @@
#
# Author: Benjamin Sergeant
# Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
#
cmake_minimum_required (VERSION 3.4.1)
project (ws_connect)
set (CMAKE_CXX_STANDARD 11)
option(USE_TLS "Add TLS support" ON)
add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket)
add_executable(ws_connect ws_connect.cpp)
if (APPLE AND USE_TLS)
target_link_libraries(ws_connect "-framework foundation" "-framework security")
endif()
if (WIN32)
target_link_libraries(ws_connect wsock32 ws2_32)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
endif()
target_link_libraries(ws_connect ixwebsocket)
install(TARGETS ws_connect DESTINATION bin)

View File

@ -0,0 +1,11 @@
# Building
1. mkdir build
2. cd build
3. cmake ..
4. make
## Disable TLS
* Enable: `cmake -DUSE_TLS=OFF ..`
* Disable: `cmake -DUSE_TLS=ON ..`

View File

@ -0,0 +1,133 @@
/*
* ws_connect.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
*/
#include <iostream>
#include <sstream>
#include <ixwebsocket/IXWebSocket.h>
#include <ixwebsocket/IXSocket.h>
using namespace ix;
namespace
{
void log(const std::string& msg)
{
std::cout << msg << std::endl;
}
class WebSocketConnect
{
public:
WebSocketConnect(const std::string& _url);
void subscribe(const std::string& channel);
void start();
void stop();
void sendMessage(const std::string& text);
private:
std::string _url;
ix::WebSocket _webSocket;
};
WebSocketConnect::WebSocketConnect(const std::string& url) :
_url(url)
{
;
}
void WebSocketConnect::stop()
{
_webSocket.stop();
}
void WebSocketConnect::start()
{
_webSocket.configure(_url);
std::stringstream ss;
log(std::string("Connecting to url: ") + _url);
_webSocket.setOnMessageCallback(
[this](ix::WebSocketMessageType messageType, const std::string& str, ix::WebSocketErrorInfo error)
{
std::stringstream ss;
if (messageType == ix::WebSocket_MessageType_Open)
{
log("ws_connect: connected");
}
else if (messageType == ix::WebSocket_MessageType_Close)
{
log("ws_connect: disconnected");
}
else if (messageType == ix::WebSocket_MessageType_Message)
{
ss << "ws_connect: received message: "
<< str;
log(ss.str());
}
else if (messageType == ix::WebSocket_MessageType_Error)
{
ss << "Connection error: " << error.reason << std::endl;
ss << "#retries: " << error.retries << std::endl;
ss << "Wait time(ms): " << error.wait_time << std::endl;
ss << "HTTP Status: " << error.http_status << std::endl;
log(ss.str());
}
else
{
ss << "Invalid ix::WebSocketMessageType";
log(ss.str());
}
});
_webSocket.start();
}
void WebSocketConnect::sendMessage(const std::string& text)
{
_webSocket.send(text);
}
void interactiveMain(const std::string& url)
{
std::cout << "Type Ctrl-D to exit prompt..." << std::endl;
WebSocketConnect webSocketChat(url);
webSocketChat.start();
while (true)
{
std::string text;
std::cout << "> " << std::flush;
std::getline(std::cin, text);
if (!std::cin)
{
break;
}
webSocketChat.sendMessage(text);
}
std::cout << std::endl;
webSocketChat.stop();
}
}
int main(int argc, char** argv)
{
if (argc != 2)
{
std::cerr << "Usage: ws_connect <url>" << std::endl;
return 1;
}
std::string url = argv[1];
Socket::init();
interactiveMain(url);
return 0;
}

View File

@ -6,22 +6,32 @@
#include "IXSocket.h" #include "IXSocket.h"
#include <netdb.h> #ifdef _WIN32
#include <netinet/tcp.h> # 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 <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/select.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h> #include <assert.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/types.h>
#include <algorithm>
// //
// Linux/Android has a special type of virtual files. select(2) will react // Linux/Android has a special type of virtual files. select(2) will react
@ -35,7 +45,7 @@
// cf Android/Kernel table here // cf Android/Kernel table here
// https://android.stackexchange.com/questions/51651/which-android-runs-which-linux-kernel // https://android.stackexchange.com/questions/51651/which-android-runs-which-linux-kernel
// //
#ifndef __APPLE__ #ifdef __linux__
# include <sys/eventfd.h> # include <sys/eventfd.h>
#endif #endif
@ -51,7 +61,7 @@ namespace ix
_sockfd(-1), _sockfd(-1),
_eventfd(-1) _eventfd(-1)
{ {
#ifndef __APPLE__ #ifdef __linux__
_eventfd = eventfd(0, 0); _eventfd = eventfd(0, 0);
assert(_eventfd != -1 && "Panic - eventfd not functioning on this platform"); assert(_eventfd != -1 && "Panic - eventfd not functioning on this platform");
#endif #endif
@ -61,14 +71,14 @@ namespace ix
{ {
close(); close();
#ifndef __APPLE__ #ifdef __linux__
::close(_eventfd); ::close(_eventfd);
#endif #endif
} }
bool connectToAddress(const struct addrinfo *address, bool Socket::connectToAddress(const struct addrinfo *address,
int& sockfd, int& sockfd,
std::string& errMsg) std::string& errMsg)
{ {
sockfd = -1; sockfd = -1;
@ -84,7 +94,7 @@ namespace ix
int maxRetries = 3; int maxRetries = 3;
for (int i = 0; i < maxRetries; ++i) for (int i = 0; i < maxRetries; ++i)
{ {
if (connect(fd, address->ai_addr, address->ai_addrlen) != -1) if (::connect(fd, address->ai_addr, address->ai_addrlen) != -1)
{ {
sockfd = fd; sockfd = fd;
return true; return true;
@ -94,7 +104,7 @@ namespace ix
if (errno != EINTR) break; if (errno != EINTR) break;
} }
::close(fd); closeSocket(fd);
sockfd = -1; sockfd = -1;
errMsg = strerror(errno); errMsg = strerror(errno);
return false; return false;
@ -142,7 +152,13 @@ namespace ix
{ {
int flag = 1; int flag = 1;
setsockopt(_sockfd, IPPROTO_TCP, TCP_NODELAY, (char*) &flag, sizeof(flag)); // Disable Nagle's algorithm setsockopt(_sockfd, IPPROTO_TCP, TCP_NODELAY, (char*) &flag, sizeof(flag)); // Disable Nagle's algorithm
#ifdef _WIN32
unsigned long nonblocking = 1;
ioctlsocket(_sockfd, FIONBIO, &nonblocking);
#else
fcntl(_sockfd, F_SETFL, O_NONBLOCK); // make socket non blocking fcntl(_sockfd, F_SETFL, O_NONBLOCK); // make socket non blocking
#endif
#ifdef SO_NOSIGPIPE #ifdef SO_NOSIGPIPE
int value = 1; int value = 1;
@ -163,12 +179,12 @@ namespace ix
FD_ZERO(&rfds); FD_ZERO(&rfds);
FD_SET(_sockfd, &rfds); FD_SET(_sockfd, &rfds);
#ifndef __APPLE__ #ifdef __linux__
FD_SET(_eventfd, &rfds); FD_SET(_eventfd, &rfds);
#endif #endif
int sockfd = _sockfd; int sockfd = _sockfd;
int nfds = std::max(sockfd, _eventfd); int nfds = (std::max)(sockfd, _eventfd);
select(nfds + 1, &rfds, nullptr, nullptr, nullptr); select(nfds + 1, &rfds, nullptr, nullptr, nullptr);
onPollCallback(); onPollCallback();
@ -191,7 +207,7 @@ namespace ix
{ {
#ifdef __APPLE__ #ifdef __APPLE__
wakeUpFromPollApple(); wakeUpFromPollApple();
#else #elif defined(__linux__)
wakeUpFromPollLinux(); wakeUpFromPollLinux();
#endif #endif
} }
@ -202,7 +218,7 @@ namespace ix
{ {
std::lock_guard<std::mutex> lock(_socketMutex); std::lock_guard<std::mutex> lock(_socketMutex);
#ifndef __APPLE__ #ifdef __linux__
if (_eventfd == -1) if (_eventfd == -1)
{ {
return false; // impossible to use this socket if eventfd is broken return false; // impossible to use this socket if eventfd is broken
@ -219,7 +235,7 @@ namespace ix
if (_sockfd == -1) return; if (_sockfd == -1) return;
::close(_sockfd); closeSocket(_sockfd);
_sockfd = -1; _sockfd = -1;
} }
@ -245,7 +261,49 @@ namespace ix
flags = MSG_NOSIGNAL; flags = MSG_NOSIGNAL;
#endif #endif
return (int) ::recv(_sockfd, buffer, length, flags); return (int) ::recv(_sockfd, (char*) buffer, length, flags);
} }
int Socket::getErrno() const
{
#ifdef _WIN32
return WSAGetLastError();
#else
return errno;
#endif
}
void Socket::closeSocket(int fd)
{
#ifdef _WIN32
closesocket(fd);
#else
::close(fd);
#endif
}
bool Socket::init()
{
#ifdef _WIN32
INT rc;
WSADATA wsaData;
rc = WSAStartup(MAKEWORD(2, 2), &wsaData);
return rc != 0;
#else
return true;
#endif
}
void Socket::cleanup()
{
#ifdef _WIN32
WSACleanup();
#endif
}
void Socket::secureSocket()
{
;
}
} }

View File

@ -11,6 +11,8 @@
#include <mutex> #include <mutex>
#include <atomic> #include <atomic>
struct addrinfo;
namespace ix namespace ix
{ {
class Socket { class Socket {
@ -20,9 +22,9 @@ namespace ix
Socket(); Socket();
virtual ~Socket(); virtual ~Socket();
static int hostname_connect(const std::string& hostname, int hostname_connect(const std::string& hostname,
int port, int port,
std::string& errMsg); std::string& errMsg);
void configure(); void configure();
virtual void poll(const OnPollCallback& onPollCallback); virtual void poll(const OnPollCallback& onPollCallback);
@ -38,13 +40,26 @@ namespace ix
virtual int send(const std::string& buffer); virtual int send(const std::string& buffer);
virtual int recv(void* buffer, size_t length); virtual int recv(void* buffer, size_t length);
virtual void secureSocket(); // Windows
int getErrno() const;
static bool init(); // Required on Windows to initialize WinSocket
static void cleanup(); // Required on Windows to cleanup WinSocket
protected: protected:
void wakeUpFromPollApple(); void wakeUpFromPollApple();
void wakeUpFromPollLinux(); void wakeUpFromPollLinux();
void closeSocket(int fd);
std::atomic<int> _sockfd; std::atomic<int> _sockfd;
int _eventfd; int _eventfd;
std::mutex _socketMutex; std::mutex _socketMutex;
private:
bool connectToAddress(const struct addrinfo *address,
int& sockfd,
std::string& errMsg);
}; };
} }

View File

@ -12,6 +12,7 @@
#include <openssl/x509v3.h> #include <openssl/x509v3.h>
#include <fnmatch.h>
#include <errno.h> #include <errno.h>
#define socketerrno errno #define socketerrno errno
@ -158,51 +159,10 @@ namespace ix
/** /**
* Check whether a hostname matches a pattern * Check whether a hostname matches a pattern
*
* The pattern MUST contain at most a single, leading asterisk. This means that
* this function cannot serve as a generic validation function, as that would
* allow for partial wildcards, too. Also, this does not check whether the
* wildcard covers multiple levels of labels. For RTM, this suffices, as we
* are only interested in the main domain name.
*
* @param[in] hostname The hostname of the server
* @param[in] pattern The hostname pattern from a SSL certificate
* @return TRUE if the pattern matches, FALSE otherwise
*/ */
bool SocketOpenSSL::checkHost(const std::string& host, const char *pattern) bool SocketOpenSSL::checkHost(const std::string& host, const char *pattern)
{ {
const char* hostname = host.c_str(); return fnmatch(pattern, host.c_str(), 0) != FNM_NOMATCH;
while (*pattern && *hostname)
{
if (*pattern == '*')
{
while (*hostname != '.' && *hostname) hostname++;
if (*(++pattern) != '.')
{
return false;
}
}
else
{
char p = *pattern;
char h = *hostname;
if ((p & ~32) >= 'A' && (p & ~32) <= 'Z')
{
p &= ~32;
h &= ~32;
}
if (*pattern != *hostname)
{
return false;
}
}
pattern++;
hostname++;
}
bool success = !(*hostname || *pattern);
return success;
} }
bool SocketOpenSSL::openSSLCheckServerCert(SSL *ssl, bool SocketOpenSSL::openSSLCheckServerCert(SSL *ssl,
@ -354,6 +314,15 @@ namespace ix
// SNI support // SNI support
SSL_set_tlsext_host_name(_ssl_connection, host.c_str()); SSL_set_tlsext_host_name(_ssl_connection, host.c_str());
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
// Support for server name verification
// (The docs say that this should work from 1.0.2, and is the default from
// 1.1.0, but it does not. To be on the safe side, the manual test below is
// enabled for all versions prior to 1.1.0.)
X509_VERIFY_PARAM *param = SSL_get0_param(_ssl_connection);
X509_VERIFY_PARAM_set1_host(param, host.c_str(), 0);
#endif
handshakeSuccessful = openSSLHandshake(host, errMsg); handshakeSuccessful = openSSLHandshake(host, errMsg);
} }

View File

@ -0,0 +1,108 @@
/*
* IXSocketSChannel.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*
* See https://docs.microsoft.com/en-us/windows/desktop/WinSock/using-secure-socket-extensions
*
* https://github.com/pauldotknopf/WindowsSDK7-Samples/blob/master/netds/winsock/securesocket/stcpclient/tcpclient.c
*
* This is the right example to look at:
* https://www.codeproject.com/Articles/1000189/A-Working-TCP-Client-and-Server-With-SSL
*/
#include "IXSocketSChannel.h"
#ifdef _WIN32
# include <basetsd.h>
# include <WinSock2.h>
# include <ws2def.h>
# include <WS2tcpip.h>
# include <schannel.h>
# include <sslsock.h>
# include <io.h>
#define WIN32_LEAN_AND_MEAN
#ifndef UNICODE
#define UNICODE
#endif
#include <windows.h>
#include <winsock2.h>
#include <mstcpip.h>
#include <ws2tcpip.h>
#include <rpc.h>
#include <ntdsapi.h>
#include <stdio.h>
#include <tchar.h>
#define RECV_DATA_BUF_SIZE 256
// Link with ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")
// link with fwpuclnt.lib for Winsock secure socket extensions
#pragma comment(lib, "fwpuclnt.lib")
// link with ntdsapi.lib for DsMakeSpn function
#pragma comment(lib, "ntdsapi.lib")
// The following function assumes that Winsock
// has already been initialized
#else
# error("This file should only be built on Windows")
#endif
namespace ix
{
SocketSChannel::SocketSChannel()
{
;
}
SocketSChannel::~SocketSChannel()
{
}
bool SocketSChannel::connect(const std::string& host,
int port,
std::string& errMsg)
{
return Socket::connect(host, port, errMsg);
}
void SocketSChannel::secureSocket()
{
// there will be a lot to do here ...
// FIXME do something with sockerror
}
void SocketSChannel::close()
{
Socket::close();
}
int SocketSChannel::send(char* buf, size_t nbyte)
{
return Socket::send(buf, nbyte);
}
int SocketSChannel::send(const std::string& buffer)
{
return Socket::send(buffer);
}
int SocketSChannel::recv(void* buf, size_t nbyte)
{
return Socket::recv(buf, nbyte);
}
}

View File

@ -0,0 +1,34 @@
/*
* IXSocketSChannel.h
* Author: Benjamin Sergeant
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXSocket.h"
namespace ix
{
class SocketSChannel : public Socket
{
public:
SocketSChannel();
~SocketSChannel();
virtual bool connect(const std::string& host,
int port,
std::string& errMsg) final;
virtual void close() final;
// The important override
virtual void secureSocket() 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;
private:
};
}

View File

@ -177,9 +177,29 @@ namespace ix {
// 3. Dispatch the incoming messages // 3. Dispatch the incoming messages
_ws.dispatch( _ws.dispatch(
[this](const std::string& msg) [this](const std::string& msg,
WebSocketTransport::MessageKind messageKind)
{ {
_onMessageCallback(WebSocket_MessageType_Message, msg, WebSocketErrorInfo()); WebSocketMessageType webSocketMessageType;
switch (messageKind)
{
case WebSocketTransport::MSG:
{
webSocketMessageType = WebSocket_MessageType_Message;
} break;
case WebSocketTransport::PING:
{
webSocketMessageType = WebSocket_MessageType_Ping;
} break;
case WebSocketTransport::PONG:
{
webSocketMessageType = WebSocket_MessageType_Pong;
} break;
}
_onMessageCallback(webSocketMessageType, msg, WebSocketErrorInfo());
WebSocket::invokeTrafficTrackerCallback(msg.size(), true); WebSocket::invokeTrafficTrackerCallback(msg.size(), true);
}); });
@ -210,6 +230,20 @@ namespace ix {
} }
bool WebSocket::send(const std::string& text) bool WebSocket::send(const std::string& text)
{
return sendMessage(text, false);
}
bool WebSocket::ping(const std::string& text)
{
// Standard limit ping message size
constexpr size_t pingMaxPayloadSize = 125;
if (text.size() > pingMaxPayloadSize) return false;
return sendMessage(text, true);
}
bool WebSocket::sendMessage(const std::string& text, bool ping)
{ {
if (!isConnected()) return false; if (!isConnected()) return false;
@ -223,7 +257,15 @@ namespace ix {
// incoming messages are arriving / there's data to be received. // incoming messages are arriving / there's data to be received.
// //
std::lock_guard<std::mutex> lock(_writeMutex); std::lock_guard<std::mutex> lock(_writeMutex);
_ws.sendBinary(text);
if (ping)
{
_ws.sendPing(text);
}
else
{
_ws.sendBinary(text);
}
WebSocket::invokeTrafficTrackerCallback(text.size(), false); WebSocket::invokeTrafficTrackerCallback(text.size(), false);

View File

@ -32,7 +32,9 @@ namespace ix
WebSocket_MessageType_Message = 0, WebSocket_MessageType_Message = 0,
WebSocket_MessageType_Open = 1, WebSocket_MessageType_Open = 1,
WebSocket_MessageType_Close = 2, WebSocket_MessageType_Close = 2,
WebSocket_MessageType_Error = 3 WebSocket_MessageType_Error = 3,
WebSocket_MessageType_Ping = 4,
WebSocket_MessageType_Pong = 5
}; };
struct WebSocketErrorInfo struct WebSocketErrorInfo
@ -56,6 +58,7 @@ namespace ix
void start(); void start();
void stop(); void stop();
bool send(const std::string& text); bool send(const std::string& text);
bool ping(const std::string& text);
void close(); void close();
void setOnMessageCallback(const OnMessageCallback& callback); void setOnMessageCallback(const OnMessageCallback& callback);
@ -70,6 +73,8 @@ namespace ix
private: private:
void run(); void run();
bool sendMessage(const std::string& text, bool ping);
WebSocketInitResult connect(); WebSocketInitResult connect();
bool isConnected() const; bool isConnected() const;
bool isClosing() const; bool isClosing() const;
@ -90,7 +95,5 @@ namespace ix
std::atomic<bool> _automaticReconnection; std::atomic<bool> _automaticReconnection;
std::thread _thread; std::thread _thread;
std::mutex _writeMutex; std::mutex _writeMutex;
static int kHeartBeatPeriod;
}; };
} }

View File

@ -11,15 +11,18 @@
#include "IXWebSocketTransport.h" #include "IXWebSocketTransport.h"
#include "IXSocket.h" #include "IXSocket.h"
#ifdef __APPLE__ #ifdef IXWEBSOCKET_USE_TLS
# include "IXSocketAppleSSL.h" # ifdef __APPLE__
#else # include "IXSocketAppleSSL.h"
# include "IXSocketOpenSSL.h" # elif defined(__linux__)
# include "IXSocketOpenSSL.h"
# elif defined(_WIN32)
# include "IXSocketSChannel.h"
# endif
#endif #endif
#include <unistd.h>
#include <errno.h>
#include <string.h> #include <string.h>
#include <stdlib.h>
#include <cstdlib> #include <cstdlib>
#include <vector> #include <vector>
@ -140,10 +143,16 @@ namespace ix {
if (protocol == "wss") if (protocol == "wss")
{ {
_socket.reset(); _socket.reset();
#ifdef __APPLE__ #ifdef IXWEBSOCKET_USE_TLS
_socket = std::make_shared<SocketAppleSSL>(); # ifdef __APPLE__
_socket = std::make_shared<SocketAppleSSL>();
# elif defined(__linux__)
_socket = std::make_shared<SocketOpenSSL>();
# elif defined(_WIN32)
_socket = std::make_shared<SocketSChannel>();
# endif
#else #else
_socket = std::make_shared<SocketOpenSSL>(); return WebSocketInitResult(false, 0, "TLS is not supported.");
#endif #endif
} }
else else
@ -267,12 +276,12 @@ namespace ix {
{ {
int N = (int) _rxbuf.size(); int N = (int) _rxbuf.size();
ssize_t ret; int ret;
_rxbuf.resize(N + 1500); _rxbuf.resize(N + 1500);
ret = _socket->recv((char*)&_rxbuf[0] + N, 1500); ret = _socket->recv((char*)&_rxbuf[0] + N, 1500);
if (ret < 0 && (errno == EWOULDBLOCK || if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK ||
errno == EAGAIN)) { _socket->getErrno() == EAGAIN)) {
_rxbuf.resize(N); _rxbuf.resize(N);
break; break;
} }
@ -443,7 +452,7 @@ namespace ix {
// fire callback with a string message // fire callback with a string message
std::string stringMessage(_receivedData.begin(), std::string stringMessage(_receivedData.begin(),
_receivedData.end()); _receivedData.end());
onMessageCallback(stringMessage); onMessageCallback(stringMessage, MSG);
_receivedData.clear(); _receivedData.clear();
} }
@ -461,10 +470,27 @@ namespace ix {
std::string pingData(_rxbuf.begin()+ws.header_size, std::string pingData(_rxbuf.begin()+ws.header_size,
_rxbuf.begin()+ws.header_size + (size_t) ws.N); _rxbuf.begin()+ws.header_size + (size_t) ws.N);
// Reply back right away
sendData(wsheader_type::PONG, pingData.size(), sendData(wsheader_type::PONG, pingData.size(),
pingData.begin(), pingData.end()); pingData.begin(), pingData.end());
onMessageCallback(pingData, PING);
}
else if (ws.opcode == wsheader_type::PONG)
{
if (ws.mask)
{
for (size_t j = 0; j != ws.N; ++j)
{
_rxbuf[j+ws.header_size] ^= ws.masking_key[j&0x3];
}
}
std::string pongData(_rxbuf.begin()+ws.header_size,
_rxbuf.begin()+ws.header_size + (size_t) ws.N);
onMessageCallback(pongData, PONG);
} }
else if (ws.opcode == wsheader_type::PONG) { }
else if (ws.opcode == wsheader_type::CLOSE) { close(); } else if (ws.opcode == wsheader_type::CLOSE) { close(); }
else { close(); } else { close(); }
@ -550,10 +576,9 @@ namespace ix {
sendOnSocket(); sendOnSocket();
} }
void WebSocketTransport::sendPing() void WebSocketTransport::sendPing(const std::string& message)
{ {
std::string empty; sendData(wsheader_type::PING, message.size(), message.begin(), message.end());
sendData(wsheader_type::PING, empty.size(), empty.begin(), empty.end());
} }
void WebSocketTransport::sendBinary(const std::string& message) void WebSocketTransport::sendBinary(const std::string& message)
@ -569,8 +594,8 @@ namespace ix {
{ {
int ret = _socket->send((char*)&_txbuf[0], _txbuf.size()); int ret = _socket->send((char*)&_txbuf[0], _txbuf.size());
if (ret < 0 && (errno == EWOULDBLOCK || if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK ||
errno == EAGAIN)) _socket->getErrno() == EAGAIN))
{ {
break; break;
} }

View File

@ -54,7 +54,15 @@ namespace ix
OPEN OPEN
}; };
using OnMessageCallback = std::function<void(const std::string&)>; enum MessageKind
{
MSG,
PING,
PONG
};
using OnMessageCallback = std::function<void(const std::string&,
MessageKind)>;
using OnStateChangeCallback = std::function<void(ReadyStateValues)>; using OnStateChangeCallback = std::function<void(ReadyStateValues)>;
WebSocketTransport(); WebSocketTransport();
@ -67,7 +75,7 @@ namespace ix
void send(const std::string& message); void send(const std::string& message);
void sendBinary(const std::string& message); void sendBinary(const std::string& message);
void sendBinary(const std::vector<uint8_t>& message); void sendBinary(const std::vector<uint8_t>& message);
void sendPing(); void sendPing(const std::string& message);
void close(); void close();
ReadyStateValues getReadyState() const; ReadyStateValues getReadyState() const;
void setReadyState(ReadyStateValues readyStateValue); void setReadyState(ReadyStateValues readyStateValue);