1 | /* |
2 | * IXWebSocketTransport.h |
3 | * Author: Benjamin Sergeant |
4 | * Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved. |
5 | */ |
6 | |
7 | #pragma once |
8 | |
9 | // |
10 | // Adapted from https://github.com/dhbaird/easywsclient |
11 | // |
12 | |
13 | #include "IXCancellationRequest.h" |
14 | #include "IXProgressCallback.h" |
15 | #include "IXSocketTLSOptions.h" |
16 | #include "IXWebSocketCloseConstants.h" |
17 | #include "IXWebSocketHandshake.h" |
18 | #include "IXWebSocketHttpHeaders.h" |
19 | #include "IXWebSocketPerMessageDeflate.h" |
20 | #include "IXWebSocketPerMessageDeflateOptions.h" |
21 | #include "IXWebSocketSendInfo.h" |
22 | #include "IXWebSocketSendData.h" |
23 | #include <atomic> |
24 | #include <functional> |
25 | #include <list> |
26 | #include <memory> |
27 | #include <mutex> |
28 | #include <string> |
29 | #include <vector> |
30 | |
31 | namespace ix |
32 | { |
33 | class Socket; |
34 | |
35 | enum class SendMessageKind |
36 | { |
37 | Text, |
38 | Binary, |
39 | Ping |
40 | }; |
41 | |
42 | class WebSocketTransport |
43 | { |
44 | public: |
45 | enum class ReadyState |
46 | { |
47 | CLOSING, |
48 | CLOSED, |
49 | CONNECTING, |
50 | OPEN |
51 | }; |
52 | |
53 | enum class MessageKind |
54 | { |
55 | MSG_TEXT, |
56 | MSG_BINARY, |
57 | PING, |
58 | PONG, |
59 | FRAGMENT |
60 | }; |
61 | |
62 | enum class PollResult |
63 | { |
64 | Succeeded, |
65 | AbnormalClose, |
66 | CannotFlushSendBuffer |
67 | }; |
68 | |
69 | using OnMessageCallback = |
70 | std::function<void(const std::string&, size_t, bool, MessageKind)>; |
71 | using OnCloseCallback = std::function<void(uint16_t, const std::string&, size_t, bool)>; |
72 | |
73 | WebSocketTransport(); |
74 | ~WebSocketTransport(); |
75 | |
76 | void configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions, |
77 | const SocketTLSOptions& socketTLSOptions, |
78 | bool enablePong, |
79 | int pingIntervalSecs); |
80 | |
81 | // Client |
82 | WebSocketInitResult connectToUrl(const std::string& url, |
83 | const WebSocketHttpHeaders& , |
84 | int timeoutSecs); |
85 | |
86 | // Server |
87 | WebSocketInitResult connectToSocket(std::unique_ptr<Socket> socket, |
88 | int timeoutSecs, |
89 | bool enablePerMessageDeflate); |
90 | |
91 | PollResult poll(); |
92 | WebSocketSendInfo sendBinary(const IXWebSocketSendData& message, |
93 | const OnProgressCallback& onProgressCallback); |
94 | WebSocketSendInfo sendText(const IXWebSocketSendData& message, |
95 | const OnProgressCallback& onProgressCallback); |
96 | WebSocketSendInfo sendPing(const IXWebSocketSendData& message); |
97 | |
98 | void close(uint16_t code = WebSocketCloseConstants::kNormalClosureCode, |
99 | const std::string& reason = WebSocketCloseConstants::kNormalClosureMessage, |
100 | size_t closeWireSize = 0, |
101 | bool remote = false); |
102 | |
103 | void closeSocket(); |
104 | |
105 | ReadyState getReadyState() const; |
106 | void setReadyState(ReadyState readyState); |
107 | void setOnCloseCallback(const OnCloseCallback& onCloseCallback); |
108 | void dispatch(PollResult pollResult, const OnMessageCallback& onMessageCallback); |
109 | size_t bufferedAmount() const; |
110 | |
111 | // internal |
112 | WebSocketSendInfo sendHeartBeat(); |
113 | |
114 | private: |
115 | std::string _url; |
116 | |
117 | struct |
118 | { |
119 | unsigned ; |
120 | bool ; |
121 | bool ; |
122 | bool ; |
123 | bool ; |
124 | bool ; |
125 | enum |
126 | { |
127 | = 0x0, |
128 | = 0x1, |
129 | = 0x2, |
130 | = 8, |
131 | = 9, |
132 | = 0xa, |
133 | } ; |
134 | int ; |
135 | uint64_t ; |
136 | uint8_t [4]; |
137 | }; |
138 | |
139 | // Tells whether we should mask the data we send. |
140 | // client should mask but server should not |
141 | std::atomic<bool> _useMask; |
142 | |
143 | // Tells whether we should flush the send buffer before |
144 | // saying that a send is complete. This is the mode for server code. |
145 | std::atomic<bool> _blockingSend; |
146 | |
147 | // Buffer for reading from our socket. That buffer is never resized. |
148 | std::vector<uint8_t> _readbuf; |
149 | |
150 | // Contains all messages that were fetched in the last socket read. |
151 | // This could be a mix of control messages (Close, Ping, etc...) and |
152 | // data messages. That buffer is resized |
153 | std::vector<uint8_t> _rxbuf; |
154 | |
155 | // Contains all messages that are waiting to be sent |
156 | std::vector<uint8_t> _txbuf; |
157 | mutable std::mutex _txbufMutex; |
158 | |
159 | // Hold fragments for multi-fragments messages in a list. We support receiving very large |
160 | // messages (tested messages up to 700M) and we cannot put them in a single |
161 | // buffer that is resized, as this operation can be slow when a buffer has its |
162 | // size increased 2 fold, while appending to a list has a fixed cost. |
163 | std::list<std::string> _chunks; |
164 | |
165 | // Record the message kind (will be TEXT or BINARY) for a fragmented |
166 | // message, present in the first chunk, since the final chunk will be a |
167 | // CONTINUATION opcode and doesn't tell the full message kind |
168 | MessageKind _fragmentedMessageKind; |
169 | |
170 | // Ditto for whether a message is compressed |
171 | bool _receivedMessageCompressed; |
172 | |
173 | // Fragments are 32K long |
174 | static constexpr size_t kChunkSize = 1 << 15; |
175 | |
176 | // Underlying TCP socket |
177 | std::unique_ptr<Socket> _socket; |
178 | std::mutex _socketMutex; |
179 | |
180 | // Hold the state of the connection (OPEN, CLOSED, etc...) |
181 | std::atomic<ReadyState> _readyState; |
182 | |
183 | OnCloseCallback _onCloseCallback; |
184 | std::string _closeReason; |
185 | mutable std::mutex _closeReasonMutex; |
186 | std::atomic<uint16_t> _closeCode; |
187 | std::atomic<size_t> _closeWireSize; |
188 | std::atomic<bool> _closeRemote; |
189 | |
190 | // Data used for Per Message Deflate compression (with zlib) |
191 | WebSocketPerMessageDeflatePtr _perMessageDeflate; |
192 | WebSocketPerMessageDeflateOptions _perMessageDeflateOptions; |
193 | std::atomic<bool> _enablePerMessageDeflate; |
194 | |
195 | std::string _decompressedMessage; |
196 | std::string _compressedMessage; |
197 | |
198 | // Used to control TLS connection behavior |
199 | SocketTLSOptions _socketTLSOptions; |
200 | |
201 | // Used to cancel dns lookup + socket connect + http upgrade |
202 | std::atomic<bool> _requestInitCancellation; |
203 | |
204 | mutable std::mutex _closingTimePointMutex; |
205 | std::chrono::time_point<std::chrono::steady_clock> _closingTimePoint; |
206 | static const int kClosingMaximumWaitingDelayInMs; |
207 | |
208 | // enable auto response to ping |
209 | std::atomic<bool> _enablePong; |
210 | static const bool kDefaultEnablePong; |
211 | |
212 | // Optional ping and pong timeout |
213 | int _pingIntervalSecs; |
214 | std::atomic<bool> _pongReceived; |
215 | |
216 | static const int kDefaultPingIntervalSecs; |
217 | static const std::string kPingMessage; |
218 | std::atomic<uint64_t> _pingCount; |
219 | |
220 | // We record when ping are being sent so that we can know when to send the next one |
221 | mutable std::mutex _lastSendPingTimePointMutex; |
222 | std::chrono::time_point<std::chrono::steady_clock> _lastSendPingTimePoint; |
223 | |
224 | // If this function returns true, it is time to send a new ping |
225 | bool pingIntervalExceeded(); |
226 | void initTimePointsAfterConnect(); |
227 | |
228 | // after calling close(), if no CLOSE frame answer is received back from the remote, we |
229 | // should close the connexion |
230 | bool closingDelayExceeded(); |
231 | |
232 | void sendCloseFrame(uint16_t code, const std::string& reason); |
233 | |
234 | void closeSocketAndSwitchToClosedState(uint16_t code, |
235 | const std::string& reason, |
236 | size_t closeWireSize, |
237 | bool remote); |
238 | |
239 | bool wakeUpFromPoll(uint64_t wakeUpCode); |
240 | |
241 | bool flushSendBuffer(); |
242 | bool sendOnSocket(); |
243 | bool receiveFromSocket(); |
244 | |
245 | WebSocketSendInfo (wsheader_type::opcode_type type, |
246 | const IXWebSocketSendData& message, |
247 | bool compress, |
248 | const OnProgressCallback& onProgressCallback = nullptr); |
249 | |
250 | template<class Iterator> |
251 | bool ( |
252 | wsheader_type::opcode_type type, bool fin, Iterator begin, Iterator end, bool compress); |
253 | |
254 | void emitMessage(MessageKind messageKind, |
255 | const std::string& message, |
256 | bool compressedMessage, |
257 | const OnMessageCallback& onMessageCallback); |
258 | |
259 | bool isSendBufferEmpty() const; |
260 | |
261 | template<class Iterator> |
262 | void appendToSendBuffer(const std::vector<uint8_t>& , |
263 | Iterator begin, |
264 | Iterator end, |
265 | uint64_t message_size, |
266 | uint8_t masking_key[4]); |
267 | |
268 | unsigned getRandomUnsigned(); |
269 | void (const wsheader_type& ws); |
270 | |
271 | std::string getMergedChunks() const; |
272 | |
273 | void setCloseReason(const std::string& reason); |
274 | const std::string& getCloseReason() const; |
275 | }; |
276 | } // namespace ix |
277 | |