iodine 0.1.21 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of iodine might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.gitignore +3 -2
- data/.travis.yml +23 -2
- data/CHANGELOG.md +9 -2
- data/README.md +232 -179
- data/Rakefile +13 -1
- data/bin/config.ru +63 -0
- data/bin/console +6 -0
- data/bin/echo +42 -32
- data/bin/http-hello +62 -0
- data/bin/http-playground +124 -0
- data/bin/playground +62 -0
- data/bin/poc/Gemfile.lock +23 -0
- data/bin/poc/README.md +37 -0
- data/bin/poc/config.ru +66 -0
- data/bin/poc/gemfile +1 -0
- data/bin/poc/www/index.html +57 -0
- data/bin/raw-rbhttp +35 -0
- data/bin/raw_broadcast +66 -0
- data/bin/test_with_faye +40 -0
- data/bin/ws-broadcast +108 -0
- data/bin/ws-echo +108 -0
- data/exe/iodine +59 -0
- data/ext/iodine/base64.c +264 -0
- data/ext/iodine/base64.h +72 -0
- data/ext/iodine/bscrypt-common.h +109 -0
- data/ext/iodine/bscrypt.h +49 -0
- data/ext/iodine/extconf.rb +41 -0
- data/ext/iodine/hex.c +123 -0
- data/ext/iodine/hex.h +70 -0
- data/ext/iodine/http.c +200 -0
- data/ext/iodine/http.h +128 -0
- data/ext/iodine/http1.c +402 -0
- data/ext/iodine/http1.h +56 -0
- data/ext/iodine/http1_simple_parser.c +473 -0
- data/ext/iodine/http1_simple_parser.h +59 -0
- data/ext/iodine/http_request.h +128 -0
- data/ext/iodine/http_response.c +1606 -0
- data/ext/iodine/http_response.h +393 -0
- data/ext/iodine/http_response_http1.h +374 -0
- data/ext/iodine/iodine_core.c +641 -0
- data/ext/iodine/iodine_core.h +70 -0
- data/ext/iodine/iodine_http.c +615 -0
- data/ext/iodine/iodine_http.h +19 -0
- data/ext/iodine/iodine_websocket.c +430 -0
- data/ext/iodine/iodine_websocket.h +21 -0
- data/ext/iodine/libasync.c +552 -0
- data/ext/iodine/libasync.h +117 -0
- data/ext/iodine/libreact.c +347 -0
- data/ext/iodine/libreact.h +244 -0
- data/ext/iodine/libserver.c +912 -0
- data/ext/iodine/libserver.h +435 -0
- data/ext/iodine/libsock.c +950 -0
- data/ext/iodine/libsock.h +478 -0
- data/ext/iodine/misc.c +181 -0
- data/ext/iodine/misc.h +76 -0
- data/ext/iodine/random.c +193 -0
- data/ext/iodine/random.h +48 -0
- data/ext/iodine/rb-call.c +127 -0
- data/ext/iodine/rb-call.h +60 -0
- data/ext/iodine/rb-libasync.h +79 -0
- data/ext/iodine/rb-rack-io.c +389 -0
- data/ext/iodine/rb-rack-io.h +17 -0
- data/ext/iodine/rb-registry.c +213 -0
- data/ext/iodine/rb-registry.h +33 -0
- data/ext/iodine/sha1.c +359 -0
- data/ext/iodine/sha1.h +85 -0
- data/ext/iodine/sha2.c +825 -0
- data/ext/iodine/sha2.h +138 -0
- data/ext/iodine/siphash.c +136 -0
- data/ext/iodine/siphash.h +15 -0
- data/ext/iodine/spnlock.h +235 -0
- data/ext/iodine/websockets.c +696 -0
- data/ext/iodine/websockets.h +120 -0
- data/ext/iodine/xor-crypt.c +189 -0
- data/ext/iodine/xor-crypt.h +107 -0
- data/iodine.gemspec +25 -18
- data/lib/iodine.rb +57 -58
- data/lib/iodine/http.rb +0 -189
- data/lib/iodine/protocol.rb +36 -245
- data/lib/iodine/version.rb +1 -1
- data/lib/rack/handler/iodine.rb +145 -2
- metadata +115 -37
- data/bin/core_http_test +0 -51
- data/bin/em playground +0 -56
- data/bin/hello_world +0 -75
- data/bin/setup +0 -7
- data/lib/iodine/client.rb +0 -5
- data/lib/iodine/core.rb +0 -102
- data/lib/iodine/core_init.rb +0 -143
- data/lib/iodine/http/hpack.rb +0 -553
- data/lib/iodine/http/http1.rb +0 -251
- data/lib/iodine/http/http2.rb +0 -507
- data/lib/iodine/http/rack_support.rb +0 -108
- data/lib/iodine/http/request.rb +0 -462
- data/lib/iodine/http/response.rb +0 -474
- data/lib/iodine/http/session.rb +0 -143
- data/lib/iodine/http/websocket_client.rb +0 -335
- data/lib/iodine/http/websocket_handler.rb +0 -101
- data/lib/iodine/http/websockets.rb +0 -336
- data/lib/iodine/io.rb +0 -56
- data/lib/iodine/logging.rb +0 -46
- data/lib/iodine/settings.rb +0 -158
- data/lib/iodine/ssl_connector.rb +0 -48
- data/lib/iodine/timers.rb +0 -95
@@ -0,0 +1,435 @@
|
|
1
|
+
/*
|
2
|
+
copyright: Boaz segev, 2016
|
3
|
+
license: MIT
|
4
|
+
|
5
|
+
Feel free to copy, use and enjoy according to the license provided.
|
6
|
+
*/
|
7
|
+
#ifndef LIB_SERVER
|
8
|
+
#define LIB_SERVER "0.4.0"
|
9
|
+
|
10
|
+
#ifndef _GNU_SOURCE
|
11
|
+
#define _GNU_SOURCE
|
12
|
+
#endif
|
13
|
+
|
14
|
+
#include <stdint.h>
|
15
|
+
#include <unistd.h>
|
16
|
+
#include <stdio.h>
|
17
|
+
#include <stdlib.h>
|
18
|
+
|
19
|
+
/* lib server is based off and requires the following libraries: */
|
20
|
+
#include "libreact.h"
|
21
|
+
#include "libasync.h"
|
22
|
+
#include "libsock.h"
|
23
|
+
#include "spnlock.h"
|
24
|
+
|
25
|
+
#ifndef SERVER_PRINT_STATE
|
26
|
+
/**
|
27
|
+
When SERVER_PRINT_STATE is set to 1, the server API will print out common
|
28
|
+
messages regarding the server state (start / finish / listen messages).
|
29
|
+
*/
|
30
|
+
#define SERVER_PRINT_STATE 1
|
31
|
+
#endif
|
32
|
+
|
33
|
+
#if LIB_ASYNC_VERSION_MINOR != 4 || LIB_REACT_VERSION_MINOR != 3 || \
|
34
|
+
LIB_SOCK_VERSION_MINOR != 2
|
35
|
+
#warning Lib-Server dependency versions are not in sync. Please review API versions.
|
36
|
+
#endif
|
37
|
+
|
38
|
+
#ifndef __unused
|
39
|
+
#define __unused __attribute__((unused))
|
40
|
+
#endif
|
41
|
+
|
42
|
+
/** \file
|
43
|
+
## LibServer - a dynamic protocol network services library
|
44
|
+
|
45
|
+
* Library (not a framework): meanning, closer to the metal, abstracting only
|
46
|
+
what is required for API simplicity, error protection and performance.
|
47
|
+
|
48
|
+
* Dynamic Protocol: meanning a service can change protocols mid-stream. Example
|
49
|
+
usecase: Websockets (HTTP Upgrade).
|
50
|
+
|
51
|
+
* Network services: meanning multiple listenning network ports, each with it's
|
52
|
+
own logic.
|
53
|
+
|
54
|
+
`libserver` utilizes `libreact`, `libasync` and `libsock` to create a simple
|
55
|
+
API wrapper around these minimalistic libraries and managing the "glue" that
|
56
|
+
makes them work together.
|
57
|
+
|
58
|
+
It's simple and it's awesome :-)
|
59
|
+
|
60
|
+
Here's a simple example that emulates an HTTP hello world server. This example
|
61
|
+
will count the number of client and messages using the server and demonstrates
|
62
|
+
some recommended implementation techniques, such as protocol inheritance.
|
63
|
+
|
64
|
+
#include "libserver.h" // the reactor library
|
65
|
+
#include <stdatomic.h> // We'll use atomics to safely count client events
|
66
|
+
|
67
|
+
#define THREADS 4
|
68
|
+
#define PROCESSES 4
|
69
|
+
|
70
|
+
void on_close(protocol_s* protocol);
|
71
|
+
void on_shutdown(intptr_t sock, protocol_s* protocol);
|
72
|
+
void on_data(intptr_t sock, protocol_s* protocol);
|
73
|
+
protocol_s* demo_on_open(intptr_t fd, void* udata);
|
74
|
+
|
75
|
+
// Our demo protocol object uses "C" style inheritance,
|
76
|
+
// where pointers location are the same so that a simple cast
|
77
|
+
// from one object to the other, allows us to access more data.
|
78
|
+
struct DemoProtocol {
|
79
|
+
protocol_s protocol; // must be first for C style inheritance
|
80
|
+
size_t _Atomic opened;
|
81
|
+
size_t _Atomic closed;
|
82
|
+
size_t _Atomic shutdown;
|
83
|
+
size_t _Atomic messages;
|
84
|
+
} demo_protocol = {
|
85
|
+
.protocol.service = "Demo", // This allows us to ID the protocol type.
|
86
|
+
.protocol.on_data = on_data,
|
87
|
+
.protocol.on_shutdown = on_shutdown,
|
88
|
+
.protocol.on_close = on_close,
|
89
|
+
};
|
90
|
+
|
91
|
+
// type-casting helper
|
92
|
+
#define pr2demo(protocol) ((struct DemoProtocol*)(protocol))
|
93
|
+
|
94
|
+
// A simple Hello World HTTP response emulation
|
95
|
+
char hello_message[] =
|
96
|
+
"HTTP/1.1 200 OK\r\n"
|
97
|
+
"Content-Length: 12\r\n"
|
98
|
+
"Connection: keep-alive\r\n"
|
99
|
+
"Keep-Alive: 1;timeout=5\r\n"
|
100
|
+
"\r\n"
|
101
|
+
"Hello World!";
|
102
|
+
|
103
|
+
protocol_s* on_open(intptr_t fd, void* udata) {
|
104
|
+
// Count events
|
105
|
+
atomic_fetch_add(&demo_protocol.opened, 1);
|
106
|
+
// Set timeout
|
107
|
+
server_set_timeout(fd, 5);
|
108
|
+
// * return pointer to the protocol.
|
109
|
+
// * This is the same as `(protocol_s *)&demo_protocol`
|
110
|
+
return &demo_protocol.protocol;
|
111
|
+
}
|
112
|
+
|
113
|
+
void on_data(intptr_t sock, protocol_s* protocol) {
|
114
|
+
// read data
|
115
|
+
char data[1024];
|
116
|
+
if (sock_read(sock, data, 1024)) {
|
117
|
+
// Count event
|
118
|
+
atomic_fetch_add(&pr2demo(protocol)->messages, 1);
|
119
|
+
// send reply
|
120
|
+
sock_write(sock, hello_message, sizeof(hello_message) - 1);
|
121
|
+
}
|
122
|
+
}
|
123
|
+
|
124
|
+
void on_close(protocol_s* protocol) {
|
125
|
+
// Count event
|
126
|
+
atomic_fetch_add(&pr2demo(protocol)->closed, 1);
|
127
|
+
}
|
128
|
+
|
129
|
+
void on_shutdown(intptr_t sock, protocol_s* protocol) {
|
130
|
+
// Count event
|
131
|
+
atomic_fetch_add(&pr2demo(protocol)->shutdown, 1);
|
132
|
+
}
|
133
|
+
|
134
|
+
void on_idle(void) {
|
135
|
+
// idle event example
|
136
|
+
fprintf(stderr, "Server was idle, with %lu connections.\n",
|
137
|
+
server_count(NULL));
|
138
|
+
}
|
139
|
+
|
140
|
+
int main() {
|
141
|
+
// this isn't required normally,
|
142
|
+
// but some systems require atomics to be initialized.
|
143
|
+
atomic_store(&demo_protocol.opened, 0);
|
144
|
+
atomic_store(&demo_protocol.closed, 0);
|
145
|
+
atomic_store(&demo_protocol.shutdown, 0);
|
146
|
+
atomic_store(&demo_protocol.messages, 0);
|
147
|
+
// run the server
|
148
|
+
server_listen(.port = "3000", .on_open = on_open, .udata = NULL);
|
149
|
+
server_run(.threads = THREADS, .processes = PROCESSES,
|
150
|
+
.on_idle = on_idle);
|
151
|
+
// print results
|
152
|
+
fprintf(stderr,
|
153
|
+
"** Server returned\n"
|
154
|
+
"** %lu clients connected.\n"
|
155
|
+
"** %lu clients disconnected.\n"
|
156
|
+
"** %lu clients were connected when shutdown was called.\n"
|
157
|
+
"** %lu messages were sent\n",
|
158
|
+
atomic_load(&demo_protocol.opened),
|
159
|
+
atomic_load(&demo_protocol.closed),
|
160
|
+
atomic_load(&demo_protocol.shutdown),
|
161
|
+
atomic_load(&demo_protocol.messages));
|
162
|
+
}
|
163
|
+
|
164
|
+
|
165
|
+
*/
|
166
|
+
|
167
|
+
/**************************************************************************/ /**
|
168
|
+
* General info
|
169
|
+
*/
|
170
|
+
|
171
|
+
/* The following types are defined for the userspace of this library: */
|
172
|
+
|
173
|
+
// struct Server; /** used internally. no public data exposed */
|
174
|
+
struct ServerSettings; /** sets up the server's behavior */
|
175
|
+
struct ServerServiceSettings; /** sets up a listenning socket's behavior */
|
176
|
+
typedef struct Protocol protocol_s; /** controls connection events */
|
177
|
+
|
178
|
+
/**************************************************************************/ /**
|
179
|
+
* The Protocol
|
180
|
+
|
181
|
+
The Protocol struct defines the callbacks used for the connection and sets the
|
182
|
+
behaviour for the connection's protocol.
|
183
|
+
|
184
|
+
All the callbacks recieve a unique connection ID (a semi UUID) that can be
|
185
|
+
converted to the original file descriptor if in need.
|
186
|
+
|
187
|
+
This allows the Server API to prevent old connection handles from sending data
|
188
|
+
to new connections after a file descriptor is "recycled" by the OS.
|
189
|
+
*/
|
190
|
+
struct Protocol {
|
191
|
+
/**
|
192
|
+
* A string to identify the protocol's service (i.e. "http").
|
193
|
+
*
|
194
|
+
* The string should be a global constant, only a pointer comparison will be
|
195
|
+
* made (not `strcmp`).
|
196
|
+
*/
|
197
|
+
const char* service;
|
198
|
+
/** called when a data is available, but will not run concurrently */
|
199
|
+
void (*on_data)(intptr_t fduuid, protocol_s* protocol);
|
200
|
+
/** called when the socket is ready to be written to. */
|
201
|
+
void (*on_ready)(intptr_t fduuid, protocol_s* protocol);
|
202
|
+
/** called when the server is shutting down,
|
203
|
+
* but before closing the connection. */
|
204
|
+
void (*on_shutdown)(intptr_t fduuid, protocol_s* protocol);
|
205
|
+
/** called when the connection was closed, but will not run concurrently */
|
206
|
+
void (*on_close)(protocol_s* protocol);
|
207
|
+
/** called when a connection's timeout was reached */
|
208
|
+
void (*ping)(intptr_t fduuid, protocol_s* protocol);
|
209
|
+
/** private metadata used for object protection */
|
210
|
+
spn_lock_i callback_lock;
|
211
|
+
};
|
212
|
+
|
213
|
+
/**************************************************************************/ /**
|
214
|
+
* The Service Settings
|
215
|
+
|
216
|
+
These settings will be used to setup listenning sockets.
|
217
|
+
*/
|
218
|
+
struct ServerServiceSettings {
|
219
|
+
/** Called whenever a new connection is accepted. Should return a pointer to
|
220
|
+
* the connection's protocol. */
|
221
|
+
protocol_s* (*on_open)(intptr_t fduuid, void* udata);
|
222
|
+
/** The network service / port. Defaults to "3000". */
|
223
|
+
const char* port;
|
224
|
+
/** The socket binding address. Defaults to the recommended NULL. */
|
225
|
+
const char* address;
|
226
|
+
/** Opaque user data. */
|
227
|
+
void* udata;
|
228
|
+
/**
|
229
|
+
* Called when the server starts, allowing for further initialization, such as
|
230
|
+
* timed event scheduling.
|
231
|
+
*
|
232
|
+
* This will be called seperately for every process. */
|
233
|
+
void (*on_start)(void* udata);
|
234
|
+
/** called when the server is done, to clean up any leftovers. */
|
235
|
+
void (*on_finish)(void* udata);
|
236
|
+
};
|
237
|
+
|
238
|
+
/**************************************************************************/ /**
|
239
|
+
* The Server Settings
|
240
|
+
|
241
|
+
These settings will be used to setup server behaviour. missing settings will be
|
242
|
+
filled in with default values.
|
243
|
+
*/
|
244
|
+
struct ServerSettings {
|
245
|
+
/** called if the event loop in cycled with no pending events. */
|
246
|
+
void (*on_idle)(void);
|
247
|
+
/**
|
248
|
+
* Called when the server starts, allowing for further initialization, such as
|
249
|
+
* timed event scheduling.
|
250
|
+
*
|
251
|
+
* This will be called seperately for every process. */
|
252
|
+
void (*on_init)(void);
|
253
|
+
/** called when the server is done, to clean up any leftovers. */
|
254
|
+
void (*on_finish)(void);
|
255
|
+
/**
|
256
|
+
Sets the amount of threads to be created for the server's thread-pool.
|
257
|
+
Defaults to 1 - the reactor and all callbacks will work using a single working
|
258
|
+
thread, allowing for an evented single threaded design.
|
259
|
+
*/
|
260
|
+
size_t threads;
|
261
|
+
/** Sets the amount of processes to be used (processes will be forked).
|
262
|
+
Defaults to 1 working processes (no forking). */
|
263
|
+
size_t processes;
|
264
|
+
};
|
265
|
+
|
266
|
+
/* *****************************************************************************
|
267
|
+
* The Server API
|
268
|
+
* (and helper functions)
|
269
|
+
*/
|
270
|
+
|
271
|
+
/* *****************************************************************************
|
272
|
+
* Server actions
|
273
|
+
*/
|
274
|
+
|
275
|
+
/**
|
276
|
+
Listens to a server with any of the following server settings:
|
277
|
+
|
278
|
+
* `.threads` the number of threads to initiate in the server's thread pool.
|
279
|
+
|
280
|
+
* `.processes` the number of processes to use (a value of more then 1 will
|
281
|
+
initiate a `fork`).
|
282
|
+
|
283
|
+
* `.on_init` an on initialization callback (for every process forked).
|
284
|
+
`void (*callback)(void);``
|
285
|
+
|
286
|
+
* `.on_finish` a post run callback (for every process forked).
|
287
|
+
`void (*callback)(void);``
|
288
|
+
|
289
|
+
* `.on_idle` an idle server callback (for every process forked).
|
290
|
+
`void (*callback)(void);``
|
291
|
+
|
292
|
+
This method blocks the current thread until the server is stopped when a
|
293
|
+
SIGINT/SIGTERM is received.
|
294
|
+
|
295
|
+
To kill the server use the `kill` function with a SIGINT.
|
296
|
+
*/
|
297
|
+
int server_listen(struct ServerServiceSettings);
|
298
|
+
#define server_listen(...) \
|
299
|
+
server_listen((struct ServerServiceSettings){__VA_ARGS__})
|
300
|
+
/** runs the server, hanging the current process and thread. */
|
301
|
+
ssize_t server_run(struct ServerSettings);
|
302
|
+
#define server_run(...) server_run((struct ServerSettings){__VA_ARGS__})
|
303
|
+
/** Stops the server, shouldn't be called unless int's impossible to send an
|
304
|
+
* INTR signal. */
|
305
|
+
void server_stop(void);
|
306
|
+
/**
|
307
|
+
Returns the last time the server reviewed any pending IO events.
|
308
|
+
*/
|
309
|
+
time_t server_last_tick(void);
|
310
|
+
/****************************************************************************
|
311
|
+
* Socket actions
|
312
|
+
*/
|
313
|
+
|
314
|
+
/**
|
315
|
+
Gets the active protocol object for the requested file descriptor.
|
316
|
+
|
317
|
+
Returns NULL on error (i.e. connection closed), otherwise returns a `protocol_s`
|
318
|
+
pointer.
|
319
|
+
*/
|
320
|
+
protocol_s* server_get_protocol(intptr_t uuid);
|
321
|
+
/**
|
322
|
+
Sets a new active protocol object for the requested file descriptor.
|
323
|
+
|
324
|
+
This also schedules the old protocol's `on_close` callback to run, making sure
|
325
|
+
all resources are released.
|
326
|
+
|
327
|
+
Returns -1 on error (i.e. connection closed), otherwise returns 0.
|
328
|
+
*/
|
329
|
+
ssize_t server_switch_protocol(intptr_t fd, protocol_s* new_protocol);
|
330
|
+
/**
|
331
|
+
Sets a connection's timeout.
|
332
|
+
|
333
|
+
Returns -1 on error (i.e. connection closed), otherwise returns 0.
|
334
|
+
*/
|
335
|
+
void server_set_timeout(intptr_t uuid, uint8_t timeout);
|
336
|
+
|
337
|
+
/** Attaches an existing connection (fd) to the server's reactor and protocol
|
338
|
+
management system, so that the server can be used also to manage connection
|
339
|
+
based resources asynchronously (i.e. database resources etc').
|
340
|
+
|
341
|
+
On failure the fduuid_u.data.fd value will be -1.
|
342
|
+
*/
|
343
|
+
intptr_t server_attach(int fd, protocol_s* protocol);
|
344
|
+
/** Hijack a socket (file descriptor) from the server, clearing up it's
|
345
|
+
resources and calling the protocol's `on_close` callback (making sure allocated
|
346
|
+
resources are freed).
|
347
|
+
|
348
|
+
The control of the socket is totally relinquished.
|
349
|
+
|
350
|
+
This method will block until all the data in the buffer is sent before
|
351
|
+
releasing control of the socket.
|
352
|
+
|
353
|
+
The returned value is the fd for the socket, or -1 on error.
|
354
|
+
*/
|
355
|
+
int server_hijack(intptr_t uuid);
|
356
|
+
/** Counts the number of connections for the specified protocol (NULL = all
|
357
|
+
protocols). */
|
358
|
+
long server_count(char* service);
|
359
|
+
|
360
|
+
/****************************************************************************
|
361
|
+
* Read and Write
|
362
|
+
*
|
363
|
+
* Simpley use `libsock` API for read/write.
|
364
|
+
*/
|
365
|
+
|
366
|
+
/****************************************************************************
|
367
|
+
* Tasks + Async
|
368
|
+
*/
|
369
|
+
|
370
|
+
/**
|
371
|
+
Schedules a specific task to run asyncronously for each connection (except the
|
372
|
+
origin connection).
|
373
|
+
a NULL service identifier == all connections (all protocols).
|
374
|
+
|
375
|
+
The task is performed within each target connection's busy "lock", meanning no
|
376
|
+
two tasks (or `on_data` events) should be performed at the same time
|
377
|
+
(concurrency will be avoided within the context of each connection, except for
|
378
|
+
`on_shutdown`, `on_close` and `ping`).
|
379
|
+
|
380
|
+
The `on_finish` callback will be called once the task is finished and it will
|
381
|
+
receive the originating connection's UUID (could be 0). The originating
|
382
|
+
connection might have been closed by that time.
|
383
|
+
|
384
|
+
The `service` string (pointer) identifier MUST be a constant string object OR
|
385
|
+
a string that will persist until the `on_finish` callback is called. In other
|
386
|
+
words, either hardcode the string or use `malloc` to allocate it before
|
387
|
+
calling `each` and `free` to release the string from within the `on_finish`
|
388
|
+
callback.
|
389
|
+
|
390
|
+
It is recommended the `on_finish` callback is only used to perform any
|
391
|
+
resource cleanup necessary.
|
392
|
+
*/
|
393
|
+
void server_each(intptr_t origin_uuid,
|
394
|
+
const char* service,
|
395
|
+
void (*task)(intptr_t uuid, protocol_s* protocol, void* arg),
|
396
|
+
void* arg,
|
397
|
+
void (*on_finish)(intptr_t origin_uuid,
|
398
|
+
protocol_s* protocol,
|
399
|
+
void* arg));
|
400
|
+
/** Schedules a specific task to run asyncronously for a specific connection.
|
401
|
+
|
402
|
+
returns -1 on failure, 0 on success (success being scheduling the task).
|
403
|
+
|
404
|
+
If a connection was terminated before performing their sceduled tasks, the
|
405
|
+
`fallback` task will be performed instead.
|
406
|
+
|
407
|
+
It is recommended to perform any resource cleanup within the fallback function
|
408
|
+
and call the fallback function from within the main task, but other designes
|
409
|
+
are valid as well.
|
410
|
+
*/
|
411
|
+
void server_task(intptr_t uuid,
|
412
|
+
void (*task)(intptr_t uuid, protocol_s* protocol, void* arg),
|
413
|
+
void* arg,
|
414
|
+
void (*fallback)(intptr_t uuid, void* arg));
|
415
|
+
/** Creates a system timer (at the cost of 1 file descriptor) and pushes the
|
416
|
+
timer to the reactor. The task will repeat `repetitions` times. if
|
417
|
+
`repetitions` is set to 0, task will repeat forever. Returns -1 on error
|
418
|
+
or the new file descriptor on succeess.
|
419
|
+
*/
|
420
|
+
int server_run_every(size_t milliseconds,
|
421
|
+
size_t repetitions,
|
422
|
+
void (*task)(void*),
|
423
|
+
void* arg,
|
424
|
+
void (*on_finish)(void*));
|
425
|
+
|
426
|
+
/** Creates a system timer (at the cost of 1 file descriptor) and pushes the
|
427
|
+
timer to the reactor. The task will NOT repeat. Returns -1 on error or the
|
428
|
+
new file descriptor on succeess. */
|
429
|
+
__unused static inline int server_run_after(size_t milliseconds,
|
430
|
+
void task(void*),
|
431
|
+
void* arg) {
|
432
|
+
return server_run_every(milliseconds, 1, task, arg, NULL);
|
433
|
+
}
|
434
|
+
|
435
|
+
#endif
|
@@ -0,0 +1,950 @@
|
|
1
|
+
/*
|
2
|
+
copyright: Boaz segev, 2016
|
3
|
+
license: MIT
|
4
|
+
|
5
|
+
Feel free to copy, use and enjoy according to the license provided.
|
6
|
+
*/
|
7
|
+
#ifndef _GNU_SOURCE
|
8
|
+
#define _GNU_SOURCE
|
9
|
+
#endif
|
10
|
+
|
11
|
+
#include "libsock.h"
|
12
|
+
|
13
|
+
#include <string.h>
|
14
|
+
#include <stdio.h>
|
15
|
+
#include <time.h>
|
16
|
+
#include <fcntl.h>
|
17
|
+
#include <errno.h>
|
18
|
+
#include <netdb.h>
|
19
|
+
#include <sys/types.h>
|
20
|
+
#include <sys/socket.h>
|
21
|
+
#include <sys/mman.h>
|
22
|
+
#include <sys/time.h>
|
23
|
+
#include <sys/resource.h>
|
24
|
+
|
25
|
+
/* *****************************************************************************
|
26
|
+
Use spinlocks "spnlock.h".
|
27
|
+
|
28
|
+
For portability, it's possible copy "spnlock.h" directly after this line.
|
29
|
+
*/
|
30
|
+
#include "spnlock.h"
|
31
|
+
|
32
|
+
/* *****************************************************************************
|
33
|
+
Support `libreact` on_close callback, if exist.
|
34
|
+
*/
|
35
|
+
|
36
|
+
#pragma weak reactor_on_close
|
37
|
+
void reactor_on_close(intptr_t uuid) {}
|
38
|
+
#pragma weak reactor_remove
|
39
|
+
int reactor_remove(intptr_t uuid) { return -1; }
|
40
|
+
|
41
|
+
/* *****************************************************************************
|
42
|
+
Support timeout setting.
|
43
|
+
*/
|
44
|
+
#pragma weak sock_touch
|
45
|
+
void sock_touch(intptr_t uuid) {}
|
46
|
+
|
47
|
+
/* *****************************************************************************
|
48
|
+
OS Sendfile settings.
|
49
|
+
*/
|
50
|
+
|
51
|
+
#ifndef USE_SENDFILE
|
52
|
+
|
53
|
+
#if defined(__linux__) /* linux sendfile works */
|
54
|
+
#include <sys/sendfile.h>
|
55
|
+
#define USE_SENDFILE 1
|
56
|
+
#elif defined(__unix__) /* BSD sendfile should work, but isn't tested */
|
57
|
+
#include <sys/uio.h>
|
58
|
+
#define USE_SENDFILE 0
|
59
|
+
#elif defined(__APPLE__) /* AIs the pple sendfile still broken? */
|
60
|
+
#include <sys/uio.h>
|
61
|
+
#define USE_SENDFILE 1
|
62
|
+
#else /* sendfile might not be available - always set to 0 */
|
63
|
+
#define USE_SENDFILE 0
|
64
|
+
#endif
|
65
|
+
|
66
|
+
#endif
|
67
|
+
|
68
|
+
/* *****************************************************************************
|
69
|
+
Buffer and socket map memory allocation. Defaults to mmap.
|
70
|
+
*/
|
71
|
+
#ifndef USE_MALLOC
|
72
|
+
#define USE_MALLOC 0
|
73
|
+
#endif
|
74
|
+
|
75
|
+
/* *****************************************************************************
|
76
|
+
Library related helper functions
|
77
|
+
*/
|
78
|
+
|
79
|
+
/**
|
80
|
+
Sets a socket to non blocking state.
|
81
|
+
*/
|
82
|
+
inline int sock_set_non_block(int fd) // Thanks to Bjorn Reese
|
83
|
+
{
|
84
|
+
/* If they have O_NONBLOCK, use the Posix way to do it */
|
85
|
+
#if defined(O_NONBLOCK)
|
86
|
+
/* Fixme: O_NONBLOCK is defined but broken on SunOS 4.1.x and AIX 3.2.5. */
|
87
|
+
int flags;
|
88
|
+
if (-1 == (flags = fcntl(fd, F_GETFL, 0)))
|
89
|
+
flags = 0;
|
90
|
+
// printf("flags initial value was %d\n", flags);
|
91
|
+
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
92
|
+
#else
|
93
|
+
/* Otherwise, use the old way of doing it */
|
94
|
+
static int flags = 1;
|
95
|
+
return ioctl(fd, FIOBIO, &flags);
|
96
|
+
#endif
|
97
|
+
}
|
98
|
+
|
99
|
+
/**
|
100
|
+
Gets the maximum number of file descriptors this process can be allowed to
|
101
|
+
access (== maximum fd value + 1).
|
102
|
+
*/
|
103
|
+
ssize_t sock_max_capacity(void) {
|
104
|
+
// get current limits
|
105
|
+
static ssize_t flim = 0;
|
106
|
+
if (flim)
|
107
|
+
return flim;
|
108
|
+
#ifdef _SC_OPEN_MAX
|
109
|
+
flim = sysconf(_SC_OPEN_MAX);
|
110
|
+
#elif defined(OPEN_MAX)
|
111
|
+
flim = OPEN_MAX;
|
112
|
+
#endif
|
113
|
+
// try to maximize limits - collect max and set to max
|
114
|
+
struct rlimit rlim;
|
115
|
+
getrlimit(RLIMIT_NOFILE, &rlim);
|
116
|
+
// printf("Meximum open files are %llu out of %llu\n", rlim.rlim_cur,
|
117
|
+
// rlim.rlim_max);
|
118
|
+
rlim.rlim_cur = rlim.rlim_max;
|
119
|
+
setrlimit(RLIMIT_NOFILE, &rlim);
|
120
|
+
getrlimit(RLIMIT_NOFILE, &rlim);
|
121
|
+
// printf("Meximum open files are %llu out of %llu\n", rlim.rlim_cur,
|
122
|
+
// rlim.rlim_max);
|
123
|
+
// if the current limit is higher than it was, update
|
124
|
+
if (flim < rlim.rlim_cur)
|
125
|
+
flim = rlim.rlim_cur;
|
126
|
+
// return what we have
|
127
|
+
return flim;
|
128
|
+
}
|
129
|
+
|
130
|
+
/* *****************************************************************************
|
131
|
+
Library Core Data
|
132
|
+
*/
|
133
|
+
|
134
|
+
typedef struct {
|
135
|
+
/** write buffer - a linked list */
|
136
|
+
sock_packet_s *packet;
|
137
|
+
/** The fd UUID for the current connection */
|
138
|
+
fduuid_u fduuid;
|
139
|
+
/** the amount of data sent from the current buffer packet */
|
140
|
+
uint32_t sent;
|
141
|
+
/** state lock */
|
142
|
+
spn_lock_i lock;
|
143
|
+
/* -- state flags -- */
|
144
|
+
/** Connection is open */
|
145
|
+
unsigned open : 1;
|
146
|
+
/** indicated that the connection should be closed. */
|
147
|
+
unsigned close : 1;
|
148
|
+
/** indicated that the connection experienced an error. */
|
149
|
+
unsigned err : 1;
|
150
|
+
/** future flags. */
|
151
|
+
unsigned rsv : 5;
|
152
|
+
/* -- placement enforces padding to guaranty memory alignment -- */
|
153
|
+
/** Read/Write hooks. */
|
154
|
+
sock_rw_hook_s *rw_hooks;
|
155
|
+
} fd_info_s;
|
156
|
+
|
157
|
+
#define LIB_SOCK_STATE_OPEN 1
|
158
|
+
#define LIB_SOCK_STATE_CLOSED 0
|
159
|
+
|
160
|
+
static fd_info_s *fd_info = NULL;
|
161
|
+
static size_t fd_capacity = 0;
|
162
|
+
|
163
|
+
#define uuid2info(uuid) fd_info[sock_uuid2fd(uuid)]
|
164
|
+
#define is_valid(uuid) \
|
165
|
+
(fd_info[sock_uuid2fd(uuid)].fduuid.data.counter == \
|
166
|
+
((fduuid_u)(uuid)).data.counter && \
|
167
|
+
uuid2info(uuid).open)
|
168
|
+
|
169
|
+
static struct {
|
170
|
+
sock_packet_s *pool;
|
171
|
+
sock_packet_s *allocated;
|
172
|
+
spn_lock_i lock;
|
173
|
+
} buffer_pool = {.lock = SPN_LOCK_INIT};
|
174
|
+
|
175
|
+
#define BUFFER_PACKET_REAL_SIZE (sizeof(sock_packet_s) + BUFFER_PACKET_SIZE)
|
176
|
+
|
177
|
+
/* reset a socket state */
|
178
|
+
static inline void set_fd(int fd, unsigned int state) {
|
179
|
+
fd_info_s old_data;
|
180
|
+
// lock and update
|
181
|
+
spn_lock(&fd_info[fd].lock);
|
182
|
+
old_data = fd_info[fd];
|
183
|
+
fd_info[fd] = (fd_info_s){
|
184
|
+
.fduuid.data.counter = fd_info[fd].fduuid.data.counter + state,
|
185
|
+
.fduuid.data.fd = fd,
|
186
|
+
.lock = fd_info[fd].lock,
|
187
|
+
.open = state,
|
188
|
+
};
|
189
|
+
// unlock
|
190
|
+
spn_unlock(&fd_info[fd].lock);
|
191
|
+
// should be called within the lock? - no function calling within a spinlock.
|
192
|
+
if (old_data.rw_hooks && old_data.rw_hooks->on_clear)
|
193
|
+
old_data.rw_hooks->on_clear(old_data.fduuid.uuid, old_data.rw_hooks);
|
194
|
+
// clear old data
|
195
|
+
if (old_data.packet)
|
196
|
+
sock_free_packet(old_data.packet);
|
197
|
+
// call callback if exists
|
198
|
+
if (old_data.open) {
|
199
|
+
// if (state == LIB_SOCK_STATE_OPEN)
|
200
|
+
// printf(
|
201
|
+
// "STRONG FD COLLISION PROTECTION: A new connection was accepted "
|
202
|
+
// "while the old one was marked as open.\n");
|
203
|
+
reactor_remove(old_data.fduuid.uuid);
|
204
|
+
reactor_on_close(old_data.fduuid.uuid);
|
205
|
+
}
|
206
|
+
}
|
207
|
+
|
208
|
+
/**
|
209
|
+
Destroys the library data.
|
210
|
+
|
211
|
+
Call this function before calling any `libsock` functions.
|
212
|
+
*/
|
213
|
+
static void destroy_lib_data(void) {
|
214
|
+
if (fd_info) {
|
215
|
+
while (fd_capacity--) { // include 0 in countdown
|
216
|
+
set_fd(fd_capacity, LIB_SOCK_STATE_CLOSED);
|
217
|
+
}
|
218
|
+
#if USE_MALLOC == 1
|
219
|
+
free(fd_info);
|
220
|
+
#else
|
221
|
+
munmap(fd_info, (BUFFER_PACKET_REAL_SIZE * BUFFER_PACKET_POOL) +
|
222
|
+
(sizeof(fd_info_s) * fd_capacity));
|
223
|
+
#endif
|
224
|
+
}
|
225
|
+
fd_info = NULL;
|
226
|
+
buffer_pool.pool = NULL;
|
227
|
+
buffer_pool.allocated = NULL;
|
228
|
+
buffer_pool.lock = SPN_LOCK_INIT;
|
229
|
+
}
|
230
|
+
|
231
|
+
/**
|
232
|
+
Initializes the library.
|
233
|
+
|
234
|
+
Call this function before calling any `libsock` functions.
|
235
|
+
*/
|
236
|
+
static void sock_lib_init(void) {
|
237
|
+
if (fd_info)
|
238
|
+
return;
|
239
|
+
|
240
|
+
fd_capacity = sock_max_capacity();
|
241
|
+
size_t fd_map_mem_size = sizeof(fd_info_s) * fd_capacity;
|
242
|
+
size_t buffer_mem_size = BUFFER_PACKET_REAL_SIZE * BUFFER_PACKET_POOL;
|
243
|
+
|
244
|
+
void *buff_mem;
|
245
|
+
#if USE_MALLOC == 1
|
246
|
+
buff_mem = malloc(fd_map_mem_size + buffer_mem_size);
|
247
|
+
if (buff_mem == NULL) {
|
248
|
+
perror("Couldn't initialize libsock - not enough memory? ");
|
249
|
+
exit(1);
|
250
|
+
}
|
251
|
+
#else
|
252
|
+
buff_mem = mmap(NULL, fd_map_mem_size + buffer_mem_size,
|
253
|
+
PROT_READ | PROT_WRITE | PROT_EXEC,
|
254
|
+
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
255
|
+
// MAP_SHARED | MAP_ANONYMOUS, -1, 0);
|
256
|
+
if (buff_mem == MAP_FAILED || buff_mem == NULL) {
|
257
|
+
perror("Couldn't initialize libsock - not enough memory? ");
|
258
|
+
exit(1);
|
259
|
+
}
|
260
|
+
#endif
|
261
|
+
fd_info = buff_mem;
|
262
|
+
for (size_t i = 0; i < fd_capacity; i++) {
|
263
|
+
fd_info[i] = (fd_info_s){.lock = SPN_LOCK_INIT};
|
264
|
+
spn_unlock(&fd_info[i].lock);
|
265
|
+
}
|
266
|
+
/* initialize pool */
|
267
|
+
buffer_pool.allocated = buff_mem + fd_map_mem_size;
|
268
|
+
buffer_pool.pool = buffer_pool.allocated;
|
269
|
+
sock_packet_s *pos = buffer_pool.pool;
|
270
|
+
for (size_t i = 0; i < BUFFER_PACKET_POOL - 1; i++) {
|
271
|
+
*pos = (sock_packet_s){
|
272
|
+
.metadata.next = (void *)(((uintptr_t)pos) + BUFFER_PACKET_REAL_SIZE),
|
273
|
+
};
|
274
|
+
pos = pos->metadata.next;
|
275
|
+
}
|
276
|
+
pos->metadata.next = 0;
|
277
|
+
/* deallocate and manage on exit */
|
278
|
+
atexit(destroy_lib_data);
|
279
|
+
#ifdef DEBUG
|
280
|
+
fprintf(stderr, "\nInitialized libsock for %lu sockets, "
|
281
|
+
"each one requires %lu bytes.\n"
|
282
|
+
"overall ovearhead: %lu bytes.\n"
|
283
|
+
"Initialized packet pool for %d elements, "
|
284
|
+
"each one %lu bytes.\n"
|
285
|
+
"overall buffer ovearhead: %lu bytes.\n"
|
286
|
+
"=== Total: %lu bytes ===\n\n",
|
287
|
+
fd_capacity, sizeof(*fd_info), sizeof(*fd_info) * fd_capacity,
|
288
|
+
BUFFER_PACKET_POOL, BUFFER_PACKET_REAL_SIZE,
|
289
|
+
BUFFER_PACKET_REAL_SIZE * BUFFER_PACKET_POOL,
|
290
|
+
(BUFFER_PACKET_REAL_SIZE * BUFFER_PACKET_POOL) +
|
291
|
+
(sizeof(*fd_info) * fd_capacity));
|
292
|
+
#endif
|
293
|
+
}
|
294
|
+
|
295
|
+
#define review_lib() \
|
296
|
+
if (fd_info == NULL) \
|
297
|
+
sock_lib_init();
|
298
|
+
|
299
|
+
/* *****************************************************************************
|
300
|
+
Read / Write internals
|
301
|
+
*/
|
302
|
+
|
303
|
+
#define ERR_OK (errno == EAGAIN || errno == EWOULDBLOCK || errno == ENOTCONN)
|
304
|
+
#define ERR_TRY_AGAIN (errno == EINTR)
|
305
|
+
|
306
|
+
static inline int sock_flush_fd_failed(int fd) {
|
307
|
+
sock_free_packet(fd_info[fd].packet);
|
308
|
+
fd_info[fd].packet = NULL;
|
309
|
+
fd_info[fd].close = 1;
|
310
|
+
fd_info[fd].err = 1;
|
311
|
+
return 0;
|
312
|
+
}
|
313
|
+
|
314
|
+
#if USE_SENDFILE == 1
|
315
|
+
|
316
|
+
#if defined(__linux__) /* linux sendfile API */
|
317
|
+
static inline int sock_flush_os_sendfile(int fd) {
|
318
|
+
size_t sent;
|
319
|
+
sock_packet_s *packet = fd_info[fd].packet;
|
320
|
+
sent =
|
321
|
+
sendfile64(fd, (int)((ssize_t)packet->buffer), &packet->metadata.offset,
|
322
|
+
packet->length - fd_info[fd].sent);
|
323
|
+
|
324
|
+
if (sent < 0) {
|
325
|
+
if (ERR_OK)
|
326
|
+
return -1;
|
327
|
+
else if (ERR_TRY_AGAIN)
|
328
|
+
return 0;
|
329
|
+
else
|
330
|
+
return sock_flush_fd_failed(fd);
|
331
|
+
}
|
332
|
+
if (sent == 0)
|
333
|
+
fd_info[fd].sent = packet->length;
|
334
|
+
fd_info[fd].sent += sent;
|
335
|
+
return 0;
|
336
|
+
}
|
337
|
+
|
338
|
+
#elif defined(__APPLE__) || defined(__unix__) /* BSD / Apple API */
|
339
|
+
|
340
|
+
static inline int sock_flush_os_sendfile(int fd) {
|
341
|
+
off_t act_sent;
|
342
|
+
sock_packet_s *packet = fd_info[fd].packet;
|
343
|
+
act_sent = packet->length - fd_info[fd].sent;
|
344
|
+
|
345
|
+
#if defined(__APPLE__)
|
346
|
+
if (sendfile((int)((ssize_t)packet->buffer), fd, packet->metadata.offset,
|
347
|
+
&act_sent, NULL, 0) < 0 &&
|
348
|
+
act_sent == 0)
|
349
|
+
#else
|
350
|
+
if (sendfile((int)((ssize_t)packet->buffer), fd, packet->metadata.offset,
|
351
|
+
(size_t)act_sent, NULL, &act_sent, 0) < 0 &&
|
352
|
+
act_sent == 0)
|
353
|
+
#endif
|
354
|
+
{
|
355
|
+
if (ERR_OK)
|
356
|
+
return -1;
|
357
|
+
else if (ERR_TRY_AGAIN)
|
358
|
+
return 0;
|
359
|
+
else
|
360
|
+
return sock_flush_fd_failed(fd);
|
361
|
+
}
|
362
|
+
if (act_sent == 0) {
|
363
|
+
fd_info[fd].sent = packet->length;
|
364
|
+
return 0;
|
365
|
+
}
|
366
|
+
packet->metadata.offset += act_sent;
|
367
|
+
fd_info[fd].sent += act_sent;
|
368
|
+
return 0;
|
369
|
+
}
|
370
|
+
#endif
|
371
|
+
|
372
|
+
#else
|
373
|
+
|
374
|
+
static inline int sock_flush_os_sendfile(int fd) { return -1; }
|
375
|
+
|
376
|
+
#endif
|
377
|
+
|
378
|
+
static inline int sock_flush_fd(int fd) {
|
379
|
+
if (USE_SENDFILE && fd_info[fd].rw_hooks == NULL)
|
380
|
+
return sock_flush_os_sendfile(fd);
|
381
|
+
ssize_t sent;
|
382
|
+
sock_packet_s *packet = fd_info[fd].packet;
|
383
|
+
// how much data are we expecting to send...?
|
384
|
+
ssize_t i_exp = (BUFFER_PACKET_SIZE > packet->length) ? packet->length
|
385
|
+
: BUFFER_PACKET_SIZE;
|
386
|
+
|
387
|
+
// read data into the internal buffer
|
388
|
+
if (packet->metadata.internal_flag == 0) {
|
389
|
+
ssize_t i_read;
|
390
|
+
i_read = pread((int)((ssize_t)packet->buffer), packet + 1, i_exp,
|
391
|
+
packet->metadata.offset);
|
392
|
+
if (i_read <= 0) {
|
393
|
+
fd_info[fd].sent = fd_info[fd].packet->length;
|
394
|
+
return 0;
|
395
|
+
} else {
|
396
|
+
packet->metadata.offset += i_read;
|
397
|
+
packet->metadata.internal_flag = 1;
|
398
|
+
}
|
399
|
+
}
|
400
|
+
// send the data
|
401
|
+
if (fd_info[fd].rw_hooks && fd_info[fd].rw_hooks->write)
|
402
|
+
sent = fd_info[fd].rw_hooks->write(
|
403
|
+
fd_info[fd].fduuid.uuid, (((void *)(packet + 1)) + fd_info[fd].sent),
|
404
|
+
i_exp - fd_info[fd].sent);
|
405
|
+
else
|
406
|
+
sent = write(fd, (((void *)(packet + 1)) + fd_info[fd].sent),
|
407
|
+
i_exp - fd_info[fd].sent);
|
408
|
+
// review result and update packet data
|
409
|
+
if (sent < 0) {
|
410
|
+
if (ERR_OK)
|
411
|
+
return -1;
|
412
|
+
else if (ERR_TRY_AGAIN)
|
413
|
+
return 0;
|
414
|
+
else
|
415
|
+
return sock_flush_fd_failed(fd);
|
416
|
+
}
|
417
|
+
fd_info[fd].sent += sent;
|
418
|
+
if (fd_info[fd].sent >= i_exp) {
|
419
|
+
packet->metadata.internal_flag = 0;
|
420
|
+
fd_info[fd].sent = 0;
|
421
|
+
packet->length -= i_exp;
|
422
|
+
}
|
423
|
+
return 0;
|
424
|
+
}
|
425
|
+
|
426
|
+
static inline int sock_flush_data(int fd) {
|
427
|
+
ssize_t sent;
|
428
|
+
if (fd_info[fd].rw_hooks && fd_info[fd].rw_hooks->write)
|
429
|
+
sent = fd_info[fd].rw_hooks->write(
|
430
|
+
fd_info[fd].fduuid.uuid, fd_info[fd].packet->buffer + fd_info[fd].sent,
|
431
|
+
fd_info[fd].packet->length - fd_info[fd].sent);
|
432
|
+
else
|
433
|
+
sent = write(fd, fd_info[fd].packet->buffer + fd_info[fd].sent,
|
434
|
+
fd_info[fd].packet->length - fd_info[fd].sent);
|
435
|
+
if (sent < 0) {
|
436
|
+
if (ERR_OK)
|
437
|
+
return -1;
|
438
|
+
else if (ERR_TRY_AGAIN)
|
439
|
+
return 0;
|
440
|
+
else
|
441
|
+
return sock_flush_fd_failed(fd);
|
442
|
+
}
|
443
|
+
fd_info[fd].sent += sent;
|
444
|
+
return 0;
|
445
|
+
}
|
446
|
+
|
447
|
+
static void sock_flush_unsafe(int fd) {
|
448
|
+
while (fd_info[fd].packet) {
|
449
|
+
if (fd_info[fd].packet->metadata.is_fd == 0) {
|
450
|
+
if (sock_flush_data(fd))
|
451
|
+
return;
|
452
|
+
} else {
|
453
|
+
if (sock_flush_fd(fd))
|
454
|
+
return;
|
455
|
+
}
|
456
|
+
if (fd_info[fd].packet && fd_info[fd].packet->length <= fd_info[fd].sent) {
|
457
|
+
sock_packet_s *packet = fd_info[fd].packet;
|
458
|
+
fd_info[fd].packet = packet->metadata.next;
|
459
|
+
packet->metadata.next = NULL;
|
460
|
+
fd_info[fd].sent = 0;
|
461
|
+
sock_free_packet(packet);
|
462
|
+
}
|
463
|
+
}
|
464
|
+
}
|
465
|
+
|
466
|
+
static inline void sock_send_packet_unsafe(int fd, sock_packet_s *packet) {
|
467
|
+
fd_info_s *sfd = fd_info + fd;
|
468
|
+
if (sfd->packet == NULL) {
|
469
|
+
/* no queue, nothing to check */
|
470
|
+
sfd->packet = packet;
|
471
|
+
sock_flush_unsafe(fd);
|
472
|
+
return;
|
473
|
+
|
474
|
+
} else if (packet->metadata.urgent == 0) {
|
475
|
+
/* not urgent, last in line */
|
476
|
+
sock_packet_s *pos = sfd->packet;
|
477
|
+
while (pos->metadata.next)
|
478
|
+
pos = pos->metadata.next;
|
479
|
+
pos->metadata.next = packet;
|
480
|
+
sock_flush_unsafe(fd);
|
481
|
+
return;
|
482
|
+
|
483
|
+
} else {
|
484
|
+
/* urgent, find a spot we can interrupt */
|
485
|
+
sock_packet_s **pos = &sfd->packet;
|
486
|
+
while (*pos && (*pos)->metadata.can_interrupt == 0)
|
487
|
+
pos = &(*pos)->metadata.next;
|
488
|
+
sock_packet_s *tail = *pos;
|
489
|
+
*pos = packet;
|
490
|
+
if (tail) {
|
491
|
+
pos = &packet->metadata.next;
|
492
|
+
while (*pos)
|
493
|
+
pos = &(*pos)->metadata.next;
|
494
|
+
*pos = tail;
|
495
|
+
}
|
496
|
+
}
|
497
|
+
sock_flush_unsafe(fd);
|
498
|
+
}
|
499
|
+
|
500
|
+
/* *****************************************************************************
|
501
|
+
Listen
|
502
|
+
*/
|
503
|
+
|
504
|
+
/**
|
505
|
+
Opens a listening non-blocking socket. Return's the socket's UUID.
|
506
|
+
*/
|
507
|
+
intptr_t sock_listen(const char *address, const char *port) {
|
508
|
+
review_lib();
|
509
|
+
int srvfd;
|
510
|
+
// setup the address
|
511
|
+
struct addrinfo hints;
|
512
|
+
struct addrinfo *servinfo; // will point to the results
|
513
|
+
memset(&hints, 0, sizeof hints); // make sure the struct is empty
|
514
|
+
hints.ai_family = AF_UNSPEC; // don't care IPv4 or IPv6
|
515
|
+
hints.ai_socktype = SOCK_STREAM; // TCP stream sockets
|
516
|
+
hints.ai_flags = AI_PASSIVE; // fill in my IP for me
|
517
|
+
if (getaddrinfo(address, port, &hints, &servinfo)) {
|
518
|
+
// perror("addr err");
|
519
|
+
return -1;
|
520
|
+
}
|
521
|
+
// get the file descriptor
|
522
|
+
srvfd =
|
523
|
+
socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol);
|
524
|
+
if (srvfd <= 0) {
|
525
|
+
// perror("socket err");
|
526
|
+
freeaddrinfo(servinfo);
|
527
|
+
return -1;
|
528
|
+
}
|
529
|
+
// make sure the socket is non-blocking
|
530
|
+
if (sock_set_non_block(srvfd) < 0) {
|
531
|
+
// perror("couldn't set socket as non blocking! ");
|
532
|
+
freeaddrinfo(servinfo);
|
533
|
+
close(srvfd);
|
534
|
+
return -1;
|
535
|
+
}
|
536
|
+
// avoid the "address taken"
|
537
|
+
{
|
538
|
+
int optval = 1;
|
539
|
+
setsockopt(srvfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
|
540
|
+
}
|
541
|
+
// bind the address to the socket
|
542
|
+
{
|
543
|
+
int bound = 0;
|
544
|
+
for (struct addrinfo *p = servinfo; p != NULL; p = p->ai_next) {
|
545
|
+
if (!bind(srvfd, p->ai_addr, p->ai_addrlen))
|
546
|
+
bound = 1;
|
547
|
+
}
|
548
|
+
|
549
|
+
if (!bound) {
|
550
|
+
// perror("bind err");
|
551
|
+
freeaddrinfo(servinfo);
|
552
|
+
close(srvfd);
|
553
|
+
return -1;
|
554
|
+
}
|
555
|
+
}
|
556
|
+
freeaddrinfo(servinfo);
|
557
|
+
// listen in
|
558
|
+
if (listen(srvfd, SOMAXCONN) < 0) {
|
559
|
+
// perror("couldn't start listening");
|
560
|
+
close(srvfd);
|
561
|
+
return -1;
|
562
|
+
}
|
563
|
+
set_fd(srvfd, LIB_SOCK_STATE_OPEN);
|
564
|
+
return fd_info[srvfd].fduuid.uuid;
|
565
|
+
}
|
566
|
+
|
567
|
+
/* *****************************************************************************
|
568
|
+
Accept
|
569
|
+
*/
|
570
|
+
|
571
|
+
intptr_t sock_accept(intptr_t srv_uuid) {
|
572
|
+
review_lib();
|
573
|
+
static socklen_t cl_addrlen = 0;
|
574
|
+
int client;
|
575
|
+
#ifdef SOCK_NONBLOCK
|
576
|
+
client = accept4(sock_uuid2fd(srv_uuid), NULL, &cl_addrlen, SOCK_NONBLOCK);
|
577
|
+
if (client <= 0)
|
578
|
+
return -1;
|
579
|
+
#else
|
580
|
+
client = accept(sock_uuid2fd(srv_uuid), NULL, &cl_addrlen);
|
581
|
+
if (client <= 0)
|
582
|
+
return -1;
|
583
|
+
sock_set_non_block(client);
|
584
|
+
#endif
|
585
|
+
set_fd(client, LIB_SOCK_STATE_OPEN);
|
586
|
+
return fd_info[client].fduuid.uuid;
|
587
|
+
}
|
588
|
+
|
589
|
+
/* *****************************************************************************
|
590
|
+
Connect
|
591
|
+
*/
|
592
|
+
intptr_t sock_connect(char *address, char *port) {
|
593
|
+
review_lib();
|
594
|
+
int fd;
|
595
|
+
// setup the address
|
596
|
+
struct addrinfo hints;
|
597
|
+
struct addrinfo *addrinfo; // will point to the results
|
598
|
+
memset(&hints, 0, sizeof hints); // make sure the struct is empty
|
599
|
+
hints.ai_family = AF_UNSPEC; // don't care IPv4 or IPv6
|
600
|
+
hints.ai_socktype = SOCK_STREAM; // TCP stream sockets
|
601
|
+
hints.ai_flags = AI_PASSIVE; // fill in my IP for me
|
602
|
+
if (getaddrinfo(address, port, &hints, &addrinfo)) {
|
603
|
+
return -1;
|
604
|
+
}
|
605
|
+
// get the file descriptor
|
606
|
+
fd =
|
607
|
+
socket(addrinfo->ai_family, addrinfo->ai_socktype, addrinfo->ai_protocol);
|
608
|
+
if (fd <= 0) {
|
609
|
+
freeaddrinfo(addrinfo);
|
610
|
+
return -1;
|
611
|
+
}
|
612
|
+
// make sure the socket is non-blocking
|
613
|
+
if (sock_set_non_block(fd) < 0) {
|
614
|
+
freeaddrinfo(addrinfo);
|
615
|
+
close(fd);
|
616
|
+
return -1;
|
617
|
+
}
|
618
|
+
|
619
|
+
if (connect(fd, addrinfo->ai_addr, addrinfo->ai_addrlen) < 0 &&
|
620
|
+
errno != EINPROGRESS) {
|
621
|
+
close(fd);
|
622
|
+
freeaddrinfo(addrinfo);
|
623
|
+
return -1;
|
624
|
+
}
|
625
|
+
freeaddrinfo(addrinfo);
|
626
|
+
set_fd(fd, LIB_SOCK_STATE_OPEN);
|
627
|
+
return fd_info[fd].fduuid.uuid;
|
628
|
+
}
|
629
|
+
|
630
|
+
/* *****************************************************************************
|
631
|
+
Open existing
|
632
|
+
*/
|
633
|
+
|
634
|
+
intptr_t sock_open(int fd) {
|
635
|
+
review_lib();
|
636
|
+
set_fd(fd, LIB_SOCK_STATE_OPEN);
|
637
|
+
return fd_info[fd].fduuid.uuid;
|
638
|
+
}
|
639
|
+
|
640
|
+
/* *****************************************************************************
|
641
|
+
Information about the socket
|
642
|
+
*/
|
643
|
+
|
644
|
+
/**
|
645
|
+
Returns 1 if the uuid refers to a valid and open, socket.
|
646
|
+
|
647
|
+
Returns 0 if not.
|
648
|
+
*/
|
649
|
+
int sock_isvalid(intptr_t uuid) { return fd_info && is_valid(uuid); }
|
650
|
+
|
651
|
+
/**
|
652
|
+
`sock_fd2uuid` takes an existing file decriptor `fd` and returns it's active
|
653
|
+
`uuid`.
|
654
|
+
*/
|
655
|
+
intptr_t sock_fd2uuid(int fd) {
|
656
|
+
return (fd_info && fd_info[fd].open) ? fd_info[fd].fduuid.uuid : -1;
|
657
|
+
}
|
658
|
+
|
659
|
+
/* *****************************************************************************
|
660
|
+
Buffer API.
|
661
|
+
*/
|
662
|
+
|
663
|
+
static inline sock_packet_s *sock_try_checkout_packet(void) {
|
664
|
+
sock_packet_s *packet;
|
665
|
+
spn_lock(&buffer_pool.lock);
|
666
|
+
packet = buffer_pool.pool;
|
667
|
+
if (packet) {
|
668
|
+
buffer_pool.pool = packet->metadata.next;
|
669
|
+
spn_unlock(&buffer_pool.lock);
|
670
|
+
*packet = (sock_packet_s){.buffer = packet + 1, .metadata.next = NULL};
|
671
|
+
return packet;
|
672
|
+
}
|
673
|
+
spn_unlock(&buffer_pool.lock);
|
674
|
+
return packet;
|
675
|
+
}
|
676
|
+
|
677
|
+
/**
|
678
|
+
Checks out a `sock_packet_s` from the packet pool, transfering the
|
679
|
+
ownership of the memory to the calling function. The function will hang until a
|
680
|
+
packet becomes available, so never check out more then a single packet at a
|
681
|
+
time.
|
682
|
+
*/
|
683
|
+
sock_packet_s *sock_checkout_packet(void) {
|
684
|
+
review_lib();
|
685
|
+
sock_packet_s *packet = NULL;
|
686
|
+
for (;;) {
|
687
|
+
spn_lock(&buffer_pool.lock);
|
688
|
+
packet = buffer_pool.pool;
|
689
|
+
if (packet) {
|
690
|
+
buffer_pool.pool = packet->metadata.next;
|
691
|
+
spn_unlock(&buffer_pool.lock);
|
692
|
+
*packet = (sock_packet_s){.buffer = packet + 1, .metadata.next = NULL};
|
693
|
+
return packet;
|
694
|
+
}
|
695
|
+
spn_unlock(&buffer_pool.lock);
|
696
|
+
reschedule_thread();
|
697
|
+
sock_flush_all();
|
698
|
+
}
|
699
|
+
}
|
700
|
+
/**
|
701
|
+
Attaches a packet to a socket's output buffer and calls `sock_flush` for the
|
702
|
+
socket.
|
703
|
+
*/
|
704
|
+
ssize_t sock_send_packet(intptr_t uuid, sock_packet_s *packet) {
|
705
|
+
if (!fd_info || !is_valid(uuid)) {
|
706
|
+
sock_free_packet(packet);
|
707
|
+
return -1;
|
708
|
+
}
|
709
|
+
spn_lock(&uuid2info(uuid).lock);
|
710
|
+
sock_send_packet_unsafe(sock_uuid2fd(uuid), packet);
|
711
|
+
spn_unlock(&uuid2info(uuid).lock);
|
712
|
+
return 0;
|
713
|
+
}
|
714
|
+
|
715
|
+
/**
|
716
|
+
Use `sock_free_packet` to free unused packets that were checked-out using
|
717
|
+
`sock_checkout_packet`.
|
718
|
+
*/
|
719
|
+
void sock_free_packet(sock_packet_s *packet) {
|
720
|
+
sock_packet_s *next = packet;
|
721
|
+
if (packet == NULL)
|
722
|
+
return;
|
723
|
+
for (;;) {
|
724
|
+
if (next->metadata.is_fd) {
|
725
|
+
if (next->metadata.keep_open == 0)
|
726
|
+
close((int)((ssize_t)next->buffer));
|
727
|
+
} else if (next->metadata.external)
|
728
|
+
free(next->buffer);
|
729
|
+
if (next->metadata.next == NULL)
|
730
|
+
break; /* next will hold the last packet in the chain. */
|
731
|
+
next = next->metadata.next;
|
732
|
+
}
|
733
|
+
spn_lock(&buffer_pool.lock);
|
734
|
+
next->metadata.next = buffer_pool.pool;
|
735
|
+
buffer_pool.pool = packet;
|
736
|
+
spn_unlock(&buffer_pool.lock);
|
737
|
+
}
|
738
|
+
|
739
|
+
/* *****************************************************************************
|
740
|
+
Reading
|
741
|
+
*/
|
742
|
+
ssize_t sock_read(intptr_t uuid, void *buf, size_t count) {
|
743
|
+
if (!fd_info || !is_valid(uuid)) {
|
744
|
+
errno = ENODEV;
|
745
|
+
return -1;
|
746
|
+
}
|
747
|
+
ssize_t i_read;
|
748
|
+
fd_info_s *sfd = fd_info + sock_uuid2fd(uuid);
|
749
|
+
if (sfd->rw_hooks && sfd->rw_hooks->read)
|
750
|
+
i_read = sfd->rw_hooks->read(uuid, buf, count);
|
751
|
+
else
|
752
|
+
i_read = read(sock_uuid2fd(uuid), buf, count);
|
753
|
+
|
754
|
+
if (i_read > 0) {
|
755
|
+
sock_touch(uuid);
|
756
|
+
return i_read;
|
757
|
+
}
|
758
|
+
if (i_read == -1 && (ERR_OK || ERR_TRY_AGAIN))
|
759
|
+
return 0;
|
760
|
+
// fprintf(stderr, "Read Error for %lu bytes from fd %d (closing))\n", count,
|
761
|
+
// sock_uuid2fd(uuid));
|
762
|
+
sock_close(uuid);
|
763
|
+
return -1;
|
764
|
+
}
|
765
|
+
|
766
|
+
/* *****************************************************************************
|
767
|
+
Flushing
|
768
|
+
*/
|
769
|
+
|
770
|
+
ssize_t sock_flush(intptr_t uuid) {
|
771
|
+
if (!fd_info || !is_valid(uuid))
|
772
|
+
return -1;
|
773
|
+
spn_lock(&uuid2info(uuid).lock);
|
774
|
+
sock_flush_unsafe(sock_uuid2fd(uuid));
|
775
|
+
spn_unlock(&uuid2info(uuid).lock);
|
776
|
+
if (uuid2info(uuid).close) {
|
777
|
+
sock_force_close(uuid);
|
778
|
+
return -1;
|
779
|
+
}
|
780
|
+
return 0;
|
781
|
+
}
|
782
|
+
/**
|
783
|
+
`sock_flush_strong` performs the same action as `sock_flush` but returns only
|
784
|
+
after all the data was sent. This is an "active" wait, polling isn't
|
785
|
+
performed.
|
786
|
+
*/
|
787
|
+
void sock_flush_strong(intptr_t uuid) {
|
788
|
+
if (!fd_info)
|
789
|
+
return;
|
790
|
+
while (is_valid(uuid) && uuid2info(uuid).packet)
|
791
|
+
sock_flush(uuid);
|
792
|
+
}
|
793
|
+
/**
|
794
|
+
Calls `sock_flush` for each file descriptor that's buffer isn't empty.
|
795
|
+
*/
|
796
|
+
void sock_flush_all(void) {
|
797
|
+
for (size_t i = 0; i < fd_capacity; i++) {
|
798
|
+
if (fd_info[i].packet == NULL || spn_is_locked(&fd_info[i].lock))
|
799
|
+
continue;
|
800
|
+
sock_flush(fd_info[i].fduuid.uuid);
|
801
|
+
}
|
802
|
+
}
|
803
|
+
|
804
|
+
/* *****************************************************************************
|
805
|
+
Writing
|
806
|
+
*/
|
807
|
+
|
808
|
+
ssize_t sock_write2_fn(sock_write_info_s options) {
|
809
|
+
if (!fd_info || !is_valid(options.fduuid)) {
|
810
|
+
errno = ENODEV;
|
811
|
+
return -1;
|
812
|
+
}
|
813
|
+
if (options.buffer == NULL)
|
814
|
+
return -1;
|
815
|
+
if (!options.length && !options.is_fd)
|
816
|
+
options.length = strlen(options.buffer);
|
817
|
+
if (options.length == 0)
|
818
|
+
return -1;
|
819
|
+
sock_packet_s *packet = sock_checkout_packet();
|
820
|
+
packet->metadata.can_interrupt = 1;
|
821
|
+
packet->metadata.urgent = options.urgent;
|
822
|
+
|
823
|
+
if (options.is_fd) {
|
824
|
+
packet->buffer = (void *)options.buffer;
|
825
|
+
packet->length = options.length;
|
826
|
+
packet->metadata.is_fd = options.is_fd;
|
827
|
+
packet->metadata.offset = options.offset;
|
828
|
+
return sock_send_packet(options.fduuid, packet);
|
829
|
+
} else {
|
830
|
+
if (options.move) {
|
831
|
+
packet->buffer = (void *)options.buffer;
|
832
|
+
packet->length = options.length;
|
833
|
+
packet->metadata.external = 1;
|
834
|
+
return sock_send_packet(options.fduuid, packet);
|
835
|
+
} else {
|
836
|
+
if (options.length <= BUFFER_PACKET_SIZE) {
|
837
|
+
memcpy(packet->buffer, options.buffer, options.length);
|
838
|
+
packet->length = options.length;
|
839
|
+
return sock_send_packet(options.fduuid, packet);
|
840
|
+
} else {
|
841
|
+
if (packet->metadata.urgent) {
|
842
|
+
fprintf(stderr, "Socket err:"
|
843
|
+
"Large data cannot be sent as an urgent packet.\n"
|
844
|
+
"Urgency silently ignored\n");
|
845
|
+
packet->metadata.urgent = 0;
|
846
|
+
}
|
847
|
+
size_t to_cpy;
|
848
|
+
spn_lock(&uuid2info(options.fduuid).lock);
|
849
|
+
for (;;) {
|
850
|
+
to_cpy = options.length > BUFFER_PACKET_SIZE ? BUFFER_PACKET_SIZE
|
851
|
+
: options.length;
|
852
|
+
memcpy(packet->buffer, options.buffer, to_cpy);
|
853
|
+
packet->length = to_cpy;
|
854
|
+
options.length -= to_cpy;
|
855
|
+
options.buffer += to_cpy;
|
856
|
+
sock_send_packet_unsafe(sock_uuid2fd(options.fduuid), packet);
|
857
|
+
if (!is_valid(options.fduuid) || uuid2info(options.fduuid).err == 1 ||
|
858
|
+
options.length == 0)
|
859
|
+
break;
|
860
|
+
packet = sock_try_checkout_packet();
|
861
|
+
while (packet == NULL) {
|
862
|
+
sock_flush_all();
|
863
|
+
sock_flush_unsafe(sock_uuid2fd(options.fduuid));
|
864
|
+
packet = sock_try_checkout_packet();
|
865
|
+
}
|
866
|
+
}
|
867
|
+
spn_unlock(&uuid2info(options.fduuid).lock);
|
868
|
+
if (uuid2info(options.fduuid).packet == NULL &&
|
869
|
+
uuid2info(options.fduuid).close) {
|
870
|
+
sock_force_close(options.fduuid);
|
871
|
+
return -1;
|
872
|
+
}
|
873
|
+
return is_valid(options.fduuid) ? 0 : -1;
|
874
|
+
}
|
875
|
+
}
|
876
|
+
}
|
877
|
+
// how did we get here?
|
878
|
+
return -1;
|
879
|
+
}
|
880
|
+
|
881
|
+
/* *****************************************************************************
|
882
|
+
Closing.
|
883
|
+
*/
|
884
|
+
|
885
|
+
void sock_close(intptr_t uuid) {
|
886
|
+
// fprintf(stderr, "called sock_close for %lu (%d)\n", uuid,
|
887
|
+
// sock_uuid2fd(uuid));
|
888
|
+
if (!fd_info || !is_valid(uuid))
|
889
|
+
return;
|
890
|
+
fd_info[sock_uuid2fd(uuid)].close = 1;
|
891
|
+
sock_flush(uuid);
|
892
|
+
}
|
893
|
+
|
894
|
+
void sock_force_close(intptr_t uuid) {
|
895
|
+
// fprintf(stderr, "called sock_force_close for %lu (%d)\n", uuid,
|
896
|
+
// sock_uuid2fd(uuid));
|
897
|
+
if (!fd_info || !is_valid(uuid))
|
898
|
+
return;
|
899
|
+
shutdown(sock_uuid2fd(uuid), SHUT_RDWR);
|
900
|
+
close(sock_uuid2fd(uuid));
|
901
|
+
set_fd(sock_uuid2fd(uuid), LIB_SOCK_STATE_CLOSED);
|
902
|
+
}
|
903
|
+
|
904
|
+
/* *****************************************************************************
|
905
|
+
RW hooks implementation
|
906
|
+
*/
|
907
|
+
|
908
|
+
/** Gets a socket hook state (a pointer to the struct). */
|
909
|
+
struct sock_rw_hook_s *sock_rw_hook_get(intptr_t uuid) {
|
910
|
+
if (!fd_info || !is_valid(uuid))
|
911
|
+
return NULL;
|
912
|
+
return uuid2info(uuid).rw_hooks;
|
913
|
+
}
|
914
|
+
|
915
|
+
/** Sets a socket hook state (a pointer to the struct). */
|
916
|
+
int sock_rw_hook_set(intptr_t uuid, sock_rw_hook_s *rw_hooks) {
|
917
|
+
if (!fd_info || !is_valid(uuid))
|
918
|
+
return -1;
|
919
|
+
spn_lock(&(uuid2info(uuid).lock));
|
920
|
+
uuid2info(uuid).rw_hooks = rw_hooks;
|
921
|
+
spn_unlock(&uuid2info(uuid).lock);
|
922
|
+
return 0;
|
923
|
+
}
|
924
|
+
|
925
|
+
/* *****************************************************************************
|
926
|
+
test
|
927
|
+
*/
|
928
|
+
#ifdef DEBUG
|
929
|
+
void sock_libtest(void) {
|
930
|
+
sock_lib_init();
|
931
|
+
sock_packet_s *p, *pl;
|
932
|
+
size_t count = 0;
|
933
|
+
fprintf(stderr, "Testing packet pool\n");
|
934
|
+
for (size_t i = 0; i < BUFFER_PACKET_POOL * 2; i++) {
|
935
|
+
count = 1;
|
936
|
+
pl = p = sock_checkout_packet();
|
937
|
+
while (buffer_pool.pool) {
|
938
|
+
count++;
|
939
|
+
pl->metadata.next = sock_checkout_packet();
|
940
|
+
pl = pl->metadata.next;
|
941
|
+
}
|
942
|
+
sock_free_packet(p);
|
943
|
+
// fprintf(stderr, "Collected and freed %lu packets.\n", count);
|
944
|
+
}
|
945
|
+
fprintf(stderr,
|
946
|
+
"liniar (no-contention) packet checkout + free shows %lu packets. "
|
947
|
+
"test %s\n",
|
948
|
+
count, count == BUFFER_PACKET_POOL ? "passed." : "FAILED!");
|
949
|
+
}
|
950
|
+
#endif
|