From 9754372d5e4125bf5850d9cd3ae93d529efdef8d Mon Sep 17 00:00:00 2001 From: "Nicolas \"Pixel\" Noble" Date: Sat, 21 Dec 2013 18:32:27 -0800 Subject: Preliminary WebSocket protocol support. --- includes/BString.h | 2 ++ includes/BWebSocket.h | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++ includes/Base64.h | 19 ++++++++++++++++ includes/Http.h | 1 + includes/HttpServer.h | 2 ++ includes/SHA1.h | 24 ++++++++++++++++++++ includes/Selectable.h | 5 ++++- includes/Task.h | 7 +++++- 8 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 includes/BWebSocket.h create mode 100644 includes/Base64.h create mode 100644 includes/SHA1.h (limited to 'includes') diff --git a/includes/BString.h b/includes/BString.h index a4178a4..e783935 100644 --- a/includes/BString.h +++ b/includes/BString.h @@ -117,6 +117,8 @@ class String : private std::string { const char & operator[](ssize_t i) const { if (i < 0) i = strlen() + i; return at(i); } char & operator[](ssize_t i) { if (i < 0) i = strlen() + i; return at(i); } + + void reserve(size_t s) { std::string::reserve(s); } }; }; diff --git a/includes/BWebSocket.h b/includes/BWebSocket.h new file mode 100644 index 0000000..41e5a7d --- /dev/null +++ b/includes/BWebSocket.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include +#include + +namespace Balau { + +class WebSocketActionBase; + +class WebSocketWorker : public StacklessTask { + public: + virtual bool parse(Http::Request & req) { return true; } + protected: + WebSocketWorker(IO socket, const String & url) : m_socket(new BStream(socket)) { m_name = String("WebSocket:") + url + "/" + m_socket->getName(); } + ~WebSocketWorker() { free(m_payload); } + private: + void processMessage(); + const char * getName() const { return m_name.to_charp(); } + void Do(); + String m_name; + IO m_socket; + enum { + READ_H, + READ_PLB, + READ_PLL, + READ_MK, + READ_PL, + } m_status = READ_H; + enum { MAX_WEBSOCKET_LIMIT = 4 * 1024 * 1024 }; + uint8_t * m_payload = NULL; + uint64_t m_payloadLen; + uint64_t m_totalLen; + uint64_t m_remainingBytes; + uint32_t m_mask; + uint8_t m_opcode; + bool m_hasMask; + bool m_fin; + bool m_firstFragment = true; + bool m_enforceServer = false; + bool m_enforceClient = false; + friend class WebSocketActionBase; +}; + +class WebSocketServerBase : public HttpServer::Action { + protected: + WebSocketServerBase(const Regex & regex) : Action(regex) { } + virtual WebSocketWorker * spawnWorker(IO socket, const String & url) = 0; + private: + void sendError(IO out, const char * serverName); + bool Do(HttpServer * server, Http::Request & req, HttpServer::Action::ActionMatch & match, IO out) throw (GeneralException); +}; + +template +class WebSocketServer : public WebSocketServerBase { + protected: + WebSocketServer(const Regex & regex) : WebSocketServerBase(regex) { } + virtual WebSocketWorker * spawnWorker(IO socket, const String & url) { return new T(socket, url); } +}; + +}; diff --git a/includes/Base64.h b/includes/Base64.h new file mode 100644 index 0000000..d039c73 --- /dev/null +++ b/includes/Base64.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +namespace Balau { + +class Base64 { +public: + static String encode(const uint8_t * data, int len); + static int decode(const String & str_in, uint8_t * data_out); + static const double ratio; + +private: + static void encode_block(unsigned char in_tab[3], int len, char out[4]); + static int stri(char); + static int decode_block(char s1, char s2, char s3, char s4, unsigned char * out_tab); +}; + +}; diff --git a/includes/Http.h b/includes/Http.h index 7c351a6..fae2ad9 100644 --- a/includes/Http.h +++ b/includes/Http.h @@ -27,6 +27,7 @@ struct Request { StringMap headers; FileList files; bool persistent; + bool upgrade; String version; }; diff --git a/includes/HttpServer.h b/includes/HttpServer.h index d044e92..683390c 100644 --- a/includes/HttpServer.h +++ b/includes/HttpServer.h @@ -24,6 +24,7 @@ class HttpServer { Response(HttpServer * server, Http::Request req, IO out) : m_server(server), m_req(req), m_out(out), m_buffer(new Buffer()), m_responseCode(200), m_type("text/html; charset=UTF-8"), m_flushed(false) { } void SetResponseCode(int code) { m_responseCode = code; } void SetContentType(const String & type) { m_type = type; } + void setNoSize() { m_noSize = true; } IO get() { return m_buffer; } IO operator->() { return m_buffer; } void Flush(); @@ -39,6 +40,7 @@ class HttpServer { String m_type; std::list m_extraHeaders; bool m_flushed; + bool m_noSize = false; Response(const Response &) = delete; Response & operator=(const Response &) = delete; diff --git a/includes/SHA1.h b/includes/SHA1.h new file mode 100644 index 0000000..f70f6b7 --- /dev/null +++ b/includes/SHA1.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +namespace Balau { + +class SHA1 { + public: + SHA1() { reset(); } + void reset(); + void update(const uint8_t* data, const size_t len); + void final(uint8_t * digest); + + enum { DIGEST_SIZE = 20 }; + + private: + void transform(uint32_t state[5], const uint8_t buffer[64]); + + uint32_t m_state[5]; + uint32_t m_count[2]; + uint8_t m_buffer[64]; +}; + +}; diff --git a/includes/Selectable.h b/includes/Selectable.h index b3b5b8c..8667415 100644 --- a/includes/Selectable.h +++ b/includes/Selectable.h @@ -24,14 +24,17 @@ class Selectable : public Handle { class SelectableEvent : public Events::BaseEvent { public: - SelectableEvent(int fd, int evt = ev::READ | ev::WRITE) : m_task(NULL) { Printer::elog(E_SELECT, "Got a new SelectableEvent at %p", this); m_evt.set(this); m_evt.set(fd, evt); } + SelectableEvent(int fd, int evt = ev::READ | ev::WRITE) : m_task(NULL), m_evtType(evt), m_fd(fd) { Printer::elog(E_SELECT, "Got a new SelectableEvent at %p", this); m_evt.set(this); m_evt.set(fd, evt); } virtual ~SelectableEvent() { Printer::elog(E_SELECT, "Destroying a SelectableEvent at %p", this); m_evt.stop(); } void stop() { Printer::elog(E_SELECT, "Stopping a SelectableEvent at %p", this); reset(); m_evt.stop(); } private: void evt_cb(ev::io & w, int revents) { Printer::elog(E_SELECT, "Got a libev callback on a SelectableEvent at %p", this); doSignal(); } virtual void gotOwner(Task * task); + virtual bool relaxed() { return true; } ev::io m_evt; + int m_evtType; + int m_fd; Task * m_task = NULL; }; diff --git a/includes/Task.h b/includes/Task.h index 1041d08..7521b9f 100644 --- a/includes/Task.h +++ b/includes/Task.h @@ -50,6 +50,10 @@ class BaseEvent { virtual ~BaseEvent() { if (m_cb) delete m_cb; } bool gotSignal() { return m_signal; } void doSignal(); + void resetMaybe() { + if (m_task) + reset(); + } void reset() { // could be potentially changed into a simple return AAssert(m_task != NULL, "Can't reset an event that doesn't have a task"); @@ -60,12 +64,13 @@ class BaseEvent { void registerOwner(Task * task) { if (m_task == task) return; - AAssert(m_task == NULL, "Can't register an event for another task"); + AAssert(m_task == NULL || relaxed(), "Can't register an event for another task"); m_task = task; gotOwner(task); } protected: virtual void gotOwner(Task * task) { } + virtual bool relaxed() { return false; } private: Callback * m_cb = NULL; bool m_signal = false; -- cgit v1.2.3