-
Notifications
You must be signed in to change notification settings - Fork 284
Expand file tree
/
Copy pathutils.hpp
More file actions
286 lines (226 loc) · 9.07 KB
/
utils.hpp
File metadata and controls
286 lines (226 loc) · 9.07 KB
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
#include <sys/socket.h>
#include <sys/epoll.h>
int create_and_bind_udp(uint16_t port);
int create_bind_and_listen_tcp(uint16_t port);
void print_addr(const sockaddr* peer_addr, socklen_t peer_addr_len);
// Base class for handling notifications from epoll.
class EPollHandlerInterface {
protected:
const int sock_;
EPollHandlerInterface(const int sock) : sock_(sock) {}
int epoll_add(const int epollfd);
public:
virtual ~EPollHandlerInterface();
// If the result of `process_read` is negative, it means that the socket
// should be closed and the epoll handler deleted.
virtual int process_read(const epoll_event* event) = 0;
// If the result of `process_write` is negative, it means that the socket
// should be closed and the epoll handler deleted.
virtual int process_write(const epoll_event* event) = 0;
};
// Rather than passing the UDP socket's file descriptor to
// RecvHandlerUDP, we pass this abstract interface so that we can
// restrict what the RecvHandlerUDP is able to do with the socket.
class SocketHandlerUDP {
public:
virtual ~SocketHandlerUDP() {}
// Send a reply. (Wrapper around sendto().)
virtual ssize_t replyto(
const char* buf, size_t buflen,
const sockaddr *dest_addr, socklen_t addrlen
) = 0;
};
class RecvHandlerUDP {
public:
virtual ~RecvHandlerUDP() {};
// This method is called by `EpollRecvHandlerUDP::process_read`
// when a message is received. Return 0 to keep receiving messages,
// or a negative number to close the socket and delete the epoll
// handler.
virtual int receive(
const uint8_t* buf, ssize_t len,
SocketHandlerUDP& sock,
const sockaddr* peer_addr, socklen_t peer_addr_len
) = 0;
};
// Rather than passing the TCP socket's file descriptor to
// RecvHandlerTCP, we pass this abstract interface so that we can
// restrict what the RecvHandlerTCP is able to do with the socket.
class SocketHandlerTCP {
public:
virtual ~SocketHandlerTCP() {}
// Send a reply. (Wrapper around send().)
virtual ssize_t reply(const char* buf, size_t buflen) = 0;
};
class RecvHandlerTCP {
public:
virtual ~RecvHandlerTCP() {};
// This is method is called immediately after the connection is
// accepted. The return value is the number of bytes expected on the next
// message. But if the return value is negative then it means that the
// socket should be closed and the epoll handler deleted.
virtual ssize_t accept(SocketHandlerTCP& sock) = 0;
// `EpollRecvHandlerTCP` calls this method when it has received the
// number of bytes that we asked for. Unlike the corresponding method in
// `RecvHandlerUDP`, this method does not need a `len` parameter, because
// we will get the exact number of bytes that we asked for. (We always
// have to tell `EpollRecvHandlerTCP` how big we are expecting the next
// message to be.) The return value is the number of bytes expected on
// the next message. But if the return value is negative then it means
// that the socket should be closed and the epoll handler deleted.
virtual ssize_t receive(SocketHandlerTCP& sock, const uint8_t* buf) = 0;
// The socket disconnected before we were finished sending/receiving.
virtual void disconnect() = 0;
};
// Factory class for constructing instances of RecvHandlerTCP.
class BuildRecvHandlerTCP {
public:
virtual ~BuildRecvHandlerTCP() {};
virtual RecvHandlerTCP* build(
sockaddr* peer_addr, socklen_t peer_addr_len
) = 0;
};
// Implementation of EPollHandlerInterface which reads data from
// a file descriptor. The data that was read is passed to the `handler_`
// field for further processing.
class EpollRecvHandlerUDP :
public EPollHandlerInterface,
virtual private SocketHandlerUDP
{
// Handler pointer is owned by this class.
RecvHandlerUDP* handler_;
// Takes ownership of `handler`.
EpollRecvHandlerUDP(const int sock, RecvHandlerUDP* handler)
: EPollHandlerInterface(sock)
, handler_(handler)
{}
// Implements SocketHandlerUDP interface.
ssize_t replyto(
const char* buf, size_t buflen,
const sockaddr *dest_addr, socklen_t addrlen
) override;
public:
// Factory method. Constructs the class and registers the file descriptor
// with epoll. Returns zero on success, otherwise negative.
static int build(
const int epollfd, const int sock, RecvHandlerUDP* handler
);
virtual ~EpollRecvHandlerUDP();
int process_read(const epoll_event *) override;
int process_write(const epoll_event *) override {
// TODO: implement buffering for UDP sends, similar
// to how it is done in EpollRecvHandlerTCP.
return 0;
}
};
// Implementation of EPollHandlerInterface which reads data from
// a file descriptor. The data that was read is passed to the `handler_`
// field for further processing.
class EpollRecvHandlerTCP :
public EPollHandlerInterface,
virtual private SocketHandlerTCP
{
// Handler pointer is owned by this class.
RecvHandlerTCP* handler_;
uint8_t* recvbuf_; // Buffer for incoming data.
size_t received_; // Number of bytes received so far. (Stored in `recvbuf_`.)
size_t remaining_; // Number of bytes we are still waiting for.
uint8_t* sendbuf_; // Buffer for outgoing data.
size_t sendbufsize_; // Total size of sendbuf_
size_t sendbufpos_; // Current position in sendbuf_
// Takes ownership of `handler`.
EpollRecvHandlerTCP(const int sock, RecvHandlerTCP* handler);
// This method is called after the class has been constructed and
// registered with epoll. Returns zero on success, otherwise negative.
int accept();
// Implements SocketHandlerTCP interface.
ssize_t reply(const char* buf, size_t buflen) override;
public:
virtual ~EpollRecvHandlerTCP();
// Factory method. Constructs the class and registers the file descriptor
// with epoll. Returns zero on success, otherwise negative.
static int build(
const int epollfd, const int sock, RecvHandlerTCP* handler
);
int process_read(const epoll_event *) override;
int process_write(const epoll_event *) override;
};
// Implementation of EPollHandlerInterface which handles TCP connection
// request.
class EpollTcpConnectHandler : public EPollHandlerInterface {
const int epollfd_;
// Factory pointer is owned by this class.
BuildRecvHandlerTCP* factory_;
// Takes ownership of `factory`.
EpollTcpConnectHandler(
const int epollfd, const int sock, BuildRecvHandlerTCP* factory
);
public:
// Factory method. Constructs the class and registers the file descriptor
// with epoll. Returns zero on success, otherwise negative.
static int build(
const int epollfd, const int sock, BuildRecvHandlerTCP* factory
);
virtual ~EpollTcpConnectHandler();
int process_read(const epoll_event *) override;
int process_write(const epoll_event *) override;
};
// If a TCP conversation is always exactly the same every time, then we can
// make it run faster by caching it. We use this class to cache the data
// from the first run, so that we can just play it back on subsequent
// runs. The proxy class TCP_Cache_Record is used to initialize the cache
// and TCP_Cache_Playback is used for playback.
class TCP_Cache {
char* sendbuf_;
size_t total_send_;
size_t total_receive_;
public:
TCP_Cache();
~TCP_Cache();
bool isInitialized() const;
// Called by EpsonHandlerCacheProxyTCP after it has collected
// the relevant data.
void init(char* sendbuf, size_t total_send, size_t total_receive);
ssize_t accept(SocketHandlerTCP& sock);
};
// Implementation of RecvHandlerTCP which just runs a previously cached
// conversation: it sends the same sequence of bytes that were sent before
// and doesn't bother to read the incoming messages. It just waits until is
// has received the expected number of bytes and then closes the
// connection.
class TCP_Cache_Playback : public RecvHandlerTCP {
TCP_Cache& cache_;
public:
explicit TCP_Cache_Playback(TCP_Cache& cache);
ssize_t accept(SocketHandlerTCP& sock) override;
ssize_t receive(SocketHandlerTCP&, const uint8_t*) override {
return 0;
}
void disconnect() override {}
};
// This proxy class is used to initialize a TCP_Cache. It intercepts all
// the data sent and received by `handler_`, which is the RecvHandlerTCP
// which we are proxying.
class TCP_Cache_Record : public RecvHandlerTCP {
// The object that we are proxying. We own this pointer.
RecvHandlerTCP* handler_;
// If caching is successful, then we call the `init()`
// method on this object.
TCP_Cache& cache_;
char* sendbuf_;
size_t total_send_;
size_t total_receive_;
class SocketHandlerProxy;
public:
TCP_Cache_Record(RecvHandlerTCP* handler, TCP_Cache& cache);
~TCP_Cache_Record();
ssize_t accept(SocketHandlerTCP& sock) override;
ssize_t receive(SocketHandlerTCP& sock, const uint8_t* buf) override;
void disconnect() override {}
};
// This function runs an infinite loop, listening for epoll events. You
// need to use `epoll_create1` to initialize epoll before calling this
// function. You also need to open the relevant sockets and register them
// with epoll before calling this function, for example by calling
// `EpollTcpConnectHandler::build()`.
[[ noreturn ]] void epoll_main_loop(const int epollfd);