Merge commit 'c992cb4e42cc223f67ede0e48d7ff3f4947af0c6' as 'test/compatibility/C/uWebSockets'
This commit is contained in:
		
							
								
								
									
										348
									
								
								test/compatibility/C/uWebSockets/src/App.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										348
									
								
								test/compatibility/C/uWebSockets/src/App.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,348 @@ | ||||
| /* | ||||
|  * Authored by Alex Hultman, 2018-2019. | ||||
|  * Intellectual property of third-party. | ||||
|  | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #ifndef UWS_APP_H | ||||
| #define UWS_APP_H | ||||
|  | ||||
| /* An app is a convenience wrapper of some of the most used fuctionalities and allows a | ||||
|  * builder-pattern kind of init. Apps operate on the implicit thread local Loop */ | ||||
|  | ||||
| #include "HttpContext.h" | ||||
| #include "HttpResponse.h" | ||||
| #include "WebSocketContext.h" | ||||
| #include "WebSocket.h" | ||||
| #include "WebSocketExtensions.h" | ||||
| #include "WebSocketHandshake.h" | ||||
|  | ||||
| namespace uWS { | ||||
|  | ||||
| /* Compress options (really more like PerMessageDeflateOptions) */ | ||||
| enum CompressOptions { | ||||
|     /* Compression disabled */ | ||||
|     DISABLED = 0, | ||||
|     /* We compress using a shared non-sliding window. No added memory usage, worse compression. */ | ||||
|     SHARED_COMPRESSOR = 1, | ||||
|     /* We compress using a dedicated sliding window. Major memory usage added, better compression of similarly repeated messages. */ | ||||
|     DEDICATED_COMPRESSOR = 2 | ||||
| }; | ||||
|  | ||||
| template <bool SSL> | ||||
| struct TemplatedApp { | ||||
| private: | ||||
|     /* The app always owns at least one http context, but creates websocket contexts on demand */ | ||||
|     HttpContext<SSL> *httpContext; | ||||
|     std::vector<WebSocketContext<SSL, true> *> webSocketContexts; | ||||
|  | ||||
| public: | ||||
|  | ||||
|     /* Attaches a "filter" function to track socket connections/disconnections */ | ||||
|     void filter(fu2::unique_function<void(HttpResponse<SSL> *, int)> &&filterHandler) { | ||||
|         httpContext->filter(std::move(filterHandler)); | ||||
|     } | ||||
|  | ||||
|     /* Publishes a message to all websocket contexts */ | ||||
|     void publish(std::string_view topic, std::string_view message, OpCode opCode, bool compress = false) { | ||||
|         for (auto *webSocketContext : webSocketContexts) { | ||||
|             webSocketContext->getExt()->publish(topic, message, opCode, compress); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     ~TemplatedApp() { | ||||
|         /* Let's just put everything here */ | ||||
|         if (httpContext) { | ||||
|             httpContext->free(); | ||||
|  | ||||
|             for (auto *webSocketContext : webSocketContexts) { | ||||
|                 webSocketContext->free(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* Disallow copying, only move */ | ||||
|     TemplatedApp(const TemplatedApp &other) = delete; | ||||
|  | ||||
|     TemplatedApp(TemplatedApp &&other) { | ||||
|         /* Move HttpContext */ | ||||
|         httpContext = other.httpContext; | ||||
|         other.httpContext = nullptr; | ||||
|  | ||||
|         /* Move webSocketContexts */ | ||||
|         webSocketContexts = std::move(other.webSocketContexts); | ||||
|     } | ||||
|  | ||||
|     TemplatedApp(us_socket_context_options_t options = {}) { | ||||
|         httpContext = uWS::HttpContext<SSL>::create(uWS::Loop::get(), options); | ||||
|     } | ||||
|  | ||||
|     bool constructorFailed() { | ||||
|         return !httpContext; | ||||
|     } | ||||
|  | ||||
|     struct WebSocketBehavior { | ||||
|         CompressOptions compression = DISABLED; | ||||
|         int maxPayloadLength = 16 * 1024; | ||||
|         int idleTimeout = 120; | ||||
|         int maxBackpressure = 1 * 1024 * 1204; | ||||
|         fu2::unique_function<void(uWS::WebSocket<SSL, true> *, HttpRequest *)> open = nullptr; | ||||
|         fu2::unique_function<void(uWS::WebSocket<SSL, true> *, std::string_view, uWS::OpCode)> message = nullptr; | ||||
|         fu2::unique_function<void(uWS::WebSocket<SSL, true> *)> drain = nullptr; | ||||
|         fu2::unique_function<void(uWS::WebSocket<SSL, true> *)> ping = nullptr; | ||||
|         fu2::unique_function<void(uWS::WebSocket<SSL, true> *)> pong = nullptr; | ||||
|         fu2::unique_function<void(uWS::WebSocket<SSL, true> *, int, std::string_view)> close = nullptr; | ||||
|     }; | ||||
|  | ||||
|     template <typename UserData> | ||||
|     TemplatedApp &&ws(std::string pattern, WebSocketBehavior &&behavior) { | ||||
|         /* Don't compile if alignment rules cannot be satisfied */ | ||||
|         static_assert(alignof(UserData) <= LIBUS_EXT_ALIGNMENT, | ||||
|         "µWebSockets cannot satisfy UserData alignment requirements. You need to recompile µSockets with LIBUS_EXT_ALIGNMENT adjusted accordingly."); | ||||
|  | ||||
|         /* Every route has its own websocket context with its own behavior and user data type */ | ||||
|         auto *webSocketContext = WebSocketContext<SSL, true>::create(Loop::get(), (us_socket_context_t *) httpContext); | ||||
|  | ||||
|         /* We need to clear this later on */ | ||||
|         webSocketContexts.push_back(webSocketContext); | ||||
|  | ||||
|         /* Quick fix to disable any compression if set */ | ||||
| #ifdef UWS_NO_ZLIB | ||||
|         behavior.compression = uWS::DISABLED; | ||||
| #endif | ||||
|  | ||||
|         /* If we are the first one to use compression, initialize it */ | ||||
|         if (behavior.compression) { | ||||
|             LoopData *loopData = (LoopData *) us_loop_ext(us_socket_context_loop(SSL, webSocketContext->getSocketContext())); | ||||
|  | ||||
|             /* Initialize loop's deflate inflate streams */ | ||||
|             if (!loopData->zlibContext) { | ||||
|                 loopData->zlibContext = new ZlibContext; | ||||
|                 loopData->inflationStream = new InflationStream; | ||||
|                 loopData->deflationStream = new DeflationStream; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /* Copy all handlers */ | ||||
|         webSocketContext->getExt()->messageHandler = std::move(behavior.message); | ||||
|         webSocketContext->getExt()->drainHandler = std::move(behavior.drain); | ||||
|         webSocketContext->getExt()->closeHandler = std::move([closeHandler = std::move(behavior.close)](WebSocket<SSL, true> *ws, int code, std::string_view message) mutable { | ||||
|             closeHandler(ws, code, message); | ||||
|  | ||||
|             /* Destruct user data after returning from close handler */ | ||||
|             ((UserData *) ws->getUserData())->~UserData(); | ||||
|         }); | ||||
|  | ||||
|         /* Copy settings */ | ||||
|         webSocketContext->getExt()->maxPayloadLength = behavior.maxPayloadLength; | ||||
|         webSocketContext->getExt()->idleTimeout = behavior.idleTimeout; | ||||
|         webSocketContext->getExt()->maxBackpressure = behavior.maxBackpressure; | ||||
|  | ||||
|         httpContext->onHttp("get", pattern, [webSocketContext, httpContext = this->httpContext, behavior = std::move(behavior)](auto *res, auto *req) mutable { | ||||
|  | ||||
|             /* If we have this header set, it's a websocket */ | ||||
|             std::string_view secWebSocketKey = req->getHeader("sec-websocket-key"); | ||||
|             if (secWebSocketKey.length() == 24) { | ||||
|                 /* Note: OpenSSL can be used here to speed this up somewhat */ | ||||
|                 char secWebSocketAccept[29] = {}; | ||||
|                 WebSocketHandshake::generate(secWebSocketKey.data(), secWebSocketAccept); | ||||
|  | ||||
|                 res->writeStatus("101 Switching Protocols") | ||||
|                     ->writeHeader("Upgrade", "websocket") | ||||
|                     ->writeHeader("Connection", "Upgrade") | ||||
|                     ->writeHeader("Sec-WebSocket-Accept", secWebSocketAccept); | ||||
|  | ||||
|                 /* Select first subprotocol if present */ | ||||
|                 std::string_view secWebSocketProtocol = req->getHeader("sec-websocket-protocol"); | ||||
|                 if (secWebSocketProtocol.length()) { | ||||
|                     res->writeHeader("Sec-WebSocket-Protocol", secWebSocketProtocol.substr(0, secWebSocketProtocol.find(','))); | ||||
|                 } | ||||
|  | ||||
|                 /* Negotiate compression */ | ||||
|                 bool perMessageDeflate = false; | ||||
|                 bool slidingDeflateWindow = false; | ||||
|                 if (behavior.compression != DISABLED) { | ||||
|                     std::string_view extensions = req->getHeader("sec-websocket-extensions"); | ||||
|                     if (extensions.length()) { | ||||
|                         /* We never support client context takeover (the client cannot compress with a sliding window). */ | ||||
|                         int wantedOptions = PERMESSAGE_DEFLATE | CLIENT_NO_CONTEXT_TAKEOVER; | ||||
|  | ||||
|                         /* Shared compressor is the default */ | ||||
|                         if (behavior.compression == SHARED_COMPRESSOR) { | ||||
|                             /* Disable per-socket compressor */ | ||||
|                             wantedOptions |= SERVER_NO_CONTEXT_TAKEOVER; | ||||
|                         } | ||||
|  | ||||
|                         /* isServer = true */ | ||||
|                         ExtensionsNegotiator<true> extensionsNegotiator(wantedOptions); | ||||
|                         extensionsNegotiator.readOffer(extensions); | ||||
|  | ||||
|                         /* Todo: remove these mid string copies */ | ||||
|                         std::string offer = extensionsNegotiator.generateOffer(); | ||||
|                         if (offer.length()) { | ||||
|                             res->writeHeader("Sec-WebSocket-Extensions", offer); | ||||
|                         } | ||||
|  | ||||
|                         /* Did we negotiate permessage-deflate? */ | ||||
|                         if (extensionsNegotiator.getNegotiatedOptions() & PERMESSAGE_DEFLATE) { | ||||
|                             perMessageDeflate = true; | ||||
|                         } | ||||
|  | ||||
|                         /* Is the server allowed to compress with a sliding window? */ | ||||
|                         if (!(extensionsNegotiator.getNegotiatedOptions() & SERVER_NO_CONTEXT_TAKEOVER)) { | ||||
|                             slidingDeflateWindow = true; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 /* This will add our mark */ | ||||
|                 res->upgrade(); | ||||
|  | ||||
|                 /* Move any backpressure */ | ||||
|                 std::string backpressure(std::move(((AsyncSocketData<SSL> *) res->getHttpResponseData())->buffer)); | ||||
|  | ||||
|                 /* Keep any fallback buffer alive until we returned from open event, keeping req valid */ | ||||
|                 std::string fallback(std::move(res->getHttpResponseData()->salvageFallbackBuffer())); | ||||
|  | ||||
|                 /* Destroy HttpResponseData */ | ||||
|                 res->getHttpResponseData()->~HttpResponseData(); | ||||
|  | ||||
|                 /* Adopting a socket invalidates it, do not rely on it directly to carry any data */ | ||||
|                 WebSocket<SSL, true> *webSocket = (WebSocket<SSL, true> *) us_socket_context_adopt_socket(SSL, | ||||
|                             (us_socket_context_t *) webSocketContext, (us_socket_t *) res, sizeof(WebSocketData) + sizeof(UserData)); | ||||
|  | ||||
|                 /* Update corked socket in case we got a new one (assuming we always are corked in handlers). */ | ||||
|                 webSocket->AsyncSocket<SSL>::cork(); | ||||
|  | ||||
|                 /* Initialize websocket with any moved backpressure intact */ | ||||
|                 httpContext->upgradeToWebSocket( | ||||
|                             webSocket->init(perMessageDeflate, slidingDeflateWindow, std::move(backpressure)) | ||||
|                             ); | ||||
|  | ||||
|                 /* Emit open event and start the timeout */ | ||||
|                 if (behavior.open) { | ||||
|                     us_socket_timeout(SSL, (us_socket_t *) webSocket, behavior.idleTimeout); | ||||
|  | ||||
|                     /* Default construct the UserData right before calling open handler */ | ||||
|                     new (webSocket->getUserData()) UserData; | ||||
|  | ||||
|                     behavior.open(webSocket, req); | ||||
|                 } | ||||
|  | ||||
|                 /* We are going to get uncorked by the Http get return */ | ||||
|  | ||||
|                 /* We do not need to check for any close or shutdown here as we immediately return from get handler */ | ||||
|  | ||||
|             } else { | ||||
|                 /* Tell the router that we did not handle this request */ | ||||
|                 req->setYield(true); | ||||
|             } | ||||
|         }, true); | ||||
|         return std::move(*this); | ||||
|     } | ||||
|  | ||||
|     TemplatedApp &&get(std::string pattern, fu2::unique_function<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) { | ||||
|         httpContext->onHttp("get", pattern, std::move(handler)); | ||||
|         return std::move(*this); | ||||
|     } | ||||
|  | ||||
|     TemplatedApp &&post(std::string pattern, fu2::unique_function<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) { | ||||
|         httpContext->onHttp("post", pattern, std::move(handler)); | ||||
|         return std::move(*this); | ||||
|     } | ||||
|  | ||||
|     TemplatedApp &&options(std::string pattern, fu2::unique_function<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) { | ||||
|         httpContext->onHttp("options", pattern, std::move(handler)); | ||||
|         return std::move(*this); | ||||
|     } | ||||
|  | ||||
|     TemplatedApp &&del(std::string pattern, fu2::unique_function<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) { | ||||
|         httpContext->onHttp("delete", pattern, std::move(handler)); | ||||
|         return std::move(*this); | ||||
|     } | ||||
|  | ||||
|     TemplatedApp &&patch(std::string pattern, fu2::unique_function<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) { | ||||
|         httpContext->onHttp("patch", pattern, std::move(handler)); | ||||
|         return std::move(*this); | ||||
|     } | ||||
|  | ||||
|     TemplatedApp &&put(std::string pattern, fu2::unique_function<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) { | ||||
|         httpContext->onHttp("put", pattern, std::move(handler)); | ||||
|         return std::move(*this); | ||||
|     } | ||||
|  | ||||
|     TemplatedApp &&head(std::string pattern, fu2::unique_function<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) { | ||||
|         httpContext->onHttp("head", pattern, std::move(handler)); | ||||
|         return std::move(*this); | ||||
|     } | ||||
|  | ||||
|     TemplatedApp &&connect(std::string pattern, fu2::unique_function<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) { | ||||
|         httpContext->onHttp("connect", pattern, std::move(handler)); | ||||
|         return std::move(*this); | ||||
|     } | ||||
|  | ||||
|     TemplatedApp &&trace(std::string pattern, fu2::unique_function<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) { | ||||
|         httpContext->onHttp("trace", pattern, std::move(handler)); | ||||
|         return std::move(*this); | ||||
|     } | ||||
|  | ||||
|     /* This one catches any method */ | ||||
|     TemplatedApp &&any(std::string pattern, fu2::unique_function<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) { | ||||
|         httpContext->onHttp("*", pattern, std::move(handler)); | ||||
|         return std::move(*this); | ||||
|     } | ||||
|  | ||||
|     /* Host, port, callback */ | ||||
|     TemplatedApp &&listen(std::string host, int port, fu2::unique_function<void(us_listen_socket_t *)> &&handler) { | ||||
|         if (!host.length()) { | ||||
|             return listen(port, std::move(handler)); | ||||
|         } | ||||
|         handler(httpContext->listen(host.c_str(), port, 0)); | ||||
|         return std::move(*this); | ||||
|     } | ||||
|  | ||||
|     /* Host, port, options, callback */ | ||||
|     TemplatedApp &&listen(std::string host, int port, int options, fu2::unique_function<void(us_listen_socket_t *)> &&handler) { | ||||
|         if (!host.length()) { | ||||
|             return listen(port, options, std::move(handler)); | ||||
|         } | ||||
|         handler(httpContext->listen(host.c_str(), port, options)); | ||||
|         return std::move(*this); | ||||
|     } | ||||
|  | ||||
|     /* Port, callback */ | ||||
|     TemplatedApp &&listen(int port, fu2::unique_function<void(us_listen_socket_t *)> &&handler) { | ||||
|         handler(httpContext->listen(nullptr, port, 0)); | ||||
|         return std::move(*this); | ||||
|     } | ||||
|  | ||||
|     /* Port, options, callback */ | ||||
|     TemplatedApp &&listen(int port, int options, fu2::unique_function<void(us_listen_socket_t *)> &&handler) { | ||||
|         handler(httpContext->listen(nullptr, port, options)); | ||||
|         return std::move(*this); | ||||
|     } | ||||
|  | ||||
|     TemplatedApp &&run() { | ||||
|         uWS::run(); | ||||
|         return std::move(*this); | ||||
|     } | ||||
|  | ||||
| }; | ||||
|  | ||||
| typedef TemplatedApp<false> App; | ||||
| typedef TemplatedApp<true> SSLApp; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // UWS_APP_H | ||||
							
								
								
									
										228
									
								
								test/compatibility/C/uWebSockets/src/AsyncSocket.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								test/compatibility/C/uWebSockets/src/AsyncSocket.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,228 @@ | ||||
| /* | ||||
|  * Authored by Alex Hultman, 2018-2019. | ||||
|  * Intellectual property of third-party. | ||||
|  | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #ifndef UWS_ASYNCSOCKET_H | ||||
| #define UWS_ASYNCSOCKET_H | ||||
|  | ||||
| /* This class implements async socket memory management strategies */ | ||||
|  | ||||
| #include "LoopData.h" | ||||
| #include "AsyncSocketData.h" | ||||
|  | ||||
| namespace uWS { | ||||
|  | ||||
|     template <bool, bool> struct WebSocketContext; | ||||
|  | ||||
| template <bool SSL> | ||||
| struct AsyncSocket { | ||||
|     template <bool> friend struct HttpContext; | ||||
|     template <bool, bool> friend struct WebSocketContext; | ||||
|     template <bool> friend struct WebSocketContextData; | ||||
|     friend struct TopicTree; | ||||
|  | ||||
| protected: | ||||
|     /* Get loop data for socket */ | ||||
|     LoopData *getLoopData() { | ||||
|         return (LoopData *) us_loop_ext(us_socket_context_loop(SSL, us_socket_context(SSL, (us_socket_t *) this))); | ||||
|     } | ||||
|  | ||||
|     /* Get socket extension */ | ||||
|     AsyncSocketData<SSL> *getAsyncSocketData() { | ||||
|         return (AsyncSocketData<SSL> *) us_socket_ext(SSL, (us_socket_t *) this); | ||||
|     } | ||||
|  | ||||
|     /* Socket timeout */ | ||||
|     void timeout(unsigned int seconds) { | ||||
|         us_socket_timeout(SSL, (us_socket_t *) this, seconds); | ||||
|     } | ||||
|  | ||||
|     /* Shutdown socket without any automatic drainage */ | ||||
|     void shutdown() { | ||||
|         us_socket_shutdown(SSL, (us_socket_t *) this); | ||||
|     } | ||||
|  | ||||
|     /* Immediately close socket */ | ||||
|     us_socket_t *close() { | ||||
|         return us_socket_close(SSL, (us_socket_t *) this); | ||||
|     } | ||||
|  | ||||
|     /* Cork this socket. Only one socket may ever be corked per-loop at any given time */ | ||||
|     void cork() { | ||||
|         /* What if another socket is corked? */ | ||||
|         getLoopData()->corkedSocket = this; | ||||
|     } | ||||
|  | ||||
|     /* Returns wheter we are corked or not */ | ||||
|     bool isCorked() { | ||||
|         return getLoopData()->corkedSocket == this; | ||||
|     } | ||||
|  | ||||
|     /* Returns whether we could cork (it is free) */ | ||||
|     bool canCork() { | ||||
|         return getLoopData()->corkedSocket == nullptr; | ||||
|     } | ||||
|  | ||||
|     /* Returns a suitable buffer for temporary assemblation of send data */ | ||||
|     std::pair<char *, bool> getSendBuffer(size_t size) { | ||||
|         /* If we are corked and we have room, return the cork buffer itself */ | ||||
|         LoopData *loopData = getLoopData(); | ||||
|         if (loopData->corkedSocket == this && loopData->corkOffset + size < LoopData::CORK_BUFFER_SIZE) { | ||||
|             char *sendBuffer = loopData->corkBuffer + loopData->corkOffset; | ||||
|             loopData->corkOffset += (int) size; | ||||
|             return {sendBuffer, false}; | ||||
|         } else { | ||||
|             /* Slow path for now, we want to always be corked if possible */ | ||||
|             return {(char *) malloc(size), true}; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* Returns the user space backpressure. */ | ||||
|     int getBufferedAmount() { | ||||
|         return (int) getAsyncSocketData()->buffer.size(); | ||||
|     } | ||||
|  | ||||
|     /* Returns the remote IP address or empty string on failure */ | ||||
|     std::string_view getRemoteAddress() { | ||||
|         static thread_local char buf[16]; | ||||
|         int ipLength = 16; | ||||
|         us_socket_remote_address(SSL, (us_socket_t *) this, buf, &ipLength); | ||||
|         return std::string_view(buf, ipLength); | ||||
|     } | ||||
|  | ||||
|     /* Write in three levels of prioritization: cork-buffer, syscall, socket-buffer. Always drain if possible. | ||||
|      * Returns pair of bytes written (anywhere) and wheter or not this call resulted in the polling for | ||||
|      * writable (or we are in a state that implies polling for writable). */ | ||||
|     std::pair<int, bool> write(const char *src, int length, bool optionally = false, int nextLength = 0) { | ||||
|         /* Fake success if closed, simple fix to allow uncork of closed socket to succeed */ | ||||
|         if (us_socket_is_closed(SSL, (us_socket_t *) this)) { | ||||
|             return {length, false}; | ||||
|         } | ||||
|  | ||||
|         LoopData *loopData = getLoopData(); | ||||
|         AsyncSocketData<SSL> *asyncSocketData = getAsyncSocketData(); | ||||
|  | ||||
|         /* We are limited if we have a per-socket buffer */ | ||||
|         if (asyncSocketData->buffer.length()) { | ||||
|             /* Write off as much as we can */ | ||||
|             int written = us_socket_write(SSL, (us_socket_t *) this, asyncSocketData->buffer.data(), (int) asyncSocketData->buffer.length(), /*nextLength != 0 | */length); | ||||
|  | ||||
|             /* On failure return, otherwise continue down the function */ | ||||
|             if ((unsigned int) written < asyncSocketData->buffer.length()) { | ||||
|  | ||||
|                 /* Update buffering (todo: we can do better here if we keep track of what happens to this guy later on) */ | ||||
|                 asyncSocketData->buffer = asyncSocketData->buffer.substr(written); | ||||
|  | ||||
|                 if (optionally) { | ||||
|                     /* Thankfully we can exit early here */ | ||||
|                     return {0, true}; | ||||
|                 } else { | ||||
|                     /* This path is horrible and points towards erroneous usage */ | ||||
|                     asyncSocketData->buffer.append(src, length); | ||||
|  | ||||
|                     return {length, true}; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             /* At this point we simply have no buffer and can continue as normal */ | ||||
|             asyncSocketData->buffer.clear(); | ||||
|         } | ||||
|  | ||||
|         if (length) { | ||||
|             if (loopData->corkedSocket == this) { | ||||
|                 /* We are corked */ | ||||
|                 if (LoopData::CORK_BUFFER_SIZE - loopData->corkOffset >= length) { | ||||
|                     /* If the entire chunk fits in cork buffer */ | ||||
|                     memcpy(loopData->corkBuffer + loopData->corkOffset, src, length); | ||||
|                     loopData->corkOffset += length; | ||||
|                     /* Fall through to default return */ | ||||
|                 } else { | ||||
|                     /* Strategy differences between SSL and non-SSL regarding syscall minimizing */ | ||||
|                     if constexpr (SSL) { | ||||
|                         /* Cork up as much as we can */ | ||||
|                         int stripped = LoopData::CORK_BUFFER_SIZE - loopData->corkOffset; | ||||
|                         memcpy(loopData->corkBuffer + loopData->corkOffset, src, stripped); | ||||
|                         loopData->corkOffset = LoopData::CORK_BUFFER_SIZE; | ||||
|  | ||||
|                         auto [written, failed] = uncork(src + stripped, length - stripped, optionally); | ||||
|                         return {written + stripped, failed}; | ||||
|                     } | ||||
|  | ||||
|                     /* For non-SSL we take the penalty of two syscalls */ | ||||
|                     return uncork(src, length, optionally); | ||||
|                 } | ||||
|             } else { | ||||
|                 /* We are not corked */ | ||||
|                 int written = us_socket_write(SSL, (us_socket_t *) this, src, length, nextLength != 0); | ||||
|  | ||||
|                 /* Did we fail? */ | ||||
|                 if (written < length) { | ||||
|                     /* If the write was optional then just bail out */ | ||||
|                     if (optionally) { | ||||
|                         return {written, true}; | ||||
|                     } | ||||
|  | ||||
|                     /* Fall back to worst possible case (should be very rare for HTTP) */ | ||||
|                     /* At least we can reserve room for next chunk if we know it up front */ | ||||
|                     if (nextLength) { | ||||
|                         asyncSocketData->buffer.reserve(asyncSocketData->buffer.length() + length - written + nextLength); | ||||
|                     } | ||||
|  | ||||
|                     /* Buffer this chunk */ | ||||
|                     asyncSocketData->buffer.append(src + written, length - written); | ||||
|  | ||||
|                     /* Return the failure */ | ||||
|                     return {length, true}; | ||||
|                 } | ||||
|                 /* Fall through to default return */ | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /* Default fall through return */ | ||||
|         return {length, false}; | ||||
|     } | ||||
|  | ||||
|     /* Uncork this socket and flush or buffer any corked and/or passed data. It is essential to remember doing this. */ | ||||
|     /* It does NOT count bytes written from cork buffer (they are already accounted for in the write call responsible for its corking)! */ | ||||
|     std::pair<int, bool> uncork(const char *src = nullptr, int length = 0, bool optionally = false) { | ||||
|         LoopData *loopData = getLoopData(); | ||||
|  | ||||
|         if (loopData->corkedSocket == this) { | ||||
|             loopData->corkedSocket = nullptr; | ||||
|  | ||||
|             if (loopData->corkOffset) { | ||||
|                 /* Corked data is already accounted for via its write call */ | ||||
|                 auto [written, failed] = write(loopData->corkBuffer, loopData->corkOffset, false, length); | ||||
|                 loopData->corkOffset = 0; | ||||
|  | ||||
|                 if (failed) { | ||||
|                     /* We do not need to care for buffering here, write does that */ | ||||
|                     return {0, true}; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             /* We should only return with new writes, not things written to cork already */ | ||||
|             return write(src, length, optionally, 0); | ||||
|         } else { | ||||
|             /* We are not even corked! */ | ||||
|             return {0, false}; | ||||
|         } | ||||
|     } | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // UWS_ASYNCSOCKET_H | ||||
							
								
								
									
										43
									
								
								test/compatibility/C/uWebSockets/src/AsyncSocketData.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								test/compatibility/C/uWebSockets/src/AsyncSocketData.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| /* | ||||
|  * Authored by Alex Hultman, 2018-2019. | ||||
|  * Intellectual property of third-party. | ||||
|  | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #ifndef UWS_ASYNCSOCKETDATA_H | ||||
| #define UWS_ASYNCSOCKETDATA_H | ||||
|  | ||||
| #include <string> | ||||
|  | ||||
| namespace uWS { | ||||
|  | ||||
| /* Depending on how we want AsyncSocket to function, this will need to change */ | ||||
|  | ||||
| template <bool SSL> | ||||
| struct AsyncSocketData { | ||||
|     /* This will do for now */ | ||||
|     std::string buffer; | ||||
|  | ||||
|     /* Allow move constructing us */ | ||||
|     AsyncSocketData(std::string &&backpressure) : buffer(std::move(backpressure)) { | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /* Or emppty */ | ||||
|     AsyncSocketData() = default; | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // UWS_ASYNCSOCKETDATA_H | ||||
							
								
								
									
										384
									
								
								test/compatibility/C/uWebSockets/src/HttpContext.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										384
									
								
								test/compatibility/C/uWebSockets/src/HttpContext.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,384 @@ | ||||
| /* | ||||
|  * Authored by Alex Hultman, 2018-2019. | ||||
|  * Intellectual property of third-party. | ||||
|  | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #ifndef UWS_HTTPCONTEXT_H | ||||
| #define UWS_HTTPCONTEXT_H | ||||
|  | ||||
| /* This class defines the main behavior of HTTP and emits various events */ | ||||
|  | ||||
| #include "Loop.h" | ||||
| #include "HttpContextData.h" | ||||
| #include "HttpResponseData.h" | ||||
| #include "AsyncSocket.h" | ||||
|  | ||||
| #include <string_view> | ||||
| #include <iostream> | ||||
| #include "f2/function2.hpp" | ||||
|  | ||||
| namespace uWS { | ||||
| template<bool> struct HttpResponse; | ||||
|  | ||||
| template <bool SSL> | ||||
| struct HttpContext { | ||||
|     template<bool> friend struct TemplatedApp; | ||||
| private: | ||||
|     HttpContext() = delete; | ||||
|  | ||||
|     /* Maximum delay allowed until an HTTP connection is terminated due to outstanding request or rejected data (slow loris protection) */ | ||||
|     static const int HTTP_IDLE_TIMEOUT_S = 10; | ||||
|  | ||||
|     us_socket_context_t *getSocketContext() { | ||||
|         return (us_socket_context_t *) this; | ||||
|     } | ||||
|  | ||||
|     static us_socket_context_t *getSocketContext(us_socket_t *s) { | ||||
|         return (us_socket_context_t *) us_socket_context(SSL, s); | ||||
|     } | ||||
|  | ||||
|     HttpContextData<SSL> *getSocketContextData() { | ||||
|         return (HttpContextData<SSL> *) us_socket_context_ext(SSL, getSocketContext()); | ||||
|     } | ||||
|  | ||||
|     static HttpContextData<SSL> *getSocketContextDataS(us_socket_t *s) { | ||||
|         return (HttpContextData<SSL> *) us_socket_context_ext(SSL, getSocketContext(s)); | ||||
|     } | ||||
|  | ||||
|     /* Init the HttpContext by registering libusockets event handlers */ | ||||
|     HttpContext<SSL> *init() { | ||||
|         /* Handle socket connections */ | ||||
|         us_socket_context_on_open(SSL, getSocketContext(), [](us_socket_t *s, int is_client, char *ip, int ip_length) { | ||||
|             /* Any connected socket should timeout until it has a request */ | ||||
|             us_socket_timeout(SSL, s, HTTP_IDLE_TIMEOUT_S); | ||||
|  | ||||
|             /* Init socket ext */ | ||||
|             new (us_socket_ext(SSL, s)) HttpResponseData<SSL>; | ||||
|  | ||||
|             /* Call filter */ | ||||
|             HttpContextData<SSL> *httpContextData = getSocketContextDataS(s); | ||||
|             for (auto &f : httpContextData->filterHandlers) { | ||||
|                 f((HttpResponse<SSL> *) s, 1); | ||||
|             } | ||||
|  | ||||
|             return s; | ||||
|         }); | ||||
|  | ||||
|         /* Handle socket disconnections */ | ||||
|         us_socket_context_on_close(SSL, getSocketContext(), [](us_socket_t *s) { | ||||
|             /* Get socket ext */ | ||||
|             HttpResponseData<SSL> *httpResponseData = (HttpResponseData<SSL> *) us_socket_ext(SSL, s); | ||||
|  | ||||
|             /* Call filter */ | ||||
|             HttpContextData<SSL> *httpContextData = getSocketContextDataS(s); | ||||
|             for (auto &f : httpContextData->filterHandlers) { | ||||
|                 f((HttpResponse<SSL> *) s, -1); | ||||
|             } | ||||
|  | ||||
|             /* Signal broken HTTP request only if we have a pending request */ | ||||
|             if (httpResponseData->onAborted) { | ||||
|                 httpResponseData->onAborted(); | ||||
|             } | ||||
|  | ||||
|             /* Destruct socket ext */ | ||||
|             httpResponseData->~HttpResponseData<SSL>(); | ||||
|  | ||||
|             return s; | ||||
|         }); | ||||
|  | ||||
|         /* Handle HTTP data streams */ | ||||
|         us_socket_context_on_data(SSL, getSocketContext(), [](us_socket_t *s, char *data, int length) { | ||||
|  | ||||
|             // total overhead is about 210k down to 180k | ||||
|             // ~210k req/sec is the original perf with write in data | ||||
|             // ~200k req/sec is with cork and formatting | ||||
|             // ~190k req/sec is with http parsing | ||||
|             // ~180k - 190k req/sec is with varying routing | ||||
|  | ||||
|             HttpContextData<SSL> *httpContextData = getSocketContextDataS(s); | ||||
|  | ||||
|             /* Do not accept any data while in shutdown state */ | ||||
|             if (us_socket_is_shut_down(SSL, (us_socket_t *) s)) { | ||||
|                 return s; | ||||
|             } | ||||
|  | ||||
|             HttpResponseData<SSL> *httpResponseData = (HttpResponseData<SSL> *) us_socket_ext(SSL, s); | ||||
|  | ||||
|             /* Cork this socket */ | ||||
|             ((AsyncSocket<SSL> *) s)->cork(); | ||||
|  | ||||
|             // clients need to know the cursor after http parse, not servers! | ||||
|             // how far did we read then? we need to know to continue with websocket parsing data? or? | ||||
|  | ||||
|             /* The return value is entirely up to us to interpret. The HttpParser only care for whether the returned value is DIFFERENT or not from passed user */ | ||||
|             void *returnedSocket = httpResponseData->consumePostPadded(data, length, s, [httpContextData](void *s, uWS::HttpRequest *httpRequest) -> void * { | ||||
|                 /* For every request we reset the timeout and hang until user makes action */ | ||||
|                 /* Warning: if we are in shutdown state, resetting the timer is a security issue! */ | ||||
|                 us_socket_timeout(SSL, (us_socket_t *) s, 0); | ||||
|  | ||||
|                 /* Reset httpResponse */ | ||||
|                 HttpResponseData<SSL> *httpResponseData = (HttpResponseData<SSL> *) us_socket_ext(SSL, (us_socket_t *) s); | ||||
|                 httpResponseData->offset = 0; | ||||
|  | ||||
|                 /* Are we not ready for another request yet? Terminate the connection. */ | ||||
|                 if (httpResponseData->state & HttpResponseData<SSL>::HTTP_RESPONSE_PENDING) { | ||||
|                     us_socket_close(SSL, (us_socket_t *) s); | ||||
|                     return nullptr; | ||||
|                 } | ||||
|  | ||||
|                 /* Mark pending request and emit it */ | ||||
|                 httpResponseData->state = HttpResponseData<SSL>::HTTP_RESPONSE_PENDING; | ||||
|  | ||||
|                 /* Route the method and URL */ | ||||
|                 httpContextData->router.getUserData() = {(HttpResponse<SSL> *) s, httpRequest}; | ||||
|                 if (!httpContextData->router.route(httpRequest->getMethod(), httpRequest->getUrl())) { | ||||
|                     /* We have to force close this socket as we have no handler for it */ | ||||
|                     us_socket_close(SSL, (us_socket_t *) s); | ||||
|                     return nullptr; | ||||
|                 } | ||||
|  | ||||
|                 /* First of all we need to check if this socket was deleted due to upgrade */ | ||||
|                 if (httpContextData->upgradedWebSocket) { | ||||
|                     /* We differ between closed and upgraded below */ | ||||
|                     return nullptr; | ||||
|                 } | ||||
|  | ||||
|                 /* Was the socket closed? */ | ||||
|                 if (us_socket_is_closed(SSL, (struct us_socket_t *) s)) { | ||||
|                     return nullptr; | ||||
|                 } | ||||
|  | ||||
|                 /* We absolutely have to terminate parsing if shutdown */ | ||||
|                 if (us_socket_is_shut_down(SSL, (us_socket_t *) s)) { | ||||
|                     return nullptr; | ||||
|                 } | ||||
|  | ||||
|                 /* Returning from a request handler without responding or attaching an onAborted handler is ill-use */ | ||||
|                 if (!((HttpResponse<SSL> *) s)->hasResponded() && !httpResponseData->onAborted) { | ||||
|                     /* Throw exception here? */ | ||||
|                     std::cerr << "Error: Returning from a request handler without responding or attaching an abort handler is forbidden!" << std::endl; | ||||
|                     std::terminate(); | ||||
|                 } | ||||
|  | ||||
|                 /* If we have not responded and we have a data handler, we need to timeout to enfore client sending the data */ | ||||
|                 if (!((HttpResponse<SSL> *) s)->hasResponded() && httpResponseData->inStream) { | ||||
|                     us_socket_timeout(SSL, (us_socket_t *) s, HTTP_IDLE_TIMEOUT_S); | ||||
|                 } | ||||
|  | ||||
|                 /* Continue parsing */ | ||||
|                 return s; | ||||
|  | ||||
|             }, [httpResponseData](void *user, std::string_view data, bool fin) -> void * { | ||||
|                 /* We always get an empty chunk even if there is no data */ | ||||
|                 if (httpResponseData->inStream) { | ||||
|  | ||||
|                     /* Todo: can this handle timeout for non-post as well? */ | ||||
|                     if (fin) { | ||||
|                         /* If we just got the last chunk (or empty chunk), disable timeout */ | ||||
|                         us_socket_timeout(SSL, (struct us_socket_t *) user, 0); | ||||
|                     } else { | ||||
|                         /* We still have some more data coming in later, so reset timeout */ | ||||
|                         us_socket_timeout(SSL, (struct us_socket_t *) user, HTTP_IDLE_TIMEOUT_S); | ||||
|                     } | ||||
|  | ||||
|                     /* We might respond in the handler, so do not change timeout after this */ | ||||
|                     httpResponseData->inStream(data, fin); | ||||
|  | ||||
|                     /* Was the socket closed? */ | ||||
|                     if (us_socket_is_closed(SSL, (struct us_socket_t *) user)) { | ||||
|                         return nullptr; | ||||
|                     } | ||||
|  | ||||
|                     /* We absolutely have to terminate parsing if shutdown */ | ||||
|                     if (us_socket_is_shut_down(SSL, (us_socket_t *) user)) { | ||||
|                         return nullptr; | ||||
|                     } | ||||
|                      | ||||
|                     /* If we were given the last data chunk, reset data handler to ensure following | ||||
|                      * requests on the same socket won't trigger any previously registered behavior */ | ||||
|                     if (fin) { | ||||
|                         httpResponseData->inStream = nullptr; | ||||
|                     } | ||||
|                 } | ||||
|                 return user; | ||||
|             }, [](void *user) { | ||||
|                  /* Close any socket on HTTP errors */ | ||||
|                 us_socket_close(SSL, (us_socket_t *) user); | ||||
|                 return nullptr; | ||||
|             }); | ||||
|  | ||||
|             /* We need to uncork in all cases, except for nullptr (closed socket, or upgraded socket) */ | ||||
|             if (returnedSocket != nullptr) { | ||||
|                 /* Timeout on uncork failure */ | ||||
|                 auto [written, failed] = ((AsyncSocket<SSL> *) returnedSocket)->uncork(); | ||||
|                 if (failed) { | ||||
|                     /* All Http sockets timeout by this, and this behavior match the one in HttpResponse::cork */ | ||||
|                     /* Warning: both HTTP_IDLE_TIMEOUT_S and HTTP_TIMEOUT_S are 10 seconds and both are used the same */ | ||||
|                     ((AsyncSocket<SSL> *) s)->timeout(HTTP_IDLE_TIMEOUT_S); | ||||
|                 } | ||||
|  | ||||
|                 return (us_socket_t *) returnedSocket; | ||||
|             } | ||||
|  | ||||
|             /* If we upgraded, check here (differ between nullptr close and nullptr upgrade) */ | ||||
|             if (httpContextData->upgradedWebSocket) { | ||||
|                 /* This path is only for upgraded websockets */ | ||||
|                 AsyncSocket<SSL> *asyncSocket = (AsyncSocket<SSL> *) httpContextData->upgradedWebSocket; | ||||
|  | ||||
|                 /* Uncork here as well (note: what if we failed to uncork and we then pub/sub before we even upgraded?) */ | ||||
|                 /*auto [written, failed] = */asyncSocket->uncork(); | ||||
|  | ||||
|                 /* Reset upgradedWebSocket before we return */ | ||||
|                 httpContextData->upgradedWebSocket = nullptr; | ||||
|  | ||||
|                 /* Return the new upgraded websocket */ | ||||
|                 return (us_socket_t *) asyncSocket; | ||||
|             } | ||||
|  | ||||
|             /* It is okay to uncork a closed socket and we need to */ | ||||
|             ((AsyncSocket<SSL> *) s)->uncork(); | ||||
|  | ||||
|             /* We cannot return nullptr to the underlying stack in any case */ | ||||
|             return s; | ||||
|         }); | ||||
|  | ||||
|         /* Handle HTTP write out (note: SSL_read may trigger this spuriously, the app need to handle spurious calls) */ | ||||
|         us_socket_context_on_writable(SSL, getSocketContext(), [](us_socket_t *s) { | ||||
|  | ||||
|             AsyncSocket<SSL> *asyncSocket = (AsyncSocket<SSL> *) s; | ||||
|             HttpResponseData<SSL> *httpResponseData = (HttpResponseData<SSL> *) asyncSocket->getAsyncSocketData(); | ||||
|  | ||||
|             /* Ask the developer to write data and return success (true) or failure (false), OR skip sending anything and return success (true). */ | ||||
|             if (httpResponseData->onWritable) { | ||||
|                 /* We are now writable, so hang timeout again, the user does not have to do anything so we should hang until end or tryEnd rearms timeout */ | ||||
|                 us_socket_timeout(SSL, s, 0); | ||||
|  | ||||
|                 /* We expect the developer to return whether or not write was successful (true). | ||||
|                  * If write was never called, the developer should still return true so that we may drain. */ | ||||
|                 bool success = httpResponseData->onWritable(httpResponseData->offset); | ||||
|  | ||||
|                 /* The developer indicated that their onWritable failed. */ | ||||
|                 if (!success) { | ||||
|                     /* Skip testing if we can drain anything since that might perform an extra syscall */ | ||||
|                     return s; | ||||
|                 } | ||||
|  | ||||
|                 /* We don't want to fall through since we don't want to mess with timeout. | ||||
|                  * It makes little sense to drain any backpressure when the user has registered onWritable. */ | ||||
|                 return s; | ||||
|             } | ||||
|  | ||||
|             /* Drain any socket buffer, this might empty our backpressure and thus finish the request */ | ||||
|             /*auto [written, failed] = */asyncSocket->write(nullptr, 0, true, 0); | ||||
|  | ||||
|             /* Expect another writable event, or another request within the timeout */ | ||||
|             asyncSocket->timeout(HTTP_IDLE_TIMEOUT_S); | ||||
|  | ||||
|             return s; | ||||
|         }); | ||||
|  | ||||
|         /* Handle FIN, HTTP does not support half-closed sockets, so simply close */ | ||||
|         us_socket_context_on_end(SSL, getSocketContext(), [](us_socket_t *s) { | ||||
|  | ||||
|             /* We do not care for half closed sockets */ | ||||
|             AsyncSocket<SSL> *asyncSocket = (AsyncSocket<SSL> *) s; | ||||
|             return asyncSocket->close(); | ||||
|  | ||||
|         }); | ||||
|  | ||||
|         /* Handle socket timeouts, simply close them so to not confuse client with FIN */ | ||||
|         us_socket_context_on_timeout(SSL, getSocketContext(), [](us_socket_t *s) { | ||||
|  | ||||
|             /* Force close rather than gracefully shutdown and risk confusing the client with a complete download */ | ||||
|             AsyncSocket<SSL> *asyncSocket = (AsyncSocket<SSL> *) s; | ||||
|             return asyncSocket->close(); | ||||
|  | ||||
|         }); | ||||
|  | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     /* Used by App in its WebSocket handler */ | ||||
|     void upgradeToWebSocket(void *newSocket) { | ||||
|         HttpContextData<SSL> *httpContextData = getSocketContextData(); | ||||
|  | ||||
|         httpContextData->upgradedWebSocket = newSocket; | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     /* Construct a new HttpContext using specified loop */ | ||||
|     static HttpContext *create(Loop *loop, us_socket_context_options_t options = {}) { | ||||
|         HttpContext *httpContext; | ||||
|  | ||||
|         httpContext = (HttpContext *) us_create_socket_context(SSL, (us_loop_t *) loop, sizeof(HttpContextData<SSL>), options); | ||||
|  | ||||
|         if (!httpContext) { | ||||
|             return nullptr; | ||||
|         } | ||||
|  | ||||
|         /* Init socket context data */ | ||||
|         new ((HttpContextData<SSL> *) us_socket_context_ext(SSL, (us_socket_context_t *) httpContext)) HttpContextData<SSL>(); | ||||
|         return httpContext->init(); | ||||
|     } | ||||
|  | ||||
|     /* Destruct the HttpContext, it does not follow RAII */ | ||||
|     void free() { | ||||
|         /* Destruct socket context data */ | ||||
|         HttpContextData<SSL> *httpContextData = getSocketContextData(); | ||||
|         httpContextData->~HttpContextData<SSL>(); | ||||
|  | ||||
|         /* Free the socket context in whole */ | ||||
|         us_socket_context_free(SSL, getSocketContext()); | ||||
|     } | ||||
|  | ||||
|     void filter(fu2::unique_function<void(HttpResponse<SSL> *, int)> &&filterHandler) { | ||||
|         getSocketContextData()->filterHandlers.emplace_back(std::move(filterHandler)); | ||||
|     } | ||||
|  | ||||
|     /* Register an HTTP route handler acording to URL pattern */ | ||||
|     void onHttp(std::string method, std::string pattern, fu2::unique_function<void(HttpResponse<SSL> *, HttpRequest *)> &&handler, bool upgrade = false) { | ||||
|         HttpContextData<SSL> *httpContextData = getSocketContextData(); | ||||
|  | ||||
|         /* Todo: This is ugly, fix */ | ||||
|         std::vector<std::string> methods; | ||||
|         if (method == "*") { | ||||
|             methods = httpContextData->router.methods; | ||||
|         } else { | ||||
|             methods = {method}; | ||||
|         } | ||||
|  | ||||
|         httpContextData->router.add(methods, pattern, [handler = std::move(handler)](auto *r) mutable { | ||||
|             auto user = r->getUserData(); | ||||
|             user.httpRequest->setYield(false); | ||||
|             user.httpRequest->setParameters(r->getParameters()); | ||||
|             handler(user.httpResponse, user.httpRequest); | ||||
|  | ||||
|             /* If any handler yielded, the router will keep looking for a suitable handler. */ | ||||
|             if (user.httpRequest->getYield()) { | ||||
|                 return false; | ||||
|             } | ||||
|             return true; | ||||
|         }, method == "*" ? httpContextData->router.LOW_PRIORITY : (upgrade ? httpContextData->router.HIGH_PRIORITY : httpContextData->router.MEDIUM_PRIORITY)); | ||||
|     } | ||||
|  | ||||
|     /* Listen to port using this HttpContext */ | ||||
|     us_listen_socket_t *listen(const char *host, int port, int options) { | ||||
|         return us_socket_context_listen(SSL, getSocketContext(), host, port, options, sizeof(HttpResponseData<SSL>)); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // UWS_HTTPCONTEXT_H | ||||
							
								
								
									
										48
									
								
								test/compatibility/C/uWebSockets/src/HttpContextData.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								test/compatibility/C/uWebSockets/src/HttpContextData.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| /* | ||||
|  * Authored by Alex Hultman, 2018-2019. | ||||
|  * Intellectual property of third-party. | ||||
|  | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #ifndef UWS_HTTPCONTEXTDATA_H | ||||
| #define UWS_HTTPCONTEXTDATA_H | ||||
|  | ||||
| #include "HttpRouter.h" | ||||
|  | ||||
| #include <vector> | ||||
| #include "f2/function2.hpp" | ||||
|  | ||||
| namespace uWS { | ||||
| template<bool> struct HttpResponse; | ||||
| struct HttpRequest; | ||||
|  | ||||
| template <bool SSL> | ||||
| struct alignas(16) HttpContextData { | ||||
|     template <bool> friend struct HttpContext; | ||||
|     template <bool> friend struct HttpResponse; | ||||
| private: | ||||
|     std::vector<fu2::unique_function<void(HttpResponse<SSL> *, int)>> filterHandlers; | ||||
|  | ||||
|     struct RouterData { | ||||
|         HttpResponse<SSL> *httpResponse; | ||||
|         HttpRequest *httpRequest; | ||||
|     }; | ||||
|  | ||||
|     HttpRouter<RouterData> router; | ||||
|     void *upgradedWebSocket = nullptr; | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // UWS_HTTPCONTEXTDATA_H | ||||
							
								
								
									
										334
									
								
								test/compatibility/C/uWebSockets/src/HttpParser.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										334
									
								
								test/compatibility/C/uWebSockets/src/HttpParser.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,334 @@ | ||||
| /* | ||||
|  * Authored by Alex Hultman, 2018-2019. | ||||
|  * Intellectual property of third-party. | ||||
|  | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #ifndef UWS_HTTPPARSER_H | ||||
| #define UWS_HTTPPARSER_H | ||||
|  | ||||
| // todo: HttpParser is in need of a few clean-ups and refactorings | ||||
|  | ||||
| /* The HTTP parser is an independent module subject to unit testing / fuzz testing */ | ||||
|  | ||||
| #include <string> | ||||
| #include <cstring> | ||||
| #include <algorithm> | ||||
| #include "f2/function2.hpp" | ||||
|  | ||||
| namespace uWS { | ||||
|  | ||||
| /* We require at least this much post padding */ | ||||
| static const int MINIMUM_HTTP_POST_PADDING = 32; | ||||
|  | ||||
| struct HttpRequest { | ||||
|  | ||||
|     friend struct HttpParser; | ||||
|  | ||||
| private: | ||||
|     const static int MAX_HEADERS = 50; | ||||
|     struct Header { | ||||
|         std::string_view key, value; | ||||
|     } headers[MAX_HEADERS]; | ||||
|     int querySeparator; | ||||
|     bool didYield; | ||||
|  | ||||
|     std::pair<int, std::string_view *> currentParameters; | ||||
|  | ||||
| public: | ||||
|     bool getYield() { | ||||
|         return didYield; | ||||
|     } | ||||
|  | ||||
|     /* Iteration over headers (key, value) */ | ||||
|     struct HeaderIterator { | ||||
|         Header *ptr; | ||||
|  | ||||
|         bool operator!=(const HeaderIterator &other) const { | ||||
|             /* Comparison with end is a special case */ | ||||
|             if (ptr != other.ptr) { | ||||
|                 return other.ptr || ptr->key.length(); | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         HeaderIterator &operator++() { | ||||
|             ptr++; | ||||
|             return *this; | ||||
|         } | ||||
|  | ||||
|         std::pair<std::string_view, std::string_view> operator*() const { | ||||
|             return {ptr->key, ptr->value}; | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     HeaderIterator begin() { | ||||
|         return {headers + 1}; | ||||
|     } | ||||
|  | ||||
|     HeaderIterator end() { | ||||
|         return {nullptr}; | ||||
|     } | ||||
|  | ||||
|     /* If you do not want to handle this route */ | ||||
|     void setYield(bool yield) { | ||||
|         didYield = yield; | ||||
|     } | ||||
|  | ||||
|     std::string_view getHeader(std::string_view lowerCasedHeader) { | ||||
|         for (Header *h = headers; (++h)->key.length(); ) { | ||||
|             if (h->key.length() == lowerCasedHeader.length() && !strncmp(h->key.data(), lowerCasedHeader.data(), lowerCasedHeader.length())) { | ||||
|                 return h->value; | ||||
|             } | ||||
|         } | ||||
|         return std::string_view(nullptr, 0); | ||||
|     } | ||||
|  | ||||
|     std::string_view getUrl() { | ||||
|         return std::string_view(headers->value.data(), querySeparator); | ||||
|     } | ||||
|  | ||||
|     std::string_view getMethod() { | ||||
|         return std::string_view(headers->key.data(), headers->key.length()); | ||||
|     } | ||||
|  | ||||
|     std::string_view getQuery() { | ||||
|         if (querySeparator < (int) headers->value.length()) { | ||||
|             /* Strip the initial ? */ | ||||
|             return std::string_view(headers->value.data() + querySeparator + 1, headers->value.length() - querySeparator - 1); | ||||
|         } else { | ||||
|             return std::string_view(nullptr, 0); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void setParameters(std::pair<int, std::string_view *> parameters) { | ||||
|         currentParameters = parameters; | ||||
|     } | ||||
|  | ||||
|     std::string_view getParameter(unsigned int index) { | ||||
|         if (currentParameters.first < (int) index) { | ||||
|             return {}; | ||||
|         } else { | ||||
|             return currentParameters.second[index]; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| }; | ||||
|  | ||||
| struct HttpParser { | ||||
|  | ||||
| private: | ||||
|     std::string fallback; | ||||
|     unsigned int remainingStreamingBytes = 0; | ||||
|  | ||||
|     const size_t MAX_FALLBACK_SIZE = 1024 * 4; | ||||
|  | ||||
|     static unsigned int toUnsignedInteger(std::string_view str) { | ||||
|         unsigned int unsignedIntegerValue = 0; | ||||
|         for (unsigned char c : str) { | ||||
|             unsignedIntegerValue = unsignedIntegerValue * 10 + (c - '0'); | ||||
|         } | ||||
|         return unsignedIntegerValue; | ||||
|     } | ||||
|  | ||||
|     static unsigned int getHeaders(char *postPaddedBuffer, char *end, struct HttpRequest::Header *headers) { | ||||
|         char *preliminaryKey, *preliminaryValue, *start = postPaddedBuffer; | ||||
|  | ||||
|         for (unsigned int i = 0; i < HttpRequest::MAX_HEADERS; i++) { | ||||
|             for (preliminaryKey = postPaddedBuffer; (*postPaddedBuffer != ':') & (*postPaddedBuffer > 32); *(postPaddedBuffer++) |= 32); | ||||
|             if (*postPaddedBuffer == '\r') { | ||||
|                 if ((postPaddedBuffer != end) & (postPaddedBuffer[1] == '\n') & (i > 0)) { | ||||
|                     headers->key = std::string_view(nullptr, 0); | ||||
|                     return (unsigned int) ((postPaddedBuffer + 2) - start); | ||||
|                 } else { | ||||
|                     return 0; | ||||
|                 } | ||||
|             } else { | ||||
|                 headers->key = std::string_view(preliminaryKey, (size_t) (postPaddedBuffer - preliminaryKey)); | ||||
|                 for (postPaddedBuffer++; (*postPaddedBuffer == ':' || *postPaddedBuffer < 33) && *postPaddedBuffer != '\r'; postPaddedBuffer++); | ||||
|                 preliminaryValue = postPaddedBuffer; | ||||
|                 postPaddedBuffer = (char *) memchr(postPaddedBuffer, '\r', end - postPaddedBuffer); | ||||
|                 if (postPaddedBuffer && postPaddedBuffer[1] == '\n') { | ||||
|                     headers->value = std::string_view(preliminaryValue, (size_t) (postPaddedBuffer - preliminaryValue)); | ||||
|                     postPaddedBuffer += 2; | ||||
|                     headers++; | ||||
|                 } else { | ||||
|                     return 0; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     // the only caller of getHeaders | ||||
|     template <int CONSUME_MINIMALLY> | ||||
|     std::pair<int, void *> fenceAndConsumePostPadded(char *data, int length, void *user, HttpRequest *req, fu2::unique_function<void *(void *, HttpRequest *)> &requestHandler, fu2::unique_function<void *(void *, std::string_view, bool)> &dataHandler) { | ||||
|         int consumedTotal = 0; | ||||
|         data[length] = '\r'; | ||||
|  | ||||
|         for (int consumed; length && (consumed = getHeaders(data, data + length, req->headers)); ) { | ||||
|             data += consumed; | ||||
|             length -= consumed; | ||||
|             consumedTotal += consumed; | ||||
|  | ||||
|             req->headers->value = std::string_view(req->headers->value.data(), std::max<int>(0, (int) req->headers->value.length() - 9)); | ||||
|  | ||||
|             /* Parse query */ | ||||
|             const char *querySeparatorPtr = (const char *) memchr(req->headers->value.data(), '?', req->headers->value.length()); | ||||
|             req->querySeparator = (int) ((querySeparatorPtr ? querySeparatorPtr : req->headers->value.data() + req->headers->value.length()) - req->headers->value.data()); | ||||
|  | ||||
|             /* If returned socket is not what we put in we need | ||||
|              * to break here as we either have upgraded to | ||||
|              * WebSockets or otherwise closed the socket. */ | ||||
|             void *returnedUser = requestHandler(user, req); | ||||
|             if (returnedUser != user) { | ||||
|                 /* We are upgraded to WebSocket or otherwise broken */ | ||||
|                 return {consumedTotal, returnedUser}; | ||||
|             } | ||||
|  | ||||
|             // todo: do not check this for GET (get should not have a body) | ||||
|             // todo: also support reading chunked streams | ||||
|             std::string_view contentLengthString = req->getHeader("content-length"); | ||||
|             if (contentLengthString.length()) { | ||||
|                 remainingStreamingBytes = toUnsignedInteger(contentLengthString); | ||||
|  | ||||
|                 if (!CONSUME_MINIMALLY) { | ||||
|                     unsigned int emittable = std::min<unsigned int>(remainingStreamingBytes, length); | ||||
|                     dataHandler(user, std::string_view(data, emittable), emittable == remainingStreamingBytes); | ||||
|                     remainingStreamingBytes -= emittable; | ||||
|  | ||||
|                     data += emittable; | ||||
|                     length -= emittable; | ||||
|                     consumedTotal += emittable; | ||||
|                 } | ||||
|             } else { | ||||
|                 /* Still emit an empty data chunk to signal no data */ | ||||
|                 dataHandler(user, {}, true); | ||||
|             } | ||||
|  | ||||
|             if (CONSUME_MINIMALLY) { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         return {consumedTotal, user}; | ||||
|     } | ||||
|  | ||||
| public: | ||||
|  | ||||
|     /* We do this to prolong the validity of parsed headers by keeping only the fallback buffer alive */ | ||||
|     std::string &&salvageFallbackBuffer() { | ||||
|         return std::move(fallback); | ||||
|     } | ||||
|  | ||||
|     void *consumePostPadded(char *data, int length, void *user, fu2::unique_function<void *(void *, HttpRequest *)> &&requestHandler, fu2::unique_function<void *(void *, std::string_view, bool)> &&dataHandler, fu2::unique_function<void *(void *)> &&errorHandler) { | ||||
|  | ||||
|         HttpRequest req; | ||||
|  | ||||
|         if (remainingStreamingBytes) { | ||||
|  | ||||
|             // this is exactly the same as below! | ||||
|             // todo: refactor this | ||||
|             if (remainingStreamingBytes >= (unsigned int) length) { | ||||
|                 void *returnedUser = dataHandler(user, std::string_view(data, length), remainingStreamingBytes == (unsigned int) length); | ||||
|                 remainingStreamingBytes -= length; | ||||
|                 return returnedUser; | ||||
|             } else { | ||||
|                 void *returnedUser = dataHandler(user, std::string_view(data, remainingStreamingBytes), true); | ||||
|  | ||||
|                 data += remainingStreamingBytes; | ||||
|                 length -= remainingStreamingBytes; | ||||
|  | ||||
|                 remainingStreamingBytes = 0; | ||||
|  | ||||
|                 if (returnedUser != user) { | ||||
|                     return returnedUser; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|         } else if (fallback.length()) { | ||||
|             int had = (int) fallback.length(); | ||||
|  | ||||
|             int maxCopyDistance = (int) std::min(MAX_FALLBACK_SIZE - fallback.length(), (size_t) length); | ||||
|  | ||||
|             /* We don't want fallback to be short string optimized, since we want to move it */ | ||||
|             fallback.reserve(fallback.length() + maxCopyDistance + std::max<int>(MINIMUM_HTTP_POST_PADDING, sizeof(std::string))); | ||||
|             fallback.append(data, maxCopyDistance); | ||||
|  | ||||
|             // break here on break | ||||
|             std::pair<int, void *> consumed = fenceAndConsumePostPadded<true>(fallback.data(), (int) fallback.length(), user, &req, requestHandler, dataHandler); | ||||
|             if (consumed.second != user) { | ||||
|                 return consumed.second; | ||||
|             } | ||||
|  | ||||
|             if (consumed.first) { | ||||
|  | ||||
|                 fallback.clear(); | ||||
|  | ||||
|                 data += consumed.first - had; | ||||
|                 length -= consumed.first - had; | ||||
|  | ||||
|                 if (remainingStreamingBytes) { | ||||
|                     // this is exactly the same as above! | ||||
|                     if (remainingStreamingBytes >= (unsigned int) length) { | ||||
|                         void *returnedUser = dataHandler(user, std::string_view(data, length), remainingStreamingBytes == (unsigned int) length); | ||||
|                         remainingStreamingBytes -= length; | ||||
|                         return returnedUser; | ||||
|                     } else { | ||||
|                         void *returnedUser = dataHandler(user, std::string_view(data, remainingStreamingBytes), true); | ||||
|  | ||||
|                         data += remainingStreamingBytes; | ||||
|                         length -= remainingStreamingBytes; | ||||
|  | ||||
|                         remainingStreamingBytes = 0; | ||||
|  | ||||
|                         if (returnedUser != user) { | ||||
|                             return returnedUser; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|             } else { | ||||
|                 if (fallback.length() == MAX_FALLBACK_SIZE) { | ||||
|                     // note: you don't really need error handler, just return something strange! | ||||
|                     // we could have it return a constant pointer to denote error! | ||||
|                     return errorHandler(user); | ||||
|                 } | ||||
|                 return user; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         std::pair<int, void *> consumed = fenceAndConsumePostPadded<false>(data, length, user, &req, requestHandler, dataHandler); | ||||
|         if (consumed.second != user) { | ||||
|             return consumed.second; | ||||
|         } | ||||
|  | ||||
|         data += consumed.first; | ||||
|         length -= consumed.first; | ||||
|  | ||||
|         if (length) { | ||||
|             if ((unsigned int) length < MAX_FALLBACK_SIZE) { | ||||
|                 fallback.append(data, length); | ||||
|             } else { | ||||
|                 return errorHandler(user); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // added for now | ||||
|         return user; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // UWS_HTTPPARSER_H | ||||
							
								
								
									
										317
									
								
								test/compatibility/C/uWebSockets/src/HttpResponse.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										317
									
								
								test/compatibility/C/uWebSockets/src/HttpResponse.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,317 @@ | ||||
| /* | ||||
|  * Authored by Alex Hultman, 2018-2019. | ||||
|  * Intellectual property of third-party. | ||||
|  | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #ifndef UWS_HTTPRESPONSE_H | ||||
| #define UWS_HTTPRESPONSE_H | ||||
|  | ||||
| /* An HttpResponse is the channel on which you send back a response */ | ||||
|  | ||||
| #include "AsyncSocket.h" | ||||
| #include "HttpResponseData.h" | ||||
| #include "HttpContextData.h" | ||||
| #include "Utilities.h" | ||||
|  | ||||
| #include "f2/function2.hpp" | ||||
|  | ||||
| /* todo: tryWrite is missing currently, only send smaller segments with write */ | ||||
|  | ||||
| namespace uWS { | ||||
|  | ||||
| /* Some pre-defined status constants to use with writeStatus */ | ||||
| static const char *HTTP_200_OK = "200 OK"; | ||||
|  | ||||
| /* The general timeout for HTTP sockets */ | ||||
| static const int HTTP_TIMEOUT_S = 10; | ||||
|  | ||||
| template <bool SSL> | ||||
| struct HttpResponse : public AsyncSocket<SSL> { | ||||
|     /* Solely used for getHttpResponseData() */ | ||||
|     template <bool> friend struct TemplatedApp; | ||||
|     typedef AsyncSocket<SSL> Super; | ||||
| private: | ||||
|     HttpResponseData<SSL> *getHttpResponseData() { | ||||
|         return (HttpResponseData<SSL> *) Super::getAsyncSocketData(); | ||||
|     } | ||||
|  | ||||
|     /* Write an unsigned 32-bit integer in hex */ | ||||
|     void writeUnsignedHex(unsigned int value) { | ||||
|         char buf[10]; | ||||
|         int length = utils::u32toaHex(value, buf); | ||||
|  | ||||
|         /* For now we do this copy */ | ||||
|         Super::write(buf, length); | ||||
|     } | ||||
|  | ||||
|     /* Write an unsigned 32-bit integer */ | ||||
|     void writeUnsigned(unsigned int value) { | ||||
|         char buf[10]; | ||||
|         int length = utils::u32toa(value, buf); | ||||
|  | ||||
|         /* For now we do this copy */ | ||||
|         Super::write(buf, length); | ||||
|     } | ||||
|  | ||||
|     /* When we are done with a response we mark it like so */ | ||||
|     void markDone(HttpResponseData<SSL> *httpResponseData) { | ||||
|         httpResponseData->onAborted = nullptr; | ||||
|         /* Also remove onWritable so that we do not emit when draining behind the scenes. */ | ||||
|         httpResponseData->onWritable = nullptr; | ||||
|  | ||||
|         /* We are done with this request */ | ||||
|         httpResponseData->state &= ~HttpResponseData<SSL>::HTTP_RESPONSE_PENDING; | ||||
|     } | ||||
|  | ||||
|     /* Called only once per request */ | ||||
|     void writeMark() { | ||||
|         writeHeader("uWebSockets", "v0.17"); | ||||
|     } | ||||
|  | ||||
|     /* Returns true on success, indicating that it might be feasible to write more data. | ||||
|      * Will start timeout if stream reaches totalSize or write failure. */ | ||||
|     bool internalEnd(std::string_view data, int totalSize, bool optional, bool allowContentLength = true) { | ||||
|         /* Write status if not already done */ | ||||
|         writeStatus(HTTP_200_OK); | ||||
|  | ||||
|         /* If no total size given then assume this chunk is everything */ | ||||
|         if (!totalSize) { | ||||
|             totalSize = (int) data.length(); | ||||
|         } | ||||
|  | ||||
|         HttpResponseData<SSL> *httpResponseData = getHttpResponseData(); | ||||
|         if (httpResponseData->state & HttpResponseData<SSL>::HTTP_WRITE_CALLED) { | ||||
|  | ||||
|             /* We do not have tryWrite-like functionalities, so ignore optional in this path */ | ||||
|  | ||||
|             /* Do not allow sending 0 chunk here */ | ||||
|             if (data.length()) { | ||||
|                 Super::write("\r\n", 2); | ||||
|                 writeUnsignedHex((unsigned int) data.length()); | ||||
|                 Super::write("\r\n", 2); | ||||
|  | ||||
|                 /* Ignoring optional for now */ | ||||
|                 Super::write(data.data(), (int) data.length()); | ||||
|             } | ||||
|  | ||||
|             /* Terminating 0 chunk */ | ||||
|             Super::write("\r\n0\r\n\r\n", 7); | ||||
|  | ||||
|             markDone(httpResponseData); | ||||
|  | ||||
|             /* tryEnd can never fail when in chunked mode, since we do not have tryWrite (yet), only write */ | ||||
|             Super::timeout(HTTP_TIMEOUT_S); | ||||
|             return true; | ||||
|         } else { | ||||
|             /* Write content-length on first call */ | ||||
|             if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_END_CALLED)) { | ||||
|                 /* Write mark, this propagates to WebSockets too */ | ||||
|                 writeMark(); | ||||
|  | ||||
|                 /* WebSocket upgrades does not allow content-length */ | ||||
|                 if (allowContentLength) { | ||||
|                     /* Even zero is a valid content-length */ | ||||
|                     Super::write("Content-Length: ", 16); | ||||
|                     writeUnsigned(totalSize); | ||||
|                     Super::write("\r\n\r\n", 4); | ||||
|                 } else { | ||||
|                     Super::write("\r\n", 2); | ||||
|                 } | ||||
|  | ||||
|                 /* Mark end called */ | ||||
|                 httpResponseData->state |= HttpResponseData<SSL>::HTTP_END_CALLED; | ||||
|             } | ||||
|  | ||||
|             /* Even if we supply no new data to write, its failed boolean is useful to know | ||||
|              * if it failed to drain any prior failed header writes */ | ||||
|  | ||||
|             /* Write as much as possible without causing backpressure */ | ||||
|             auto [written, failed] = Super::write(data.data(), (int) data.length(), optional); | ||||
|             httpResponseData->offset += written; | ||||
|  | ||||
|             /* Success is when we wrote the entire thing without any failures */ | ||||
|             bool success = (unsigned int) written == data.length() && !failed; | ||||
|  | ||||
|             /* If we are now at the end, start a timeout. Also start a timeout if we failed. */ | ||||
|             if (!success || httpResponseData->offset == totalSize) { | ||||
|                 Super::timeout(HTTP_TIMEOUT_S); | ||||
|             } | ||||
|  | ||||
|             /* Remove onAborted function if we reach the end */ | ||||
|             if (httpResponseData->offset == totalSize) { | ||||
|                 markDone(httpResponseData); | ||||
|             } | ||||
|  | ||||
|             return success; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* This call is identical to end, but will never write content-length and is thus suitable for upgrades */ | ||||
|     void upgrade() { | ||||
|         internalEnd({nullptr, 0}, 0, false, false); | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     /* Immediately terminate this Http response */ | ||||
|     using Super::close; | ||||
|  | ||||
|     using Super::getRemoteAddress; | ||||
|  | ||||
|     /* Note: Headers are not checked in regards to timeout. | ||||
|      * We only check when you actively push data or end the request */ | ||||
|  | ||||
|     /* Write the HTTP status */ | ||||
|     HttpResponse *writeStatus(std::string_view status) { | ||||
|         HttpResponseData<SSL> *httpResponseData = getHttpResponseData(); | ||||
|  | ||||
|         /* Do not allow writing more than one status */ | ||||
|         if (httpResponseData->state & HttpResponseData<SSL>::HTTP_STATUS_CALLED) { | ||||
|             return this; | ||||
|         } | ||||
|  | ||||
|         /* Update status */ | ||||
|         httpResponseData->state |= HttpResponseData<SSL>::HTTP_STATUS_CALLED; | ||||
|  | ||||
|         Super::write("HTTP/1.1 ", 9); | ||||
|         Super::write(status.data(), (int) status.length()); | ||||
|         Super::write("\r\n", 2); | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     /* Write an HTTP header with string value */ | ||||
|     HttpResponse *writeHeader(std::string_view key, std::string_view value) { | ||||
|         writeStatus(HTTP_200_OK); | ||||
|  | ||||
|         Super::write(key.data(), (int) key.length()); | ||||
|         Super::write(": ", 2); | ||||
|         Super::write(value.data(), (int) value.length()); | ||||
|         Super::write("\r\n", 2); | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     /* Write an HTTP header with unsigned int value */ | ||||
|     HttpResponse *writeHeader(std::string_view key, unsigned int value) { | ||||
|         Super::write(key.data(), (int) key.length()); | ||||
|         Super::write(": ", 2); | ||||
|         writeUnsigned(value); | ||||
|         Super::write("\r\n", 2); | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     /* End the response with an optional data chunk. Always starts a timeout. */ | ||||
|     void end(std::string_view data = {}) { | ||||
|         internalEnd(data, (int) data.length(), false); | ||||
|     } | ||||
|  | ||||
|     /* Try and end the response. Returns [true, true] on success. | ||||
|      * Starts a timeout in some cases. Returns [ok, hasResponded] */ | ||||
|     std::pair<bool, bool> tryEnd(std::string_view data, int totalSize = 0) { | ||||
|         return {internalEnd(data, totalSize, true), hasResponded()}; | ||||
|     } | ||||
|  | ||||
|     /* Write parts of the response in chunking fashion. Starts timeout if failed. */ | ||||
|     bool write(std::string_view data) { | ||||
|         writeStatus(HTTP_200_OK); | ||||
|  | ||||
|         /* Do not allow sending 0 chunks, they mark end of response */ | ||||
|         if (!data.length()) { | ||||
|             /* If you called us, then according to you it was fine to call us so it's fine to still call us */ | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         HttpResponseData<SSL> *httpResponseData = getHttpResponseData(); | ||||
|  | ||||
|         if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_WRITE_CALLED)) { | ||||
|             /* Write mark on first call to write */ | ||||
|             writeMark(); | ||||
|  | ||||
|             writeHeader("Transfer-Encoding", "chunked"); | ||||
|             httpResponseData->state |= HttpResponseData<SSL>::HTTP_WRITE_CALLED; | ||||
|         } | ||||
|  | ||||
|         Super::write("\r\n", 2); | ||||
|         writeUnsignedHex((unsigned int) data.length()); | ||||
|         Super::write("\r\n", 2); | ||||
|  | ||||
|         auto [written, failed] = Super::write(data.data(), (int) data.length()); | ||||
|         if (failed) { | ||||
|             Super::timeout(HTTP_TIMEOUT_S); | ||||
|         } | ||||
|  | ||||
|         /* If we did not fail the write, accept more */ | ||||
|         return !failed; | ||||
|     } | ||||
|  | ||||
|     /* Get the current byte write offset for this Http response */ | ||||
|     int getWriteOffset() { | ||||
|         HttpResponseData<SSL> *httpResponseData = getHttpResponseData(); | ||||
|  | ||||
|         return httpResponseData->offset; | ||||
|     } | ||||
|  | ||||
|     /* Checking if we have fully responded and are ready for another request */ | ||||
|     bool hasResponded() { | ||||
|         HttpResponseData<SSL> *httpResponseData = getHttpResponseData(); | ||||
|  | ||||
|         return !(httpResponseData->state & HttpResponseData<SSL>::HTTP_RESPONSE_PENDING); | ||||
|     } | ||||
|  | ||||
|     /* Corks the response if possible. Leaves already corked socket be. */ | ||||
|     HttpResponse *cork(fu2::unique_function<void()> &&handler) { | ||||
|         if (!Super::isCorked() && Super::canCork()) { | ||||
|             Super::cork(); | ||||
|             handler(); | ||||
|  | ||||
|             /* Timeout on uncork failure, since most writes will succeed while corked */ | ||||
|             auto [written, failed] = Super::uncork(); | ||||
|             if (failed) { | ||||
|                 /* For now we only have one single timeout so let's use it */ | ||||
|                 /* This behavior should equal the behavior in HttpContext when uncorking fails */ | ||||
|                 Super::timeout(HTTP_TIMEOUT_S); | ||||
|             } | ||||
|         } else { | ||||
|             /* We are already corked, or can't cork so let's just call the handler */ | ||||
|             handler(); | ||||
|         } | ||||
|  | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     /* Attach handler for writable HTTP response */ | ||||
|     HttpResponse *onWritable(fu2::unique_function<bool(int)> &&handler) { | ||||
|         HttpResponseData<SSL> *httpResponseData = getHttpResponseData(); | ||||
|  | ||||
|         httpResponseData->onWritable = std::move(handler); | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     /* Attach handler for aborted HTTP request */ | ||||
|     HttpResponse *onAborted(fu2::unique_function<void()> &&handler) { | ||||
|         HttpResponseData<SSL> *httpResponseData = getHttpResponseData(); | ||||
|  | ||||
|         httpResponseData->onAborted = std::move(handler); | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     /* Attach a read handler for data sent. Will be called with FIN set true if last segment. */ | ||||
|     void onData(fu2::unique_function<void(std::string_view, bool)> &&handler) { | ||||
|         HttpResponseData<SSL> *data = getHttpResponseData(); | ||||
|         data->inStream = std::move(handler); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // UWS_HTTPRESPONSE_H | ||||
							
								
								
									
										57
									
								
								test/compatibility/C/uWebSockets/src/HttpResponseData.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								test/compatibility/C/uWebSockets/src/HttpResponseData.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| /* | ||||
|  * Authored by Alex Hultman, 2018-2019. | ||||
|  * Intellectual property of third-party. | ||||
|  | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #ifndef UWS_HTTPRESPONSEDATA_H | ||||
| #define UWS_HTTPRESPONSEDATA_H | ||||
|  | ||||
| /* This data belongs to the HttpResponse */ | ||||
|  | ||||
| #include "HttpParser.h" | ||||
| #include "AsyncSocketData.h" | ||||
|  | ||||
| #include "f2/function2.hpp" | ||||
|  | ||||
| namespace uWS { | ||||
|  | ||||
| template <bool SSL> | ||||
| struct HttpResponseData : AsyncSocketData<SSL>, HttpParser { | ||||
|     template <bool> friend struct HttpResponse; | ||||
|     template <bool> friend struct HttpContext; | ||||
| private: | ||||
|     /* Bits of status */ | ||||
|     enum { | ||||
|         HTTP_STATUS_CALLED = 1, // used | ||||
|         HTTP_WRITE_CALLED = 2, // used | ||||
|         HTTP_END_CALLED = 4, // used | ||||
|         HTTP_RESPONSE_PENDING = 8, // used | ||||
|         HTTP_ENDED_STREAM_OUT = 16 // not used | ||||
|     }; | ||||
|  | ||||
|     /* Per socket event handlers */ | ||||
|     fu2::unique_function<bool(int)> onWritable; | ||||
|     fu2::unique_function<void()> onAborted; | ||||
|     fu2::unique_function<void(std::string_view, bool)> inStream; // onData | ||||
|     /* Outgoing offset */ | ||||
|     int offset = 0; | ||||
|  | ||||
|     /* Current state (content-length sent, status sent, write called, etc */ | ||||
|     int state = 0; | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // UWS_HTTPRESPONSEDATA_H | ||||
							
								
								
									
										233
									
								
								test/compatibility/C/uWebSockets/src/HttpRouter.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										233
									
								
								test/compatibility/C/uWebSockets/src/HttpRouter.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,233 @@ | ||||
| /* | ||||
|  * Authored by Alex Hultman, 2018-2019. | ||||
|  * Intellectual property of third-party. | ||||
|  | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #ifndef UWS_HTTPROUTER_HPP | ||||
| #define UWS_HTTPROUTER_HPP | ||||
|  | ||||
| #include <map> | ||||
| #include <vector> | ||||
| #include <cstring> | ||||
| #include <string_view> | ||||
| #include <string> | ||||
| #include <algorithm> | ||||
| #include <memory> | ||||
|  | ||||
| #include "f2/function2.hpp" | ||||
|  | ||||
| namespace uWS { | ||||
|  | ||||
| template <class USERDATA> | ||||
| struct HttpRouter { | ||||
|     /* These are public for now */ | ||||
|     std::vector<std::string> methods = {"get", "post", "head", "put", "delete", "connect", "options", "trace", "patch"}; | ||||
|     static const uint32_t HIGH_PRIORITY = 0xd0000000, MEDIUM_PRIORITY = 0xe0000000, LOW_PRIORITY = 0xf0000000; | ||||
|  | ||||
| private: | ||||
|     USERDATA userData; | ||||
|     static const unsigned int MAX_URL_SEGMENTS = 100; | ||||
|  | ||||
|     /* Handler ids are 32-bit */ | ||||
|     static const uint32_t HANDLER_MASK = 0x0fffffff; | ||||
|      | ||||
|     /* Methods and their respective priority */ | ||||
|     std::map<std::string, int> priority; | ||||
|  | ||||
|     /* List of handlers */ | ||||
|     std::vector<fu2::unique_function<bool(HttpRouter *)>> handlers; | ||||
|  | ||||
|     /* Current URL cache */ | ||||
|     std::string_view currentUrl; | ||||
|     std::string_view urlSegmentVector[MAX_URL_SEGMENTS]; | ||||
|     int urlSegmentTop; | ||||
|  | ||||
|     /* The matching tree */ | ||||
|     struct Node { | ||||
|         std::string name; | ||||
|         std::vector<std::unique_ptr<Node>> children; | ||||
|         std::vector<uint32_t> handlers; | ||||
|     } root = {"rootNode"}; | ||||
|  | ||||
|     /* Advance from parent to child, adding child if necessary */ | ||||
|     Node *getNode(Node *parent, std::string child) { | ||||
|         for (std::unique_ptr<Node> &node : parent->children) { | ||||
|             if (node->name == child) { | ||||
|                 return node.get(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /* Insert sorted, but keep order if parent is root (we sort methods by priority elsewhere) */ | ||||
|         std::unique_ptr<Node> newNode(new Node({child})); | ||||
|         return parent->children.emplace(std::upper_bound(parent->children.begin(), parent->children.end(), newNode, [parent, this](auto &a, auto &b) { | ||||
|             return b->name.length() && (parent != &root) && (b->name < a->name); | ||||
|         }), std::move(newNode))->get(); | ||||
|     } | ||||
|  | ||||
|     /* Basically a pre-allocated stack */ | ||||
|     struct RouteParameters { | ||||
|         friend struct HttpRouter; | ||||
|     private: | ||||
|         std::string_view params[MAX_URL_SEGMENTS]; | ||||
|         int paramsTop; | ||||
|  | ||||
|         void reset() { | ||||
|             paramsTop = -1; | ||||
|         } | ||||
|  | ||||
|         void push(std::string_view param) { | ||||
|             /* We check these bounds indirectly via the urlSegments limit */ | ||||
|             params[++paramsTop] = param; | ||||
|         } | ||||
|  | ||||
|         void pop() { | ||||
|             /* Same here, we cannot pop outside */ | ||||
|             paramsTop--; | ||||
|         } | ||||
|     } routeParameters; | ||||
|  | ||||
|     /* Set URL for router. Will reset any URL cache */ | ||||
|     inline void setUrl(std::string_view url) { | ||||
|         /* Remove / from input URL */ | ||||
|         currentUrl = url.substr(std::min<unsigned int>((unsigned int) url.length(), 1)); | ||||
|         urlSegmentTop = -1; | ||||
|     } | ||||
|  | ||||
|     /* Lazily parse or read from cache */ | ||||
|     inline std::string_view getUrlSegment(int urlSegment) { | ||||
|         if (urlSegment > urlSegmentTop) { | ||||
|             /* Return empty segment if we are out of URL or stack space, but never for first url segment */ | ||||
|             if (!currentUrl.length() || urlSegment > 99) { | ||||
|                 return {}; | ||||
|             } | ||||
|  | ||||
|             auto segmentLength = currentUrl.find('/'); | ||||
|             if (segmentLength == std::string::npos) { | ||||
|                 segmentLength = currentUrl.length(); | ||||
|  | ||||
|                 /* Push to url segment vector */ | ||||
|                 urlSegmentVector[urlSegment] = currentUrl.substr(0, segmentLength); | ||||
|                 urlSegmentTop++; | ||||
|  | ||||
|                 /* Update currentUrl */ | ||||
|                 currentUrl = currentUrl.substr(segmentLength); | ||||
|             } else { | ||||
|                 /* Push to url segment vector */ | ||||
|                 urlSegmentVector[urlSegment] = currentUrl.substr(0, segmentLength); | ||||
|                 urlSegmentTop++; | ||||
|  | ||||
|                 /* Update currentUrl */ | ||||
|                 currentUrl = currentUrl.substr(segmentLength + 1); | ||||
|             } | ||||
|         } | ||||
|         /* In any case we return it */ | ||||
|         return urlSegmentVector[urlSegment]; | ||||
|     } | ||||
|  | ||||
|     /* Executes as many handlers it can */ | ||||
|     bool executeHandlers(Node *parent, int urlSegment, USERDATA &userData) { | ||||
|         /* If we have no more URL and not on first round, return where we may stand */ | ||||
|         if (urlSegment && !getUrlSegment(urlSegment).length()) { | ||||
|             /* We have reached accross the entire URL with no stoppage, execute */ | ||||
|             for (int handler : parent->handlers) { | ||||
|                 if (handlers[handler & HANDLER_MASK](this)) { | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|             /* We reached the end, so go back */ | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         for (auto &p : parent->children) { | ||||
|             if (p->name.length() && p->name[0] == '*') { | ||||
|                 /* Wildcard match (can be seen as a shortcut) */ | ||||
|                 for (int handler : p->handlers) { | ||||
|                     if (handlers[handler & HANDLER_MASK](this)) { | ||||
|                         return true; | ||||
|                     } | ||||
|                 } | ||||
|             } else if (p->name.length() && p->name[0] == ':' && getUrlSegment(urlSegment).length()) { | ||||
|                 /* Parameter match */ | ||||
|                 routeParameters.push(getUrlSegment(urlSegment)); | ||||
|                 if (executeHandlers(p.get(), urlSegment + 1, userData)) { | ||||
|                     return true; | ||||
|                 } | ||||
|                 routeParameters.pop(); | ||||
|             } else if (p->name == getUrlSegment(urlSegment)) { | ||||
|                 /* Static match */ | ||||
|                 if (executeHandlers(p.get(), urlSegment + 1, userData)) { | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     HttpRouter() { | ||||
|         int p = 0; | ||||
|         for (std::string &method : methods) { | ||||
|             priority[method] = p++; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     std::pair<int, std::string_view *> getParameters() { | ||||
|         return {routeParameters.paramsTop, routeParameters.params}; | ||||
|     } | ||||
|  | ||||
|     USERDATA &getUserData() { | ||||
|         return userData; | ||||
|     } | ||||
|  | ||||
|     /* Fast path */ | ||||
|     bool route(std::string_view method, std::string_view url) { | ||||
|         /* Reset url parsing cache */ | ||||
|         setUrl(url); | ||||
|         routeParameters.reset(); | ||||
|  | ||||
|         /* Begin by finding the method node */ | ||||
|         for (auto &p : root.children) { | ||||
|             if (p->name == method) { | ||||
|                 /* Then route the url */ | ||||
|                 return executeHandlers(p.get(), 0, userData); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /* We did not find any handler for this method and url */ | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     /* Adds the corresponding entires in matching tree and handler list */ | ||||
|     void add(std::vector<std::string> methods, std::string pattern, fu2::unique_function<bool(HttpRouter *)> &&handler, int priority = MEDIUM_PRIORITY) { | ||||
|         for (std::string method : methods) { | ||||
|             /* Lookup method */ | ||||
|             Node *node = getNode(&root, method); | ||||
|             /* Iterate over all segments */ | ||||
|             setUrl(pattern); | ||||
|             for (int i = 0; getUrlSegment(i).length() || i == 0; i++) { | ||||
|                 node = getNode(node, std::string(getUrlSegment(i))); | ||||
|             } | ||||
|             /* Insert handler in order sorted by priority (most significant 1 byte) */ | ||||
|             node->handlers.insert(std::upper_bound(node->handlers.begin(), node->handlers.end(), (uint32_t) (priority | handlers.size())), (uint32_t) (priority | handlers.size())); | ||||
|         } | ||||
|  | ||||
|         /* Alloate this handler */ | ||||
|         handlers.emplace_back(std::move(handler)); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // UWS_HTTPROUTER_HPP | ||||
							
								
								
									
										169
									
								
								test/compatibility/C/uWebSockets/src/Loop.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								test/compatibility/C/uWebSockets/src/Loop.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,169 @@ | ||||
| /* | ||||
|  * Authored by Alex Hultman, 2018-2019. | ||||
|  * Intellectual property of third-party. | ||||
|  | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #ifndef UWS_LOOP_H | ||||
| #define UWS_LOOP_H | ||||
|  | ||||
| /* The loop is lazily created per-thread and run with uWS::run() */ | ||||
|  | ||||
| #include "LoopData.h" | ||||
| #include <libusockets.h> | ||||
|  | ||||
| namespace uWS { | ||||
| struct Loop { | ||||
| private: | ||||
|     static void wakeupCb(us_loop_t *loop) { | ||||
|         LoopData *loopData = (LoopData *) us_loop_ext(loop); | ||||
|  | ||||
|         /* Swap current deferQueue */ | ||||
|         loopData->deferMutex.lock(); | ||||
|         int oldDeferQueue = loopData->currentDeferQueue; | ||||
|         loopData->currentDeferQueue = (loopData->currentDeferQueue + 1) % 2; | ||||
|         loopData->deferMutex.unlock(); | ||||
|  | ||||
|         /* Drain the queue */ | ||||
|         for (auto &x : loopData->deferQueues[oldDeferQueue]) { | ||||
|             x(); | ||||
|         } | ||||
|         loopData->deferQueues[oldDeferQueue].clear(); | ||||
|     } | ||||
|  | ||||
|     static void preCb(us_loop_t *loop) { | ||||
|         LoopData *loopData = (LoopData *) us_loop_ext(loop); | ||||
|  | ||||
|         for (auto &p : loopData->preHandlers) { | ||||
|             p.second((Loop *) loop); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     static void postCb(us_loop_t *loop) { | ||||
|         LoopData *loopData = (LoopData *) us_loop_ext(loop); | ||||
|  | ||||
|         for (auto &p : loopData->postHandlers) { | ||||
|             p.second((Loop *) loop); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Loop() = delete; | ||||
|     ~Loop() = default; | ||||
|  | ||||
|     Loop *init() { | ||||
|         new (us_loop_ext((us_loop_t *) this)) LoopData; | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     static Loop *create(void *hint) { | ||||
|         return ((Loop *) us_create_loop(hint, wakeupCb, preCb, postCb, sizeof(LoopData)))->init(); | ||||
|     } | ||||
|  | ||||
|     /* What to do with loops created with existingNativeLoop? */ | ||||
|     struct LoopCleaner { | ||||
|         ~LoopCleaner() { | ||||
|             if(loop && cleanMe) { | ||||
|                 loop->free(); | ||||
|             } | ||||
|         } | ||||
|         Loop *loop = nullptr; | ||||
|         bool cleanMe = false; | ||||
|     }; | ||||
|      | ||||
| public: | ||||
|     /* Lazily initializes a per-thread loop and returns it. | ||||
|      * Will automatically free all initialized loops at exit. */ | ||||
|     static Loop *get(void *existingNativeLoop = nullptr) { | ||||
|         static thread_local LoopCleaner lazyLoop; | ||||
|         if (!lazyLoop.loop) { | ||||
|             /* If we are given a native loop pointer we pass that to uSockets and let it deal with it */ | ||||
|             if (existingNativeLoop) { | ||||
|                 /* Todo: here we want to pass the pointer, not a boolean */ | ||||
|                 lazyLoop.loop = create(existingNativeLoop); | ||||
|                 /* We cannot register automatic free here, must be manually done */ | ||||
|             } else { | ||||
|                 lazyLoop.loop = create(nullptr); | ||||
|                 lazyLoop.cleanMe = true; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return lazyLoop.loop; | ||||
|     } | ||||
|  | ||||
|     /* Freeing the default loop should be done once */ | ||||
|     void free() { | ||||
|         LoopData *loopData = (LoopData *) us_loop_ext((us_loop_t *) this); | ||||
|         loopData->~LoopData(); | ||||
|         /* uSockets will track whether this loop is owned by us or a borrowed alien loop */ | ||||
|         us_loop_free((us_loop_t *) this); | ||||
|     } | ||||
|  | ||||
|     void addPostHandler(void *key, fu2::unique_function<void(Loop *)> &&handler) { | ||||
|         LoopData *loopData = (LoopData *) us_loop_ext((us_loop_t *) this); | ||||
|  | ||||
|         loopData->postHandlers.emplace(key, std::move(handler)); | ||||
|     } | ||||
|  | ||||
|     /* Bug: what if you remove a handler while iterating them? */ | ||||
|     void removePostHandler(void *key) { | ||||
|         LoopData *loopData = (LoopData *) us_loop_ext((us_loop_t *) this); | ||||
|  | ||||
|         loopData->postHandlers.erase(key); | ||||
|     } | ||||
|  | ||||
|     void addPreHandler(void *key, fu2::unique_function<void(Loop *)> &&handler) { | ||||
|         LoopData *loopData = (LoopData *) us_loop_ext((us_loop_t *) this); | ||||
|  | ||||
|         loopData->preHandlers.emplace(key, std::move(handler)); | ||||
|     } | ||||
|  | ||||
|     /* Bug: what if you remove a handler while iterating them? */ | ||||
|     void removePreHandler(void *key) { | ||||
|         LoopData *loopData = (LoopData *) us_loop_ext((us_loop_t *) this); | ||||
|  | ||||
|         loopData->preHandlers.erase(key); | ||||
|     } | ||||
|  | ||||
|     /* Defer this callback on Loop's thread of execution */ | ||||
|     void defer(fu2::unique_function<void()> &&cb) { | ||||
|         LoopData *loopData = (LoopData *) us_loop_ext((us_loop_t *) this); | ||||
|  | ||||
|         //if (std::thread::get_id() == ) // todo: add fast path for same thread id | ||||
|         loopData->deferMutex.lock(); | ||||
|         loopData->deferQueues[loopData->currentDeferQueue].emplace_back(std::move(cb)); | ||||
|         loopData->deferMutex.unlock(); | ||||
|  | ||||
|         us_wakeup_loop((us_loop_t *) this); | ||||
|     } | ||||
|  | ||||
|     /* Actively block and run this loop */ | ||||
|     void run() { | ||||
|         us_loop_run((us_loop_t *) this); | ||||
|     } | ||||
|  | ||||
|     /* Passively integrate with the underlying default loop */ | ||||
|     /* Used to seamlessly integrate with third parties such as Node.js */ | ||||
|     void integrate() { | ||||
|         us_loop_integrate((us_loop_t *) this); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| /* Can be called from any thread to run the thread local loop */ | ||||
| inline void run() { | ||||
|     Loop::get()->run(); | ||||
| } | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // UWS_LOOP_H | ||||
							
								
								
									
										72
									
								
								test/compatibility/C/uWebSockets/src/LoopData.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								test/compatibility/C/uWebSockets/src/LoopData.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| /* | ||||
|  * Authored by Alex Hultman, 2018-2019. | ||||
|  * Intellectual property of third-party. | ||||
|  | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #ifndef UWS_LOOPDATA_H | ||||
| #define UWS_LOOPDATA_H | ||||
|  | ||||
| #include <thread> | ||||
| #include <functional> | ||||
| #include <vector> | ||||
| #include <mutex> | ||||
| #include <map> | ||||
|  | ||||
| #include "PerMessageDeflate.h" | ||||
|  | ||||
| #include "f2/function2.hpp" | ||||
|  | ||||
| namespace uWS { | ||||
|  | ||||
| struct Loop; | ||||
|  | ||||
| struct alignas(16) LoopData { | ||||
|     friend struct Loop; | ||||
| private: | ||||
|     std::mutex deferMutex; | ||||
|     int currentDeferQueue = 0; | ||||
|     std::vector<fu2::unique_function<void()>> deferQueues[2]; | ||||
|  | ||||
|     /* Map from void ptr to handler */ | ||||
|     std::map<void *, fu2::unique_function<void(Loop *)>> postHandlers, preHandlers; | ||||
|  | ||||
| public: | ||||
|     ~LoopData() { | ||||
|         /* If we have had App.ws called with compression we need to clear this */ | ||||
|         if (zlibContext) { | ||||
|             delete zlibContext; | ||||
|             delete inflationStream; | ||||
|             delete deflationStream; | ||||
|         } | ||||
|         delete [] corkBuffer; | ||||
|     } | ||||
|  | ||||
|     /* Good 16k for SSL perf. */ | ||||
|     static const int CORK_BUFFER_SIZE = 16 * 1024; | ||||
|  | ||||
|     /* Cork data */ | ||||
|     char *corkBuffer = new char[CORK_BUFFER_SIZE]; | ||||
|     int corkOffset = 0; | ||||
|     void *corkedSocket = nullptr; | ||||
|  | ||||
|     /* Per message deflate data */ | ||||
|     ZlibContext *zlibContext = nullptr; | ||||
|     InflationStream *inflationStream = nullptr; | ||||
|     DeflationStream *deflationStream = nullptr; | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // UWS_LOOPDATA_H | ||||
							
								
								
									
										187
									
								
								test/compatibility/C/uWebSockets/src/PerMessageDeflate.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								test/compatibility/C/uWebSockets/src/PerMessageDeflate.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,187 @@ | ||||
| /* | ||||
|  * Authored by Alex Hultman, 2018-2019. | ||||
|  * Intellectual property of third-party. | ||||
|  | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| /* This standalone module implements deflate / inflate streams */ | ||||
|  | ||||
| #ifndef UWS_PERMESSAGEDEFLATE_H | ||||
| #define UWS_PERMESSAGEDEFLATE_H | ||||
|  | ||||
| #ifndef UWS_NO_ZLIB | ||||
| #include <zlib.h> | ||||
| #endif | ||||
|  | ||||
| #include <string> | ||||
|  | ||||
| namespace uWS { | ||||
|  | ||||
| /* Do not compile this module if we don't want it */ | ||||
| #ifdef UWS_NO_ZLIB | ||||
| struct ZlibContext {}; | ||||
| struct InflationStream { | ||||
|     std::string_view inflate(ZlibContext *zlibContext, std::string_view compressed, size_t maxPayloadLength) { | ||||
|         return compressed; | ||||
|     } | ||||
| }; | ||||
| struct DeflationStream { | ||||
|     std::string_view deflate(ZlibContext *zlibContext, std::string_view raw, bool reset) { | ||||
|         return raw; | ||||
|     } | ||||
| }; | ||||
| #else | ||||
|  | ||||
| #define LARGE_BUFFER_SIZE 1024 * 16 // todo: fix this | ||||
|  | ||||
| struct ZlibContext { | ||||
|     /* Any returned data is valid until next same-class call. | ||||
|      * We need to have two classes to allow inflation followed | ||||
|      * by many deflations without modifying the inflation */ | ||||
|     std::string dynamicDeflationBuffer; | ||||
|     std::string dynamicInflationBuffer; | ||||
|     char *deflationBuffer; | ||||
|     char *inflationBuffer; | ||||
|  | ||||
|     ZlibContext() { | ||||
|         deflationBuffer = (char *) malloc(LARGE_BUFFER_SIZE); | ||||
|         inflationBuffer = (char *) malloc(LARGE_BUFFER_SIZE); | ||||
|     } | ||||
|  | ||||
|     ~ZlibContext() { | ||||
|         free(deflationBuffer); | ||||
|         free(inflationBuffer); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| struct DeflationStream { | ||||
|     z_stream deflationStream = {}; | ||||
|  | ||||
|     DeflationStream() { | ||||
|         deflateInit2(&deflationStream, 1, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY); | ||||
|     } | ||||
|  | ||||
|     /* Deflate and optionally reset */ | ||||
|     std::string_view deflate(ZlibContext *zlibContext, std::string_view raw, bool reset) { | ||||
|         /* Odd place to clear this one, fix */ | ||||
|         zlibContext->dynamicDeflationBuffer.clear(); | ||||
|  | ||||
|         deflationStream.next_in = (Bytef *) raw.data(); | ||||
|         deflationStream.avail_in = (unsigned int) raw.length(); | ||||
|  | ||||
|         /* This buffer size has to be at least 6 bytes for Z_SYNC_FLUSH to work */ | ||||
|         const int DEFLATE_OUTPUT_CHUNK = LARGE_BUFFER_SIZE; | ||||
|  | ||||
|         int err; | ||||
|         do { | ||||
|             deflationStream.next_out = (Bytef *) zlibContext->deflationBuffer; | ||||
|             deflationStream.avail_out = DEFLATE_OUTPUT_CHUNK; | ||||
|  | ||||
|             err = ::deflate(&deflationStream, Z_SYNC_FLUSH); | ||||
|             if (Z_OK == err && deflationStream.avail_out == 0) { | ||||
|                 zlibContext->dynamicDeflationBuffer.append(zlibContext->deflationBuffer, DEFLATE_OUTPUT_CHUNK - deflationStream.avail_out); | ||||
|                 continue; | ||||
|             } else { | ||||
|                 break; | ||||
|             } | ||||
|         } while (true); | ||||
|  | ||||
|         /* This must not change avail_out */ | ||||
|         if (reset) { | ||||
|             deflateReset(&deflationStream); | ||||
|         } | ||||
|  | ||||
|         if (zlibContext->dynamicDeflationBuffer.length()) { | ||||
|             zlibContext->dynamicDeflationBuffer.append(zlibContext->deflationBuffer, DEFLATE_OUTPUT_CHUNK - deflationStream.avail_out); | ||||
|  | ||||
|             return {(char *) zlibContext->dynamicDeflationBuffer.data(), zlibContext->dynamicDeflationBuffer.length() - 4}; | ||||
|         } | ||||
|  | ||||
|         return { | ||||
|             zlibContext->deflationBuffer, | ||||
|             DEFLATE_OUTPUT_CHUNK - deflationStream.avail_out - 4 | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     ~DeflationStream() { | ||||
|         deflateEnd(&deflationStream); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| struct InflationStream { | ||||
|     z_stream inflationStream = {}; | ||||
|  | ||||
|     InflationStream() { | ||||
|         inflateInit2(&inflationStream, -15); | ||||
|     } | ||||
|  | ||||
|     ~InflationStream() { | ||||
|         inflateEnd(&inflationStream); | ||||
|     } | ||||
|  | ||||
|     std::string_view inflate(ZlibContext *zlibContext, std::string_view compressed, size_t maxPayloadLength) { | ||||
|  | ||||
|         /* We clear this one here, could be done better */ | ||||
|         zlibContext->dynamicInflationBuffer.clear(); | ||||
|  | ||||
|         inflationStream.next_in = (Bytef *) compressed.data(); | ||||
|         inflationStream.avail_in = (unsigned int) compressed.length(); | ||||
|  | ||||
|         int err; | ||||
|         do { | ||||
|             inflationStream.next_out = (Bytef *) zlibContext->inflationBuffer; | ||||
|             inflationStream.avail_out = LARGE_BUFFER_SIZE; | ||||
|  | ||||
|             err = ::inflate(&inflationStream, Z_SYNC_FLUSH); | ||||
|             if (err == Z_OK && inflationStream.avail_out) { | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             zlibContext->dynamicInflationBuffer.append(zlibContext->inflationBuffer, LARGE_BUFFER_SIZE - inflationStream.avail_out); | ||||
|  | ||||
|  | ||||
|         } while (inflationStream.avail_out == 0 && zlibContext->dynamicInflationBuffer.length() <= maxPayloadLength); | ||||
|  | ||||
|         inflateReset(&inflationStream); | ||||
|  | ||||
|         if ((err != Z_BUF_ERROR && err != Z_OK) || zlibContext->dynamicInflationBuffer.length() > maxPayloadLength) { | ||||
|             return {nullptr, 0}; | ||||
|         } | ||||
|  | ||||
|         if (zlibContext->dynamicInflationBuffer.length()) { | ||||
|             zlibContext->dynamicInflationBuffer.append(zlibContext->inflationBuffer, LARGE_BUFFER_SIZE - inflationStream.avail_out); | ||||
|  | ||||
|             /* Let's be strict about the max size */ | ||||
|             if (zlibContext->dynamicInflationBuffer.length() > maxPayloadLength) { | ||||
|                 return {nullptr, 0}; | ||||
|             } | ||||
|  | ||||
|             return {zlibContext->dynamicInflationBuffer.data(), zlibContext->dynamicInflationBuffer.length()}; | ||||
|         } | ||||
|  | ||||
|         /* Let's be strict about the max size */ | ||||
|         if ((LARGE_BUFFER_SIZE - inflationStream.avail_out) > maxPayloadLength) { | ||||
|             return {nullptr, 0}; | ||||
|         } | ||||
|  | ||||
|         return {zlibContext->inflationBuffer, LARGE_BUFFER_SIZE - inflationStream.avail_out}; | ||||
|     } | ||||
|  | ||||
| }; | ||||
|  | ||||
| #endif | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // UWS_PERMESSAGEDEFLATE_H | ||||
							
								
								
									
										429
									
								
								test/compatibility/C/uWebSockets/src/TopicTreeDraft.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										429
									
								
								test/compatibility/C/uWebSockets/src/TopicTreeDraft.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,429 @@ | ||||
| /* | ||||
|  * Authored by Alex Hultman, 2018-2019. | ||||
|  * Intellectual property of third-party. | ||||
|  | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #ifndef UWS_TOPICTREE_H | ||||
| #define UWS_TOPICTREE_H | ||||
|  | ||||
| #include <iostream> | ||||
| #include <vector> | ||||
| #include <map> | ||||
| #include <string_view> | ||||
| #include <functional> | ||||
| #include <set> | ||||
| #include <chrono> | ||||
| #include <list> | ||||
|  | ||||
| namespace uWS { | ||||
|  | ||||
| /* A Subscriber is an extension of a socket */ | ||||
| struct Subscriber { | ||||
|     std::list<struct Topic *> subscriptions; | ||||
|     void *user; | ||||
|  | ||||
|     Subscriber(void *user) : user(user) {} | ||||
| }; | ||||
|  | ||||
| struct Topic { | ||||
|     /* Memory for our name */ | ||||
|     char *name; | ||||
|     size_t length; | ||||
|  | ||||
|     /* Our parent or nullptr */ | ||||
|     Topic *parent = nullptr; | ||||
|  | ||||
|     /* Next triggered Topic */ | ||||
|     bool triggered = false; | ||||
|  | ||||
|     /* Exact string matches */ | ||||
|     std::map<std::string_view, Topic *> children; | ||||
|  | ||||
|     /* Wildcard child */ | ||||
|     Topic *wildcardChild = nullptr; | ||||
|  | ||||
|     /* Terminating wildcard child */ | ||||
|     Topic *terminatingWildcardChild = nullptr; | ||||
|  | ||||
|     /* What we published */ | ||||
|     std::map<unsigned int, std::string> messages; | ||||
|  | ||||
|     std::set<Subscriber *> subs; | ||||
| }; | ||||
|  | ||||
| struct TopicTree { | ||||
| private: | ||||
|     std::function<int(Subscriber *, std::string_view)> cb; | ||||
|  | ||||
|     Topic *root = new Topic; | ||||
|  | ||||
|     /* Global messageId for deduplication of overlapping topics and ordering between topics */ | ||||
|     unsigned int messageId = 0; | ||||
|  | ||||
|     /* The triggered topics */ | ||||
|     Topic *triggeredTopics[64]; | ||||
|     int numTriggeredTopics = 0; | ||||
|     Subscriber *min = (Subscriber *) UINTPTR_MAX; | ||||
|      | ||||
|     /* Cull or trim unused Topic nodes from leaf to root */ | ||||
|     void trimTree(Topic *topic) { | ||||
|         if (!topic->subs.size() && !topic->children.size() && !topic->terminatingWildcardChild && !topic->wildcardChild) { | ||||
|             Topic *parent = topic->parent; | ||||
|  | ||||
|             if (topic->length == 1) { | ||||
|                 if (topic->name[0] == '#') { | ||||
|                     parent->terminatingWildcardChild = nullptr; | ||||
|                 } else if (topic->name[0] == '+') { | ||||
|                     parent->wildcardChild = nullptr; | ||||
|                 } | ||||
|             } | ||||
|             /* Erase us from our parents set (wildcards also live here) */ | ||||
|             parent->children.erase(std::string_view(topic->name, topic->length)); | ||||
|  | ||||
|             /* If this node is triggered, make sure to remove it from the triggered list */ | ||||
|             if (topic->triggered) { | ||||
|                 Topic *tmp[64]; | ||||
|                 int length = 0; | ||||
|                 for (int i = 0; i < numTriggeredTopics; i++) { | ||||
|                     if (triggeredTopics[i] != topic) { | ||||
|                         tmp[length++] = triggeredTopics[i]; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 for (int i = 0; i < length; i++) { | ||||
|                     triggeredTopics[i] = tmp[i]; | ||||
|                 } | ||||
|                 numTriggeredTopics = length; | ||||
|             } | ||||
|  | ||||
|             /* Free various memory for the node */ | ||||
|             delete [] topic->name; | ||||
|             delete topic; | ||||
|  | ||||
|             if (parent != root) { | ||||
|                 trimTree(parent); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* Should be getData and commit? */ | ||||
|     void publish(Topic *iterator, size_t start, size_t stop, std::string_view topic, std::string_view message) { | ||||
|         /* If we already have 64 triggered topics make sure to drain it here */ | ||||
|         if (numTriggeredTopics == 64) { | ||||
|             drain(); | ||||
|         } | ||||
|  | ||||
|         for (; stop != std::string::npos; start = stop + 1) { | ||||
|             stop = topic.find('/', start); | ||||
|             std::string_view segment = topic.substr(start, stop - start); | ||||
|  | ||||
|             /* Do we have a terminating wildcard child? */ | ||||
|             if (iterator->terminatingWildcardChild) { | ||||
|                 iterator->terminatingWildcardChild->messages[messageId] = message; | ||||
|  | ||||
|                 /* Add this topic to triggered */ | ||||
|                 if (!iterator->terminatingWildcardChild->triggered) { | ||||
|                     triggeredTopics[numTriggeredTopics++] = iterator->terminatingWildcardChild; | ||||
|  | ||||
|                     /* Keep track of lowest subscriber */ | ||||
|                     if (*iterator->terminatingWildcardChild->subs.begin() < min) { | ||||
|                         min = *iterator->terminatingWildcardChild->subs.begin(); | ||||
|                     } | ||||
|  | ||||
|                     iterator->terminatingWildcardChild->triggered = true; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             /* Do we have a wildcard child? */ | ||||
|             if (iterator->wildcardChild) { | ||||
|                 publish(iterator->wildcardChild, stop + 1, stop, topic, message); | ||||
|             } | ||||
|  | ||||
|             std::map<std::string_view, Topic *>::iterator it = iterator->children.find(segment); | ||||
|             if (it == iterator->children.end()) { | ||||
|                 /* Stop trying to match by exact string */ | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             iterator = it->second; | ||||
|         } | ||||
|  | ||||
|         /* If we went all the way we matched exactly */ | ||||
|         iterator->messages[messageId] = message; | ||||
|  | ||||
|         /* Add this topic to triggered */ | ||||
|         if (!iterator->triggered) { | ||||
|             triggeredTopics[numTriggeredTopics++] = iterator; | ||||
|  | ||||
|             /* Keep track of lowest subscriber */ | ||||
|             if (*iterator->subs.begin() < min) { | ||||
|                 min = *iterator->subs.begin(); | ||||
|             } | ||||
|  | ||||
|             iterator->triggered = true; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| public: | ||||
|  | ||||
|     TopicTree(std::function<int(Subscriber *, std::string_view)> cb) { | ||||
|         this->cb = cb; | ||||
|     } | ||||
|  | ||||
|     ~TopicTree() { | ||||
|         delete root; | ||||
|     } | ||||
|  | ||||
|     void subscribe(std::string_view topic, Subscriber *subscriber) { | ||||
|         /* Start iterating from the root */ | ||||
|         Topic *iterator = root; | ||||
|  | ||||
|         /* Traverse the topic, inserting a node for every new segment separated by / */ | ||||
|         for (size_t start = 0, stop = 0; stop != std::string::npos; start = stop + 1) { | ||||
|             stop = topic.find('/', start); | ||||
|             std::string_view segment = topic.substr(start, stop - start); | ||||
|  | ||||
|             auto lb = iterator->children.lower_bound(segment); | ||||
|  | ||||
|             if (lb != iterator->children.end() && !(iterator->children.key_comp()(segment, lb->first))) { | ||||
|                 iterator = lb->second; | ||||
|             } else { | ||||
|                 /* Allocate and insert new node */ | ||||
|                 Topic *newTopic = new Topic; | ||||
|                 newTopic->parent = iterator; | ||||
|                 newTopic->name = new char[segment.length()]; | ||||
|                 newTopic->length = segment.length(); | ||||
|                 newTopic->terminatingWildcardChild = nullptr; | ||||
|                 newTopic->wildcardChild = nullptr; | ||||
|                 memcpy(newTopic->name, segment.data(), segment.length()); | ||||
|                  | ||||
|                 /* For simplicity we do insert wildcards with text */ | ||||
|                 iterator->children.insert(lb, {std::string_view(newTopic->name, segment.length()), newTopic}); | ||||
|  | ||||
|                 /* Store fast lookup to wildcards */ | ||||
|                 if (segment.length() == 1) { | ||||
|                     /* If this segment is '+' it is a wildcard */ | ||||
|                     if (segment[0] == '+') { | ||||
|                         iterator->wildcardChild = newTopic; | ||||
|                     } | ||||
|                     /* If this segment is '#' it is a terminating wildcard */ | ||||
|                     if (segment[0] == '#') { | ||||
|                         iterator->terminatingWildcardChild = newTopic; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 iterator = newTopic; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /* Add socket to Topic's Set */ | ||||
|         auto [it, inserted] = iterator->subs.insert(subscriber); | ||||
|  | ||||
|         /* Add Topic to list of subscriptions only if we weren't already subscribed */ | ||||
|         if (inserted) { | ||||
|             subscriber->subscriptions.push_back(iterator); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void publish(std::string_view topic, std::string_view message) { | ||||
|         publish(root, 0, 0, topic, message); | ||||
|         messageId++; | ||||
|     } | ||||
|  | ||||
|     /* Returns whether we were subscribed prior */ | ||||
|     bool unsubscribe(std::string_view topic, Subscriber *subscriber) { | ||||
|         /* Subscribers are likely to have very few subscriptions (20 or fewer) */ | ||||
|         if (subscriber) { | ||||
|             /* Lookup exact Topic ptr from string */ | ||||
|             Topic *iterator = root; | ||||
|             for (size_t start = 0, stop = 0; stop != std::string::npos; start = stop + 1) { | ||||
|                 stop = topic.find('/', start); | ||||
|                 std::string_view segment = topic.substr(start, stop - start); | ||||
|  | ||||
|                 std::map<std::string_view, Topic *>::iterator it = iterator->children.find(segment); | ||||
|                 if (it == iterator->children.end()) { | ||||
|                     /* This topic does not even exist */ | ||||
|                     return false; | ||||
|                 } | ||||
|  | ||||
|                 iterator = it->second; | ||||
|             } | ||||
|  | ||||
|             /* Try and remove this topic from our list */ | ||||
|             for (auto it = subscriber->subscriptions.begin(); it != subscriber->subscriptions.end(); it++) { | ||||
|                 if (*it == iterator) { | ||||
|                     /* Remove topic ptr from our list */ | ||||
|                     subscriber->subscriptions.erase(it); | ||||
|  | ||||
|                     /* Remove us from Topic's subs */ | ||||
|                     iterator->subs.erase(subscriber); | ||||
|                     trimTree(iterator); | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     /* Can be called with nullptr, ignore it then */ | ||||
|     void unsubscribeAll(Subscriber *subscriber) { | ||||
|         if (subscriber) { | ||||
|             for (Topic *topic : subscriber->subscriptions) { | ||||
|                 topic->subs.erase(subscriber); | ||||
|                 trimTree(topic); | ||||
|             } | ||||
|             subscriber->subscriptions.clear(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* Drain the tree by emitting what to send with every Subscriber */ | ||||
|     /* Better name would be commit() and making it public so that one can commit and shutdown, etc */ | ||||
|     void drain() { | ||||
|  | ||||
|         /* Do nothing if nothing to send */ | ||||
|         if (!numTriggeredTopics) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         /* bug fix: Filter triggered topics without subscribers */ | ||||
|         int numFilteredTriggeredTopics = 0; | ||||
|         for (int i = 0; i < numTriggeredTopics; i++) { | ||||
|             if (triggeredTopics[i]->subs.size()) { | ||||
|                 triggeredTopics[numFilteredTriggeredTopics++] = triggeredTopics[i]; | ||||
|             } | ||||
|         } | ||||
|         numTriggeredTopics = numFilteredTriggeredTopics; | ||||
|  | ||||
|         if (!numTriggeredTopics) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         /* bug fix: update min, as the one tracked via subscribe gets invalid as you unsubscribe */ | ||||
|         min = (Subscriber *)UINTPTR_MAX; | ||||
|         for (int i = 0; i < numTriggeredTopics; i++) { | ||||
|             if ((triggeredTopics[i]->subs.size()) && (min > *triggeredTopics[i]->subs.begin())) { | ||||
|                 min = *triggeredTopics[i]->subs.begin(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /* Check if we really have any sockets still */ | ||||
|         if (min != (Subscriber *)UINTPTR_MAX) { | ||||
|  | ||||
|             /* Up to 64 triggered Topics per batch */ | ||||
|             std::map<uint64_t, std::string> intersectionCache; | ||||
|  | ||||
|             /* Loop over these here */ | ||||
|             std::set<Subscriber *>::iterator it[64]; | ||||
|             std::set<Subscriber *>::iterator end[64]; | ||||
|             for (int i = 0; i < numTriggeredTopics; i++) { | ||||
|                 it[i] = triggeredTopics[i]->subs.begin(); | ||||
|                 end[i] = triggeredTopics[i]->subs.end(); | ||||
|             } | ||||
|              | ||||
|             /* Empty all sets from unique subscribers */ | ||||
|             for (int nonEmpty = numTriggeredTopics; nonEmpty; ) { | ||||
|  | ||||
|                 Subscriber *nextMin = (Subscriber *)UINTPTR_MAX; | ||||
|  | ||||
|                 /* The message sets relevant for this intersection */ | ||||
|                 std::map<unsigned int, std::string> *perSubscriberIntersectingTopicMessages[64]; | ||||
|                 int numPerSubscriberIntersectingTopicMessages = 0; | ||||
|  | ||||
|                 uint64_t intersection = 0; | ||||
|  | ||||
|                 for (int i = 0; i < numTriggeredTopics; i++) { | ||||
|                     if ((it[i] != end[i]) && (*it[i] == min)) { | ||||
|  | ||||
|                         /* Mark this intersection */ | ||||
|                         intersection |= ((uint64_t)1 << i); | ||||
|                         perSubscriberIntersectingTopicMessages[numPerSubscriberIntersectingTopicMessages++] = &triggeredTopics[i]->messages; | ||||
|  | ||||
|                         it[i]++; | ||||
|                         if (it[i] == end[i]) { | ||||
|                             nonEmpty--; | ||||
|                         } | ||||
|                         else { | ||||
|                             if (nextMin > *it[i]) { | ||||
|                                 nextMin = *it[i]; | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                     else { | ||||
|                         /* We need to lower nextMin to us, in the case of min being the last in a set */ | ||||
|                         if ((it[i] != end[i]) && (nextMin > *it[i])) { | ||||
|                             nextMin = *it[i]; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 /* Generate cache for intersection */ | ||||
|                 if (intersectionCache[intersection].length() == 0) { | ||||
|  | ||||
|                     /* Build the union in order without duplicates */ | ||||
|                     std::map<unsigned int, std::string> complete; | ||||
|                     for (int i = 0; i < numPerSubscriberIntersectingTopicMessages; i++) { | ||||
|                         complete.insert(perSubscriberIntersectingTopicMessages[i]->begin(), perSubscriberIntersectingTopicMessages[i]->end()); | ||||
|                     } | ||||
|  | ||||
|                     /* Create the linear cache */ | ||||
|                     std::string res; | ||||
|                     for (auto &p : complete) { | ||||
|                         res.append(p.second); | ||||
|                     } | ||||
|  | ||||
|                     cb(min, intersectionCache[intersection] = std::move(res)); | ||||
|                 } | ||||
|                 else { | ||||
|                     cb(min, intersectionCache[intersection]); | ||||
|                 } | ||||
|  | ||||
|                 min = nextMin; | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         /* Clear messages of triggered Topics */ | ||||
|         for (int i = 0; i < numTriggeredTopics; i++) { | ||||
|             triggeredTopics[i]->messages.clear(); | ||||
|             triggeredTopics[i]->triggered = false; | ||||
|         } | ||||
|         numTriggeredTopics = 0; | ||||
|     } | ||||
|  | ||||
|     void print(Topic *root = nullptr, int indentation = 1) { | ||||
|         if (root == nullptr) { | ||||
|             std::cout << "Print of tree:" << std::endl; | ||||
|             root = this->root; | ||||
|         } | ||||
|  | ||||
|         for (auto p : root->children) { | ||||
|             for (int i = 0; i < indentation; i++) { | ||||
|                 std::cout << "  "; | ||||
|             } | ||||
|             std::cout << std::string_view(p.second->name, p.second->length) << " = " << p.second->messages.size() << " publishes, " << p.second->subs.size() << " subscribers {"; | ||||
|  | ||||
|             for (auto &p : p.second->subs) { | ||||
|                 std::cout << p << " referring to socket: " << p->user << ", "; | ||||
|             } | ||||
|             std::cout << "}" << std::endl; | ||||
|  | ||||
|             print(p.second, indentation + 1); | ||||
|         } | ||||
|     } | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										66
									
								
								test/compatibility/C/uWebSockets/src/Utilities.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								test/compatibility/C/uWebSockets/src/Utilities.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| /* | ||||
|  * Authored by Alex Hultman, 2018-2019. | ||||
|  * Intellectual property of third-party. | ||||
|  | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #ifndef UWS_UTILITIES_H | ||||
| #define UWS_UTILITIES_H | ||||
|  | ||||
| /* Various common utilities */ | ||||
|  | ||||
| #include <cstdint> | ||||
|  | ||||
| namespace uWS { | ||||
| namespace utils { | ||||
|  | ||||
| inline int u32toaHex(uint32_t value, char *dst) { | ||||
|     char palette[] = "0123456789abcdef"; | ||||
|     char temp[10]; | ||||
|     char *p = temp; | ||||
|     do { | ||||
|         *p++ = palette[value % 16]; | ||||
|         value /= 16; | ||||
|     } while (value > 0); | ||||
|  | ||||
|     int ret = (int) (p - temp); | ||||
|  | ||||
|     do { | ||||
|         *dst++ = *--p; | ||||
|     } while (p != temp); | ||||
|  | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| inline int u32toa(uint32_t value, char *dst) { | ||||
|     char temp[10]; | ||||
|     char *p = temp; | ||||
|     do { | ||||
|         *p++ = (char) ((value % 10) + '0'); | ||||
|         value /= 10; | ||||
|     } while (value > 0); | ||||
|  | ||||
|     int ret = (int) (p - temp); | ||||
|  | ||||
|     do { | ||||
|         *dst++ = *--p; | ||||
|     } while (p != temp); | ||||
|  | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| } | ||||
| } | ||||
|  | ||||
| #endif // UWS_UTILITIES_H | ||||
							
								
								
									
										213
									
								
								test/compatibility/C/uWebSockets/src/WebSocket.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										213
									
								
								test/compatibility/C/uWebSockets/src/WebSocket.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,213 @@ | ||||
| /* | ||||
|  * Authored by Alex Hultman, 2018-2019. | ||||
|  * Intellectual property of third-party. | ||||
|  | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #ifndef UWS_WEBSOCKET_H | ||||
| #define UWS_WEBSOCKET_H | ||||
|  | ||||
| #include "WebSocketData.h" | ||||
| #include "WebSocketProtocol.h" | ||||
| #include "AsyncSocket.h" | ||||
| #include "WebSocketContextData.h" | ||||
|  | ||||
| #include <string_view> | ||||
|  | ||||
| namespace uWS { | ||||
|  | ||||
| template <bool SSL, bool isServer> | ||||
| struct WebSocket : AsyncSocket<SSL> { | ||||
|     template <bool> friend struct TemplatedApp; | ||||
| private: | ||||
|     typedef AsyncSocket<SSL> Super; | ||||
|  | ||||
|     void *init(bool perMessageDeflate, bool slidingCompression, std::string &&backpressure) { | ||||
|         new (us_socket_ext(SSL, (us_socket_t *) this)) WebSocketData(perMessageDeflate, slidingCompression, std::move(backpressure)); | ||||
|         return this; | ||||
|     } | ||||
| public: | ||||
|  | ||||
|     /* Returns pointer to the per socket user data */ | ||||
|     void *getUserData() { | ||||
|         WebSocketData *webSocketData = (WebSocketData *) us_socket_ext(SSL, (us_socket_t *) this); | ||||
|         /* We just have it overallocated by sizeof type */ | ||||
|         return (webSocketData + 1); | ||||
|     } | ||||
|  | ||||
|     /* See AsyncSocket */ | ||||
|     using Super::getBufferedAmount; | ||||
|     using Super::getRemoteAddress; | ||||
|  | ||||
|     /* Simple, immediate close of the socket. Emits close event */ | ||||
|     using Super::close; | ||||
|  | ||||
|     /* Send or buffer a WebSocket frame, compressed or not. Returns false on increased user space backpressure. */ | ||||
|     bool send(std::string_view message, uWS::OpCode opCode = uWS::OpCode::BINARY, bool compress = false) { | ||||
|         /* Transform the message to compressed domain if requested */ | ||||
|         if (compress) { | ||||
|             WebSocketData *webSocketData = (WebSocketData *) Super::getAsyncSocketData(); | ||||
|  | ||||
|             /* Check and correct the compress hint */ | ||||
|             if (opCode < 3 && webSocketData->compressionStatus == WebSocketData::ENABLED) { | ||||
|                 LoopData *loopData = Super::getLoopData(); | ||||
|                 /* Compress using either shared or dedicated deflationStream */ | ||||
|                 if (webSocketData->deflationStream) { | ||||
|                     message = webSocketData->deflationStream->deflate(loopData->zlibContext, message, false); | ||||
|                 } else { | ||||
|                     message = loopData->deflationStream->deflate(loopData->zlibContext, message, true); | ||||
|                 } | ||||
|             } else { | ||||
|                 compress = false; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /* Check to see if we can cork for the user */ | ||||
|         bool automaticallyCorked = false; | ||||
|         if (!Super::isCorked() && Super::canCork()) { | ||||
|             automaticallyCorked = true; | ||||
|             Super::cork(); | ||||
|         } | ||||
|  | ||||
|         /* Get size, alloate size, write if needed */ | ||||
|         size_t messageFrameSize = protocol::messageFrameSize(message.length()); | ||||
|         auto[sendBuffer, requiresWrite] = Super::getSendBuffer(messageFrameSize); | ||||
|         protocol::formatMessage<isServer>(sendBuffer, message.data(), message.length(), opCode, message.length(), compress); | ||||
|         /* This is the slow path, when we couldn't cork for the user */ | ||||
|         if (requiresWrite) { | ||||
|             auto[written, failed] = Super::write(sendBuffer, (int) messageFrameSize); | ||||
|  | ||||
|             /* For now, we are slow here */ | ||||
|             free(sendBuffer); | ||||
|  | ||||
|             if (failed) { | ||||
|                 /* Return false for failure, skipping to reset the timeout below */ | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /* Uncork here if we automatically corked for the user */ | ||||
|         if (automaticallyCorked) { | ||||
|             auto [written, failed] = Super::uncork(); | ||||
|             if (failed) { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /* Every successful send resets the timeout */ | ||||
|         WebSocketContextData<SSL> *webSocketContextData = (WebSocketContextData<SSL> *) us_socket_context_ext(SSL, | ||||
|             (us_socket_context_t *) us_socket_context(SSL, (us_socket_t *) this) | ||||
|         ); | ||||
|         AsyncSocket<SSL>::timeout(webSocketContextData->idleTimeout); | ||||
|  | ||||
|         /* Return success */ | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     /* Send websocket close frame, emit close event, send FIN if successful */ | ||||
|     void end(int code, std::string_view message = {}) { | ||||
|         /* Check if we already called this one */ | ||||
|         WebSocketData *webSocketData = (WebSocketData *) us_socket_ext(SSL, (us_socket_t *) this); | ||||
|         if (webSocketData->isShuttingDown) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         /* We postpone any FIN sending to either drainage or uncorking */ | ||||
|         webSocketData->isShuttingDown = true; | ||||
|  | ||||
|         /* Format and send the close frame */ | ||||
|         static const int MAX_CLOSE_PAYLOAD = 123; | ||||
|         int length = (int) std::min<size_t>(MAX_CLOSE_PAYLOAD, message.length()); | ||||
|         char closePayload[MAX_CLOSE_PAYLOAD + 2]; | ||||
|         int closePayloadLength = (int) protocol::formatClosePayload(closePayload, (uint16_t) code, message.data(), length); | ||||
|         bool ok = send(std::string_view(closePayload, closePayloadLength), OpCode::CLOSE); | ||||
|  | ||||
|         /* FIN if we are ok and not corked */ | ||||
|         WebSocket<SSL, true> *webSocket = (WebSocket<SSL, true> *) this; | ||||
|         if (!webSocket->isCorked()) { | ||||
|             if (ok) { | ||||
|                 /* If we are not corked, and we just sent off everything, we need to FIN right here. | ||||
|                  * In all other cases, we need to fin either if uncork was successful, or when drainage is complete. */ | ||||
|                 webSocket->shutdown(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /* Emit close event */ | ||||
|         WebSocketContextData<SSL> *webSocketContextData = (WebSocketContextData<SSL> *) us_socket_context_ext(SSL, | ||||
|             (us_socket_context_t *) us_socket_context(SSL, (us_socket_t *) this) | ||||
|         ); | ||||
|         if (webSocketContextData->closeHandler) { | ||||
|             webSocketContextData->closeHandler(this, code, message); | ||||
|         } | ||||
|  | ||||
|         /* Make sure to unsubscribe from any pub/sub node at exit */ | ||||
|         webSocketContextData->topicTree.unsubscribeAll(webSocketData->subscriber); | ||||
|         delete webSocketData->subscriber; | ||||
|         webSocketData->subscriber = nullptr; | ||||
|     } | ||||
|  | ||||
|     /* Corks the response if possible. Leaves already corked socket be. */ | ||||
|     void cork(fu2::unique_function<void()> &&handler) { | ||||
|         if (!Super::isCorked() && Super::canCork()) { | ||||
|             Super::cork(); | ||||
|             handler(); | ||||
|  | ||||
|             /* There is no timeout when failing to uncork for WebSockets, | ||||
|              * as that is handled by idleTimeout */ | ||||
|             auto [written, failed] = Super::uncork(); | ||||
|         } else { | ||||
|             /* We are already corked, or can't cork so let's just call the handler */ | ||||
|             handler(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* Subscribe to a topic according to MQTT rules and syntax */ | ||||
|     void subscribe(std::string_view topic) { | ||||
|         WebSocketContextData<SSL> *webSocketContextData = (WebSocketContextData<SSL> *) us_socket_context_ext(SSL, | ||||
|             (us_socket_context_t *) us_socket_context(SSL, (us_socket_t *) this) | ||||
|         ); | ||||
|  | ||||
|         /* Make us a subscriber if we aren't yet */ | ||||
|         WebSocketData *webSocketData = (WebSocketData *) us_socket_ext(SSL, (us_socket_t *) this); | ||||
|         if (!webSocketData->subscriber) { | ||||
|             webSocketData->subscriber = new Subscriber(this); | ||||
|         } | ||||
|  | ||||
|         webSocketContextData->topicTree.subscribe(topic, webSocketData->subscriber); | ||||
|     } | ||||
|  | ||||
|     /* Unsubscribe from a topic, returns true if we were subscribed */ | ||||
|     bool unsubscribe(std::string_view topic) { | ||||
|         WebSocketContextData<SSL> *webSocketContextData = (WebSocketContextData<SSL> *) us_socket_context_ext(SSL, | ||||
|             (us_socket_context_t *) us_socket_context(SSL, (us_socket_t *) this) | ||||
|         ); | ||||
|  | ||||
|         WebSocketData *webSocketData = (WebSocketData *) us_socket_ext(SSL, (us_socket_t *) this); | ||||
|  | ||||
|         return webSocketContextData->topicTree.unsubscribe(topic, webSocketData->subscriber); | ||||
|     } | ||||
|  | ||||
|     /* Publish a message to a topic according to MQTT rules and syntax */ | ||||
|     void publish(std::string_view topic, std::string_view message, OpCode opCode = OpCode::TEXT, bool compress = false) { | ||||
|         WebSocketContextData<SSL> *webSocketContextData = (WebSocketContextData<SSL> *) us_socket_context_ext(SSL, | ||||
|             (us_socket_context_t *) us_socket_context(SSL, (us_socket_t *) this) | ||||
|         ); | ||||
|         /* Is the same as publishing per websocket context */ | ||||
|         webSocketContextData->publish(topic, message, opCode, compress); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // UWS_WEBSOCKET_H | ||||
							
								
								
									
										380
									
								
								test/compatibility/C/uWebSockets/src/WebSocketContext.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										380
									
								
								test/compatibility/C/uWebSockets/src/WebSocketContext.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,380 @@ | ||||
| /* | ||||
|  * Authored by Alex Hultman, 2018-2019. | ||||
|  * Intellectual property of third-party. | ||||
|  | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #ifndef UWS_WEBSOCKETCONTEXT_H | ||||
| #define UWS_WEBSOCKETCONTEXT_H | ||||
|  | ||||
| #include "WebSocketContextData.h" | ||||
| #include "WebSocketProtocol.h" | ||||
| #include "WebSocketData.h" | ||||
| #include "WebSocket.h" | ||||
|  | ||||
| namespace uWS { | ||||
|  | ||||
| template <bool SSL, bool isServer> | ||||
| struct WebSocketContext { | ||||
|     template <bool> friend struct TemplatedApp; | ||||
|     template <bool, typename> friend struct WebSocketProtocol; | ||||
| private: | ||||
|     WebSocketContext() = delete; | ||||
|  | ||||
|     us_socket_context_t *getSocketContext() { | ||||
|         return (us_socket_context_t *) this; | ||||
|     } | ||||
|  | ||||
|     WebSocketContextData<SSL> *getExt() { | ||||
|         return (WebSocketContextData<SSL> *) us_socket_context_ext(SSL, (us_socket_context_t *) this); | ||||
|     } | ||||
|  | ||||
|     /* If we have negotiated compression, set this frame compressed */ | ||||
|     static bool setCompressed(uWS::WebSocketState<isServer> *wState, void *s) { | ||||
|         WebSocketData *webSocketData = (WebSocketData *) us_socket_ext(SSL, (us_socket_t *) s); | ||||
|  | ||||
|         if (webSocketData->compressionStatus == WebSocketData::CompressionStatus::ENABLED) { | ||||
|             webSocketData->compressionStatus = WebSocketData::CompressionStatus::COMPRESSED_FRAME; | ||||
|             return true; | ||||
|         } else { | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     static void forceClose(uWS::WebSocketState<isServer> *wState, void *s) { | ||||
|         us_socket_close(SSL, (us_socket_t *) s); | ||||
|     } | ||||
|  | ||||
|     /* Returns true on breakage */ | ||||
|     static bool handleFragment(char *data, size_t length, unsigned int remainingBytes, int opCode, bool fin, uWS::WebSocketState<isServer> *webSocketState, void *s) { | ||||
|         /* WebSocketData and WebSocketContextData */ | ||||
|         WebSocketContextData<SSL> *webSocketContextData = (WebSocketContextData<SSL> *) us_socket_context_ext(SSL, us_socket_context(SSL, (us_socket_t *) s)); | ||||
|         WebSocketData *webSocketData = (WebSocketData *) us_socket_ext(SSL, (us_socket_t *) s); | ||||
|  | ||||
|         /* Is this a non-control frame? */ | ||||
|         if (opCode < 3) { | ||||
|             /* Did we get everything in one go? */ | ||||
|             if (!remainingBytes && fin && !webSocketData->fragmentBuffer.length()) { | ||||
|  | ||||
|                 /* Handle compressed frame */ | ||||
|                 if (webSocketData->compressionStatus == WebSocketData::CompressionStatus::COMPRESSED_FRAME) { | ||||
|                         webSocketData->compressionStatus = WebSocketData::CompressionStatus::ENABLED; | ||||
|  | ||||
|                         LoopData *loopData = (LoopData *) us_loop_ext(us_socket_context_loop(SSL, us_socket_context(SSL, (us_socket_t *) s))); | ||||
|                         std::string_view inflatedFrame = loopData->inflationStream->inflate(loopData->zlibContext, {data, length}, webSocketContextData->maxPayloadLength); | ||||
|                         if (!inflatedFrame.length()) { | ||||
|                             forceClose(webSocketState, s); | ||||
|                             return true; | ||||
|                         } else { | ||||
|                             data = (char *) inflatedFrame.data(); | ||||
|                             length = inflatedFrame.length(); | ||||
|                         } | ||||
|                 } | ||||
|  | ||||
|                 /* Check text messages for Utf-8 validity */ | ||||
|                 if (opCode == 1 && !protocol::isValidUtf8((unsigned char *) data, length)) { | ||||
|                     forceClose(webSocketState, s); | ||||
|                     return true; | ||||
|                 } | ||||
|  | ||||
|                 /* Emit message event & break if we are closed or shut down when returning */ | ||||
|                 if (webSocketContextData->messageHandler) { | ||||
|                     webSocketContextData->messageHandler((WebSocket<SSL, isServer> *) s, std::string_view(data, length), (uWS::OpCode) opCode); | ||||
|                     if (us_socket_is_closed(SSL, (us_socket_t *) s) || webSocketData->isShuttingDown) { | ||||
|                         return true; | ||||
|                     } | ||||
|                 } | ||||
|             } else { | ||||
|                 /* Allocate fragment buffer up front first time */ | ||||
|                 if (!webSocketData->fragmentBuffer.length()) { | ||||
|                     webSocketData->fragmentBuffer.reserve(length + remainingBytes); | ||||
|                 } | ||||
|                 /* Fragments forming a big message are not caught until appending them */ | ||||
|                 if (refusePayloadLength(length + webSocketData->fragmentBuffer.length(), webSocketState, s)) { | ||||
|                     forceClose(webSocketState, s); | ||||
|                     return true; | ||||
|                 } | ||||
|                 webSocketData->fragmentBuffer.append(data, length); | ||||
|  | ||||
|                 /* Are we done now? */ | ||||
|                 // todo: what if we don't have any remaining bytes yet we are not fin? forceclose! | ||||
|                 if (!remainingBytes && fin) { | ||||
|  | ||||
|                     /* Handle compression */ | ||||
|                     if (webSocketData->compressionStatus == WebSocketData::CompressionStatus::COMPRESSED_FRAME) { | ||||
|                             webSocketData->compressionStatus = WebSocketData::CompressionStatus::ENABLED; | ||||
|  | ||||
|                             // what's really the story here? | ||||
|                             webSocketData->fragmentBuffer.append("...."); | ||||
|  | ||||
|                             LoopData *loopData = (LoopData *) us_loop_ext( | ||||
|                                 us_socket_context_loop(SSL, | ||||
|                                     us_socket_context(SSL, (us_socket_t *) s) | ||||
|                                 ) | ||||
|                             ); | ||||
|  | ||||
|                             std::string_view inflatedFrame = loopData->inflationStream->inflate(loopData->zlibContext, {webSocketData->fragmentBuffer.data(), webSocketData->fragmentBuffer.length() - 4}, webSocketContextData->maxPayloadLength); | ||||
|                             if (!inflatedFrame.length()) { | ||||
|                                 forceClose(webSocketState, s); | ||||
|                                 return true; | ||||
|                             } else { | ||||
|                                 data = (char *) inflatedFrame.data(); | ||||
|                                 length = inflatedFrame.length(); | ||||
|                             } | ||||
|  | ||||
|  | ||||
|                     } else { | ||||
|                         // reset length and data ptrs | ||||
|                         length = webSocketData->fragmentBuffer.length(); | ||||
|                         data = webSocketData->fragmentBuffer.data(); | ||||
|                     } | ||||
|  | ||||
|                     /* Check text messages for Utf-8 validity */ | ||||
|                     if (opCode == 1 && !protocol::isValidUtf8((unsigned char *) data, length)) { | ||||
|                         forceClose(webSocketState, s); | ||||
|                         return true; | ||||
|                     } | ||||
|  | ||||
|                     /* Emit message and check for shutdown or close */ | ||||
|                     if (webSocketContextData->messageHandler) { | ||||
|                         webSocketContextData->messageHandler((WebSocket<SSL, isServer> *) s, std::string_view(data, length), (uWS::OpCode) opCode); | ||||
|                         if (us_socket_is_closed(SSL, (us_socket_t *) s) || webSocketData->isShuttingDown) { | ||||
|                             return true; | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     /* If we shutdown or closed, this will be taken care of elsewhere */ | ||||
|                     webSocketData->fragmentBuffer.clear(); | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             /* Control frames need the websocket to send pings, pongs and close */ | ||||
|             WebSocket<SSL, isServer> *webSocket = (WebSocket<SSL, isServer> *) s; | ||||
|  | ||||
|             if (!remainingBytes && fin && !webSocketData->controlTipLength) { | ||||
|                 if (opCode == CLOSE) { | ||||
|                     auto closeFrame = protocol::parseClosePayload(data, length); | ||||
|                     webSocket->end(closeFrame.code, std::string_view(closeFrame.message, closeFrame.length)); | ||||
|                     return true; | ||||
|                 } else { | ||||
|                     if (opCode == PING) { | ||||
|                         webSocket->send(std::string_view(data, length), (OpCode) OpCode::PONG); | ||||
|                         /*group->pingHandler(webSocket, data, length); | ||||
|                         if (webSocket->isClosed() || webSocket->isShuttingDown()) { | ||||
|                             return true; | ||||
|                         }*/ | ||||
|                     } else if (opCode == PONG) { | ||||
|                         /*group->pongHandler(webSocket, data, length); | ||||
|                         if (webSocket->isClosed() || webSocket->isShuttingDown()) { | ||||
|                             return true; | ||||
|                         }*/ | ||||
|                     } | ||||
|                 } | ||||
|             } else { | ||||
|                 /* Here we never mind any size optimizations as we are in the worst possible path */ | ||||
|                 webSocketData->fragmentBuffer.append(data, length); | ||||
|                 webSocketData->controlTipLength += (int) length; | ||||
|  | ||||
|                 if (!remainingBytes && fin) { | ||||
|                     char *controlBuffer = (char *) webSocketData->fragmentBuffer.data() + webSocketData->fragmentBuffer.length() - webSocketData->controlTipLength; | ||||
|                     if (opCode == CLOSE) { | ||||
|                         protocol::CloseFrame closeFrame = protocol::parseClosePayload(controlBuffer, webSocketData->controlTipLength); | ||||
|                         webSocket->end(closeFrame.code, std::string_view(closeFrame.message, closeFrame.length)); | ||||
|                         return true; | ||||
|                     } else { | ||||
|                         if (opCode == PING) { | ||||
|                             webSocket->send(std::string_view(controlBuffer, webSocketData->controlTipLength), (OpCode) OpCode::PONG); | ||||
|                             /*group->pingHandler(webSocket, controlBuffer, webSocket->controlTipLength); | ||||
|                             if (webSocket->isClosed() || webSocket->isShuttingDown()) { | ||||
|                                 return true; | ||||
|                             }*/ | ||||
|                         } else if (opCode == PONG) { | ||||
|                             /*group->pongHandler(webSocket, controlBuffer, webSocket->controlTipLength); | ||||
|                             if (webSocket->isClosed() || webSocket->isShuttingDown()) { | ||||
|                                 return true; | ||||
|                             }*/ | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     /* Same here, we do not care for any particular smart allocation scheme */ | ||||
|                     webSocketData->fragmentBuffer.resize(webSocketData->fragmentBuffer.length() - webSocketData->controlTipLength); | ||||
|                     webSocketData->controlTipLength = 0; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     static bool refusePayloadLength(uint64_t length, uWS::WebSocketState<isServer> *wState, void *s) { | ||||
|         auto *webSocketContextData = (WebSocketContextData<SSL> *) us_socket_context_ext(SSL, us_socket_context(SSL, (us_socket_t *) s)); | ||||
|  | ||||
|         /* Return true for refuse, false for accept */ | ||||
|         return webSocketContextData->maxPayloadLength < length; | ||||
|     } | ||||
|  | ||||
|     WebSocketContext<SSL, isServer> *init() { | ||||
|         /* Adopting a socket does not trigger open event. | ||||
|          * We arreive as WebSocket with timeout set and | ||||
|          * any backpressure from HTTP state kept. */ | ||||
|  | ||||
|         /* Handle socket disconnections */ | ||||
|         us_socket_context_on_close(SSL, getSocketContext(), [](auto *s) { | ||||
|             /* For whatever reason, if we already have emitted close event, do not emit it again */ | ||||
|             WebSocketData *webSocketData = (WebSocketData *) (us_socket_ext(SSL, s)); | ||||
|             if (!webSocketData->isShuttingDown) { | ||||
|                 /* Emit close event */ | ||||
|                 auto *webSocketContextData = (WebSocketContextData<SSL> *) us_socket_context_ext(SSL, us_socket_context(SSL, (us_socket_t *) s)); | ||||
|  | ||||
|                 if (webSocketContextData->closeHandler) { | ||||
|                     webSocketContextData->closeHandler((WebSocket<SSL, true> *) s, 1006, {}); | ||||
|                 } | ||||
|  | ||||
|                 /* Make sure to unsubscribe from any pub/sub node at exit */ | ||||
|                 webSocketContextData->topicTree.unsubscribeAll(webSocketData->subscriber); | ||||
|                 delete webSocketData->subscriber; | ||||
|                 webSocketData->subscriber = nullptr; | ||||
|             } | ||||
|  | ||||
|             /* Destruct in-placed data struct */ | ||||
|             webSocketData->~WebSocketData(); | ||||
|  | ||||
|             return s; | ||||
|         }); | ||||
|  | ||||
|         /* Handle WebSocket data streams */ | ||||
|         us_socket_context_on_data(SSL, getSocketContext(), [](auto *s, char *data, int length) { | ||||
|  | ||||
|             /* We need the websocket data */ | ||||
|             WebSocketData *webSocketData = (WebSocketData *) (us_socket_ext(SSL, s)); | ||||
|  | ||||
|             /* When in websocket shutdown mode, we do not care for ANY message, whether responding close frame or not. | ||||
|              * We only care for the TCP FIN really, not emitting any message after closing is key */ | ||||
|             if (webSocketData->isShuttingDown) { | ||||
|                 return s; | ||||
|             } | ||||
|  | ||||
|             auto *webSocketContextData = (WebSocketContextData<SSL> *) us_socket_context_ext(SSL, us_socket_context(SSL, (us_socket_t *) s)); | ||||
|             auto *asyncSocket = (AsyncSocket<SSL> *) s; | ||||
|  | ||||
|             /* Every time we get data and not in shutdown state we simply reset the timeout */ | ||||
|             asyncSocket->timeout(webSocketContextData->idleTimeout); | ||||
|  | ||||
|             /* We always cork on data */ | ||||
|             asyncSocket->cork(); | ||||
|  | ||||
|             /* This parser has virtually no overhead */ | ||||
|             uWS::WebSocketProtocol<isServer, WebSocketContext<SSL, isServer>>::consume(data, length, (WebSocketState<isServer> *) webSocketData, s); | ||||
|  | ||||
|             /* Uncorking a closed socekt is fine, in fact it is needed */ | ||||
|             asyncSocket->uncork(); | ||||
|  | ||||
|             /* If uncorking was successful and we are in shutdown state then send TCP FIN */ | ||||
|             if (asyncSocket->getBufferedAmount() == 0) { | ||||
|                 /* We can now be in shutdown state */ | ||||
|                 if (webSocketData->isShuttingDown) { | ||||
|                     /* Shutting down a closed socket is handled by uSockets and just fine */ | ||||
|                     asyncSocket->shutdown(); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return s; | ||||
|         }); | ||||
|  | ||||
|         /* Handle HTTP write out (note: SSL_read may trigger this spuriously, the app need to handle spurious calls) */ | ||||
|         us_socket_context_on_writable(SSL, getSocketContext(), [](auto *s) { | ||||
|  | ||||
|             /* It makes sense to check for us_is_shut_down here and return if so, to avoid shutting down twice */ | ||||
|             if (us_socket_is_shut_down(SSL, (us_socket_t *) s)) { | ||||
|                 return s; | ||||
|             } | ||||
|  | ||||
|             AsyncSocket<SSL> *asyncSocket = (AsyncSocket<SSL> *) s; | ||||
|             WebSocketData *webSocketData = (WebSocketData *)(us_socket_ext(SSL, s)); | ||||
|  | ||||
|             /* We store old backpressure since it is unclear whether write drained anything */ | ||||
|             int backpressure = asyncSocket->getBufferedAmount(); | ||||
|  | ||||
|             /* Drain as much as possible */ | ||||
|             asyncSocket->write(nullptr, 0); | ||||
|  | ||||
|             /* Behavior: if we actively drain backpressure, always reset timeout (even if we are in shutdown) */ | ||||
|             if (backpressure < asyncSocket->getBufferedAmount()) { | ||||
|                 auto *webSocketContextData = (WebSocketContextData<SSL> *) us_socket_context_ext(SSL, us_socket_context(SSL, (us_socket_t *) s)); | ||||
|                 asyncSocket->timeout(webSocketContextData->idleTimeout); | ||||
|             } | ||||
|  | ||||
|             /* Are we in (WebSocket) shutdown mode? */ | ||||
|             if (webSocketData->isShuttingDown) { | ||||
|                 /* Check if we just now drained completely */ | ||||
|                 if (asyncSocket->getBufferedAmount() == 0) { | ||||
|                     /* Now perform the actual TCP/TLS shutdown which was postponed due to backpressure */ | ||||
|                     asyncSocket->shutdown(); | ||||
|                 } | ||||
|             } else if (backpressure > asyncSocket->getBufferedAmount()) { | ||||
|                 /* Only call drain if we actually drained backpressure */ | ||||
|                 auto *webSocketContextData = (WebSocketContextData<SSL> *) us_socket_context_ext(SSL, us_socket_context(SSL, (us_socket_t *) s)); | ||||
|                 if (webSocketContextData->drainHandler) { | ||||
|                     webSocketContextData->drainHandler((WebSocket<SSL, isServer> *) s); | ||||
|                 } | ||||
|                 /* No need to check for closed here as we leave the handler immediately*/ | ||||
|             } | ||||
|  | ||||
|             return s; | ||||
|         }); | ||||
|  | ||||
|         /* Handle FIN, HTTP does not support half-closed sockets, so simply close */ | ||||
|         us_socket_context_on_end(SSL, getSocketContext(), [](auto *s) { | ||||
|  | ||||
|             /* If we get a fin, we just close I guess */ | ||||
|             us_socket_close(SSL, (us_socket_t *) s); | ||||
|  | ||||
|             return s; | ||||
|         }); | ||||
|  | ||||
|         /* Handle socket timeouts, simply close them so to not confuse client with FIN */ | ||||
|         us_socket_context_on_timeout(SSL, getSocketContext(), [](auto *s) { | ||||
|  | ||||
|             /* Timeout is very simple; we just close it */ | ||||
|             us_socket_close(SSL, (us_socket_t *) s); | ||||
|  | ||||
|             return s; | ||||
|         }); | ||||
|  | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     void free() { | ||||
|         WebSocketContextData<SSL> *webSocketContextData = (WebSocketContextData<SSL> *) us_socket_context_ext(SSL, (us_socket_context_t *) this); | ||||
|         webSocketContextData->~WebSocketContextData(); | ||||
|  | ||||
|         us_socket_context_free(SSL, (us_socket_context_t *) this); | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     /* WebSocket contexts are always child contexts to a HTTP context so no SSL options are needed as they are inherited */ | ||||
|     static WebSocketContext *create(Loop *loop, us_socket_context_t *parentSocketContext) { | ||||
|         WebSocketContext *webSocketContext = (WebSocketContext *) us_create_child_socket_context(SSL, parentSocketContext, sizeof(WebSocketContextData<SSL>)); | ||||
|         if (!webSocketContext) { | ||||
|             return nullptr; | ||||
|         } | ||||
|  | ||||
|         /* Init socket context data */ | ||||
|         new ((WebSocketContextData<SSL> *) us_socket_context_ext(SSL, (us_socket_context_t *)webSocketContext)) WebSocketContextData<SSL>; | ||||
|         return webSocketContext->init(); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // UWS_WEBSOCKETCONTEXT_H | ||||
							
								
								
									
										100
									
								
								test/compatibility/C/uWebSockets/src/WebSocketContextData.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								test/compatibility/C/uWebSockets/src/WebSocketContextData.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | ||||
| /* | ||||
|  * Authored by Alex Hultman, 2018-2019. | ||||
|  * Intellectual property of third-party. | ||||
|  | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #ifndef UWS_WEBSOCKETCONTEXTDATA_H | ||||
| #define UWS_WEBSOCKETCONTEXTDATA_H | ||||
|  | ||||
| #include "f2/function2.hpp" | ||||
| #include <string_view> | ||||
|  | ||||
| #include "WebSocketProtocol.h" | ||||
| #include "TopicTreeDraft.h" | ||||
|  | ||||
| namespace uWS { | ||||
|  | ||||
| template <bool, bool> struct WebSocket; | ||||
|  | ||||
| /* todo: this looks identical to WebSocketBehavior, why not just std::move that entire thing in? */ | ||||
|  | ||||
| template <bool SSL> | ||||
| struct WebSocketContextData { | ||||
|     /* The callbacks for this context */ | ||||
|     fu2::unique_function<void(WebSocket<SSL, true> *, std::string_view, uWS::OpCode)> messageHandler = nullptr; | ||||
|     fu2::unique_function<void(WebSocket<SSL, true> *)> drainHandler = nullptr; | ||||
|     fu2::unique_function<void(WebSocket<SSL, true> *, int, std::string_view)> closeHandler = nullptr; | ||||
|  | ||||
|     /* Settings for this context */ | ||||
|     size_t maxPayloadLength = 0; | ||||
|     int idleTimeout = 0; | ||||
|  | ||||
|     /* There needs to be a maxBackpressure which will force close everything over that limit */ | ||||
|     size_t maxBackpressure = 0; | ||||
|  | ||||
|     /* Each websocket context has a topic tree for pub/sub */ | ||||
|     TopicTree topicTree; | ||||
|  | ||||
|     ~WebSocketContextData() { | ||||
|         /* We must unregister any loop post handler here */ | ||||
|         Loop::get()->removePostHandler(this); | ||||
|         Loop::get()->removePreHandler(this); | ||||
|     } | ||||
|  | ||||
|     WebSocketContextData() : topicTree([this](Subscriber *s, std::string_view data) -> int { | ||||
|         /* We rely on writing to regular asyncSockets */ | ||||
|         auto *asyncSocket = (AsyncSocket<SSL> *) s->user; | ||||
|  | ||||
|         auto [written, failed] = asyncSocket->write(data.data(), (int) data.length()); | ||||
|         if (!failed) { | ||||
|             asyncSocket->timeout(this->idleTimeout); | ||||
|         } else { | ||||
|             /* Note: this assumes we are not corked, as corking will swallow things and fail later on */ | ||||
|  | ||||
|             /* Check if we now have too much backpressure (todo: don't buffer up before check) */ | ||||
|             if ((unsigned int) asyncSocket->getBufferedAmount() > maxBackpressure) { | ||||
|                 asyncSocket->close(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /* Reserved, unused */ | ||||
|         return 0; | ||||
|     }) { | ||||
|         /* We empty for both pre and post just to make sure */ | ||||
|         Loop::get()->addPostHandler(this, [this](Loop *loop) { | ||||
|             /* Commit pub/sub batches every loop iteration */ | ||||
|             topicTree.drain(); | ||||
|         }); | ||||
|  | ||||
|         Loop::get()->addPreHandler(this, [this](Loop *loop) { | ||||
|             /* Commit pub/sub batches every loop iteration */ | ||||
|             topicTree.drain(); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     /* Helper for topictree publish, common path from app and ws */ | ||||
|     void publish(std::string_view topic, std::string_view message, OpCode opCode, bool compress) { | ||||
|         /* We frame the message right here and only pass raw bytes to the pub/subber */ | ||||
|         char *dst = (char *) malloc(protocol::messageFrameSize(message.size())); | ||||
|         size_t dst_length = protocol::formatMessage<true>(dst, message.data(), message.length(), opCode, message.length(), false); | ||||
|  | ||||
|         topicTree.publish(topic, std::string_view(dst, dst_length)); | ||||
|         ::free(dst); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // UWS_WEBSOCKETCONTEXTDATA_H | ||||
							
								
								
									
										70
									
								
								test/compatibility/C/uWebSockets/src/WebSocketData.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								test/compatibility/C/uWebSockets/src/WebSocketData.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| /* | ||||
|  * Authored by Alex Hultman, 2018-2019. | ||||
|  * Intellectual property of third-party. | ||||
|  | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #ifndef UWS_WEBSOCKETDATA_H | ||||
| #define UWS_WEBSOCKETDATA_H | ||||
|  | ||||
| #include "WebSocketProtocol.h" | ||||
| #include "AsyncSocketData.h" | ||||
| #include "PerMessageDeflate.h" | ||||
|  | ||||
| #include <string> | ||||
|  | ||||
| namespace uWS { | ||||
|  | ||||
| struct WebSocketData : AsyncSocketData<false>, WebSocketState<true> { | ||||
|     template <bool, bool> friend struct WebSocketContext; | ||||
|     template <bool, bool> friend struct WebSocket; | ||||
| private: | ||||
|     std::string fragmentBuffer; | ||||
|     int controlTipLength = 0; | ||||
|     bool isShuttingDown = 0; | ||||
|     enum CompressionStatus : char { | ||||
|         DISABLED, | ||||
|         ENABLED, | ||||
|         COMPRESSED_FRAME | ||||
|     } compressionStatus; | ||||
|  | ||||
|     /* We might have a dedicated compressor */ | ||||
|     DeflationStream *deflationStream = nullptr; | ||||
|  | ||||
|     /* We could be a subscriber */ | ||||
|     Subscriber *subscriber = nullptr; | ||||
| public: | ||||
|     WebSocketData(bool perMessageDeflate, bool slidingCompression, std::string &&backpressure) : AsyncSocketData<false>(std::move(backpressure)), WebSocketState<true>() { | ||||
|         compressionStatus = perMessageDeflate ? ENABLED : DISABLED; | ||||
|  | ||||
|         /* Initialize the dedicated sliding window */ | ||||
|         if (perMessageDeflate && slidingCompression) { | ||||
|             deflationStream = new DeflationStream; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     ~WebSocketData() { | ||||
|         if (deflationStream) { | ||||
|             delete deflationStream; | ||||
|         } | ||||
|  | ||||
|         if (subscriber) { | ||||
|             delete subscriber; | ||||
|         } | ||||
|     } | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // UWS_WEBSOCKETDATA_H | ||||
							
								
								
									
										169
									
								
								test/compatibility/C/uWebSockets/src/WebSocketExtensions.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								test/compatibility/C/uWebSockets/src/WebSocketExtensions.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,169 @@ | ||||
| /* | ||||
|  * Authored by Alex Hultman, 2018-2019. | ||||
|  * Intellectual property of third-party. | ||||
|  | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #ifndef UWS_WEBSOCKETEXTENSIONS_H | ||||
| #define UWS_WEBSOCKETEXTENSIONS_H | ||||
|  | ||||
| #include <climits> | ||||
| #include <string_view> | ||||
|  | ||||
| namespace uWS { | ||||
|  | ||||
| enum Options : unsigned int { | ||||
|     NO_OPTIONS = 0, | ||||
|     PERMESSAGE_DEFLATE = 1, | ||||
|     SERVER_NO_CONTEXT_TAKEOVER = 2, // remove this | ||||
|     CLIENT_NO_CONTEXT_TAKEOVER = 4, // remove this | ||||
|     NO_DELAY = 8, | ||||
|     SLIDING_DEFLATE_WINDOW = 16 | ||||
| }; | ||||
|  | ||||
| enum ExtensionTokens { | ||||
|     TOK_PERMESSAGE_DEFLATE = 1838, | ||||
|     TOK_SERVER_NO_CONTEXT_TAKEOVER = 2807, | ||||
|     TOK_CLIENT_NO_CONTEXT_TAKEOVER = 2783, | ||||
|     TOK_SERVER_MAX_WINDOW_BITS = 2372, | ||||
|     TOK_CLIENT_MAX_WINDOW_BITS = 2348 | ||||
| }; | ||||
|  | ||||
| struct ExtensionsParser { | ||||
| private: | ||||
|     int *lastInteger = nullptr; | ||||
|  | ||||
| public: | ||||
|     bool perMessageDeflate = false; | ||||
|     bool serverNoContextTakeover = false; | ||||
|     bool clientNoContextTakeover = false; | ||||
|     int serverMaxWindowBits = 0; | ||||
|     int clientMaxWindowBits = 0; | ||||
|  | ||||
|     int getToken(const char *&in, const char *stop) { | ||||
|         while (in != stop && !isalnum(*in)) { | ||||
|             in++; | ||||
|         } | ||||
|  | ||||
|         /* Don't care more than this for now */ | ||||
|         static_assert(SHRT_MIN > INT_MIN, "Integer overflow fix is invalid for this platform, report this as a bug!"); | ||||
|  | ||||
|         int hashedToken = 0; | ||||
|         while (in != stop && (isalnum(*in) || *in == '-' || *in == '_')) { | ||||
|             if (isdigit(*in)) { | ||||
|                 /* This check is a quick and incorrect fix for integer overflow | ||||
|                  * in oss-fuzz but we don't care as it doesn't matter either way */ | ||||
|                 if (hashedToken > SHRT_MIN && hashedToken < SHRT_MAX) { | ||||
|                     hashedToken = hashedToken * 10 - (*in - '0'); | ||||
|                 } | ||||
|             } else { | ||||
|                 hashedToken += *in; | ||||
|             } | ||||
|             in++; | ||||
|         } | ||||
|         return hashedToken; | ||||
|     } | ||||
|  | ||||
|     ExtensionsParser(const char *data, size_t length) { | ||||
|         const char *stop = data + length; | ||||
|         int token = 1; | ||||
|         for (; token && token != TOK_PERMESSAGE_DEFLATE; token = getToken(data, stop)); | ||||
|  | ||||
|         perMessageDeflate = (token == TOK_PERMESSAGE_DEFLATE); | ||||
|         while ((token = getToken(data, stop))) { | ||||
|             switch (token) { | ||||
|             case TOK_PERMESSAGE_DEFLATE: | ||||
|                 return; | ||||
|             case TOK_SERVER_NO_CONTEXT_TAKEOVER: | ||||
|                 serverNoContextTakeover = true; | ||||
|                 break; | ||||
|             case TOK_CLIENT_NO_CONTEXT_TAKEOVER: | ||||
|                 clientNoContextTakeover = true; | ||||
|                 break; | ||||
|             case TOK_SERVER_MAX_WINDOW_BITS: | ||||
|                 serverMaxWindowBits = 1; | ||||
|                 lastInteger = &serverMaxWindowBits; | ||||
|                 break; | ||||
|             case TOK_CLIENT_MAX_WINDOW_BITS: | ||||
|                 clientMaxWindowBits = 1; | ||||
|                 lastInteger = &clientMaxWindowBits; | ||||
|                 break; | ||||
|             default: | ||||
|                 if (token < 0 && lastInteger) { | ||||
|                     *lastInteger = -token; | ||||
|                 } | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| }; | ||||
|  | ||||
| template <bool isServer> | ||||
| struct ExtensionsNegotiator { | ||||
| protected: | ||||
|     int options; | ||||
|  | ||||
| public: | ||||
|     ExtensionsNegotiator(int wantedOptions) { | ||||
|         options = wantedOptions; | ||||
|     } | ||||
|  | ||||
|     std::string generateOffer() { | ||||
|         std::string extensionsOffer; | ||||
|         if (options & Options::PERMESSAGE_DEFLATE) { | ||||
|             extensionsOffer += "permessage-deflate"; | ||||
|  | ||||
|             if (options & Options::CLIENT_NO_CONTEXT_TAKEOVER) { | ||||
|                 extensionsOffer += "; client_no_context_takeover"; | ||||
|             } | ||||
|  | ||||
|             /* It is questionable sending this improves anything */ | ||||
|             /*if (options & Options::SERVER_NO_CONTEXT_TAKEOVER) { | ||||
|                 extensionsOffer += "; server_no_context_takeover"; | ||||
|             }*/ | ||||
|         } | ||||
|  | ||||
|         return extensionsOffer; | ||||
|     } | ||||
|  | ||||
|     void readOffer(std::string_view offer) { | ||||
|         if (isServer) { | ||||
|             ExtensionsParser extensionsParser(offer.data(), offer.length()); | ||||
|             if ((options & PERMESSAGE_DEFLATE) && extensionsParser.perMessageDeflate) { | ||||
|                 if (extensionsParser.clientNoContextTakeover || (options & CLIENT_NO_CONTEXT_TAKEOVER)) { | ||||
|                     options |= CLIENT_NO_CONTEXT_TAKEOVER; | ||||
|                 } | ||||
|  | ||||
|                 /* We leave this option for us to read even if the client did not send it */ | ||||
|                 if (extensionsParser.serverNoContextTakeover) { | ||||
|                     options |= SERVER_NO_CONTEXT_TAKEOVER; | ||||
|                 }/* else { | ||||
|                     options &= ~SERVER_NO_CONTEXT_TAKEOVER; | ||||
|                 }*/ | ||||
|             } else { | ||||
|                 options &= ~PERMESSAGE_DEFLATE; | ||||
|             } | ||||
|         } else { | ||||
|             // todo! | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     int getNegotiatedOptions() { | ||||
|         return options; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // UWS_WEBSOCKETEXTENSIONS_H | ||||
							
								
								
									
										134
									
								
								test/compatibility/C/uWebSockets/src/WebSocketHandshake.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								test/compatibility/C/uWebSockets/src/WebSocketHandshake.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,134 @@ | ||||
| /* | ||||
|  * Authored by Alex Hultman, 2018-2019. | ||||
|  * Intellectual property of third-party. | ||||
|  | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
| */ | ||||
|  | ||||
| #ifndef UWS_WEBSOCKETHANDSHAKE_H | ||||
| #define UWS_WEBSOCKETHANDSHAKE_H | ||||
|  | ||||
| #include <cstdint> | ||||
| #include <cstddef> | ||||
|  | ||||
| namespace uWS { | ||||
|  | ||||
| struct WebSocketHandshake { | ||||
|     template <int N, typename T> | ||||
|     struct static_for { | ||||
|         void operator()(uint32_t *a, uint32_t *b) { | ||||
|             static_for<N - 1, T>()(a, b); | ||||
|             T::template f<N - 1>(a, b); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     template <typename T> | ||||
|     struct static_for<0, T> { | ||||
|         void operator()(uint32_t *a, uint32_t *hash) {} | ||||
|     }; | ||||
|  | ||||
|     template <int state> | ||||
|     struct Sha1Loop { | ||||
|         static inline uint32_t rol(uint32_t value, size_t bits) {return (value << bits) | (value >> (32 - bits));} | ||||
|         static inline uint32_t blk(uint32_t b[16], size_t i) { | ||||
|             return rol(b[(i + 13) & 15] ^ b[(i + 8) & 15] ^ b[(i + 2) & 15] ^ b[i], 1); | ||||
|         } | ||||
|  | ||||
|         template <int i> | ||||
|         static inline void f(uint32_t *a, uint32_t *b) { | ||||
|             switch (state) { | ||||
|             case 1: | ||||
|                 a[i % 5] += ((a[(3 + i) % 5] & (a[(2 + i) % 5] ^ a[(1 + i) % 5])) ^ a[(1 + i) % 5]) + b[i] + 0x5a827999 + rol(a[(4 + i) % 5], 5); | ||||
|                 a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30); | ||||
|                 break; | ||||
|             case 2: | ||||
|                 b[i] = blk(b, i); | ||||
|                 a[(1 + i) % 5] += ((a[(4 + i) % 5] & (a[(3 + i) % 5] ^ a[(2 + i) % 5])) ^ a[(2 + i) % 5]) + b[i] + 0x5a827999 + rol(a[(5 + i) % 5], 5); | ||||
|                 a[(4 + i) % 5] = rol(a[(4 + i) % 5], 30); | ||||
|                 break; | ||||
|             case 3: | ||||
|                 b[(i + 4) % 16] = blk(b, (i + 4) % 16); | ||||
|                 a[i % 5] += (a[(3 + i) % 5] ^ a[(2 + i) % 5] ^ a[(1 + i) % 5]) + b[(i + 4) % 16] + 0x6ed9eba1 + rol(a[(4 + i) % 5], 5); | ||||
|                 a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30); | ||||
|                 break; | ||||
|             case 4: | ||||
|                 b[(i + 8) % 16] = blk(b, (i + 8) % 16); | ||||
|                 a[i % 5] += (((a[(3 + i) % 5] | a[(2 + i) % 5]) & a[(1 + i) % 5]) | (a[(3 + i) % 5] & a[(2 + i) % 5])) + b[(i + 8) % 16] + 0x8f1bbcdc + rol(a[(4 + i) % 5], 5); | ||||
|                 a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30); | ||||
|                 break; | ||||
|             case 5: | ||||
|                 b[(i + 12) % 16] = blk(b, (i + 12) % 16); | ||||
|                 a[i % 5] += (a[(3 + i) % 5] ^ a[(2 + i) % 5] ^ a[(1 + i) % 5]) + b[(i + 12) % 16] + 0xca62c1d6 + rol(a[(4 + i) % 5], 5); | ||||
|                 a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30); | ||||
|                 break; | ||||
|             case 6: | ||||
|                 b[i] += a[4 - i]; | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     static inline void sha1(uint32_t hash[5], uint32_t b[16]) { | ||||
|         uint32_t a[5] = {hash[4], hash[3], hash[2], hash[1], hash[0]}; | ||||
|         static_for<16, Sha1Loop<1>>()(a, b); | ||||
|         static_for<4, Sha1Loop<2>>()(a, b); | ||||
|         static_for<20, Sha1Loop<3>>()(a, b); | ||||
|         static_for<20, Sha1Loop<4>>()(a, b); | ||||
|         static_for<20, Sha1Loop<5>>()(a, b); | ||||
|         static_for<5, Sha1Loop<6>>()(a, hash); | ||||
|     } | ||||
|  | ||||
|     static inline void base64(unsigned char *src, char *dst) { | ||||
|         const char *b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | ||||
|         for (int i = 0; i < 18; i += 3) { | ||||
|             *dst++ = b64[(src[i] >> 2) & 63]; | ||||
|             *dst++ = b64[((src[i] & 3) << 4) | ((src[i + 1] & 240) >> 4)]; | ||||
|             *dst++ = b64[((src[i + 1] & 15) << 2) | ((src[i + 2] & 192) >> 6)]; | ||||
|             *dst++ = b64[src[i + 2] & 63]; | ||||
|         } | ||||
|         *dst++ = b64[(src[18] >> 2) & 63]; | ||||
|         *dst++ = b64[((src[18] & 3) << 4) | ((src[19] & 240) >> 4)]; | ||||
|         *dst++ = b64[((src[19] & 15) << 2)]; | ||||
|         *dst++ = '='; | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     static inline void generate(const char input[24], char output[28]) { | ||||
|         uint32_t b_output[5] = { | ||||
|             0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 | ||||
|         }; | ||||
|         uint32_t b_input[16] = { | ||||
|             0, 0, 0, 0, 0, 0, 0x32353845, 0x41464135, 0x2d453931, 0x342d3437, 0x44412d39, | ||||
|             0x3543412d, 0x43354142, 0x30444338, 0x35423131, 0x80000000 | ||||
|         }; | ||||
|  | ||||
|         for (int i = 0; i < 6; i++) { | ||||
|             b_input[i] = (input[4 * i + 3] & 0xff) | (input[4 * i + 2] & 0xff) << 8 | (input[4 * i + 1] & 0xff) << 16 | (input[4 * i + 0] & 0xff) << 24; | ||||
|         } | ||||
|         sha1(b_output, b_input); | ||||
|         uint32_t last_b[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 480}; | ||||
|         sha1(b_output, last_b); | ||||
|         for (int i = 0; i < 5; i++) { | ||||
|             uint32_t tmp = b_output[i]; | ||||
|             char *bytes = (char *) &b_output[i]; | ||||
|             bytes[3] = (char) (tmp & 0xff); | ||||
|             bytes[2] = (char) ((tmp >> 8) & 0xff); | ||||
|             bytes[1] = (char) ((tmp >> 16) & 0xff); | ||||
|             bytes[0] = (char) ((tmp >> 24) & 0xff); | ||||
|         } | ||||
|         base64((unsigned char *) b_output, output); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // UWS_WEBSOCKETHANDSHAKE_H | ||||
							
								
								
									
										443
									
								
								test/compatibility/C/uWebSockets/src/WebSocketProtocol.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										443
									
								
								test/compatibility/C/uWebSockets/src/WebSocketProtocol.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,443 @@ | ||||
| /* | ||||
|  * Authored by Alex Hultman, 2018-2019. | ||||
|  * Intellectual property of third-party. | ||||
|  | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #ifndef UWS_WEBSOCKETPROTOCOL_H | ||||
| #define UWS_WEBSOCKETPROTOCOL_H | ||||
|  | ||||
| #include <cstdint> | ||||
| #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]; | ||||
| }; | ||||
|  | ||||
| namespace protocol { | ||||
|  | ||||
| template <typename T> | ||||
| T bit_cast(char *c) { | ||||
|     T val; | ||||
|     memcpy(&val, c, sizeof(T)); | ||||
|     return val; | ||||
| } | ||||
|  | ||||
| /* Byte swap for little-endian systems */ | ||||
| template <typename T> | ||||
| T cond_byte_swap(T value) { | ||||
|     uint32_t endian_test = 1; | ||||
|     if (*((char *)&endian_test)) { | ||||
|         union { | ||||
|             T i; | ||||
|             uint8_t b[sizeof(T)]; | ||||
|         } src = { value }, dst; | ||||
|  | ||||
|         for (unsigned int i = 0; i < sizeof(value); i++) { | ||||
|             dst.b[i] = src.b[sizeof(value) - 1 - i]; | ||||
|         } | ||||
|  | ||||
|         return dst.i; | ||||
|     } | ||||
|     return value; | ||||
| } | ||||
|  | ||||
| // 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 tmp; | ||||
|             memcpy(&tmp, s, 4); | ||||
|             if ((tmp & 0x80808080) == 0) { | ||||
|                 s += 4; | ||||
|                 continue; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         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 = {cond_byte_swap<uint16_t>(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 = cond_byte_swap<uint16_t>(code); | ||||
|         memcpy(dst, &code, 2); | ||||
|         /* It is invalid to pass nullptr to memcpy, even though length is 0 */ | ||||
|         if (message) { | ||||
|             memcpy(dst + 2, message, length); | ||||
|         } | ||||
|         return length + 2; | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| static inline size_t messageFrameSize(size_t messageSize) { | ||||
|     if (messageSize < 126) { | ||||
|         return 2 + messageSize; | ||||
|     } else if (messageSize <= UINT16_MAX) { | ||||
|         return 4 + messageSize; | ||||
|     } | ||||
|     return 10 + messageSize; | ||||
| } | ||||
|  | ||||
| enum { | ||||
|     SND_CONTINUATION = 1, | ||||
|     SND_NO_FIN = 2, | ||||
|     SND_COMPRESSED = 64 | ||||
| }; | ||||
|  | ||||
| template <bool isServer> | ||||
| 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] = (char) reportedLength; | ||||
|     } else if (reportedLength <= UINT16_MAX) { | ||||
|         headerLength = 4; | ||||
|         dst[1] = 126; | ||||
|         uint16_t tmp = cond_byte_swap<uint16_t>((uint16_t) reportedLength); | ||||
|         memcpy(&dst[2], &tmp, sizeof(uint16_t)); | ||||
|     } else { | ||||
|         headerLength = 10; | ||||
|         dst[1] = 127; | ||||
|         uint64_t tmp = cond_byte_swap<uint64_t>((uint64_t) reportedLength); | ||||
|         memcpy(&dst[2], &tmp, sizeof(uint64_t)); | ||||
|     } | ||||
|  | ||||
|     int flags = 0; | ||||
|     dst[0] = (char) ((flags & SND_NO_FIN ? 0 : 128) | (compressed ? SND_COMPRESSED : 0)); | ||||
|     if (!(flags & SND_CONTINUATION)) { | ||||
|         dst[0] |= (char) 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; | ||||
| } | ||||
|  | ||||
| } | ||||
|  | ||||
| // essentially this is only a parser | ||||
| template <const bool isServer, typename Impl> | ||||
| struct 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; | ||||
|  | ||||
| protected: | ||||
|     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]; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     template <unsigned int MESSAGE_HEADER, typename T> | ||||
|     static inline bool consumeMessage(T payLength, char *&src, unsigned int &length, WebSocketState<isServer> *wState, void *user) { | ||||
|         if (getOpCode(src)) { | ||||
|             if (wState->state.opStack == 1 || (!wState->state.lastFin && getOpCode(src) < 2)) { | ||||
|                 Impl::forceClose(wState, user); | ||||
|                 return true; | ||||
|             } | ||||
|             wState->state.opCode[++wState->state.opStack] = (OpCode) getOpCode(src); | ||||
|         } else if (wState->state.opStack == -1) { | ||||
|             Impl::forceClose(wState, user); | ||||
|             return true; | ||||
|         } | ||||
|         wState->state.lastFin = isFin(src); | ||||
|  | ||||
|         if (Impl::refusePayloadLength(payLength, wState, user)) { | ||||
|             Impl::forceClose(wState, user); | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         if (payLength + MESSAGE_HEADER <= length) { | ||||
|             if (isServer) { | ||||
|                 unmaskImpreciseCopyMask(src + MESSAGE_HEADER - 4, src + MESSAGE_HEADER, src + MESSAGE_HEADER - 4, (unsigned int) payLength); | ||||
|                 if (Impl::handleFragment(src + MESSAGE_HEADER - 4, payLength, 0, wState->state.opCode[wState->state.opStack], isFin(src), wState, user)) { | ||||
|                     return true; | ||||
|                 } | ||||
|             } else { | ||||
|                 if (Impl::handleFragment(src + MESSAGE_HEADER, payLength, 0, wState->state.opCode[wState->state.opStack], isFin(src), wState, user)) { | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (isFin(src)) { | ||||
|                 wState->state.opStack--; | ||||
|             } | ||||
|  | ||||
|             src += payLength + MESSAGE_HEADER; | ||||
|             length -= (unsigned int) (payLength + MESSAGE_HEADER); | ||||
|             wState->state.spillLength = 0; | ||||
|             return false; | ||||
|         } else { | ||||
|             wState->state.spillLength = 0; | ||||
|             wState->state.wantsHead = false; | ||||
|             wState->remainingBytes = (unsigned int) (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, user); | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     static inline bool consumeContinuation(char *&src, unsigned int &length, WebSocketState<isServer> *wState, void *user) { | ||||
|         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, user)) { | ||||
|                 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, user)) { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             if (isServer && length % 4) { | ||||
|                 rotateMask(4 - (length % 4), wState->mask); | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     WebSocketProtocol() { | ||||
|  | ||||
|     } | ||||
|  | ||||
|     static inline void consume(char *src, unsigned int length, WebSocketState<isServer> *wState, void *user) { | ||||
|         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, user)) || rsv23(src) || (getOpCode(src) > 2 && getOpCode(src) < 8) || | ||||
|                     getOpCode(src) > 10 || (getOpCode(src) > 2 && (!isFin(src) || payloadLength(src) > 125))) { | ||||
|                     Impl::forceClose(wState, user); | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 if (payloadLength(src) < 126) { | ||||
|                     if (consumeMessage<SHORT_MESSAGE_HEADER, uint8_t>(payloadLength(src), src, length, wState, user)) { | ||||
|                         return; | ||||
|                     } | ||||
|                 } else if (payloadLength(src) == 126) { | ||||
|                     if (length < MEDIUM_MESSAGE_HEADER) { | ||||
|                         break; | ||||
|                     } else if(consumeMessage<MEDIUM_MESSAGE_HEADER, uint16_t>(protocol::cond_byte_swap<uint16_t>(protocol::bit_cast<uint16_t>(src + 2)), src, length, wState, user)) { | ||||
|                         return; | ||||
|                     } | ||||
|                 } else if (length < LONG_MESSAGE_HEADER) { | ||||
|                     break; | ||||
|                 } else if (consumeMessage<LONG_MESSAGE_HEADER, uint64_t>(protocol::cond_byte_swap<uint64_t>(protocol::bit_cast<uint64_t>(src + 2)), src, length, wState, user)) { | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
|             if (length) { | ||||
|                 memcpy(wState->state.spill, src, length); | ||||
|                 wState->state.spillLength = length & 0xf; | ||||
|             } | ||||
|         } else if (consumeContinuation(src, length, wState, user)) { | ||||
|             goto parseNext; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     static const int CONSUME_POST_PADDING = 4; | ||||
|     static const int CONSUME_PRE_PADDING = LONG_MESSAGE_HEADER - 1; | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // UWS_WEBSOCKETPROTOCOL_H | ||||
							
								
								
									
										23
									
								
								test/compatibility/C/uWebSockets/src/f2/LICENSE.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								test/compatibility/C/uWebSockets/src/f2/LICENSE.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| Boost Software License - Version 1.0 - August 17th, 2003 | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person or organization | ||||
| obtaining a copy of the software and accompanying documentation covered by | ||||
| this license (the "Software") to use, reproduce, display, distribute, | ||||
| execute, and transmit the Software, and to prepare derivative works of the | ||||
| Software, and to permit third-parties to whom the Software is furnished to | ||||
| do so, all subject to the following: | ||||
|  | ||||
| The copyright notices in the Software and this entire statement, including | ||||
| the above license grant, this restriction and the following disclaimer, | ||||
| must be included in all copies of the Software, in whole or in part, and | ||||
| all derivative works of the Software, unless such copies or derivative | ||||
| works are solely in the form of machine-executable object code generated by | ||||
| a source language processor. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT | ||||
| SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE | ||||
| FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, | ||||
| ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | ||||
| DEALINGS IN THE SOFTWARE. | ||||
							
								
								
									
										1764
									
								
								test/compatibility/C/uWebSockets/src/f2/function2.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1764
									
								
								test/compatibility/C/uWebSockets/src/f2/function2.hpp
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user