detect_websocket.hpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. /*
  2. * Copyright (c) 2017-2023 zhllxt
  3. *
  4. * author : zhllxt
  5. * email : 37792738@qq.com
  6. *
  7. * refrenced from : /beast/core/detect_ssl.hpp
  8. *
  9. * Distributed under the Boost Software License, Version 1.0. (See accompanying
  10. * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  11. *
  12. * Note : this functionality not yet implemented
  13. *
  14. */
  15. #ifndef __ASIO2_MQTT_DETECT_WEBSOCKET_HPP__
  16. #define __ASIO2_MQTT_DETECT_WEBSOCKET_HPP__
  17. #if defined(_MSC_VER) && (_MSC_VER >= 1200)
  18. #pragma once
  19. #endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
  20. #include <type_traits>
  21. #include <asio2/external/asio.hpp>
  22. #include <asio2/external/beast.hpp>
  23. #include <asio2/base/error.hpp>
  24. #ifdef ASIO_STANDALONE
  25. #include <asio/yield.hpp>
  26. #else
  27. #include <boost/asio/yield.hpp>
  28. #endif
  29. namespace asio2::detail
  30. {
  31. template<class DetectHandler, class AsyncReadStream, class DynamicBuffer>
  32. class detect_websocket_op : public asio::coroutine
  33. {
  34. DetectHandler handler_;
  35. AsyncReadStream& stream_;
  36. // The callers buffer is used to hold all received data
  37. DynamicBuffer& buffer_;
  38. // We're going to need this in case we have to post the handler
  39. error_code ec_;
  40. beast::tribool result_ = false;
  41. public:
  42. // Completion handlers must be MoveConstructible.
  43. detect_websocket_op(detect_websocket_op&&) = default;
  44. // Construct the operation. The handler is deduced through
  45. // the template type `DetectHandler`, this lets the same constructor
  46. // work properly for both lvalues and rvalues.
  47. //
  48. detect_websocket_op(
  49. DetectHandler&& handler,
  50. AsyncReadStream& stream,
  51. DynamicBuffer& buffer)
  52. : handler_(std::forward<DetectHandler>(handler))
  53. , stream_(stream)
  54. , buffer_(buffer)
  55. {
  56. // This starts the operation. We pass `false` to tell the
  57. // algorithm that it needs to use asio::post if it wants to
  58. // complete immediately. This is required by Networking,
  59. // as initiating functions are not allowed to invoke the
  60. // completion handler on the caller's thread before
  61. // returning.
  62. (*this)({}, 0, false);
  63. }
  64. // Our main entry point. This will get called as our
  65. // intermediate operations complete. Definition below.
  66. //
  67. // The parameter `cont` indicates if we are being called subsequently
  68. // from the original invocation
  69. //
  70. void operator()(error_code ec, std::size_t bytes_transferred, bool cont = true)
  71. {
  72. // This introduces the scope of the stackless coroutine
  73. reenter(*this)
  74. {
  75. // Loop until an error occurs or we get a definitive answer
  76. for(;;)
  77. {
  78. // There could already be a hello in the buffer so check first
  79. result_ = is_websocket_upgrade_request(buffer_.data());
  80. // If we got an answer, then the operation is complete
  81. if(! beast::indeterminate(result_))
  82. break;
  83. // Try to fill our buffer by reading from the stream.
  84. // The function read_size calculates a reasonable size for the
  85. // amount to read next, using existing capacity if possible to
  86. // avoid allocating memory, up to the limit of 1536 bytes which
  87. // is the size of a normal TCP frame.
  88. //
  89. // `async_read_some` expects a ReadHandler as the completion
  90. // handler. The signature of a read handler is void(error_code, size_t),
  91. // and this function matches that signature (the `cont` parameter has
  92. // a default of true). We pass `std::move(*this)` as the completion
  93. // handler for the read operation. This transfers ownership of this
  94. // entire state machine back into the `async_read_some` operation.
  95. // Care must be taken with this idiom, to ensure that parameters
  96. // passed to the initiating function which could be invalidated
  97. // by the move, are first moved to the stack before calling the
  98. // initiating function.
  99. yield stream_.async_read_some(buffer_.prepare(
  100. read_size(buffer_, 1536)), std::move(*this));
  101. // Commit what we read into the buffer's input area.
  102. buffer_.commit(bytes_transferred);
  103. // Check for an error
  104. if(ec)
  105. break;
  106. }
  107. // If `cont` is true, the handler will be invoked directly.
  108. //
  109. // Otherwise, the handler cannot be invoked directly, because
  110. // initiating functions are not allowed to call the handler
  111. // before returning. Instead, the handler must be posted to
  112. // the I/O context. We issue a zero-byte read using the same
  113. // type of buffers used in the ordinary read above, to prevent
  114. // the compiler from creating an extra instantiation of the
  115. // function template. This reduces compile times and the size
  116. // of the program executable.
  117. if(! cont)
  118. {
  119. // Save the error, otherwise it will be overwritten with
  120. // a successful error code when this read completes
  121. // immediately.
  122. ec_ = ec;
  123. // Zero-byte reads and writes are guaranteed to complete
  124. // immediately with succcess. The type of buffers and the
  125. // type of handler passed here need to exactly match the types
  126. // used in the call to async_read_some above, to avoid
  127. // instantiating another version of the function template.
  128. yield stream_.async_read_some(buffer_.prepare(0), std::move(*this));
  129. // Restore the saved error code
  130. ec = ec_;
  131. }
  132. // Invoke the final handler.
  133. // At this point, we are guaranteed that the original initiating
  134. // function is no longer on our stack frame.
  135. this->handler_(ec, static_cast<bool>(result_));
  136. }
  137. }
  138. };
  139. struct run_detect_websocket_op
  140. {
  141. template<class DetectHandler, class AsyncReadStream, class DynamicBuffer>
  142. void operator()(DetectHandler&& h, AsyncReadStream* s, DynamicBuffer& b)
  143. {
  144. detect_websocket_op<DetectHandler, AsyncReadStream, DynamicBuffer>(
  145. std::forward<DetectHandler>(h), *s, b);
  146. }
  147. };
  148. template <class ConstBufferSequence>
  149. beast::tribool is_websocket_upgrade_request(ConstBufferSequence const& buffers)
  150. {
  151. // Make sure buffers meets the requirements
  152. static_assert(
  153. asio::is_const_buffer_sequence<ConstBufferSequence>::value,
  154. "ConstBufferSequence type requirements not met");
  155. // Flatten the input buffers into a single contiguous range
  156. // of bytes on the stack to make it easier to work with the data.
  157. unsigned char buf[9];
  158. auto const n = asio::buffer_copy(
  159. asio::mutable_buffer(buf, sizeof(buf)), buffers);
  160. // Can't do much without any bytes
  161. if(n < 1)
  162. return beast::indeterminate;
  163. // Require the first byte to be 0x16, indicating a TLS handshake record
  164. if(buf[0] != 0x16)
  165. return false;
  166. // We need at least 5 bytes to know the record payload size
  167. if(n < 5)
  168. return beast::indeterminate;
  169. // Calculate the record payload size
  170. std::uint32_t const length = (buf[3] << 8) + buf[4];
  171. // A ClientHello message payload is at least 34 bytes.
  172. // There can be multiple handshake messages in the same record.
  173. if(length < 34)
  174. return false;
  175. // We need at least 6 bytes to know the handshake type
  176. if(n < 6)
  177. return beast::indeterminate;
  178. // The handshake_type must be 0x01 == client_hello
  179. if(buf[5] != 0x01)
  180. return false;
  181. // We need at least 9 bytes to know the payload size
  182. if(n < 9)
  183. return beast::indeterminate;
  184. // Calculate the message payload size
  185. std::uint32_t const size =
  186. (buf[6] << 16) + (buf[7] << 8) + buf[8];
  187. // The message payload can't be bigger than the enclosing record
  188. if(size + 4 > length)
  189. return false;
  190. // This can only be a TLS client_hello message
  191. return true;
  192. }
  193. template<class SyncReadStream, class DynamicBuffer>
  194. bool detect_websocket(SyncReadStream& stream, DynamicBuffer& buffer, error_code& ec)
  195. {
  196. // Make sure arguments meet the requirements
  197. static_assert(
  198. is_sync_read_stream<SyncReadStream>::value,
  199. "SyncReadStream type requirements not met");
  200. static_assert(
  201. asio::is_dynamic_buffer<DynamicBuffer>::value,
  202. "DynamicBuffer type requirements not met");
  203. // Loop until an error occurs or we get a definitive answer
  204. for(;;)
  205. {
  206. // There could already be data in the buffer
  207. // so we do this first, before reading from the stream.
  208. auto const result = detail::is_websocket_upgrade_request(buffer.data());
  209. // If we got an answer, return it
  210. if(! beast::indeterminate(result))
  211. {
  212. // A definite answer is a success
  213. ec = {};
  214. return static_cast<bool>(result);
  215. }
  216. // Try to fill our buffer by reading from the stream.
  217. // The function read_size calculates a reasonable size for the
  218. // amount to read next, using existing capacity if possible to
  219. // avoid allocating memory, up to the limit of 1536 bytes which
  220. // is the size of a normal TCP frame.
  221. std::size_t const bytes_transferred = stream.read_some(
  222. buffer.prepare(beast::read_size(buffer, 1536)), ec);
  223. // Commit what we read into the buffer's input area.
  224. buffer.commit(bytes_transferred);
  225. // Check for an error
  226. if(ec)
  227. break;
  228. }
  229. // error
  230. return false;
  231. }
  232. // Here is the implementation of the asynchronous initiation function
  233. template<
  234. class AsyncReadStream,
  235. class DynamicBuffer,
  236. class CompletionToken = asio::default_completion_token_t<beast::executor_type<AsyncReadStream>>
  237. >
  238. auto async_detect_websocket(
  239. AsyncReadStream& stream,
  240. DynamicBuffer& buffer,
  241. CompletionToken&& token = asio::default_completion_token_t<beast::executor_type<AsyncReadStream>>{}) ->
  242. typename asio::async_result< /*< `async_result` customizes the return value based on the completion token >*/
  243. typename std::decay<CompletionToken>::type,
  244. void(error_code, bool)>::return_type /*< This is the signature for the completion handler >*/
  245. {
  246. // Make sure arguments meet the type requirements
  247. static_assert(
  248. is_async_read_stream<AsyncReadStream>::value,
  249. "SyncReadStream type requirements not met");
  250. static_assert(
  251. asio::is_dynamic_buffer<DynamicBuffer>::value,
  252. "DynamicBuffer type requirements not met");
  253. // The function `asio::async_initate` uses customization points
  254. // to allow one asynchronous initiating function to work with
  255. // all sorts of notification systems, such as callbacks but also
  256. // fibers, futures, coroutines, and user-defined types.
  257. //
  258. // It works by capturing all of the arguments using perfect
  259. // forwarding, and then depending on the specialization of
  260. // `asio::async_result` for the type of `CompletionToken`,
  261. // the `initiation` object will be invoked with the saved
  262. // parameters and the actual completion handler. Our
  263. // initiating object is `run_detect_websocket_op`.
  264. //
  265. // Non-const references need to be passed as pointers,
  266. // since we don't want a decay-copy.
  267. return asio::async_initiate<CompletionToken, void(error_code, bool)>(
  268. detail::run_detect_websocket_op{},
  269. std::forward<CompletionToken>(token),
  270. &stream, // pass the reference by pointer
  271. buffer);
  272. }
  273. }
  274. #ifdef ASIO_STANDALONE
  275. #include <asio/unyield.hpp>
  276. #else
  277. #include <boost/asio/unyield.hpp>
  278. #endif
  279. #endif // !__ASIO2_MQTT_DETECT_WEBSOCKET_HPP__