Add netease_voice projects
This commit is contained in:
parent
73c1fc0013
commit
0bd780b103
Binary file not shown.
Binary file not shown.
|
@ -1,6 +1,6 @@
|
|||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=jansson
|
||||
PKG_NAME:=libjansson
|
||||
PKG_VERSION:=2.11
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
|
@ -12,41 +12,39 @@ PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
|||
CMAKE_INSTALL:=1
|
||||
PKG_RELEASE=$(PKG_SOURCE_VERSION)
|
||||
|
||||
|
||||
CMAKE_OPTIONS += -DJANSSON_BUILD_SHARED_LIBS=1
|
||||
|
||||
PKG_BUILD_DIR:=$(COMPILE_DIR)/$(PKG_SOURCE_SUBDIR)
|
||||
|
||||
include $(BUILD_DIR)/package.mk
|
||||
include $(BUILD_DIR)/cmake.mk
|
||||
|
||||
define Package/libjansson
|
||||
SECTION:=libs
|
||||
define Package/$(PKG_NAME)
|
||||
SECTION:=$(PKG_NAME)
|
||||
CATEGORY:=Libraries
|
||||
DEPENDS:=+libc
|
||||
TITLE:=A small library for encoding, decoding and manipulating JSON data
|
||||
TITLE:=Libjansson is Ultralightweight JSON parser in ANSI C
|
||||
endef
|
||||
|
||||
define Package/libjansson/description
|
||||
Jansson is a C library for encoding, decoding and manipulating JSON data. It features:
|
||||
Simple and intuitive API and data model
|
||||
Comprehensive documentation
|
||||
No dependencies on other libraries
|
||||
Full Unicode support (UTF-8)
|
||||
Extensive test suite
|
||||
endef
|
||||
|
||||
|
||||
define Package/libjansson/install
|
||||
$(INSTALL_DIR) $(1)/lib
|
||||
$(CP) $(PKG_INSTALL_DIR)/usr/lib/lib* $(1)/lib/
|
||||
define Package/$(PKG_NAME)/description
|
||||
Jansson is a C library for encoding, decoding and manipulating JSON data. It features:
|
||||
Simple and intuitive API and data model
|
||||
Comprehensive documentation
|
||||
No dependencies on other libraries
|
||||
Full Unicode support (UTF-8)
|
||||
Extensive test suite
|
||||
endef
|
||||
|
||||
define Build/InstallDev
|
||||
$(INSTALL_DIR) $(1)/usr/include/
|
||||
$(CP) $(PKG_INSTALL_DIR)/usr/include/*.h $(1)/usr/include/
|
||||
|
||||
$(INSTALL_DIR) $(1)/lib
|
||||
$(CP) $(PKG_INSTALL_DIR)/usr/lib/lib* $(1)/lib/
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/install
|
||||
$(INSTALL_DIR) $(1)/lib
|
||||
$(CP) $(PKG_INSTALL_DIR)/usr/lib/lib* $(1)/lib/
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,libjansson))
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,45 @@
|
|||
include $(TOPDIR)/rules.mk
|
||||
|
||||
# Name and release number of this package
|
||||
PKG_NAME:=libuws
|
||||
PKG_VERSION:=1.0.0
|
||||
|
||||
PKG_BUILD_DIR:=$(COMPILE_DIR)/$(PKG_NAME)
|
||||
|
||||
include $(BUILD_DIR)/package.mk
|
||||
|
||||
define Package/$(PKG_NAME)
|
||||
SECTION:=libs
|
||||
CATEGORY:=Libraries
|
||||
TITLE:=uws lib
|
||||
DEPENDS:=+libstdcpp
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/description
|
||||
This is a uws dynamic library by rokid added by wk!
|
||||
endef
|
||||
|
||||
define Build/Prepare
|
||||
mkdir -p $(PKG_BUILD_DIR)
|
||||
$(CP) -d ./lib/* $(PKG_BUILD_DIR)/
|
||||
endef
|
||||
|
||||
define Build/Configure
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Build/InstallDev
|
||||
$(INSTALL_DIR) $(1)/usr/lib
|
||||
$(INSTALL_DIR) $(1)/usr/include/uWS
|
||||
$(INSTALL_DATA) $(PKG_BUILD_DIR)/*.so $(1)/usr/lib
|
||||
$(INSTALL_DATA) $(PKG_BUILD_DIR)/include/*.h $(1)/usr/include/uWS
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/install
|
||||
$(INSTALL_DIR) $(1)/usr/lib
|
||||
$(CP) -d $(PKG_BUILD_DIR)/*.so $(1)/usr/lib/
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
|
@ -0,0 +1,184 @@
|
|||
#ifndef ASIO_H
|
||||
#define ASIO_H
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
typedef boost::asio::ip::tcp::socket::native_type uv_os_sock_t;
|
||||
static const int UV_READABLE = 1;
|
||||
static const int UV_WRITABLE = 2;
|
||||
|
||||
struct Loop : boost::asio::io_service {
|
||||
|
||||
static Loop *createLoop(bool defaultLoop = true) {
|
||||
return new Loop;
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
delete this;
|
||||
}
|
||||
|
||||
void run() {
|
||||
boost::asio::io_service::run();
|
||||
}
|
||||
};
|
||||
|
||||
struct Timer {
|
||||
boost::asio::deadline_timer asio_timer;
|
||||
void *data;
|
||||
|
||||
Timer(Loop *loop) : asio_timer(*loop) {
|
||||
|
||||
}
|
||||
|
||||
void start(void (*cb)(Timer *), int first, int repeat) {
|
||||
asio_timer.expires_from_now(boost::posix_time::milliseconds(first));
|
||||
asio_timer.async_wait([this, cb, repeat](const boost::system::error_code &ec) {
|
||||
if (ec != boost::asio::error::operation_aborted) {
|
||||
if (repeat) {
|
||||
start(cb, repeat, repeat);
|
||||
}
|
||||
cb(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void setData(void *data) {
|
||||
this->data = data;
|
||||
}
|
||||
|
||||
void *getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
// bug: cancel does not cancel expired timers!
|
||||
// it has to guarantee that the timer is not called after
|
||||
// stop is called! ffs boost!
|
||||
void stop() {
|
||||
asio_timer.cancel();
|
||||
}
|
||||
|
||||
void close() {
|
||||
asio_timer.get_io_service().post([this]() {
|
||||
delete this;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
struct Async {
|
||||
Loop *loop;
|
||||
void (*cb)(Async *);
|
||||
void *data;
|
||||
|
||||
boost::asio::io_service::work asio_work;
|
||||
|
||||
Async(Loop *loop) : loop(loop), asio_work(*loop) {
|
||||
}
|
||||
|
||||
void start(void (*cb)(Async *)) {
|
||||
this->cb = cb;
|
||||
}
|
||||
|
||||
void send() {
|
||||
loop->post([this]() {
|
||||
cb(this);
|
||||
});
|
||||
}
|
||||
|
||||
void close() {
|
||||
loop->post([this]() {
|
||||
delete this;
|
||||
});
|
||||
}
|
||||
|
||||
void setData(void *data) {
|
||||
this->data = data;
|
||||
}
|
||||
|
||||
void *getData() {
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
struct Poll {
|
||||
boost::asio::posix::stream_descriptor *socket;
|
||||
void (*cb)(Poll *p, int status, int events);
|
||||
|
||||
Poll(Loop *loop, uv_os_sock_t fd) {
|
||||
socket = new boost::asio::posix::stream_descriptor(*loop, fd);
|
||||
socket->non_blocking(true);
|
||||
}
|
||||
|
||||
bool isClosed() {
|
||||
return !socket;
|
||||
}
|
||||
|
||||
boost::asio::ip::tcp::socket::native_type getFd() {
|
||||
return socket ? socket->native_handle() : -1;
|
||||
}
|
||||
|
||||
void setCb(void (*cb)(Poll *p, int status, int events)) {
|
||||
this->cb = cb;
|
||||
}
|
||||
|
||||
void (*getCb())(Poll *, int, int) {
|
||||
return cb;
|
||||
}
|
||||
|
||||
void reInit(Loop *loop, uv_os_sock_t fd) {
|
||||
delete socket;
|
||||
socket = new boost::asio::posix::stream_descriptor(*loop, fd);
|
||||
socket->non_blocking(true);
|
||||
}
|
||||
|
||||
void start(Loop *, Poll *self, int events) {
|
||||
if (events & UV_READABLE) {
|
||||
socket->async_read_some(boost::asio::null_buffers(), [self](boost::system::error_code ec, std::size_t) {
|
||||
if (ec != boost::asio::error::operation_aborted) {
|
||||
self->start(nullptr, self, UV_READABLE);
|
||||
self->cb(self, ec ? -1 : 0, UV_READABLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (events & UV_WRITABLE) {
|
||||
socket->async_write_some(boost::asio::null_buffers(), [self](boost::system::error_code ec, std::size_t) {
|
||||
if (ec != boost::asio::error::operation_aborted) {
|
||||
self->start(nullptr, self, UV_WRITABLE);
|
||||
self->cb(self, ec ? -1 : 0, UV_WRITABLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void change(Loop *, Poll *self, int events) {
|
||||
socket->cancel();
|
||||
start(nullptr, self, events);
|
||||
}
|
||||
|
||||
bool fastTransfer(Loop *loop, Loop *newLoop, int events) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// todo: asio is thread safe, use it!
|
||||
bool threadSafeChange(Loop *loop, Poll *self, int events) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void stop(Loop *) {
|
||||
socket->cancel();
|
||||
}
|
||||
|
||||
// this is not correct, but it works for now
|
||||
// think about transfer - should allow one to not delete
|
||||
// but in this case it doesn't matter at all
|
||||
void close(Loop *loop, void (*cb)(Poll *)) {
|
||||
socket->release();
|
||||
socket->get_io_service().post([cb, this]() {
|
||||
cb(this);
|
||||
});
|
||||
delete socket;
|
||||
socket = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // ASIO_H
|
|
@ -0,0 +1,15 @@
|
|||
#ifndef BACKEND_H
|
||||
#define BACKEND_H
|
||||
|
||||
// Default to Epoll if nothing specified and on Linux
|
||||
// Default to Libuv if nothing specified and not on Linux
|
||||
#ifdef USE_ASIO
|
||||
#include "Asio.h"
|
||||
#elif !defined(__linux__) || defined(USE_LIBUV)
|
||||
#include "Libuv.h"
|
||||
#else
|
||||
#define USE_EPOLL
|
||||
#include "Epoll.h"
|
||||
#endif
|
||||
|
||||
#endif // BACKEND_H
|
|
@ -0,0 +1,257 @@
|
|||
#ifndef EPOLL_H
|
||||
#define EPOLL_H
|
||||
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
typedef int uv_os_sock_t;
|
||||
static const int UV_READABLE = EPOLLIN;
|
||||
static const int UV_WRITABLE = EPOLLOUT;
|
||||
|
||||
struct Poll;
|
||||
struct Timer;
|
||||
|
||||
extern std::recursive_mutex cbMutex;
|
||||
extern void (*callbacks[16])(Poll *, int, int);
|
||||
extern int cbHead;
|
||||
|
||||
struct Timepoint {
|
||||
void (*cb)(Timer *);
|
||||
Timer *timer;
|
||||
std::chrono::system_clock::time_point timepoint;
|
||||
int nextDelay;
|
||||
};
|
||||
|
||||
struct Loop {
|
||||
int epfd;
|
||||
int numPolls = 0;
|
||||
bool cancelledLastTimer;
|
||||
int delay = -1;
|
||||
epoll_event readyEvents[1024];
|
||||
std::chrono::system_clock::time_point timepoint;
|
||||
std::vector<Timepoint> timers;
|
||||
std::vector<std::pair<Poll *, void (*)(Poll *)>> closing;
|
||||
|
||||
void (*preCb)(void *) = nullptr;
|
||||
void (*postCb)(void *) = nullptr;
|
||||
void *preCbData, *postCbData;
|
||||
|
||||
Loop(bool defaultLoop) {
|
||||
epfd = epoll_create1(EPOLL_CLOEXEC);
|
||||
timepoint = std::chrono::system_clock::now();
|
||||
}
|
||||
|
||||
static Loop *createLoop(bool defaultLoop = true) {
|
||||
return new Loop(defaultLoop);
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
::close(epfd);
|
||||
delete this;
|
||||
}
|
||||
|
||||
void run();
|
||||
|
||||
int getEpollFd() {
|
||||
return epfd;
|
||||
}
|
||||
};
|
||||
|
||||
struct Timer {
|
||||
Loop *loop;
|
||||
void *data;
|
||||
|
||||
Timer(Loop *loop) {
|
||||
this->loop = loop;
|
||||
}
|
||||
|
||||
void start(void (*cb)(Timer *), int timeout, int repeat) {
|
||||
loop->timepoint = std::chrono::system_clock::now();
|
||||
std::chrono::system_clock::time_point timepoint = loop->timepoint + std::chrono::milliseconds(timeout);
|
||||
|
||||
Timepoint t = {cb, this, timepoint, repeat};
|
||||
loop->timers.insert(
|
||||
std::upper_bound(loop->timers.begin(), loop->timers.end(), t, [](const Timepoint &a, const Timepoint &b) {
|
||||
return a.timepoint < b.timepoint;
|
||||
}),
|
||||
t
|
||||
);
|
||||
|
||||
loop->delay = -1;
|
||||
if (loop->timers.size()) {
|
||||
loop->delay = std::max<int>(std::chrono::duration_cast<std::chrono::milliseconds>(loop->timers[0].timepoint - loop->timepoint).count(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
void setData(void *data) {
|
||||
this->data = data;
|
||||
}
|
||||
|
||||
void *getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
// always called before destructor
|
||||
void stop() {
|
||||
auto pos = loop->timers.begin();
|
||||
for (Timepoint &t : loop->timers) {
|
||||
if (t.timer == this) {
|
||||
loop->timers.erase(pos);
|
||||
break;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
loop->cancelledLastTimer = true;
|
||||
|
||||
loop->delay = -1;
|
||||
if (loop->timers.size()) {
|
||||
loop->delay = std::max<int>(std::chrono::duration_cast<std::chrono::milliseconds>(loop->timers[0].timepoint - loop->timepoint).count(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
void close() {
|
||||
delete this;
|
||||
}
|
||||
};
|
||||
|
||||
// 4 bytes
|
||||
struct Poll {
|
||||
protected:
|
||||
struct {
|
||||
int fd : 28;
|
||||
unsigned int cbIndex : 4;
|
||||
} state = {-1, 0};
|
||||
|
||||
Poll(Loop *loop, uv_os_sock_t fd) {
|
||||
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
|
||||
state.fd = fd;
|
||||
loop->numPolls++;
|
||||
}
|
||||
|
||||
// todo: pre-set all of callbacks up front and remove mutex
|
||||
void setCb(void (*cb)(Poll *p, int status, int events)) {
|
||||
cbMutex.lock();
|
||||
state.cbIndex = cbHead;
|
||||
for (int i = 0; i < cbHead; i++) {
|
||||
if (callbacks[i] == cb) {
|
||||
state.cbIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (state.cbIndex == cbHead) {
|
||||
callbacks[cbHead++] = cb;
|
||||
}
|
||||
cbMutex.unlock();
|
||||
}
|
||||
|
||||
void (*getCb())(Poll *, int, int) {
|
||||
return callbacks[state.cbIndex];
|
||||
}
|
||||
|
||||
void reInit(Loop *loop, uv_os_sock_t fd) {
|
||||
state.fd = fd;
|
||||
loop->numPolls++;
|
||||
}
|
||||
|
||||
void start(Loop *loop, Poll *self, int events) {
|
||||
epoll_event event;
|
||||
event.events = events;
|
||||
event.data.ptr = self;
|
||||
epoll_ctl(loop->epfd, EPOLL_CTL_ADD, state.fd, &event);
|
||||
}
|
||||
|
||||
void change(Loop *loop, Poll *self, int events) {
|
||||
epoll_event event;
|
||||
event.events = events;
|
||||
event.data.ptr = self;
|
||||
epoll_ctl(loop->epfd, EPOLL_CTL_MOD, state.fd, &event);
|
||||
}
|
||||
|
||||
void stop(Loop *loop) {
|
||||
epoll_event event;
|
||||
epoll_ctl(loop->epfd, EPOLL_CTL_DEL, state.fd, &event);
|
||||
}
|
||||
|
||||
bool fastTransfer(Loop *loop, Loop *newLoop, int events) {
|
||||
stop(loop);
|
||||
start(newLoop, this, events);
|
||||
loop->numPolls--;
|
||||
// needs to lock the newLoop's numPolls!
|
||||
newLoop->numPolls++;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool threadSafeChange(Loop *loop, Poll *self, int events) {
|
||||
change(loop, self, events);
|
||||
return true;
|
||||
}
|
||||
|
||||
void close(Loop *loop, void (*cb)(Poll *)) {
|
||||
state.fd = -1;
|
||||
loop->closing.push_back({this, cb});
|
||||
}
|
||||
|
||||
public:
|
||||
bool isClosed() {
|
||||
return state.fd == -1;
|
||||
}
|
||||
|
||||
uv_os_sock_t getFd() {
|
||||
return state.fd;
|
||||
}
|
||||
|
||||
friend struct Loop;
|
||||
};
|
||||
|
||||
// this should be put in the Loop as a general "post" function always available
|
||||
struct Async : Poll {
|
||||
void (*cb)(Async *);
|
||||
Loop *loop;
|
||||
void *data;
|
||||
|
||||
Async(Loop *loop) : Poll(loop, ::eventfd(0, EFD_CLOEXEC)) {
|
||||
this->loop = loop;
|
||||
}
|
||||
|
||||
void start(void (*cb)(Async *)) {
|
||||
this->cb = cb;
|
||||
Poll::setCb([](Poll *p, int, int) {
|
||||
uint64_t val;
|
||||
if (::read(((Async *) p)->state.fd, &val, 8) == 8) {
|
||||
((Async *) p)->cb((Async *) p);
|
||||
}
|
||||
});
|
||||
Poll::start(loop, this, UV_READABLE);
|
||||
}
|
||||
|
||||
void send() {
|
||||
uint64_t one = 1;
|
||||
if (::write(state.fd, &one, 8) != 8) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void close() {
|
||||
Poll::stop(loop);
|
||||
::close(state.fd);
|
||||
Poll::close(loop, [](Poll *p) {
|
||||
delete p;
|
||||
});
|
||||
}
|
||||
|
||||
void setData(void *data) {
|
||||
this->data = data;
|
||||
}
|
||||
|
||||
void *getData() {
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // EPOLL_H
|
|
@ -0,0 +1,29 @@
|
|||
#ifndef EXTENSIONS_UWS_H
|
||||
#define EXTENSIONS_UWS_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace uWS {
|
||||
|
||||
enum Options : unsigned int {
|
||||
NO_OPTIONS = 0,
|
||||
PERMESSAGE_DEFLATE = 1,
|
||||
SERVER_NO_CONTEXT_TAKEOVER = 2,
|
||||
CLIENT_NO_CONTEXT_TAKEOVER = 4,
|
||||
NO_DELAY = 8
|
||||
};
|
||||
|
||||
template <bool isServer>
|
||||
class ExtensionsNegotiator {
|
||||
private:
|
||||
int options;
|
||||
public:
|
||||
ExtensionsNegotiator(int wantedOptions);
|
||||
std::string generateOffer();
|
||||
void readOffer(std::string offer);
|
||||
int getNegotiatedOptions();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // EXTENSIONS_UWS_H
|
|
@ -0,0 +1,148 @@
|
|||
#ifndef GROUP_UWS_H
|
||||
#define GROUP_UWS_H
|
||||
|
||||
#include "WebSocket.h"
|
||||
#include "HTTPSocket.h"
|
||||
#include "Extensions.h"
|
||||
#include <functional>
|
||||
#include <stack>
|
||||
|
||||
namespace uWS {
|
||||
|
||||
enum ListenOptions {
|
||||
TRANSFERS
|
||||
};
|
||||
|
||||
struct Hub;
|
||||
|
||||
template <bool isServer>
|
||||
struct WIN32_EXPORT Group : private uS::NodeData {
|
||||
protected:
|
||||
friend struct Hub;
|
||||
friend struct WebSocket<isServer>;
|
||||
friend struct HttpSocket<false>;
|
||||
friend struct HttpSocket<true>;
|
||||
|
||||
std::function<void(WebSocket<isServer> *, HttpRequest)> connectionHandler;
|
||||
std::function<void(WebSocket<isServer> *)> transferHandler;
|
||||
std::function<void(WebSocket<isServer> *, char *message, size_t length, OpCode opCode)> messageHandler;
|
||||
std::function<void(WebSocket<isServer> *, int code, char *message, size_t length)> disconnectionHandler;
|
||||
std::function<void(WebSocket<isServer> *, char *, size_t)> pingHandler;
|
||||
std::function<void(WebSocket<isServer> *, char *, size_t)> pongHandler;
|
||||
std::function<void(HttpSocket<isServer> *)> httpConnectionHandler;
|
||||
std::function<void(HttpResponse *, HttpRequest, char *, size_t, size_t)> httpRequestHandler;
|
||||
std::function<void(HttpResponse *, char *, size_t, size_t)> httpDataHandler;
|
||||
std::function<void(HttpResponse *)> httpCancelledRequestHandler;
|
||||
std::function<void(HttpSocket<isServer> *)> httpDisconnectionHandler;
|
||||
std::function<void(HttpSocket<isServer> *, HttpRequest)> httpUpgradeHandler;
|
||||
|
||||
using errorType = typename std::conditional<isServer, int, void *>::type;
|
||||
std::function<void(errorType)> errorHandler;
|
||||
|
||||
unsigned int maxPayload;
|
||||
Hub *hub;
|
||||
int extensionOptions;
|
||||
Timer *timer = nullptr, *httpTimer = nullptr;
|
||||
std::string userPingMessage;
|
||||
std::stack<Poll *> iterators;
|
||||
|
||||
// todo: cannot be named user, collides with parent!
|
||||
void *userData = nullptr;
|
||||
static void timerCallback(Timer *timer);
|
||||
static void dummyCallback(Timer *timer);
|
||||
|
||||
WebSocket<isServer> *webSocketHead = nullptr;
|
||||
HttpSocket<isServer> *httpSocketHead = nullptr;
|
||||
|
||||
void addWebSocket(WebSocket<isServer> *webSocket);
|
||||
void removeWebSocket(WebSocket<isServer> *webSocket);
|
||||
|
||||
// todo: remove these, template
|
||||
void addHttpSocket(HttpSocket<isServer> *httpSocket);
|
||||
void removeHttpSocket(HttpSocket<isServer> *httpSocket);
|
||||
|
||||
Group(int extensionOptions, unsigned int maxPayload, Hub *hub, uS::NodeData *nodeData);
|
||||
void stopListening();
|
||||
|
||||
public:
|
||||
void onConnection(std::function<void(WebSocket<isServer> *, HttpRequest)> handler);
|
||||
void onTransfer(std::function<void(WebSocket<isServer> *)> handler);
|
||||
void onMessage(std::function<void(WebSocket<isServer> *, char *, size_t, OpCode)> handler);
|
||||
void onDisconnection(std::function<void(WebSocket<isServer> *, int code, char *message, size_t length)> handler);
|
||||
void onPing(std::function<void(WebSocket<isServer> *, char *, size_t)> handler);
|
||||
void onPong(std::function<void(WebSocket<isServer> *, char *, size_t)> handler);
|
||||
void onError(std::function<void(errorType)> handler);
|
||||
void onHttpConnection(std::function<void(HttpSocket<isServer> *)> handler);
|
||||
void onHttpRequest(std::function<void(HttpResponse *, HttpRequest, char *data, size_t length, size_t remainingBytes)> handler);
|
||||
void onHttpData(std::function<void(HttpResponse *, char *data, size_t length, size_t remainingBytes)> handler);
|
||||
void onHttpDisconnection(std::function<void(HttpSocket<isServer> *)> handler);
|
||||
void onCancelledHttpRequest(std::function<void(HttpResponse *)> handler);
|
||||
void onHttpUpgrade(std::function<void(HttpSocket<isServer> *, HttpRequest)> handler);
|
||||
|
||||
// Thread safe
|
||||
void broadcast(const char *message, size_t length, OpCode opCode);
|
||||
void setUserData(void *user);
|
||||
void *getUserData();
|
||||
|
||||
// Not thread safe
|
||||
void terminate();
|
||||
void close(int code = 1000, char *message = nullptr, size_t length = 0);
|
||||
void startAutoPing(int intervalMs, std::string userMessage = "");
|
||||
// workaround for loop not quit when server no response
|
||||
// if has timer, epoll_wait will awake periodic
|
||||
void setTimer(int intervalMs);
|
||||
|
||||
// same as listen(TRANSFERS), backwards compatible API for now
|
||||
void addAsync() {
|
||||
if (!async) {
|
||||
NodeData::addAsync();
|
||||
}
|
||||
}
|
||||
|
||||
void listen(ListenOptions listenOptions) {
|
||||
if (listenOptions == TRANSFERS && !async) {
|
||||
addAsync();
|
||||
}
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void forEach(const F &cb) {
|
||||
Poll *iterator = webSocketHead;
|
||||
iterators.push(iterator);
|
||||
while (iterator) {
|
||||
Poll *lastIterator = iterator;
|
||||
cb((WebSocket<isServer> *) iterator);
|
||||
iterator = iterators.top();
|
||||
if (lastIterator == iterator) {
|
||||
iterator = ((uS::Socket *) iterator)->next;
|
||||
iterators.top() = iterator;
|
||||
}
|
||||
}
|
||||
iterators.pop();
|
||||
}
|
||||
|
||||
// duplicated code for now!
|
||||
template <class F>
|
||||
void forEachHttpSocket(const F &cb) {
|
||||
Poll *iterator = httpSocketHead;
|
||||
iterators.push(iterator);
|
||||
while (iterator) {
|
||||
Poll *lastIterator = iterator;
|
||||
cb((HttpSocket<isServer> *) iterator);
|
||||
iterator = iterators.top();
|
||||
if (lastIterator == iterator) {
|
||||
iterator = ((uS::Socket *) iterator)->next;
|
||||
iterators.top() = iterator;
|
||||
}
|
||||
}
|
||||
iterators.pop();
|
||||
}
|
||||
|
||||
static Group<isServer> *from(uS::Socket *s) {
|
||||
return static_cast<Group<isServer> *>(s->getNodeData());
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // GROUP_UWS_H
|
|
@ -0,0 +1,285 @@
|
|||
#ifndef HTTPSOCKET_UWS_H
|
||||
#define HTTPSOCKET_UWS_H
|
||||
|
||||
#include "Socket.h"
|
||||
#include <string>
|
||||
// #include <experimental/string_view>
|
||||
|
||||
namespace uWS {
|
||||
|
||||
struct Header {
|
||||
char *key, *value;
|
||||
unsigned int keyLength, valueLength;
|
||||
|
||||
operator bool() {
|
||||
return key;
|
||||
}
|
||||
|
||||
// slow without string_view!
|
||||
std::string toString() {
|
||||
return std::string(value, valueLength);
|
||||
}
|
||||
};
|
||||
|
||||
enum HttpMethod {
|
||||
METHOD_GET,
|
||||
METHOD_POST,
|
||||
METHOD_PUT,
|
||||
METHOD_DELETE,
|
||||
METHOD_PATCH,
|
||||
METHOD_OPTIONS,
|
||||
METHOD_HEAD,
|
||||
METHOD_TRACE,
|
||||
METHOD_CONNECT,
|
||||
METHOD_INVALID
|
||||
};
|
||||
|
||||
struct HttpRequest {
|
||||
Header *headers;
|
||||
Header getHeader(const char *key) {
|
||||
return getHeader(key, strlen(key));
|
||||
}
|
||||
|
||||
HttpRequest(Header *headers = nullptr) : headers(headers) {}
|
||||
|
||||
Header getHeader(const char *key, size_t length) {
|
||||
if (headers) {
|
||||
for (Header *h = headers; *++h; ) {
|
||||
if (h->keyLength == length && !strncmp(h->key, key, length)) {
|
||||
return *h;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {nullptr, nullptr, 0, 0};
|
||||
}
|
||||
|
||||
Header getUrl() {
|
||||
if (headers->key) {
|
||||
return *headers;
|
||||
}
|
||||
return {nullptr, nullptr, 0, 0};
|
||||
}
|
||||
|
||||
HttpMethod getMethod() {
|
||||
if (!headers->key) {
|
||||
return METHOD_INVALID;
|
||||
}
|
||||
switch (headers->keyLength) {
|
||||
case 3:
|
||||
if (!strncmp(headers->key, "get", 3)) {
|
||||
return METHOD_GET;
|
||||
} else if (!strncmp(headers->key, "put", 3)) {
|
||||
return METHOD_PUT;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
if (!strncmp(headers->key, "post", 4)) {
|
||||
return METHOD_POST;
|
||||
} else if (!strncmp(headers->key, "head", 4)) {
|
||||
return METHOD_HEAD;
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
if (!strncmp(headers->key, "patch", 5)) {
|
||||
return METHOD_PATCH;
|
||||
} else if (!strncmp(headers->key, "trace", 5)) {
|
||||
return METHOD_TRACE;
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
if (!strncmp(headers->key, "delete", 6)) {
|
||||
return METHOD_DELETE;
|
||||
}
|
||||
break;
|
||||
case 7:
|
||||
if (!strncmp(headers->key, "options", 7)) {
|
||||
return METHOD_OPTIONS;
|
||||
} else if (!strncmp(headers->key, "connect", 7)) {
|
||||
return METHOD_CONNECT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return METHOD_INVALID;
|
||||
}
|
||||
};
|
||||
|
||||
struct HttpResponse;
|
||||
|
||||
template <const bool isServer>
|
||||
struct WIN32_EXPORT HttpSocket : uS::Socket {
|
||||
void *httpUser; // remove this later, setTimeout occupies user for now
|
||||
HttpResponse *outstandingResponsesHead = nullptr;
|
||||
HttpResponse *outstandingResponsesTail = nullptr;
|
||||
HttpResponse *preAllocatedResponse = nullptr;
|
||||
|
||||
std::string httpBuffer;
|
||||
size_t contentLength = 0;
|
||||
bool missedDeadline = false;
|
||||
|
||||
HttpSocket(uS::Socket *socket) : uS::Socket(std::move(*socket)) {}
|
||||
|
||||
void terminate() {
|
||||
onEnd(this);
|
||||
}
|
||||
|
||||
void upgrade(const char *secKey, const char *extensions,
|
||||
size_t extensionsLength, const char *subprotocol,
|
||||
size_t subprotocolLength, bool *perMessageDeflate);
|
||||
|
||||
private:
|
||||
friend struct uS::Socket;
|
||||
friend struct HttpResponse;
|
||||
friend struct Hub;
|
||||
static uS::Socket *onData(uS::Socket *s, char *data, size_t length);
|
||||
static void onEnd(uS::Socket *s);
|
||||
};
|
||||
|
||||
struct HttpResponse {
|
||||
HttpSocket<true> *httpSocket;
|
||||
HttpResponse *next = nullptr;
|
||||
void *userData = nullptr;
|
||||
void *extraUserData = nullptr;
|
||||
HttpSocket<true>::Queue::Message *messageQueue = nullptr;
|
||||
bool hasEnded = false;
|
||||
bool hasHead = false;
|
||||
|
||||
HttpResponse(HttpSocket<true> *httpSocket) : httpSocket(httpSocket) {
|
||||
|
||||
}
|
||||
|
||||
template <bool isServer>
|
||||
static HttpResponse *allocateResponse(HttpSocket<isServer> *httpSocket) {
|
||||
if (httpSocket->preAllocatedResponse) {
|
||||
HttpResponse *ret = httpSocket->preAllocatedResponse;
|
||||
httpSocket->preAllocatedResponse = nullptr;
|
||||
return ret;
|
||||
} else {
|
||||
return new HttpResponse((HttpSocket<true> *) httpSocket);
|
||||
}
|
||||
}
|
||||
|
||||
//template <bool isServer>
|
||||
void freeResponse(HttpSocket<true> *httpData) {
|
||||
if (httpData->preAllocatedResponse) {
|
||||
delete this;
|
||||
} else {
|
||||
httpData->preAllocatedResponse = this;
|
||||
}
|
||||
}
|
||||
|
||||
void write(const char *message, size_t length = 0,
|
||||
void(*callback)(void *httpSocket, void *data, bool cancelled, void *reserved) = nullptr,
|
||||
void *callbackData = nullptr) {
|
||||
|
||||
struct NoopTransformer {
|
||||
static size_t estimate(const char *data, size_t length) {
|
||||
return length;
|
||||
}
|
||||
|
||||
static size_t transform(const char *src, char *dst, size_t length, int transformData) {
|
||||
memcpy(dst, src, length);
|
||||
return length;
|
||||
}
|
||||
};
|
||||
|
||||
httpSocket->sendTransformed<NoopTransformer>(message, length, callback, callbackData, 0);
|
||||
hasHead = true;
|
||||
}
|
||||
|
||||
// todo: maybe this function should have a fast path for 0 length?
|
||||
void end(const char *message = nullptr, size_t length = 0,
|
||||
void(*callback)(void *httpResponse, void *data, bool cancelled, void *reserved) = nullptr,
|
||||
void *callbackData = nullptr) {
|
||||
|
||||
struct TransformData {
|
||||
bool hasHead;
|
||||
} transformData = {hasHead};
|
||||
|
||||
struct HttpTransformer {
|
||||
|
||||
// todo: this should get TransformData!
|
||||
static size_t estimate(const char *data, size_t length) {
|
||||
return length + 128;
|
||||
}
|
||||
|
||||
static size_t transform(const char *src, char *dst, size_t length, TransformData transformData) {
|
||||
// todo: sprintf is extremely slow
|
||||
int offset = transformData.hasHead ? 0 : sprintf(dst, "HTTP/1.1 200 OK\r\nContent-Length: %u\r\n\r\n", (unsigned int) length);
|
||||
memcpy(dst + offset, src, length);
|
||||
return length + offset;
|
||||
}
|
||||
};
|
||||
|
||||
if (httpSocket->outstandingResponsesHead != this) {
|
||||
HttpSocket<true>::Queue::Message *messagePtr = httpSocket->allocMessage(HttpTransformer::estimate(message, length));
|
||||
messagePtr->length = HttpTransformer::transform(message, (char *) messagePtr->data, length, transformData);
|
||||
messagePtr->callback = callback;
|
||||
messagePtr->callbackData = callbackData;
|
||||
messagePtr->nextMessage = messageQueue;
|
||||
messageQueue = messagePtr;
|
||||
hasEnded = true;
|
||||
} else {
|
||||
httpSocket->sendTransformed<HttpTransformer>(message, length, callback, callbackData, transformData);
|
||||
// move head as far as possible
|
||||
HttpResponse *head = next;
|
||||
while (head) {
|
||||
// empty message queue
|
||||
HttpSocket<true>::Queue::Message *messagePtr = head->messageQueue;
|
||||
while (messagePtr) {
|
||||
HttpSocket<true>::Queue::Message *nextMessage = messagePtr->nextMessage;
|
||||
|
||||
bool wasTransferred;
|
||||
if (httpSocket->write(messagePtr, wasTransferred)) {
|
||||
if (!wasTransferred) {
|
||||
httpSocket->freeMessage(messagePtr);
|
||||
if (callback) {
|
||||
callback(this, callbackData, false, nullptr);
|
||||
}
|
||||
} else {
|
||||
messagePtr->callback = callback;
|
||||
messagePtr->callbackData = callbackData;
|
||||
}
|
||||
} else {
|
||||
httpSocket->freeMessage(messagePtr);
|
||||
if (callback) {
|
||||
callback(this, callbackData, true, nullptr);
|
||||
}
|
||||
goto updateHead;
|
||||
}
|
||||
messagePtr = nextMessage;
|
||||
}
|
||||
// cannot go beyond unfinished responses
|
||||
if (!head->hasEnded) {
|
||||
break;
|
||||
} else {
|
||||
HttpResponse *next = head->next;
|
||||
head->freeResponse(httpSocket);
|
||||
head = next;
|
||||
}
|
||||
}
|
||||
updateHead:
|
||||
httpSocket->outstandingResponsesHead = head;
|
||||
if (!head) {
|
||||
httpSocket->outstandingResponsesTail = nullptr;
|
||||
}
|
||||
|
||||
freeResponse(httpSocket);
|
||||
}
|
||||
}
|
||||
|
||||
void setUserData(void *userData) {
|
||||
this->userData = userData;
|
||||
}
|
||||
|
||||
void *getUserData() {
|
||||
return userData;
|
||||
}
|
||||
|
||||
HttpSocket<true> *getHttpSocket() {
|
||||
return httpSocket;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // HTTPSOCKET_UWS_H
|
|
@ -0,0 +1,97 @@
|
|||
#ifndef HUB_UWS_H
|
||||
#define HUB_UWS_H
|
||||
|
||||
#include "Group.h"
|
||||
#include "Node.h"
|
||||
#include <string>
|
||||
#include <zlib.h>
|
||||
#include <mutex>
|
||||
#include <map>
|
||||
|
||||
namespace uWS {
|
||||
|
||||
struct WIN32_EXPORT Hub : private uS::Node, public Group<SERVER>, public Group<CLIENT> {
|
||||
protected:
|
||||
struct ConnectionData {
|
||||
std::string path;
|
||||
void *user;
|
||||
Group<CLIENT> *group;
|
||||
};
|
||||
|
||||
z_stream inflationStream = {};
|
||||
char *inflationBuffer;
|
||||
char *inflate(char *data, size_t &length, size_t maxPayload);
|
||||
std::string dynamicInflationBuffer;
|
||||
static const int LARGE_BUFFER_SIZE = 300 * 1024;
|
||||
|
||||
static void onServerAccept(uS::Socket *s);
|
||||
static void onClientConnection(uS::Socket *s, bool error);
|
||||
|
||||
public:
|
||||
template <bool isServer>
|
||||
Group<isServer> *createGroup(int extensionOptions = 0, unsigned int maxPayload = 16777216) {
|
||||
return new Group<isServer>(extensionOptions, maxPayload, this, nodeData);
|
||||
}
|
||||
|
||||
template <bool isServer>
|
||||
Group<isServer> &getDefaultGroup() {
|
||||
return static_cast<Group<isServer> &>(*this);
|
||||
}
|
||||
|
||||
bool listen(int port, uS::TLS::Context sslContext = nullptr, int options = 0, Group<SERVER> *eh = nullptr);
|
||||
bool listen(const char *host, int port, uS::TLS::Context sslContext = nullptr, int options = 0, Group<SERVER> *eh = nullptr);
|
||||
void connect(std::string uri, void *user = nullptr, std::map<std::string, std::string> extraHeaders = {}, int timeoutMs = 5000, Group<CLIENT> *eh = nullptr);
|
||||
void upgrade(uv_os_sock_t fd, const char *secKey, SSL *ssl, const char *extensions, size_t extensionsLength, const char *subprotocol, size_t subprotocolLength, Group<SERVER> *serverGroup = nullptr);
|
||||
|
||||
Hub(int extensionOptions = 0, bool useDefaultLoop = false, unsigned int maxPayload = 16777216) : uS::Node(LARGE_BUFFER_SIZE, WebSocketProtocol<SERVER, WebSocket<SERVER>>::CONSUME_PRE_PADDING, WebSocketProtocol<SERVER, WebSocket<SERVER>>::CONSUME_POST_PADDING, useDefaultLoop),
|
||||
Group<SERVER>(extensionOptions, maxPayload, this, nodeData), Group<CLIENT>(0, maxPayload, this, nodeData) {
|
||||
inflateInit2(&inflationStream, -15);
|
||||
inflationBuffer = new char[LARGE_BUFFER_SIZE];
|
||||
|
||||
#ifdef UWS_THREADSAFE
|
||||
getLoop()->preCbData = nodeData;
|
||||
getLoop()->preCb = [](void *nodeData) {
|
||||
static_cast<uS::NodeData *>(nodeData)->asyncMutex->lock();
|
||||
};
|
||||
|
||||
getLoop()->postCbData = nodeData;
|
||||
getLoop()->postCb = [](void *nodeData) {
|
||||
static_cast<uS::NodeData *>(nodeData)->asyncMutex->unlock();
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
||||
~Hub() {
|
||||
inflateEnd(&inflationStream);
|
||||
delete [] inflationBuffer;
|
||||
}
|
||||
|
||||
using uS::Node::run;
|
||||
using uS::Node::getLoop;
|
||||
using Group<SERVER>::onConnection;
|
||||
using Group<CLIENT>::onConnection;
|
||||
using Group<SERVER>::onTransfer;
|
||||
using Group<SERVER>::onMessage;
|
||||
using Group<CLIENT>::onMessage;
|
||||
using Group<SERVER>::onDisconnection;
|
||||
using Group<CLIENT>::onDisconnection;
|
||||
using Group<SERVER>::onPing;
|
||||
using Group<CLIENT>::onPing;
|
||||
using Group<SERVER>::onPong;
|
||||
using Group<CLIENT>::onPong;
|
||||
using Group<SERVER>::onError;
|
||||
using Group<CLIENT>::onError;
|
||||
using Group<SERVER>::onHttpRequest;
|
||||
using Group<SERVER>::onHttpData;
|
||||
using Group<SERVER>::onHttpConnection;
|
||||
using Group<SERVER>::onHttpDisconnection;
|
||||
using Group<SERVER>::onHttpUpgrade;
|
||||
using Group<SERVER>::onCancelledHttpRequest;
|
||||
|
||||
friend struct WebSocket<SERVER>;
|
||||
friend struct WebSocket<CLIENT>;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // HUB_UWS_H
|
|
@ -0,0 +1,175 @@
|
|||
#ifndef LIBUV_H
|
||||
#define LIBUV_H
|
||||
|
||||
#include <uv.h>
|
||||
static_assert (UV_VERSION_MINOR >= 3, "µWebSockets requires libuv >=1.3.0");
|
||||
|
||||
struct Loop : uv_loop_t {
|
||||
static Loop *createLoop(bool defaultLoop = true) {
|
||||
if (defaultLoop) {
|
||||
return (Loop *) uv_default_loop();
|
||||
} else {
|
||||
return (Loop *) uv_loop_new();
|
||||
}
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
if (this != uv_default_loop()) {
|
||||
uv_loop_delete(this);
|
||||
}
|
||||
}
|
||||
|
||||
void run() {
|
||||
uv_run(this, UV_RUN_DEFAULT);
|
||||
}
|
||||
};
|
||||
|
||||
struct Async {
|
||||
uv_async_t uv_async;
|
||||
|
||||
Async(Loop *loop) {
|
||||
uv_async.loop = loop;
|
||||
}
|
||||
|
||||
void start(void (*cb)(Async *)) {
|
||||
uv_async_init(uv_async.loop, &uv_async, (uv_async_cb) cb);
|
||||
}
|
||||
|
||||
void send() {
|
||||
uv_async_send(&uv_async);
|
||||
}
|
||||
|
||||
void close() {
|
||||
uv_close((uv_handle_t *) &uv_async, [](uv_handle_t *a) {
|
||||
delete (Async *) a;
|
||||
});
|
||||
}
|
||||
|
||||
void setData(void *data) {
|
||||
uv_async.data = data;
|
||||
}
|
||||
|
||||
void *getData() {
|
||||
return uv_async.data;
|
||||
}
|
||||
};
|
||||
|
||||
struct Timer {
|
||||
uv_timer_t uv_timer;
|
||||
|
||||
Timer(Loop *loop) {
|
||||
uv_timer_init(loop, &uv_timer);
|
||||
}
|
||||
|
||||
void start(void (*cb)(Timer *), int first, int repeat) {
|
||||
uv_timer_start(&uv_timer, (uv_timer_cb) cb, first, repeat);
|
||||
}
|
||||
|
||||
void setData(void *data) {
|
||||
uv_timer.data = data;
|
||||
}
|
||||
|
||||
void *getData() {
|
||||
return uv_timer.data;
|
||||
}
|
||||
|
||||
void stop() {
|
||||
uv_timer_stop(&uv_timer);
|
||||
}
|
||||
|
||||
void close() {
|
||||
uv_close((uv_handle_t *) &uv_timer, [](uv_handle_t *t) {
|
||||
delete (Timer *) t;
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
~Timer() {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
struct Poll {
|
||||
uv_poll_t *uv_poll;
|
||||
void (*cb)(Poll *p, int status, int events);
|
||||
|
||||
Poll(Loop *loop, uv_os_sock_t fd) {
|
||||
uv_poll = new uv_poll_t;
|
||||
uv_poll_init_socket(loop, uv_poll, fd);
|
||||
}
|
||||
|
||||
Poll(Poll &&other) {
|
||||
uv_poll = other.uv_poll;
|
||||
cb = other.cb;
|
||||
other.uv_poll = nullptr;
|
||||
}
|
||||
|
||||
Poll(const Poll &other) = delete;
|
||||
|
||||
~Poll() {
|
||||
delete uv_poll;
|
||||
}
|
||||
|
||||
bool isClosed() {
|
||||
return uv_is_closing((uv_handle_t *) uv_poll);
|
||||
}
|
||||
|
||||
uv_os_sock_t getFd() {
|
||||
#ifdef _WIN32
|
||||
uv_os_sock_t fd;
|
||||
uv_fileno((uv_handle_t *) uv_poll, (uv_os_fd_t *) &fd);
|
||||
return fd;
|
||||
#else
|
||||
return uv_poll->io_watcher.fd;
|
||||
#endif
|
||||
}
|
||||
|
||||
void setCb(void (*cb)(Poll *p, int status, int events)) {
|
||||
this->cb = cb;
|
||||
}
|
||||
|
||||
void (*getCb())(Poll *, int, int) {
|
||||
return cb;
|
||||
}
|
||||
|
||||
void reInit(Loop *loop, uv_os_sock_t fd) {
|
||||
delete uv_poll;
|
||||
uv_poll = new uv_poll_t;
|
||||
uv_poll_init_socket(loop, uv_poll, fd);
|
||||
}
|
||||
|
||||
void start(Loop *, Poll *self, int events) {
|
||||
uv_poll->data = self;
|
||||
uv_poll_start(uv_poll, events, [](uv_poll_t *p, int status, int events) {
|
||||
Poll *self = (Poll *) p->data;
|
||||
self->cb(self, status, events);
|
||||
});
|
||||
}
|
||||
|
||||
void change(Loop *, Poll *self, int events) {
|
||||
start(nullptr, self, events);
|
||||
}
|
||||
|
||||
void stop(Loop *loop) {
|
||||
uv_poll_stop(uv_poll);
|
||||
}
|
||||
|
||||
bool fastTransfer(Loop *loop, Loop *newLoop, int events) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool threadSafeChange(Loop *, Poll *self, int events) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void close(Loop *loop, void (*cb)(Poll *)) {
|
||||
this->cb = (void(*)(Poll *, int, int)) cb;
|
||||
uv_close((uv_handle_t *) uv_poll, [](uv_handle_t *p) {
|
||||
Poll *poll = (Poll *) p->data;
|
||||
void (*cb)(Poll *) = (void(*)(Poll *)) poll->cb;
|
||||
cb(poll);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
#endif // LIBUV_H
|
|
@ -0,0 +1,259 @@
|
|||
// the purpose of this header should be to provide SSL and networking wrapped in a common interface
|
||||
// it should allow cross-platform networking and SSL and also easy usage of mTCP and similar tech
|
||||
|
||||
#ifndef NETWORKING_UWS_H
|
||||
#define NETWORKING_UWS_H
|
||||
|
||||
#include <openssl/opensslv.h>
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
#define SSL_CTX_up_ref(x) x->references++
|
||||
#define SSL_up_ref(x) x->references++
|
||||
#endif
|
||||
|
||||
#ifndef __linux
|
||||
#define MSG_NOSIGNAL 0
|
||||
#else
|
||||
#include <endian.h>
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <libkern/OSByteOrder.h>
|
||||
#define htobe64(x) OSSwapHostToBigInt64(x)
|
||||
#define be64toh(x) OSSwapBigToHostInt64(x)
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#define NOMINMAX
|
||||
#include <WinSock2.h>
|
||||
#include <Ws2tcpip.h>
|
||||
#pragma comment(lib, "ws2_32.lib")
|
||||
#define SHUT_WR SD_SEND
|
||||
#ifdef __MINGW32__
|
||||
// Windows has always been tied to LE
|
||||
#define htobe64(x) __builtin_bswap64(x)
|
||||
#define be64toh(x) __builtin_bswap64(x)
|
||||
#else
|
||||
#define __thread __declspec(thread)
|
||||
#define htobe64(x) htonll(x)
|
||||
#define be64toh(x) ntohll(x)
|
||||
#define pthread_t DWORD
|
||||
#define pthread_self GetCurrentThreadId
|
||||
#endif
|
||||
#define WIN32_EXPORT __declspec(dllexport)
|
||||
|
||||
inline void close(SOCKET fd) {closesocket(fd);}
|
||||
inline int setsockopt(SOCKET fd, int level, int optname, const void *optval, socklen_t optlen) {
|
||||
return setsockopt(fd, level, optname, (const char *) optval, optlen);
|
||||
}
|
||||
|
||||
inline SOCKET dup(SOCKET socket) {
|
||||
WSAPROTOCOL_INFOW pi;
|
||||
if (WSADuplicateSocketW(socket, GetCurrentProcessId(), &pi) == SOCKET_ERROR) {
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
return WSASocketW(pi.iAddressFamily, pi.iSocketType, pi.iProtocol, &pi, 0, WSA_FLAG_OVERLAPPED);
|
||||
}
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <cstring>
|
||||
#define SOCKET_ERROR -1
|
||||
#define INVALID_SOCKET -1
|
||||
#define WIN32_EXPORT
|
||||
#endif
|
||||
|
||||
#include "Backend.h"
|
||||
#include <openssl/ssl.h>
|
||||
#include <csignal>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
namespace uS {
|
||||
|
||||
// todo: mark sockets nonblocking in these functions
|
||||
// todo: probably merge this Context with the TLS::Context for same interface for SSL and non-SSL!
|
||||
struct Context {
|
||||
|
||||
#ifdef USE_MTCP
|
||||
mtcp_context *mctx;
|
||||
#endif
|
||||
|
||||
Context() {
|
||||
// mtcp_create_context
|
||||
#ifdef USE_MTCP
|
||||
mctx = mtcp_create_context(0); // cpu index?
|
||||
#endif
|
||||
}
|
||||
|
||||
~Context() {
|
||||
#ifdef USE_MTCP
|
||||
mtcp_destroy_context(mctx);
|
||||
#endif
|
||||
}
|
||||
|
||||
// returns INVALID_SOCKET on error
|
||||
uv_os_sock_t acceptSocket(uv_os_sock_t fd) {
|
||||
uv_os_sock_t acceptedFd;
|
||||
#if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK)
|
||||
// Linux, FreeBSD
|
||||
acceptedFd = accept4(fd, nullptr, nullptr, SOCK_CLOEXEC | SOCK_NONBLOCK);
|
||||
#else
|
||||
// Windows, OS X
|
||||
acceptedFd = accept(fd, nullptr, nullptr);
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
if (acceptedFd != INVALID_SOCKET) {
|
||||
int noSigpipe = 1;
|
||||
setsockopt(acceptedFd, SOL_SOCKET, SO_NOSIGPIPE, &noSigpipe, sizeof(int));
|
||||
}
|
||||
#endif
|
||||
return acceptedFd;
|
||||
}
|
||||
|
||||
// returns INVALID_SOCKET on error
|
||||
uv_os_sock_t createSocket(int domain, int type, int protocol) {
|
||||
int flags = 0;
|
||||
#if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK)
|
||||
flags = SOCK_CLOEXEC | SOCK_NONBLOCK;
|
||||
#endif
|
||||
|
||||
uv_os_sock_t createdFd = socket(domain, type | flags, protocol);
|
||||
|
||||
#ifdef __APPLE__
|
||||
if (createdFd != INVALID_SOCKET) {
|
||||
int noSigpipe = 1;
|
||||
setsockopt(createdFd, SOL_SOCKET, SO_NOSIGPIPE, &noSigpipe, sizeof(int));
|
||||
}
|
||||
#endif
|
||||
|
||||
return createdFd;
|
||||
}
|
||||
|
||||
void closeSocket(uv_os_sock_t fd) {
|
||||
#ifdef _WIN32
|
||||
closesocket(fd);
|
||||
#else
|
||||
close(fd);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool wouldBlock() {
|
||||
#ifdef _WIN32
|
||||
return WSAGetLastError() == WSAEWOULDBLOCK;
|
||||
#else
|
||||
return errno == EWOULDBLOCK;// || errno == EAGAIN;
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
namespace TLS {
|
||||
|
||||
class WIN32_EXPORT Context {
|
||||
private:
|
||||
SSL_CTX *context = nullptr;
|
||||
std::shared_ptr<std::string> password;
|
||||
|
||||
static int passwordCallback(char *buf, int size, int rwflag, void *u)
|
||||
{
|
||||
std::string *password = (std::string *) u;
|
||||
int length = std::min<int>(size, password->length());
|
||||
memcpy(buf, password->data(), length);
|
||||
buf[length] = '\0';
|
||||
return length;
|
||||
}
|
||||
|
||||
public:
|
||||
friend Context WIN32_EXPORT createContext(std::string certChainFileName, std::string keyFileName, std::string keyFilePassword);
|
||||
Context(SSL_CTX *context) : context(context) {
|
||||
|
||||
}
|
||||
|
||||
Context() = default;
|
||||
Context(const Context &other);
|
||||
Context &operator=(const Context &other);
|
||||
~Context();
|
||||
operator bool() {
|
||||
return context;
|
||||
}
|
||||
|
||||
SSL_CTX *getNativeContext() {
|
||||
return context;
|
||||
}
|
||||
};
|
||||
|
||||
Context WIN32_EXPORT createContext(std::string certChainFileName, std::string keyFileName, std::string keyFilePassword = std::string());
|
||||
|
||||
}
|
||||
|
||||
struct Socket;
|
||||
|
||||
// NodeData is like a Context, maybe merge them?
|
||||
struct WIN32_EXPORT NodeData {
|
||||
char *recvBufferMemoryBlock;
|
||||
char *recvBuffer;
|
||||
int recvLength;
|
||||
Loop *loop;
|
||||
uS::Context *netContext;
|
||||
void *user = nullptr;
|
||||
static const int preAllocMaxSize = 1024;
|
||||
char **preAlloc;
|
||||
SSL_CTX *clientContext;
|
||||
|
||||
Async *async = nullptr;
|
||||
pthread_t tid;
|
||||
|
||||
std::recursive_mutex *asyncMutex;
|
||||
std::vector<Poll *> transferQueue;
|
||||
std::vector<Poll *> changePollQueue;
|
||||
static void asyncCallback(Async *async);
|
||||
|
||||
static int getMemoryBlockIndex(size_t length) {
|
||||
return (length >> 4) + bool(length & 15);
|
||||
}
|
||||
|
||||
char *getSmallMemoryBlock(int index) {
|
||||
if (preAlloc[index]) {
|
||||
char *memory = preAlloc[index];
|
||||
preAlloc[index] = nullptr;
|
||||
return memory;
|
||||
} else {
|
||||
return new char[index << 4];
|
||||
}
|
||||
}
|
||||
|
||||
void freeSmallMemoryBlock(char *memory, int index) {
|
||||
if (!preAlloc[index]) {
|
||||
preAlloc[index] = memory;
|
||||
} else {
|
||||
delete [] memory;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void addAsync() {
|
||||
async = new Async(loop);
|
||||
async->setData(this);
|
||||
async->start(NodeData::asyncCallback);
|
||||
}
|
||||
|
||||
void clearPendingPollChanges(Poll *p) {
|
||||
asyncMutex->lock();
|
||||
changePollQueue.erase(
|
||||
std::remove(changePollQueue.begin(), changePollQueue.end(), p),
|
||||
changePollQueue.end()
|
||||
);
|
||||
asyncMutex->unlock();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // NETWORKING_UWS_H
|
|
@ -0,0 +1,202 @@
|
|||
#ifndef NODE_UWS_H
|
||||
#define NODE_UWS_H
|
||||
|
||||
#include "Socket.h"
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
namespace uS {
|
||||
|
||||
enum ListenOptions : int {
|
||||
REUSE_PORT = 1,
|
||||
ONLY_IPV4 = 2
|
||||
};
|
||||
|
||||
class WIN32_EXPORT Node {
|
||||
private:
|
||||
template <void C(Socket *p, bool error)>
|
||||
static void connect_cb(Poll *p, int status, int events) {
|
||||
C((Socket *) p, status < 0);
|
||||
}
|
||||
|
||||
template <void A(Socket *s)>
|
||||
static void accept_poll_cb(Poll *p, int status, int events) {
|
||||
ListenSocket *listenData = (ListenSocket *) p;
|
||||
accept_cb<A, false>(listenData);
|
||||
}
|
||||
|
||||
template <void A(Socket *s)>
|
||||
static void accept_timer_cb(Timer *p) {
|
||||
ListenSocket *listenData = (ListenSocket *) p->getData();
|
||||
accept_cb<A, true>(listenData);
|
||||
}
|
||||
|
||||
template <void A(Socket *s), bool TIMER>
|
||||
static void accept_cb(ListenSocket *listenSocket) {
|
||||
uv_os_sock_t serverFd = listenSocket->getFd();
|
||||
Context *netContext = listenSocket->nodeData->netContext;
|
||||
uv_os_sock_t clientFd = netContext->acceptSocket(serverFd);
|
||||
if (clientFd == INVALID_SOCKET) {
|
||||
/*
|
||||
* If accept is failing, the pending connection won't be removed and the
|
||||
* polling will cause the server to spin, using 100% cpu. Switch to a timer
|
||||
* event instead to avoid this.
|
||||
*/
|
||||
if (!TIMER && !netContext->wouldBlock()) {
|
||||
listenSocket->stop(listenSocket->nodeData->loop);
|
||||
|
||||
listenSocket->timer = new Timer(listenSocket->nodeData->loop);
|
||||
listenSocket->timer->setData(listenSocket);
|
||||
listenSocket->timer->start(accept_timer_cb<A>, 1000, 1000);
|
||||
}
|
||||
return;
|
||||
} else if (TIMER) {
|
||||
listenSocket->timer->stop();
|
||||
listenSocket->timer->close();
|
||||
listenSocket->timer = nullptr;
|
||||
|
||||
listenSocket->setCb(accept_poll_cb<A>);
|
||||
listenSocket->start(listenSocket->nodeData->loop, listenSocket, UV_READABLE);
|
||||
}
|
||||
do {
|
||||
SSL *ssl = nullptr;
|
||||
if (listenSocket->sslContext) {
|
||||
ssl = SSL_new(listenSocket->sslContext.getNativeContext());
|
||||
SSL_set_accept_state(ssl);
|
||||
}
|
||||
|
||||
Socket *socket = new Socket(listenSocket->nodeData, listenSocket->nodeData->loop, clientFd, ssl);
|
||||
socket->setPoll(UV_READABLE);
|
||||
A(socket);
|
||||
} while ((clientFd = netContext->acceptSocket(serverFd)) != INVALID_SOCKET);
|
||||
}
|
||||
|
||||
protected:
|
||||
Loop *loop;
|
||||
NodeData *nodeData;
|
||||
std::recursive_mutex asyncMutex;
|
||||
|
||||
public:
|
||||
Node(int recvLength = 1024, int prePadding = 0, int postPadding = 0, bool useDefaultLoop = false);
|
||||
~Node();
|
||||
void run();
|
||||
|
||||
Loop *getLoop() {
|
||||
return loop;
|
||||
}
|
||||
|
||||
template <uS::Socket *I(Socket *s), void C(Socket *p, bool error)>
|
||||
Socket *connect(const char *hostname, int port, bool secure, NodeData *nodeData) {
|
||||
Context *netContext = nodeData->netContext;
|
||||
|
||||
addrinfo hints, *result;
|
||||
memset(&hints, 0, sizeof(addrinfo));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
char portstr[8];
|
||||
snprintf(portstr, sizeof(portstr), "%d", port);
|
||||
if (getaddrinfo(hostname, portstr, &hints, &result) != 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uv_os_sock_t fd = netContext->createSocket(result->ai_family, result->ai_socktype, result->ai_protocol);
|
||||
if (fd == INVALID_SOCKET) {
|
||||
freeaddrinfo(result);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
::connect(fd, result->ai_addr, result->ai_addrlen);
|
||||
freeaddrinfo(result);
|
||||
|
||||
SSL *ssl = nullptr;
|
||||
if (secure) {
|
||||
ssl = SSL_new(nodeData->clientContext);
|
||||
SSL_set_connect_state(ssl);
|
||||
SSL_set_tlsext_host_name(ssl, hostname);
|
||||
}
|
||||
|
||||
Socket initialSocket(nodeData, getLoop(), fd, ssl);
|
||||
uS::Socket *socket = I(&initialSocket);
|
||||
|
||||
socket->setCb(connect_cb<C>);
|
||||
socket->start(loop, socket, socket->setPoll(UV_WRITABLE));
|
||||
return socket;
|
||||
}
|
||||
|
||||
// todo: hostname, backlog
|
||||
template <void A(Socket *s)>
|
||||
bool listen(const char *host, int port, uS::TLS::Context sslContext, int options, uS::NodeData *nodeData, void *user) {
|
||||
addrinfo hints, *result;
|
||||
memset(&hints, 0, sizeof(addrinfo));
|
||||
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
Context *netContext = nodeData->netContext;
|
||||
|
||||
char portstr[8];
|
||||
snprintf(portstr, sizeof(portstr), "%d", port);
|
||||
if (getaddrinfo(host, portstr, &hints, &result)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
uv_os_sock_t listenFd = SOCKET_ERROR;
|
||||
addrinfo *listenAddr;
|
||||
if ((options & uS::ONLY_IPV4) == 0) {
|
||||
for (addrinfo *a = result; a && listenFd == SOCKET_ERROR; a = a->ai_next) {
|
||||
if (a->ai_family == AF_INET6) {
|
||||
listenFd = netContext->createSocket(a->ai_family, a->ai_socktype, a->ai_protocol);
|
||||
listenAddr = a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (addrinfo *a = result; a && listenFd == SOCKET_ERROR; a = a->ai_next) {
|
||||
if (a->ai_family == AF_INET) {
|
||||
listenFd = netContext->createSocket(a->ai_family, a->ai_socktype, a->ai_protocol);
|
||||
listenAddr = a;
|
||||
}
|
||||
}
|
||||
|
||||
if (listenFd == SOCKET_ERROR) {
|
||||
freeaddrinfo(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef __linux
|
||||
#ifdef SO_REUSEPORT
|
||||
if (options & REUSE_PORT) {
|
||||
int optval = 1;
|
||||
setsockopt(listenFd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval));
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
int enabled = true;
|
||||
setsockopt(listenFd, SOL_SOCKET, SO_REUSEADDR, &enabled, sizeof(enabled));
|
||||
|
||||
if (::bind(listenFd, listenAddr->ai_addr, listenAddr->ai_addrlen) || ::listen(listenFd, 512)) {
|
||||
netContext->closeSocket(listenFd);
|
||||
freeaddrinfo(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
ListenSocket *listenSocket = new ListenSocket(nodeData, loop, listenFd, nullptr);
|
||||
listenSocket->sslContext = sslContext;
|
||||
listenSocket->nodeData = nodeData;
|
||||
|
||||
listenSocket->setCb(accept_poll_cb<A>);
|
||||
listenSocket->start(loop, listenSocket, UV_READABLE);
|
||||
|
||||
// should be vector of listen data! one group can have many listeners!
|
||||
nodeData->user = listenSocket;
|
||||
|
||||
freeaddrinfo(result);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // NODE_UWS_H
|
|
@ -0,0 +1,507 @@
|
|||
#ifndef SOCKET_UWS_H
|
||||
#define SOCKET_UWS_H
|
||||
|
||||
#include "Networking.h"
|
||||
|
||||
namespace uS {
|
||||
|
||||
struct TransferData {
|
||||
// Connection state
|
||||
uv_os_sock_t fd;
|
||||
SSL *ssl;
|
||||
|
||||
// Poll state
|
||||
void (*pollCb)(Poll *, int, int);
|
||||
int pollEvents;
|
||||
|
||||
// User state
|
||||
void *userData;
|
||||
|
||||
// Destination
|
||||
NodeData *destination;
|
||||
void (*transferCb)(Poll *);
|
||||
};
|
||||
|
||||
// perfectly 64 bytes (4 + 60)
|
||||
struct WIN32_EXPORT Socket : Poll {
|
||||
protected:
|
||||
struct {
|
||||
int poll : 4;
|
||||
int shuttingDown : 4;
|
||||
} state = {0, false};
|
||||
|
||||
SSL *ssl;
|
||||
void *user = nullptr;
|
||||
NodeData *nodeData;
|
||||
|
||||
// this is not needed by HttpSocket!
|
||||
struct Queue {
|
||||
struct Message {
|
||||
const char *data;
|
||||
size_t length;
|
||||
Message *nextMessage = nullptr;
|
||||
void (*callback)(void *socket, void *data, bool cancelled, void *reserved) = nullptr;
|
||||
void *callbackData = nullptr, *reserved = nullptr;
|
||||
};
|
||||
|
||||
Message *head = nullptr, *tail = nullptr;
|
||||
void pop()
|
||||
{
|
||||
Message *nextMessage;
|
||||
if ((nextMessage = head->nextMessage)) {
|
||||
delete [] (char *) head;
|
||||
head = nextMessage;
|
||||
} else {
|
||||
delete [] (char *) head;
|
||||
head = tail = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool empty() {return head == nullptr;}
|
||||
Message *front() {return head;}
|
||||
|
||||
void push(Message *message)
|
||||
{
|
||||
message->nextMessage = nullptr;
|
||||
if (tail) {
|
||||
tail->nextMessage = message;
|
||||
tail = message;
|
||||
} else {
|
||||
head = message;
|
||||
tail = message;
|
||||
}
|
||||
}
|
||||
} messageQueue;
|
||||
|
||||
int getPoll() {
|
||||
return state.poll;
|
||||
}
|
||||
|
||||
int setPoll(int poll) {
|
||||
state.poll = poll;
|
||||
return poll;
|
||||
}
|
||||
|
||||
void setShuttingDown(bool shuttingDown) {
|
||||
state.shuttingDown = shuttingDown;
|
||||
}
|
||||
|
||||
void transfer(NodeData *nodeData, void (*cb)(Poll *)) {
|
||||
// userData is invalid from now on till onTransfer
|
||||
setUserData(new TransferData({getFd(), ssl, getCb(), getPoll(), getUserData(), nodeData, cb}));
|
||||
stop(this->nodeData->loop);
|
||||
close(this->nodeData->loop, [](Poll *p) {
|
||||
Socket *s = (Socket *) p;
|
||||
TransferData *transferData = (TransferData *) s->getUserData();
|
||||
|
||||
transferData->destination->asyncMutex->lock();
|
||||
bool wasEmpty = transferData->destination->transferQueue.empty();
|
||||
transferData->destination->transferQueue.push_back(s);
|
||||
transferData->destination->asyncMutex->unlock();
|
||||
|
||||
if (wasEmpty) {
|
||||
transferData->destination->async->send();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void changePoll(Socket *socket) {
|
||||
if (!threadSafeChange(nodeData->loop, this, socket->getPoll())) {
|
||||
if (socket->nodeData->tid != pthread_self()) {
|
||||
socket->nodeData->asyncMutex->lock();
|
||||
socket->nodeData->changePollQueue.push_back(socket);
|
||||
socket->nodeData->asyncMutex->unlock();
|
||||
socket->nodeData->async->send();
|
||||
} else {
|
||||
change(socket->nodeData->loop, socket, socket->getPoll());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clears user data!
|
||||
template <void onTimeout(Socket *)>
|
||||
void startTimeout(int timeoutMs = 15000) {
|
||||
Timer *timer = new Timer(nodeData->loop);
|
||||
timer->setData(this);
|
||||
timer->start([](Timer *timer) {
|
||||
Socket *s = (Socket *) timer->getData();
|
||||
s->cancelTimeout();
|
||||
onTimeout(s);
|
||||
}, timeoutMs, 0);
|
||||
|
||||
user = timer;
|
||||
}
|
||||
|
||||
void cancelTimeout() {
|
||||
Timer *timer = (Timer *) getUserData();
|
||||
if (timer) {
|
||||
timer->stop();
|
||||
timer->close();
|
||||
user = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template <class STATE>
|
||||
static void sslIoHandler(Poll *p, int status, int events) {
|
||||
Socket *socket = (Socket *) p;
|
||||
|
||||
if (status < 0) {
|
||||
STATE::onEnd((Socket *) p);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!socket->messageQueue.empty() && ((events & UV_WRITABLE) || SSL_want(socket->ssl) == SSL_READING)) {
|
||||
socket->cork(true);
|
||||
while (true) {
|
||||
Queue::Message *messagePtr = socket->messageQueue.front();
|
||||
int sent = SSL_write(socket->ssl, messagePtr->data, messagePtr->length);
|
||||
if (sent == (ssize_t) messagePtr->length) {
|
||||
if (messagePtr->callback) {
|
||||
messagePtr->callback(p, messagePtr->callbackData, false, messagePtr->reserved);
|
||||
}
|
||||
socket->messageQueue.pop();
|
||||
if (socket->messageQueue.empty()) {
|
||||
if ((socket->state.poll & UV_WRITABLE) && SSL_want(socket->ssl) != SSL_WRITING) {
|
||||
socket->change(socket->nodeData->loop, socket, socket->setPoll(UV_READABLE));
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if (sent <= 0) {
|
||||
switch (SSL_get_error(socket->ssl, sent)) {
|
||||
case SSL_ERROR_WANT_READ:
|
||||
break;
|
||||
case SSL_ERROR_WANT_WRITE:
|
||||
if ((socket->getPoll() & UV_WRITABLE) == 0) {
|
||||
socket->change(socket->nodeData->loop, socket, socket->setPoll(socket->getPoll() | UV_WRITABLE));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
STATE::onEnd((Socket *) p);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
socket->cork(false);
|
||||
}
|
||||
|
||||
if (events & UV_READABLE) {
|
||||
do {
|
||||
int length = SSL_read(socket->ssl, socket->nodeData->recvBuffer, socket->nodeData->recvLength);
|
||||
if (length <= 0) {
|
||||
switch (SSL_get_error(socket->ssl, length)) {
|
||||
case SSL_ERROR_WANT_READ:
|
||||
break;
|
||||
case SSL_ERROR_WANT_WRITE:
|
||||
if ((socket->getPoll() & UV_WRITABLE) == 0) {
|
||||
socket->change(socket->nodeData->loop, socket, socket->setPoll(socket->getPoll() | UV_WRITABLE));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
STATE::onEnd((Socket *) p);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
// Warning: onData can delete the socket! Happens when HttpSocket upgrades
|
||||
socket = STATE::onData((Socket *) p, socket->nodeData->recvBuffer, length);
|
||||
if (socket->isClosed() || socket->isShuttingDown()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} while (SSL_pending(socket->ssl));
|
||||
}
|
||||
}
|
||||
|
||||
template <class STATE>
|
||||
static void ioHandler(Poll *p, int status, int events) {
|
||||
Socket *socket = (Socket *) p;
|
||||
NodeData *nodeData = socket->nodeData;
|
||||
Context *netContext = nodeData->netContext;
|
||||
|
||||
if (status < 0) {
|
||||
STATE::onEnd((Socket *) p);
|
||||
return;
|
||||
}
|
||||
|
||||
if (events & UV_WRITABLE) {
|
||||
if (!socket->messageQueue.empty() && (events & UV_WRITABLE)) {
|
||||
socket->cork(true);
|
||||
while (true) {
|
||||
Queue::Message *messagePtr = socket->messageQueue.front();
|
||||
ssize_t sent = ::send(socket->getFd(), messagePtr->data, messagePtr->length, MSG_NOSIGNAL);
|
||||
if (sent == (ssize_t) messagePtr->length) {
|
||||
if (messagePtr->callback) {
|
||||
messagePtr->callback(p, messagePtr->callbackData, false, messagePtr->reserved);
|
||||
}
|
||||
socket->messageQueue.pop();
|
||||
if (socket->messageQueue.empty()) {
|
||||
// todo, remove bit, don't set directly
|
||||
socket->change(socket->nodeData->loop, socket, socket->setPoll(UV_READABLE));
|
||||
break;
|
||||
}
|
||||
} else if (sent == SOCKET_ERROR) {
|
||||
if (!netContext->wouldBlock()) {
|
||||
STATE::onEnd((Socket *) p);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
messagePtr->length -= sent;
|
||||
messagePtr->data += sent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
socket->cork(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (events & UV_READABLE) {
|
||||
int length = recv(socket->getFd(), nodeData->recvBuffer, nodeData->recvLength, 0);
|
||||
if (length > 0) {
|
||||
STATE::onData((Socket *) p, nodeData->recvBuffer, length);
|
||||
} else if (length <= 0 || (length == SOCKET_ERROR && !netContext->wouldBlock())) {
|
||||
STATE::onEnd((Socket *) p);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template<class STATE>
|
||||
void setState() {
|
||||
if (ssl) {
|
||||
setCb(sslIoHandler<STATE>);
|
||||
} else {
|
||||
setCb(ioHandler<STATE>);
|
||||
}
|
||||
}
|
||||
|
||||
bool hasEmptyQueue() {
|
||||
return messageQueue.empty();
|
||||
}
|
||||
|
||||
void enqueue(Queue::Message *message) {
|
||||
messageQueue.push(message);
|
||||
}
|
||||
|
||||
Queue::Message *allocMessage(size_t length, const char *data = 0) {
|
||||
Queue::Message *messagePtr = (Queue::Message *) new char[sizeof(Queue::Message) + length];
|
||||
messagePtr->length = length;
|
||||
messagePtr->data = ((char *) messagePtr) + sizeof(Queue::Message);
|
||||
messagePtr->nextMessage = nullptr;
|
||||
|
||||
if (data) {
|
||||
memcpy((char *) messagePtr->data, data, messagePtr->length);
|
||||
}
|
||||
|
||||
return messagePtr;
|
||||
}
|
||||
|
||||
void freeMessage(Queue::Message *message) {
|
||||
delete [] (char *) message;
|
||||
}
|
||||
|
||||
bool write(Queue::Message *message, bool &wasTransferred) {
|
||||
ssize_t sent = 0;
|
||||
if (messageQueue.empty()) {
|
||||
|
||||
if (ssl) {
|
||||
sent = SSL_write(ssl, message->data, message->length);
|
||||
if (sent == (ssize_t) message->length) {
|
||||
wasTransferred = false;
|
||||
return true;
|
||||
} else if (sent < 0) {
|
||||
switch (SSL_get_error(ssl, sent)) {
|
||||
case SSL_ERROR_WANT_READ:
|
||||
break;
|
||||
case SSL_ERROR_WANT_WRITE:
|
||||
if ((getPoll() & UV_WRITABLE) == 0) {
|
||||
setPoll(getPoll() | UV_WRITABLE);
|
||||
changePoll(this);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sent = ::send(getFd(), message->data, message->length, MSG_NOSIGNAL);
|
||||
if (sent == (ssize_t) message->length) {
|
||||
wasTransferred = false;
|
||||
return true;
|
||||
} else if (sent == SOCKET_ERROR) {
|
||||
if (!nodeData->netContext->wouldBlock()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
message->length -= sent;
|
||||
message->data += sent;
|
||||
}
|
||||
|
||||
if ((getPoll() & UV_WRITABLE) == 0) {
|
||||
setPoll(getPoll() | UV_WRITABLE);
|
||||
changePoll(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
messageQueue.push(message);
|
||||
wasTransferred = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class T, class D>
|
||||
void sendTransformed(const char *message, size_t length, void(*callback)(void *socket, void *data, bool cancelled, void *reserved), void *callbackData, D transformData) {
|
||||
size_t estimatedLength = T::estimate(message, length) + sizeof(Queue::Message);
|
||||
|
||||
if (hasEmptyQueue()) {
|
||||
if (estimatedLength <= uS::NodeData::preAllocMaxSize) {
|
||||
int memoryLength = estimatedLength;
|
||||
int memoryIndex = nodeData->getMemoryBlockIndex(memoryLength);
|
||||
|
||||
Queue::Message *messagePtr = (Queue::Message *) nodeData->getSmallMemoryBlock(memoryIndex);
|
||||
messagePtr->data = ((char *) messagePtr) + sizeof(Queue::Message);
|
||||
messagePtr->length = T::transform(message, (char *) messagePtr->data, length, transformData);
|
||||
|
||||
bool wasTransferred;
|
||||
if (write(messagePtr, wasTransferred)) {
|
||||
if (!wasTransferred) {
|
||||
nodeData->freeSmallMemoryBlock((char *) messagePtr, memoryIndex);
|
||||
if (callback) {
|
||||
callback(this, callbackData, false, nullptr);
|
||||
}
|
||||
} else {
|
||||
messagePtr->callback = callback;
|
||||
messagePtr->callbackData = callbackData;
|
||||
}
|
||||
} else {
|
||||
nodeData->freeSmallMemoryBlock((char *) messagePtr, memoryIndex);
|
||||
if (callback) {
|
||||
callback(this, callbackData, true, nullptr);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Queue::Message *messagePtr = allocMessage(estimatedLength - sizeof(Queue::Message));
|
||||
messagePtr->length = T::transform(message, (char *) messagePtr->data, length, transformData);
|
||||
|
||||
bool wasTransferred;
|
||||
if (write(messagePtr, wasTransferred)) {
|
||||
if (!wasTransferred) {
|
||||
freeMessage(messagePtr);
|
||||
if (callback) {
|
||||
callback(this, callbackData, false, nullptr);
|
||||
}
|
||||
} else {
|
||||
messagePtr->callback = callback;
|
||||
messagePtr->callbackData = callbackData;
|
||||
}
|
||||
} else {
|
||||
freeMessage(messagePtr);
|
||||
if (callback) {
|
||||
callback(this, callbackData, true, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Queue::Message *messagePtr = allocMessage(estimatedLength - sizeof(Queue::Message));
|
||||
messagePtr->length = T::transform(message, (char *) messagePtr->data, length, transformData);
|
||||
messagePtr->callback = callback;
|
||||
messagePtr->callbackData = callbackData;
|
||||
enqueue(messagePtr);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
Socket(NodeData *nodeData, Loop *loop, uv_os_sock_t fd, SSL *ssl) : Poll(loop, fd), ssl(ssl), nodeData(nodeData) {
|
||||
if (ssl) {
|
||||
// OpenSSL treats SOCKETs as int
|
||||
SSL_set_fd(ssl, (int) fd);
|
||||
SSL_set_mode(ssl, SSL_MODE_RELEASE_BUFFERS);
|
||||
}
|
||||
}
|
||||
|
||||
NodeData *getNodeData() {
|
||||
return nodeData;
|
||||
}
|
||||
|
||||
Poll *next = nullptr, *prev = nullptr;
|
||||
|
||||
void *getUserData() {
|
||||
return user;
|
||||
}
|
||||
|
||||
void setUserData(void *user) {
|
||||
this->user = user;
|
||||
}
|
||||
|
||||
struct Address {
|
||||
unsigned int port;
|
||||
const char *address;
|
||||
const char *family;
|
||||
};
|
||||
|
||||
Address getAddress();
|
||||
|
||||
void setNoDelay(int enable) {
|
||||
setsockopt(getFd(), IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
|
||||
}
|
||||
|
||||
void cork(int enable) {
|
||||
#if defined(TCP_CORK)
|
||||
// Linux & SmartOS have proper TCP_CORK
|
||||
setsockopt(getFd(), IPPROTO_TCP, TCP_CORK, &enable, sizeof(int));
|
||||
#elif defined(TCP_NOPUSH)
|
||||
// Mac OS X & FreeBSD have TCP_NOPUSH
|
||||
setsockopt(getFd(), IPPROTO_TCP, TCP_NOPUSH, &enable, sizeof(int));
|
||||
if (!enable) {
|
||||
// Tested on OS X, FreeBSD situation is unclear
|
||||
::send(getFd(), "", 0, MSG_NOSIGNAL);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
if (ssl) {
|
||||
//todo: poll in/out - have the io_cb recall shutdown if failed
|
||||
SSL_shutdown(ssl);
|
||||
} else {
|
||||
::shutdown(getFd(), SHUT_WR);
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void closeSocket() {
|
||||
uv_os_sock_t fd = getFd();
|
||||
Context *netContext = nodeData->netContext;
|
||||
stop(nodeData->loop);
|
||||
netContext->closeSocket(fd);
|
||||
|
||||
if (ssl) {
|
||||
SSL_free(ssl);
|
||||
}
|
||||
|
||||
Poll::close(nodeData->loop, [](Poll *p) {
|
||||
delete (T *) p;
|
||||
});
|
||||
}
|
||||
|
||||
bool isShuttingDown() {
|
||||
return state.shuttingDown;
|
||||
}
|
||||
|
||||
friend class Node;
|
||||
friend struct NodeData;
|
||||
};
|
||||
|
||||
struct ListenSocket : Socket {
|
||||
|
||||
ListenSocket(NodeData *nodeData, Loop *loop, uv_os_sock_t fd, SSL *ssl) : Socket(nodeData, loop, fd, ssl) {
|
||||
|
||||
}
|
||||
|
||||
Timer *timer = nullptr;
|
||||
uS::TLS::Context sslContext;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // SOCKET_UWS_H
|
|
@ -0,0 +1,89 @@
|
|||
#ifndef WEBSOCKET_UWS_H
|
||||
#define WEBSOCKET_UWS_H
|
||||
|
||||
#include "WebSocketProtocol.h"
|
||||
#include "Socket.h"
|
||||
|
||||
namespace uWS {
|
||||
|
||||
template <bool isServer>
|
||||
struct Group;
|
||||
|
||||
template <bool isServer>
|
||||
struct HttpSocket;
|
||||
|
||||
template <const bool isServer>
|
||||
struct WIN32_EXPORT WebSocket : uS::Socket, WebSocketState<isServer> {
|
||||
protected:
|
||||
std::string fragmentBuffer;
|
||||
enum CompressionStatus : char {
|
||||
DISABLED,
|
||||
ENABLED,
|
||||
COMPRESSED_FRAME
|
||||
} compressionStatus;
|
||||
unsigned char controlTipLength = 0, hasOutstandingPong = false;
|
||||
|
||||
WebSocket(bool perMessageDeflate, uS::Socket *socket) : uS::Socket(std::move(*socket)) {
|
||||
compressionStatus = perMessageDeflate ? CompressionStatus::ENABLED : CompressionStatus::DISABLED;
|
||||
}
|
||||
|
||||
static uS::Socket *onData(uS::Socket *s, char *data, size_t length);
|
||||
static void onEnd(uS::Socket *s);
|
||||
using uS::Socket::closeSocket;
|
||||
|
||||
static bool refusePayloadLength(uint64_t length, WebSocketState<isServer> *webSocketState) {
|
||||
WebSocket<isServer> *webSocket = static_cast<WebSocket<isServer> *>(webSocketState);
|
||||
return length > Group<isServer>::from(webSocket)->maxPayload;
|
||||
}
|
||||
|
||||
static bool setCompressed(WebSocketState<isServer> *webSocketState) {
|
||||
WebSocket<isServer> *webSocket = static_cast<WebSocket<isServer> *>(webSocketState);
|
||||
|
||||
if (webSocket->compressionStatus == WebSocket<isServer>::CompressionStatus::ENABLED) {
|
||||
webSocket->compressionStatus = WebSocket<isServer>::CompressionStatus::COMPRESSED_FRAME;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void forceClose(WebSocketState<isServer> *webSocketState) {
|
||||
WebSocket<isServer> *webSocket = static_cast<WebSocket<isServer> *>(webSocketState);
|
||||
webSocket->terminate();
|
||||
}
|
||||
|
||||
static bool handleFragment(char *data, size_t length, unsigned int remainingBytes, int opCode, bool fin, WebSocketState<isServer> *webSocketState);
|
||||
|
||||
public:
|
||||
struct PreparedMessage {
|
||||
char *buffer;
|
||||
size_t length;
|
||||
int references;
|
||||
void(*callback)(void *webSocket, void *data, bool cancelled, void *reserved);
|
||||
};
|
||||
|
||||
// Not thread safe
|
||||
void sendPrepared(PreparedMessage *preparedMessage, void *callbackData = nullptr);
|
||||
static void finalizeMessage(PreparedMessage *preparedMessage);
|
||||
void close(int code = 1000, const char *message = nullptr, size_t length = 0);
|
||||
void transfer(Group<isServer> *group);
|
||||
|
||||
// Thread safe
|
||||
void terminate();
|
||||
void ping(const char *message) {send(message, OpCode::PING);}
|
||||
void send(const char *message, OpCode opCode = OpCode::TEXT) {send(message, strlen(message), opCode);}
|
||||
void send(const char *message, size_t length, OpCode opCode, void(*callback)(WebSocket<isServer> *webSocket, void *data, bool cancelled, void *reserved) = nullptr, void *callbackData = nullptr);
|
||||
static PreparedMessage *prepareMessage(char *data, size_t length, OpCode opCode, bool compressed, void(*callback)(WebSocket<isServer> *webSocket, void *data, bool cancelled, void *reserved) = nullptr);
|
||||
static PreparedMessage *prepareMessageBatch(std::vector<std::string> &messages, std::vector<int> &excludedMessages,
|
||||
OpCode opCode, bool compressed, void(*callback)(WebSocket<isServer> *webSocket, void *data, bool cancelled, void *reserved) = nullptr);
|
||||
|
||||
friend struct Hub;
|
||||
friend struct Group<isServer>;
|
||||
friend struct HttpSocket<isServer>;
|
||||
friend struct uS::Socket;
|
||||
friend class WebSocketProtocol<isServer, WebSocket<isServer>>;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // WEBSOCKET_UWS_H
|
|
@ -0,0 +1,377 @@
|
|||
#ifndef WEBSOCKETPROTOCOL_UWS_H
|
||||
#define WEBSOCKETPROTOCOL_UWS_H
|
||||
|
||||
// we do need to include this for htobe64, should be moved from networking!
|
||||
#include "Networking.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace uWS {
|
||||
|
||||
enum OpCode : unsigned char {
|
||||
TEXT = 1,
|
||||
BINARY = 2,
|
||||
CLOSE = 8,
|
||||
PING = 9,
|
||||
PONG = 10
|
||||
};
|
||||
|
||||
enum {
|
||||
CLIENT,
|
||||
SERVER
|
||||
};
|
||||
|
||||
// 24 bytes perfectly
|
||||
template <bool isServer>
|
||||
struct WebSocketState {
|
||||
public:
|
||||
static const unsigned int SHORT_MESSAGE_HEADER = isServer ? 6 : 2;
|
||||
static const unsigned int MEDIUM_MESSAGE_HEADER = isServer ? 8 : 4;
|
||||
static const unsigned int LONG_MESSAGE_HEADER = isServer ? 14 : 10;
|
||||
|
||||
// 16 bytes
|
||||
struct State {
|
||||
unsigned int wantsHead : 1;
|
||||
unsigned int spillLength : 4;
|
||||
int opStack : 2; // -1, 0, 1
|
||||
unsigned int lastFin : 1;
|
||||
|
||||
// 15 bytes
|
||||
unsigned char spill[LONG_MESSAGE_HEADER - 1];
|
||||
OpCode opCode[2];
|
||||
|
||||
State() {
|
||||
wantsHead = true;
|
||||
spillLength = 0;
|
||||
opStack = -1;
|
||||
lastFin = true;
|
||||
}
|
||||
|
||||
} state;
|
||||
|
||||
// 8 bytes
|
||||
unsigned int remainingBytes = 0;
|
||||
char mask[isServer ? 4 : 1];
|
||||
};
|
||||
|
||||
template <const bool isServer, class Impl>
|
||||
class WIN32_EXPORT WebSocketProtocol {
|
||||
public:
|
||||
static const unsigned int SHORT_MESSAGE_HEADER = isServer ? 6 : 2;
|
||||
static const unsigned int MEDIUM_MESSAGE_HEADER = isServer ? 8 : 4;
|
||||
static const unsigned int LONG_MESSAGE_HEADER = isServer ? 14 : 10;
|
||||
|
||||
private:
|
||||
static inline bool isFin(char *frame) {return *((unsigned char *) frame) & 128;}
|
||||
static inline unsigned char getOpCode(char *frame) {return *((unsigned char *) frame) & 15;}
|
||||
static inline unsigned char payloadLength(char *frame) {return ((unsigned char *) frame)[1] & 127;}
|
||||
static inline bool rsv23(char *frame) {return *((unsigned char *) frame) & 48;}
|
||||
static inline bool rsv1(char *frame) {return *((unsigned char *) frame) & 64;}
|
||||
|
||||
static inline void unmaskImprecise(char *dst, char *src, char *mask, unsigned int length) {
|
||||
for (unsigned int n = (length >> 2) + 1; n; n--) {
|
||||
*(dst++) = *(src++) ^ mask[0];
|
||||
*(dst++) = *(src++) ^ mask[1];
|
||||
*(dst++) = *(src++) ^ mask[2];
|
||||
*(dst++) = *(src++) ^ mask[3];
|
||||
}
|
||||
}
|
||||
|
||||
static inline void unmaskImpreciseCopyMask(char *dst, char *src, char *maskPtr, unsigned int length) {
|
||||
char mask[4] = {maskPtr[0], maskPtr[1], maskPtr[2], maskPtr[3]};
|
||||
unmaskImprecise(dst, src, mask, length);
|
||||
}
|
||||
|
||||
static inline void rotateMask(unsigned int offset, char *mask) {
|
||||
char originalMask[4] = {mask[0], mask[1], mask[2], mask[3]};
|
||||
mask[(0 + offset) % 4] = originalMask[0];
|
||||
mask[(1 + offset) % 4] = originalMask[1];
|
||||
mask[(2 + offset) % 4] = originalMask[2];
|
||||
mask[(3 + offset) % 4] = originalMask[3];
|
||||
}
|
||||
|
||||
static inline void unmaskInplace(char *data, char *stop, char *mask) {
|
||||
while (data < stop) {
|
||||
*(data++) ^= mask[0];
|
||||
*(data++) ^= mask[1];
|
||||
*(data++) ^= mask[2];
|
||||
*(data++) ^= mask[3];
|
||||
}
|
||||
}
|
||||
|
||||
enum {
|
||||
SND_CONTINUATION = 1,
|
||||
SND_NO_FIN = 2,
|
||||
SND_COMPRESSED = 64
|
||||
};
|
||||
|
||||
template <unsigned int MESSAGE_HEADER, typename T>
|
||||
static inline bool consumeMessage(T payLength, char *&src, unsigned int &length, WebSocketState<isServer> *wState) {
|
||||
if (getOpCode(src)) {
|
||||
if (wState->state.opStack == 1 || (!wState->state.lastFin && getOpCode(src) < 2)) {
|
||||
Impl::forceClose(wState);
|
||||
return true;
|
||||
}
|
||||
wState->state.opCode[++wState->state.opStack] = (OpCode) getOpCode(src);
|
||||
} else if (wState->state.opStack == -1) {
|
||||
Impl::forceClose(wState);
|
||||
return true;
|
||||
}
|
||||
wState->state.lastFin = isFin(src);
|
||||
|
||||
if (Impl::refusePayloadLength(payLength, wState)) {
|
||||
Impl::forceClose(wState);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (payLength + MESSAGE_HEADER <= length) {
|
||||
if (isServer) {
|
||||
unmaskImpreciseCopyMask(src + MESSAGE_HEADER - 4, src + MESSAGE_HEADER, src + MESSAGE_HEADER - 4, payLength);
|
||||
if (Impl::handleFragment(src + MESSAGE_HEADER - 4, payLength, 0, wState->state.opCode[wState->state.opStack], isFin(src), wState)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (Impl::handleFragment(src + MESSAGE_HEADER, payLength, 0, wState->state.opCode[wState->state.opStack], isFin(src), wState)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isFin(src)) {
|
||||
wState->state.opStack--;
|
||||
}
|
||||
|
||||
src += payLength + MESSAGE_HEADER;
|
||||
length -= payLength + MESSAGE_HEADER;
|
||||
wState->state.spillLength = 0;
|
||||
return false;
|
||||
} else {
|
||||
wState->state.spillLength = 0;
|
||||
wState->state.wantsHead = false;
|
||||
wState->remainingBytes = payLength - length + MESSAGE_HEADER;
|
||||
bool fin = isFin(src);
|
||||
if (isServer) {
|
||||
memcpy(wState->mask, src + MESSAGE_HEADER - 4, 4);
|
||||
unmaskImprecise(src, src + MESSAGE_HEADER, wState->mask, length - MESSAGE_HEADER);
|
||||
rotateMask(4 - (length - MESSAGE_HEADER) % 4, wState->mask);
|
||||
} else {
|
||||
src += MESSAGE_HEADER;
|
||||
}
|
||||
Impl::handleFragment(src, length - MESSAGE_HEADER, wState->remainingBytes, wState->state.opCode[wState->state.opStack], fin, wState);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool consumeContinuation(char *&src, unsigned int &length, WebSocketState<isServer> *wState) {
|
||||
if (wState->remainingBytes <= length) {
|
||||
if (isServer) {
|
||||
int n = wState->remainingBytes >> 2;
|
||||
unmaskInplace(src, src + n * 4, wState->mask);
|
||||
for (int i = 0, s = wState->remainingBytes % 4; i < s; i++) {
|
||||
src[n * 4 + i] ^= wState->mask[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (Impl::handleFragment(src, wState->remainingBytes, 0, wState->state.opCode[wState->state.opStack], wState->state.lastFin, wState)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (wState->state.lastFin) {
|
||||
wState->state.opStack--;
|
||||
}
|
||||
|
||||
src += wState->remainingBytes;
|
||||
length -= wState->remainingBytes;
|
||||
wState->state.wantsHead = true;
|
||||
return true;
|
||||
} else {
|
||||
if (isServer) {
|
||||
unmaskInplace(src, src + ((length >> 2) + 1) * 4, wState->mask);
|
||||
}
|
||||
|
||||
wState->remainingBytes -= length;
|
||||
if (Impl::handleFragment(src, length, wState->remainingBytes, wState->state.opCode[wState->state.opStack], wState->state.lastFin, wState)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isServer && length % 4) {
|
||||
rotateMask(4 - (length % 4), wState->mask);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
WebSocketProtocol() {
|
||||
|
||||
}
|
||||
|
||||
// Based on utf8_check.c by Markus Kuhn, 2005
|
||||
// https://www.cl.cam.ac.uk/~mgk25/ucs/utf8_check.c
|
||||
// Optimized for predominantly 7-bit content by Alex Hultman, 2016
|
||||
// Licensed as Zlib, like the rest of this project
|
||||
static bool isValidUtf8(unsigned char *s, size_t length)
|
||||
{
|
||||
for (unsigned char *e = s + length; s != e; ) {
|
||||
if (s + 4 <= e && ((*(uint32_t *) s) & 0x80808080) == 0) {
|
||||
s += 4;
|
||||
} else {
|
||||
while (!(*s & 0x80)) {
|
||||
if (++s == e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ((s[0] & 0x60) == 0x40) {
|
||||
if (s + 1 >= e || (s[1] & 0xc0) != 0x80 || (s[0] & 0xfe) == 0xc0) {
|
||||
return false;
|
||||
}
|
||||
s += 2;
|
||||
} else if ((s[0] & 0xf0) == 0xe0) {
|
||||
if (s + 2 >= e || (s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 ||
|
||||
(s[0] == 0xe0 && (s[1] & 0xe0) == 0x80) || (s[0] == 0xed && (s[1] & 0xe0) == 0xa0)) {
|
||||
return false;
|
||||
}
|
||||
s += 3;
|
||||
} else if ((s[0] & 0xf8) == 0xf0) {
|
||||
if (s + 3 >= e || (s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 || (s[3] & 0xc0) != 0x80 ||
|
||||
(s[0] == 0xf0 && (s[1] & 0xf0) == 0x80) || (s[0] == 0xf4 && s[1] > 0x8f) || s[0] > 0xf4) {
|
||||
return false;
|
||||
}
|
||||
s += 4;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
struct CloseFrame {
|
||||
uint16_t code;
|
||||
char *message;
|
||||
size_t length;
|
||||
};
|
||||
|
||||
static inline CloseFrame parseClosePayload(char *src, size_t length) {
|
||||
CloseFrame cf = {};
|
||||
if (length >= 2) {
|
||||
memcpy(&cf.code, src, 2);
|
||||
cf = {ntohs(cf.code), src + 2, length - 2};
|
||||
if (cf.code < 1000 || cf.code > 4999 || (cf.code > 1011 && cf.code < 4000) ||
|
||||
(cf.code >= 1004 && cf.code <= 1006) || !isValidUtf8((unsigned char *) cf.message, cf.length)) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
return cf;
|
||||
}
|
||||
|
||||
static inline size_t formatClosePayload(char *dst, uint16_t code, const char *message, size_t length) {
|
||||
if (code) {
|
||||
code = htons(code);
|
||||
memcpy(dst, &code, 2);
|
||||
memcpy(dst + 2, message, length);
|
||||
return length + 2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline size_t formatMessage(char *dst, const char *src, size_t length, OpCode opCode, size_t reportedLength, bool compressed) {
|
||||
size_t messageLength;
|
||||
size_t headerLength;
|
||||
if (reportedLength < 126) {
|
||||
headerLength = 2;
|
||||
dst[1] = reportedLength;
|
||||
} else if (reportedLength <= UINT16_MAX) {
|
||||
headerLength = 4;
|
||||
dst[1] = 126;
|
||||
*((uint16_t *) &dst[2]) = htons(reportedLength);
|
||||
} else {
|
||||
headerLength = 10;
|
||||
dst[1] = 127;
|
||||
*((uint64_t *) &dst[2]) = htobe64(reportedLength);
|
||||
}
|
||||
|
||||
int flags = 0;
|
||||
dst[0] = (flags & SND_NO_FIN ? 0 : 128) | (compressed ? SND_COMPRESSED : 0);
|
||||
if (!(flags & SND_CONTINUATION)) {
|
||||
dst[0] |= opCode;
|
||||
}
|
||||
|
||||
char mask[4];
|
||||
if (!isServer) {
|
||||
dst[1] |= 0x80;
|
||||
uint32_t random = rand();
|
||||
memcpy(mask, &random, 4);
|
||||
memcpy(dst + headerLength, &random, 4);
|
||||
headerLength += 4;
|
||||
}
|
||||
|
||||
messageLength = headerLength + length;
|
||||
memcpy(dst + headerLength, src, length);
|
||||
|
||||
if (!isServer) {
|
||||
|
||||
// overwrites up to 3 bytes outside of the given buffer!
|
||||
//WebSocketProtocol<isServer>::unmaskInplace(dst + headerLength, dst + headerLength + length, mask);
|
||||
|
||||
// this is not optimal
|
||||
char *start = dst + headerLength;
|
||||
char *stop = start + length;
|
||||
int i = 0;
|
||||
while (start != stop) {
|
||||
(*start++) ^= mask[i++ % 4];
|
||||
}
|
||||
}
|
||||
return messageLength;
|
||||
}
|
||||
|
||||
static inline void consume(char *src, unsigned int length, WebSocketState<isServer> *wState) {
|
||||
if (wState->state.spillLength) {
|
||||
src -= wState->state.spillLength;
|
||||
length += wState->state.spillLength;
|
||||
memcpy(src, wState->state.spill, wState->state.spillLength);
|
||||
}
|
||||
if (wState->state.wantsHead) {
|
||||
parseNext:
|
||||
while (length >= SHORT_MESSAGE_HEADER) {
|
||||
|
||||
// invalid reserved bits / invalid opcodes / invalid control frames / set compressed frame
|
||||
if ((rsv1(src) && !Impl::setCompressed(wState)) || rsv23(src) || (getOpCode(src) > 2 && getOpCode(src) < 8) ||
|
||||
getOpCode(src) > 10 || (getOpCode(src) > 2 && (!isFin(src) || payloadLength(src) > 125))) {
|
||||
Impl::forceClose(wState);
|
||||
return;
|
||||
}
|
||||
|
||||
if (payloadLength(src) < 126) {
|
||||
if (consumeMessage<SHORT_MESSAGE_HEADER, uint8_t>(payloadLength(src), src, length, wState)) {
|
||||
return;
|
||||
}
|
||||
} else if (payloadLength(src) == 126) {
|
||||
if (length < MEDIUM_MESSAGE_HEADER) {
|
||||
break;
|
||||
} else if(consumeMessage<MEDIUM_MESSAGE_HEADER, uint16_t>(ntohs(*(uint16_t *) &src[2]), src, length, wState)) {
|
||||
return;
|
||||
}
|
||||
} else if (length < LONG_MESSAGE_HEADER) {
|
||||
break;
|
||||
} else if (consumeMessage<LONG_MESSAGE_HEADER, uint64_t>(be64toh(*(uint64_t *) &src[2]), src, length, wState)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (length) {
|
||||
memcpy(wState->state.spill, src, length);
|
||||
wState->state.spillLength = length;
|
||||
}
|
||||
} else if (consumeContinuation(src, length, wState)) {
|
||||
goto parseNext;
|
||||
}
|
||||
}
|
||||
|
||||
static const int CONSUME_POST_PADDING = 4;
|
||||
static const int CONSUME_PRE_PADDING = LONG_MESSAGE_HEADER - 1;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // WEBSOCKETPROTOCOL_UWS_H
|
|
@ -0,0 +1,6 @@
|
|||
#ifndef UWS_UWS_H
|
||||
#define UWS_UWS_H
|
||||
|
||||
#include "Hub.h"
|
||||
|
||||
#endif // UWS_UWS_H
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -21,7 +21,7 @@ define Package/$(PKG_NAME)
|
|||
CATEGORY:=Netease
|
||||
TITLE:=Netease voice main program
|
||||
MAINTAINER:=Wang zijiao <hzwangzijiao@corp.netease.com>
|
||||
DEPENDS:= +libjansson +libspeex +alsa-lib +libuws +libjson-c +libyunxin +libcutils $(MAKE_COMMON_DEPEND)
|
||||
DEPENDS:= +libduilite +libjansson +libspeex +alsa-lib +libuws +libjson-c +libyunxin +libcutils $(MAKE_COMMON_DEPEND)
|
||||
|
||||
ifeq ('$(CONFIG_XUNFEI_MSC_SDK)', 'y')
|
||||
DEPENDS+=+libmsc
|
||||
|
|
|
@ -2329,7 +2329,7 @@ CONFIG_PACKAGE_bluez-libs=y
|
|||
# CONFIG_PACKAGE_libbfd is not set
|
||||
# CONFIG_PACKAGE_libblkid is not set
|
||||
CONFIG_PACKAGE_libblobmsg-json=y
|
||||
# CONFIG_PACKAGE_libcae is not set
|
||||
CONFIG_PACKAGE_libcae=y
|
||||
# CONFIG_PACKAGE_libcap is not set
|
||||
# CONFIG_PACKAGE_libcares is not set
|
||||
# CONFIG_PACKAGE_libcharset is not set
|
||||
|
@ -2387,7 +2387,7 @@ CONFIG_LIBCURL_PROXY=y
|
|||
# CONFIG_PACKAGE_libdb47xx is not set
|
||||
# CONFIG_PACKAGE_libdevmapper is not set
|
||||
# CONFIG_PACKAGE_libdrm is not set
|
||||
# CONFIG_PACKAGE_libduilite is not set
|
||||
CONFIG_PACKAGE_libduilite=y
|
||||
# CONFIG_PACKAGE_libelf1 is not set
|
||||
CONFIG_PACKAGE_libdbus=y
|
||||
# CONFIG_PACKAGE_libepoxy is not set
|
||||
|
@ -2427,7 +2427,7 @@ CONFIG_PACKAGE_libiconv-full=y
|
|||
# CONFIG_PACKAGE_libintl-full is not set
|
||||
# CONFIG_PACKAGE_libiw is not set
|
||||
# CONFIG_PACKAGE_libiwinfo is not set
|
||||
# CONFIG_PACKAGE_libjansson is not set
|
||||
CONFIG_PACKAGE_libjansson=y
|
||||
# CONFIG_PACKAGE_libjpeg is not set
|
||||
CONFIG_PACKAGE_libjson-c=y
|
||||
# CONFIG_PACKAGE_libkmod is not set
|
||||
|
@ -2451,9 +2451,9 @@ CONFIG_PACKAGE_libjson-c=y
|
|||
# CONFIG_PACKAGE_libmysqlclient-r is not set
|
||||
CONFIG_PACKAGE_libncurses=y
|
||||
CONFIG_PACKAGE_libncursesw=y
|
||||
# CONFIG_PACKAGE_libneteasedc is not set
|
||||
CONFIG_PACKAGE_libneteasedc=y
|
||||
# CONFIG_PACKAGE_libneteasevoice is not set
|
||||
# CONFIG_PACKAGE_libneteasevoicews is not set
|
||||
CONFIG_PACKAGE_libneteasevoicews=y
|
||||
# CONFIG_PACKAGE_libnetfilter-conntrack is not set
|
||||
# CONFIG_PACKAGE_libnetfilter-cthelper is not set
|
||||
# CONFIG_PACKAGE_libnetfilter-cttimeout is not set
|
||||
|
@ -2532,6 +2532,7 @@ CONFIG_PACKAGE_libuclient=y
|
|||
# CONFIG_PACKAGE_libustream-openssl is not set
|
||||
CONFIG_PACKAGE_libuuid=y
|
||||
CONFIG_PACKAGE_libuv=y
|
||||
CONFIG_PACKAGE_libuws=y
|
||||
# CONFIG_PACKAGE_libv4l is not set
|
||||
# CONFIG_PACKAGE_libvorbis is not set
|
||||
# CONFIG_PACKAGE_libvorbisidec is not set
|
||||
|
@ -2543,7 +2544,7 @@ CONFIG_PACKAGE_libuv=y
|
|||
# CONFIG_PACKAGE_libxkbcommon is not set
|
||||
# CONFIG_PACKAGE_libxml2 is not set
|
||||
# CONFIG_PACKAGE_libxslt is not set
|
||||
# CONFIG_PACKAGE_libyunxin is not set
|
||||
CONFIG_PACKAGE_libyunxin=y
|
||||
# CONFIG_PACKAGE_libzmq-curve is not set
|
||||
# CONFIG_PACKAGE_libzmq-nc is not set
|
||||
# CONFIG_PACKAGE_linux-atm is not set
|
||||
|
@ -2785,7 +2786,20 @@ CONFIG_RES_NORMAL_MODE=y
|
|||
# CONFIG_PACKAGE_mcu_ota is not set
|
||||
CONFIG_PACKAGE_netease_control_center=y
|
||||
# CONFIG_PACKAGE_netease_test is not set
|
||||
# CONFIG_PACKAGE_netease_voice is not set
|
||||
CONFIG_PACKAGE_netease_voice=y
|
||||
|
||||
#
|
||||
# SDK Select
|
||||
#
|
||||
CONFIG_NETEASE_MSC_SDK=y
|
||||
# CONFIG_XUNFEI_MSC_SDK is not set
|
||||
# CONFIG_NETEASE_CAE_SDK is not set
|
||||
CONFIG_XUNFEI_CAE_SDK=y
|
||||
CONFIG_NETEASE_DUILITE_SDK=y
|
||||
CONFIG_NETEASE_TTS_SDK=y
|
||||
# CONFIG_XUNFEI_TTS_SDK is not set
|
||||
# CONFIG_USED_NONE is not set
|
||||
CONFIG_USED_DC_SDK=y
|
||||
# CONFIG_PACKAGE_ntes_record is not set
|
||||
CONFIG_PACKAGE_ota=y
|
||||
CONFIG_PACKAGE_pv1res=y
|
||||
|
|
Loading…
Reference in New Issue