forked from LDLDL/FileUploader
-
Notifications
You must be signed in to change notification settings - Fork 0
/
protocol.h
390 lines (336 loc) · 13.6 KB
/
protocol.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
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
//
// Created by TYTY on 2019-05-07 007.
//
#ifndef FILE_TRANSFER_PROTOCOL_H
#define FILE_TRANSFER_PROTOCOL_H
#ifdef WIN32
#include <winsock2.h>
#endif
#include "./third_party/easyloggingpp/src/easylogging++.h"
#include <functional>
#include <stdexcept>
#include <exception>
#include <charconv>
#include <iomanip>
#include <boost/random/random_device.hpp>
#include <boost/random/uniform_int_distribution.hpp>
#include <boost/io/ios_state.hpp>
#include "encrypt.h"
#define MAGIC_HEADER "TY"
#define MAGIC_HEADER_TRANSFER "YT"
#define VERSION "\x01\x01\x01\x01"
/// \file protocol.h
/// \brief Header for the implement of the nultithread file transfer protocol
/// \note All things are in `file` namespace
/** PACKET STRUCT OF THE PROTOCOL
* Data packet struct
* Client: Server Hello
* | MAGIC_HEADER 2 | LENGTH 8 | [ Encrypted [ MAGIC_HEADER 2 | VERSION 4] ]
*
* Server: Client Hello
* - Check is our header -> Close Connection
* - Try decrypt with our key ->
* | MAGIC_HEADER 2 | LENGTH 8 | 1 1
* - Check version compability ->
* | MAGIC_HEADER 2 | LENGTH 8 | 2 1 | [ Encrypted VERSION 4 ]
* ( If all passed )
* | MAGIC_HEADER 2 | LENGTH 8 | 0 1 | [ Encrypted SESSION 32 ]
* Where SESSION is a random 32 bytes data for further file transfer
*
* Client: File Negotiation
* | MAGIC_HEADER 2 | LENGTH 8 |
* [ Encrypted [ SESSION 32 | PIECE_SIZE 4 | FILE_LENGTH 8 | FILE_PATH 256 ] ]
* File with too long path can not be upload.
*
* Server: File Negotiation
* Can't open file for write
* | MAGIC_HEADER 2 | LENGTH 8 | [ Encrypted [ SESSION 32 | 1 1 ]
* OK. Wait for data
* | MAGIC_HEADER 2 | LENGTH 8 | [ Encrypted [ SESSION 32 | 0 1 ]
*
* Client: Start Transfering file
* Start forking threads:
* | MAGIC_HEADER_TRANSFER 2 | LENGTH 8 |
* [ Encrypted [ SESSION 32 | FILE_PIECE_ORDER 8 | FILE_PIECE PIECE_SIZE ] ]
*
*/
namespace protocol {
#ifdef WIN32
/// \brief WSAStartup process. Only need on windows.
/// \return status of startup process. 0 means ok.
inline int init_environment() {
WSADATA wsaData;
WORD sockVersion = MAKEWORD(2, 2);
if (WSAStartup(sockVersion, &wsaData) != 0) { return -1; }
return 0;
}
/// \brief WSACleanup process. Only need on windows.
inline void clear_environment() {
WSACleanup();
}
#endif
typedef unsigned char byte;
using std::string;
struct NotOurMsg : public std::runtime_error {
NotOurMsg(std::string const &message)
: std::runtime_error("Received data but not our msg: " + message) {}
NotOurMsg()
: std::runtime_error("Received data but not our msg.") {}
};
struct OtherSideError : public std::runtime_error {
OtherSideError(std::string const &message)
: std::runtime_error("The other side met an error." + message) {}
OtherSideError()
: std::runtime_error("The other side met an error.") {}
};
/// \brief class to generate session id.
/// \detail session is a 32 bytes long string generated by server
/// randomly during negotiation. It is used to identify which
/// file the new connected file transfer thread belongs to,
/// and attach the right file pointer to the thread handle object.
/// \datamember boost::random::random_device rng
/// random generator
/// \datamember boost::random::uniform_int_distribution<> index_dist
/// generator distribution
class Randomsession {
private:
boost::random::random_device rng;
boost::random::uniform_int_distribution<> index_dist;
public:
/// \brief constructor
Randomsession() {
index_dist = boost::random::uniform_int_distribution<>(0, 255);
}
/// \brief session generator
/// \param length length of the session to generate
string session(int length);
};
/// \brief @debug show raw value of the given data.
/// \param str data to show
/// \param length length of the data. Not required for string as it
/// holds size info.
void _debug_value(const string &str);
void _debug_value(const char *str, int length);
/// \brief @deprecated determine if the msg is from this program
/// \param msg received message
/// \return true for valid message, false for invalid message
bool is_ourmsg(const string &msg);
bool is_ourmsg_tansfer(const string &msg);
/// \brief read the message
/// \param read the function to call to get message read from socket.
/// read should receive an int as input, and return the string
/// with the length of given int.
/// \return the body data of the message (without unencrypted header
/// and length info)
/// \throw NotOurMsg - indicate that the connection is not from this
/// program and should be reset immediately
/// \note transfer connection and handshake connection uses different
/// header to identify each other, so there are another function
/// with transfer variant to handle transfer message.
/// all function with _transfer is of this reason.
string read_msg(std::function<string(int)> &read);
string read_msg_transfer(std::function<string(int)> &read);
/// \brief read the message length
/// \param read the function to call to get message read from socket.
/// read should receive an int as input, and return the string
/// with the length of given int.
/// \return the length of the body to be read.
/// \throw NotOurMsg - indicate that the connection is not from this
/// program and should be reset immediately
/// \note due to limitation of asynchorous programming, we can't straight
/// get the data right after we call the function. So this function will
/// to determine how many bytes the body have and read it.
uint32_t read_msg_len(std::function<string(int)> &read);
uint32_t read_msg_transfer_len(std::function<string(int)> &read);
/// \brief read the message without knowing the connection type
/// \param read the function to call to get message read from socket.
/// read should receive an int as input, and return the string
/// with the length of given int.
/// \param type the type of the incoming message.
/// 0 to negotiation message;
/// 1 to transfer message;
/// -1 to unknown message.
/// \return the body data of the message (without unencrypted header
/// and length info)
/// \note transfer connection and handshake connection uses different
/// header to identify each other.
string read_msg_guess(std::function<string(int)> &read, int &type);
/// \brief build raw message with header and length info
/// \param raw_msg encrypted headless message.
/// \return message with header and length info which can be send
/// to the other side.
/// \note this function will get the length info from the raw_msg
/// using .size() member function.
string build_msg(const string &raw_msg);
string build_msg_transfer(const string &raw_msg);
/// \brief @template convert number into fixed length string
/// \param value the number to be convert
/// \param digits the digit length of the output
/// \return constructed string
template<typename uintType>
std::string fixedLength(uintType value, int digits) {
std::stringstream stream;
stream << std::setfill('0') << std::setw(digits)
<< std::hex << value;
return stream.str();
}
using encrypt::AESEncrypter;
using encrypt::AESDecrypter;
/// \note all parameters in the functions below, except dec and enc
/// will be modified unless with const mark in type declaration
/// \brief @client build server-hello message
/// \param enc encrypter object
/// \return built encrypted raw server-hello message
string server_hello_build(
AESEncrypter enc
);
/// \brief @server verify server-hello message
/// \param dec decrypter object
/// \param msg encrypted raw message received
/// \return status code
/// 0: good server-hello data
/// 1: server-hello with wrong header. This often indicate the
/// client using another key
/// 2: clitent version is not the same as server. This will cause
/// some problem
int server_hello_verify(AESDecrypter dec,
const string &msg
);
/// \brief @server build client-hello message
/// \param enc encrypter object
/// \param status status code from server_hello_verify
/// \param session generated session string
/// \return built encrypted raw server-hello message
/// \throw std::invalid_argument when undefined status given
string client_hello_build(AESEncrypter enc,
const int &status,
const string &session
);
/// \brief @client verify client-hello message
/// \param dec decrypter object
/// \param msg encrypted raw message received
/// \return status code
/// 0: good server-hello data
/// 1: server-hello with wrong header. This often indicate the
/// client using another key
/// 2: clitent version is not the same as server. This will cause
/// some problem
/// \throw std::invalid_argument when undefined status received
/// \throw std::runtime_error when message received is too short
int client_hello_verify(AESDecrypter dec,
const string &msg,
string &session
);
/// \brief @client build negotiate file info
/// \param enc encrypter object
/// \param session generated session string
/// \param piece_size divided piece size which will be send each time
/// \param file_length length of the file to be transfered
/// \param file_path name (and place) of the file to be transfered
/// \return built encrypted raw file negotiation message
string file_negotiation_build(AESEncrypter enc,
const string &session,
const uint32_t &piece_size,
const uint64_t &file_length,
const string &file_path
);
/// \brief @server read negotiate file info
/// \param dec decrypter object
/// \param msg encrypted raw message received
/// \param session generated session string
/// \param piece_size divided piece size which will be send each time
/// \param file_length length of the file to be transfered
/// \param file_path name (and place) of the file to be transfered
/// \return verify status
/// 0: ok
/// 1: session conflict
/// \throw std::runtime_error when message received is too short
int file_negotiation_verify(AESDecrypter dec,
const string &msg,
const string &session,
uint32_t &piece_size,
uint64_t &file_length,
string &file_path
);
/// \brief @server reply file open result
/// \param enc encrypter object
/// \param session generated session string
/// \param status file open status
/// 0: no error
/// 1: file open failed
/// \return built encrypted raw file negotiation message
string file_negotiation_reply(AESEncrypter enc,
const string &session,
const int &status
);
/// \brief @client finish file negotiation
/// \param dec decrypter object
/// \param msg encrypted raw message received
/// \param session generated session string
/// \return status code from server
/// \throw std::runtime_error if session conflict or message too short
int file_negotiation_finish(AESDecrypter dec,
const string &msg,
const string &session
);
/// \brief @client transfer connection init
/// \param enc encrypter object
/// \param session generated session string
/// \return built encrypted raw file transfer message
string file_transfer_init(AESEncrypter enc,
const string &session
);
/// \brief @server read transfer connection init
/// \param dec decrypter object
/// \param msg encrypted raw message received
/// \return session of connected transfer thread
string file_transfer_init_read(AESDecrypter dec,
const string &msg
);
/// \brief @server @unused reply the transfer info
string file_transfer_init_reply(AESEncrypter enc,
const int &status
);
/// \brief @client @unused confirm the transfer info
int file_transfer_init_confirm(AESDecrypter dec,
const string &msg
);
/// \brief @client build transfer message with payload
/// \param enc encrypter object
/// \param session generated session string
/// \param order order of the sending piece
/// \param size size of the sending piece
/// \param piece file piece data
/// \return built encrypted raw file transfer message
string file_transfer_build(AESEncrypter enc,
const string &session,
const uint32_t &order,
const uint32_t &size,
const string &piece
);
/// \brief @server read transfer connection init
/// \param dec decrypter object
/// \param msg encrypted raw message received
/// \param session of connected transfer thread
/// \param order order of the sending piece
/// \param size size of the sending piece
/// \param piece file piece data
/// \return always 0
/// \throw std::runtime_error when message received is too short
int file_transfer_read(AESDecrypter dec,
const string &msg,
const string &session,
uint32_t &order,
uint32_t &size,
string &piece
);
/// \brief @server @unused reply client the data has received
string file_transfer_receive(AESEncrypter enc,
const string &session,
const int &status);
/// \brief @client @unused check the data has received
int file_transfer_confirm(AESDecrypter dec,
const string &msg,
const string &session);
}
#endif //FILE_TRANSFER_PROTOCOL_H