-
-
Notifications
You must be signed in to change notification settings - Fork 170
/
Copy pathwsclient.h
252 lines (215 loc) · 6.4 KB
/
wsclient.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/export.h>
#include <string>
#include <map>
#include <dpp/sslconnection.h>
namespace dpp {
/**
* @brief Websocket protocol types available on Discord
*/
enum websocket_protocol_t : uint8_t {
/**
* @brief JSON data, text, UTF-8 character set
*/
ws_json = 0,
/**
* @brief Erlang Term Format (ETF) binary protocol
*/
ws_etf = 1
};
/**
* @brief Websocket connection status
*/
enum ws_state : uint8_t {
/**
* @brief Sending/receiving HTTP headers, acting as a standard HTTP connection.
* This is the state prior to receiving "HTTP/1.1 101 Switching Protocols" from the
* server side.
*/
HTTP_HEADERS,
/**
* @brief Connected as a websocket, and "upgraded". Now talking using binary frames.
*/
CONNECTED
};
/**
* @brief Low-level websocket opcodes for frames
*/
enum ws_opcode : uint8_t {
/**
* @brief Continuation.
*/
OP_CONTINUATION = 0x00,
/**
* @brief Text frame.
*/
OP_TEXT = 0x01,
/**
* @brief Binary frame.
*/
OP_BINARY = 0x02,
/**
* @brief Close notification with close code.
*/
OP_CLOSE = 0x08,
/**
* @brief Low level ping.
*/
OP_PING = 0x09,
/**
* @brief Low level pong.
*/
OP_PONG = 0x0a,
/**
* @brief Automatic selection of type
*/
OP_AUTO = 0xff,
};
/**
* @brief Implements a websocket client based on the SSL client
*/
class DPP_EXPORT websocket_client : public ssl_connection {
/**
* @brief Connection key used in the HTTP headers
*/
std::string key;
/**
* @brief Current websocket state
*/
ws_state state;
/**
* @brief Path part of URL for websocket
*/
std::string path;
/**
* @brief Data opcode, represents the type of frames we send
*/
ws_opcode data_opcode;
/**
* @brief HTTP headers received on connecting/upgrading
*/
std::map<std::string, std::string> http_headers;
/**
* @brief Parse headers for a websocket frame from the buffer.
* @param buffer The buffer to operate on. Will modify the string removing completed items from the head of the queue
* @return true if a complete header has been received
*/
bool parseheader(std::string& buffer);
/**
* @brief Fill a header for outbound messages
* @param outbuf The raw frame to fill
* @param sendlength The size of the data to encapsulate
* @param opcode the ws_opcode to send in the header
* @return size of filled header
*/
size_t fill_header(unsigned char* outbuf, size_t sendlength, ws_opcode opcode);
/**
* @brief Handle ping requests.
* @param payload The ping payload, to be returned as-is for a pong
*/
void handle_ping(const std::string& payload);
protected:
/**
* @brief Connect to websocket server
*/
virtual void connect() override;
/**
* @brief Get websocket state
* @return websocket state
*/
[[nodiscard]] ws_state get_state() const;
/**
* @brief If true the connection timed out while waiting,
* when waiting for SSL negotiation, TCP connect(), or HTTP.
*/
bool timed_out;
/**
* @brief Time at which the connection should be abandoned,
* if we are still connecting or negotiating with a HTTP server
*/
time_t timeout;
public:
/**
* @brief Connect to a specific websocket server.
* @param creator Creating cluster
* @param hostname Hostname to connect to
* @param port Port to connect to
* @param urlpath The URL path components of the HTTP request to send
* @param opcode The encoding type to use, either OP_BINARY or OP_TEXT
* @note This just indicates the default for frames sent. Certain sockets,
* such as voice websockets, may send a combination of OP_TEXT and OP_BINARY
* frames, whereas shard websockets will only ever send OP_BINARY for ETF and
* OP_TEXT for JSON.
*/
websocket_client(cluster* creator, const std::string& hostname, const std::string& port = "443", const std::string& urlpath = "", ws_opcode opcode = OP_BINARY);
/**
* @brief Destroy the websocket client object
*/
virtual ~websocket_client() = default;
/**
* @brief Write to websocket. Encapsulates data in frames if the status is CONNECTED.
* @param data The data to send.
* @param _opcode The opcode of the data to send, either binary or text. The default
* is to use the socket's opcode as set in the constructor.
*/
virtual void write(const std::string_view data, ws_opcode _opcode = OP_AUTO);
/**
* @brief Processes incoming frames from the SSL socket input buffer.
* @param buffer The buffer contents. Can modify this value removing the head elements when processed.
*/
virtual bool handle_buffer(std::string& buffer) override;
/**
* @brief Close websocket
*/
virtual void close() override;
/**
* @brief Receives raw frame content only without headers
*
* @param buffer The buffer contents
* @param opcode Frame type, e.g. OP_TEXT, OP_BINARY
* @return True if the frame was successfully handled. False if no valid frame is in the buffer.
*/
virtual bool handle_frame(const std::string& buffer, ws_opcode opcode);
/**
* @brief Called upon error frame.
*
* @param errorcode The error code from the websocket server
*/
virtual void error(uint32_t errorcode);
/**
* @brief Fires every second from the underlying socket I/O loop, used for sending websocket pings
*/
virtual void one_second_timer() override;
/**
* @brief Send OP_CLOSE error code 1000 to the other side of the connection.
* This indicates graceful close.
* @note This informs Discord to invalidate the session, you cannot resume if you send this
*/
void send_close_packet();
/**
* @brief Called on HTTP socket closure
*/
virtual void on_disconnect();
};
}