iodine 0.4.19 → 0.5.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.

Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -2
  3. data/CHANGELOG.md +22 -0
  4. data/LIMITS.md +19 -9
  5. data/README.md +92 -77
  6. data/SPEC-PubSub-Draft.md +113 -0
  7. data/SPEC-Websocket-Draft.md +127 -143
  8. data/bin/http-hello +0 -1
  9. data/bin/raw-rbhttp +1 -1
  10. data/bin/raw_broadcast +8 -10
  11. data/bin/updated api +2 -2
  12. data/bin/ws-broadcast +2 -4
  13. data/bin/ws-echo +2 -2
  14. data/examples/config.ru +13 -13
  15. data/examples/echo.ru +5 -6
  16. data/examples/hello.ru +2 -3
  17. data/examples/info.md +316 -0
  18. data/examples/pubsub_engine.ru +81 -0
  19. data/examples/redis.ru +9 -9
  20. data/examples/shootout.ru +45 -11
  21. data/ext/iodine/defer.c +194 -297
  22. data/ext/iodine/defer.h +61 -53
  23. data/ext/iodine/evio.c +0 -260
  24. data/ext/iodine/evio.h +50 -22
  25. data/ext/iodine/evio_callbacks.c +26 -0
  26. data/ext/iodine/evio_epoll.c +251 -0
  27. data/ext/iodine/evio_kqueue.c +193 -0
  28. data/ext/iodine/extconf.rb +1 -1
  29. data/ext/iodine/facil.c +1420 -542
  30. data/ext/iodine/facil.h +151 -64
  31. data/ext/iodine/fio_ary.h +418 -0
  32. data/ext/iodine/{base64.c → fio_base64.c} +33 -24
  33. data/ext/iodine/{base64.h → fio_base64.h} +6 -7
  34. data/ext/iodine/{fio_cli_helper.c → fio_cli.c} +77 -58
  35. data/ext/iodine/{fio_cli_helper.h → fio_cli.h} +9 -4
  36. data/ext/iodine/fio_hashmap.h +759 -0
  37. data/ext/iodine/fio_json_parser.h +651 -0
  38. data/ext/iodine/fio_llist.h +257 -0
  39. data/ext/iodine/fio_mem.c +672 -0
  40. data/ext/iodine/fio_mem.h +140 -0
  41. data/ext/iodine/fio_random.c +248 -0
  42. data/ext/iodine/{random.h → fio_random.h} +11 -14
  43. data/ext/iodine/{sha1.c → fio_sha1.c} +28 -24
  44. data/ext/iodine/{sha1.h → fio_sha1.h} +38 -16
  45. data/ext/iodine/{sha2.c → fio_sha2.c} +66 -49
  46. data/ext/iodine/{sha2.h → fio_sha2.h} +57 -26
  47. data/ext/iodine/{fiobj_internal.c → fio_siphash.c} +9 -90
  48. data/ext/iodine/fio_siphash.h +18 -0
  49. data/ext/iodine/fio_tmpfile.h +38 -0
  50. data/ext/iodine/fiobj.h +24 -7
  51. data/ext/iodine/fiobj4sock.h +23 -0
  52. data/ext/iodine/fiobj_ary.c +143 -226
  53. data/ext/iodine/fiobj_ary.h +17 -16
  54. data/ext/iodine/fiobj_data.c +1160 -0
  55. data/ext/iodine/fiobj_data.h +164 -0
  56. data/ext/iodine/fiobj_hash.c +298 -406
  57. data/ext/iodine/fiobj_hash.h +101 -54
  58. data/ext/iodine/fiobj_json.c +478 -601
  59. data/ext/iodine/fiobj_json.h +34 -9
  60. data/ext/iodine/fiobj_numbers.c +383 -51
  61. data/ext/iodine/fiobj_numbers.h +87 -11
  62. data/ext/iodine/fiobj_str.c +423 -184
  63. data/ext/iodine/fiobj_str.h +81 -32
  64. data/ext/iodine/fiobject.c +273 -522
  65. data/ext/iodine/fiobject.h +477 -112
  66. data/ext/iodine/http.c +2243 -83
  67. data/ext/iodine/http.h +842 -121
  68. data/ext/iodine/http1.c +810 -385
  69. data/ext/iodine/http1.h +16 -39
  70. data/ext/iodine/http1_parser.c +146 -74
  71. data/ext/iodine/http1_parser.h +15 -4
  72. data/ext/iodine/http_internal.c +1258 -0
  73. data/ext/iodine/http_internal.h +226 -0
  74. data/ext/iodine/http_mime_parser.h +341 -0
  75. data/ext/iodine/iodine.c +86 -68
  76. data/ext/iodine/iodine.h +26 -11
  77. data/ext/iodine/iodine_helpers.c +8 -7
  78. data/ext/iodine/iodine_http.c +487 -324
  79. data/ext/iodine/iodine_json.c +304 -0
  80. data/ext/iodine/iodine_json.h +6 -0
  81. data/ext/iodine/iodine_protocol.c +107 -45
  82. data/ext/iodine/iodine_pubsub.c +526 -225
  83. data/ext/iodine/iodine_pubsub.h +10 -0
  84. data/ext/iodine/iodine_websockets.c +268 -510
  85. data/ext/iodine/iodine_websockets.h +2 -4
  86. data/ext/iodine/pubsub.c +726 -432
  87. data/ext/iodine/pubsub.h +85 -103
  88. data/ext/iodine/rb-call.c +4 -4
  89. data/ext/iodine/rb-defer.c +46 -22
  90. data/ext/iodine/rb-fiobj2rb.h +117 -0
  91. data/ext/iodine/rb-rack-io.c +73 -238
  92. data/ext/iodine/rb-rack-io.h +2 -2
  93. data/ext/iodine/rb-registry.c +35 -93
  94. data/ext/iodine/rb-registry.h +1 -0
  95. data/ext/iodine/redis_engine.c +742 -304
  96. data/ext/iodine/redis_engine.h +42 -39
  97. data/ext/iodine/resp_parser.h +311 -0
  98. data/ext/iodine/sock.c +627 -490
  99. data/ext/iodine/sock.h +345 -297
  100. data/ext/iodine/spnlock.inc +15 -4
  101. data/ext/iodine/websocket_parser.h +16 -20
  102. data/ext/iodine/websockets.c +188 -257
  103. data/ext/iodine/websockets.h +24 -133
  104. data/lib/iodine.rb +52 -7
  105. data/lib/iodine/cli.rb +6 -24
  106. data/lib/iodine/json.rb +40 -0
  107. data/lib/iodine/version.rb +1 -1
  108. data/lib/iodine/websocket.rb +5 -3
  109. data/lib/rack/handler/iodine.rb +58 -13
  110. metadata +38 -48
  111. data/bin/ws-shootout +0 -107
  112. data/examples/broadcast.ru +0 -56
  113. data/ext/iodine/bscrypt-common.h +0 -116
  114. data/ext/iodine/bscrypt.h +0 -49
  115. data/ext/iodine/fio2resp.c +0 -60
  116. data/ext/iodine/fio2resp.h +0 -51
  117. data/ext/iodine/fio_dict.c +0 -446
  118. data/ext/iodine/fio_dict.h +0 -99
  119. data/ext/iodine/fio_hash_table.h +0 -370
  120. data/ext/iodine/fio_list.h +0 -111
  121. data/ext/iodine/fiobj_internal.h +0 -280
  122. data/ext/iodine/fiobj_primitives.c +0 -131
  123. data/ext/iodine/fiobj_primitives.h +0 -55
  124. data/ext/iodine/fiobj_sym.c +0 -135
  125. data/ext/iodine/fiobj_sym.h +0 -60
  126. data/ext/iodine/hex.c +0 -124
  127. data/ext/iodine/hex.h +0 -70
  128. data/ext/iodine/http1_request.c +0 -81
  129. data/ext/iodine/http1_request.h +0 -58
  130. data/ext/iodine/http1_response.c +0 -417
  131. data/ext/iodine/http1_response.h +0 -95
  132. data/ext/iodine/http_request.c +0 -111
  133. data/ext/iodine/http_request.h +0 -102
  134. data/ext/iodine/http_response.c +0 -1703
  135. data/ext/iodine/http_response.h +0 -250
  136. data/ext/iodine/misc.c +0 -182
  137. data/ext/iodine/misc.h +0 -74
  138. data/ext/iodine/random.c +0 -208
  139. data/ext/iodine/redis_connection.c +0 -278
  140. data/ext/iodine/redis_connection.h +0 -86
  141. data/ext/iodine/resp.c +0 -842
  142. data/ext/iodine/resp.h +0 -261
  143. data/ext/iodine/siphash.c +0 -154
  144. data/ext/iodine/siphash.h +0 -22
  145. data/ext/iodine/xor-crypt.c +0 -193
  146. data/ext/iodine/xor-crypt.h +0 -107
@@ -1,86 +0,0 @@
1
- /*
2
- Copyright: Boaz segev, 2017
3
- License: MIT except for any non-public-domain algorithms (none that I'm aware
4
- of), which might be subject to their own licenses.
5
-
6
- Feel free to copy, use and enjoy in accordance with to the license(s).
7
- */
8
- #ifndef H_REDIS_CONNECTION_H
9
- #define H_REDIS_CONNECTION_H
10
- #include "facil.h"
11
- #include "resp.h"
12
-
13
- /* support C++ */
14
- #ifdef __cplusplus
15
- extern "C" {
16
- #endif
17
-
18
- /* *****************************************************************************
19
- Connectivity
20
- ***************************************************************************** */
21
-
22
- /**
23
- To create a new Redis connection, create a Redis context object using the
24
- following optings and the `redis_create_context` function.
25
-
26
- The context should be passed along as the `udata` parameter to either
27
- `facil_connect` or `facil_listen`.
28
-
29
- The `on_connect` / `on_open` argument for `facil_connect` and
30
- `facil_listen`should be the `redis_create_protocol` function pointer.
31
-
32
- The `on_fail` / `on_finish` argument for `facil_connect` and
33
- `facil_listen`should be the `redis_protocol_cleanup` function pointer.
34
- */
35
-
36
- struct redis_context_args {
37
- /** REQUIRED: the RESP parser used by the connection. */
38
- resp_parser_pt parser;
39
- /** REQUIRED: called when the RESP messages are received. */
40
- void (*on_message)(intptr_t uuid, resp_object_s *msg, void *udata);
41
- /** called when the Redix connection closes or fails to open. */
42
- void (*on_close)(intptr_t uuid, void *udata);
43
- /** called when the Redix connection opens. */
44
- void (*on_open)(intptr_t uuid, void *udata);
45
- /** Authentication string (password). */
46
- char *auth;
47
- /** Authentication string (password) length. */
48
- size_t auth_len;
49
- /** Opaque user data. */
50
- void *udata;
51
- /** PING intervals. */
52
- uint8_t ping;
53
- /** The context, not the computer... */
54
- uint8_t autodestruct;
55
- };
56
-
57
- void *redis_create_context(struct redis_context_args);
58
-
59
- #define redis_create_context(...) \
60
- redis_create_context((struct redis_context_args){__VA_ARGS__})
61
-
62
- /**
63
- * This function is used as a function pointer for the `facil_connect` and
64
- * calls (the `on_connect` callback).
65
- */
66
- protocol_s *redis_create_client_protocol(intptr_t uuid, void *settings);
67
-
68
- /**
69
- * This function is used as a function pointer for the `facil_listen` calls (the
70
- * `on_open` callbacks).
71
- */
72
- protocol_s *redis_create_server_protocol(intptr_t uuid, void *settings);
73
-
74
- /**
75
- * This function is used as a function pointer for both `facil_connect` and
76
- * `facil_listen` calls (the `on_fail` / `on_finish` callbacks).
77
- *
78
- * It's main responsibility is to free the context and protocol resources.
79
- */
80
- void redis_protocol_cleanup(intptr_t uuid, void *settings);
81
-
82
- #ifdef __cplusplus
83
- } /* extern "C" */
84
- #endif
85
-
86
- #endif
data/ext/iodine/resp.c DELETED
@@ -1,842 +0,0 @@
1
- /*
2
- Copyright: Boaz Segev, 2017
3
- License: MIT
4
-
5
- Feel free to copy, use and enjoy according to the license provided.
6
-
7
- Copyright refers to the parser, not the protocol.
8
- */
9
-
10
- /* for atomic operations when reference counting: `spn_add` and `spn_sub` */
11
- #include "spnlock.inc"
12
-
13
- #include "resp.h"
14
- #include <math.h>
15
- #include <stdio.h>
16
- #include <string.h>
17
-
18
- /* *****************************************************************************
19
- Error Reporting
20
- ***************************************************************************** */
21
- #ifdef DEBUG
22
- #define REPORT(...) fprintf(stderr, __VA_ARGS__);
23
- #else
24
- #define REPORT(...)
25
- #endif
26
- /* *****************************************************************************
27
- The parser object
28
- ***************************************************************************** */
29
-
30
- typedef struct resp_parser_s {
31
- resp_object_s *obj;
32
- size_t missing;
33
- uint8_t *leftovers;
34
- size_t llen;
35
- unsigned has_first_object : 1;
36
- unsigned exclude_pubsub : 1;
37
- unsigned multiplex_pubsub : 1;
38
- } resp_parser_s;
39
-
40
- /* *****************************************************************************
41
- Pub/Sub state and Extension Flags
42
- ***************************************************************************** */
43
-
44
- /** Set the parsing mode to enable the experimental pub/sub duplexing. */
45
- void resp_enable_duplex_pubsub(resp_parser_pt p) { p->multiplex_pubsub = 1; }
46
-
47
- /* *****************************************************************************
48
- Object management
49
- +
50
- Sometimes you just need a dirty stack.
51
- ***************************************************************************** */
52
-
53
- typedef struct {
54
- volatile uintptr_t ref;
55
- resp_object_s *link;
56
- } resp_objhead_s;
57
-
58
- #define OBJHEAD(obj) (((resp_objhead_s *)(obj)) - 1)
59
-
60
- static resp_object_s *resp_alloc_obj(enum resp_type_enum type, size_t length) {
61
- resp_objhead_s *head = NULL;
62
- switch (type) {
63
- /* Fallthrough */
64
- case RESP_ERR:
65
- case RESP_STRING:
66
- /* + 1 for NULL (safety) */
67
- head = malloc(sizeof(*head) + sizeof(resp_string_s) + length + 1);
68
- *head = (resp_objhead_s){.ref = 1};
69
- resp_string_s *s = (void *)(head + 1);
70
- *s = (resp_string_s){.type = type, .len = length};
71
- return (void *)s;
72
- break;
73
- case RESP_NULL:
74
- case RESP_OK:
75
- head = malloc(sizeof(*head) + sizeof(resp_object_s));
76
- *head = (resp_objhead_s){.ref = 1};
77
- resp_object_s *ok = (void *)(head + 1);
78
- *ok = (resp_object_s){.type = type};
79
- return (void *)ok;
80
- break;
81
- case RESP_ARRAY:
82
- case RESP_PUBSUB:
83
- head = malloc(sizeof(*head) + sizeof(resp_array_s) +
84
- (sizeof(resp_object_s *) * length));
85
- *head = (resp_objhead_s){.ref = 1};
86
- resp_array_s *ar = (void *)(head + 1);
87
- *ar = (resp_array_s){.type = type, .len = length};
88
- return (void *)ar;
89
- break;
90
- case RESP_NUMBER:
91
- head = malloc(sizeof(*head) + sizeof(resp_number_s));
92
- *head = (resp_objhead_s){.ref = 1};
93
- resp_number_s *i = (void *)(head + 1);
94
- *i = (resp_number_s){.type = RESP_NUMBER, .number = length};
95
- return (void *)i;
96
- break;
97
- }
98
- return NULL;
99
- }
100
-
101
- static int resp_dealloc_obj(resp_parser_pt p, resp_object_s *obj, void *arg) {
102
- if (obj && !spn_sub(&OBJHEAD(obj)->ref, 1))
103
- free(OBJHEAD(obj));
104
- (void)arg;
105
- (void)p;
106
- return 0;
107
- }
108
-
109
- inline static resp_object_s *pop_obj(resp_object_s *obj) {
110
- resp_object_s *ret = OBJHEAD(obj)->link;
111
- if (ret) {
112
- OBJHEAD(obj)->link = OBJHEAD(ret)->link;
113
- OBJHEAD(ret)->link = NULL;
114
- } else
115
- OBJHEAD(obj)->link = NULL;
116
- return ret;
117
- }
118
-
119
- inline static void push_obj(resp_object_s *dest, resp_object_s *obj) {
120
- if (!dest)
121
- return;
122
- if (obj) {
123
- OBJHEAD(obj)->link = OBJHEAD(dest)->link;
124
- }
125
- OBJHEAD(dest)->link = obj;
126
- }
127
-
128
- static int mock_obj_tks(resp_parser_pt p, resp_object_s *obj, void *arg) {
129
- return 0;
130
- (void)arg;
131
- (void)p;
132
- (void)obj;
133
- }
134
-
135
- /** Performs a task on each object. Protects from loop-backs. */
136
- size_t resp_obj_each(resp_parser_pt p, resp_object_s *obj,
137
- int (*task)(resp_parser_pt p, resp_object_s *obj,
138
- void *arg),
139
- void *arg) {
140
-
141
- if (!obj)
142
- return 0;
143
- if (!task)
144
- task = mock_obj_tks;
145
- /* make sure the object isn't "dirty"*/
146
- OBJHEAD(obj)->link = NULL;
147
- /* a searchable store. */
148
- resp_objhead_s performed[3] = {{.link = NULL}}; /* a memory lump. */
149
- resp_object_s *past = (resp_object_s *)(performed + 1);
150
- resp_object_s *next = (resp_object_s *)(performed + 2);
151
- OBJHEAD(past)->link = NULL;
152
- OBJHEAD(next)->link = NULL; /* a straight line */
153
- push_obj(next, obj);
154
- resp_object_s *err = NULL; /* might host an error object... if we need one. */
155
- resp_object_s *tmp;
156
- size_t count = 0;
157
- obj = pop_obj(next);
158
- while (obj) {
159
- count++;
160
- if (obj->type == RESP_ARRAY || obj->type == RESP_PUBSUB)
161
- goto is_array;
162
-
163
- perform:
164
- /* in case the task is to free `obj`, "pop" the next object first */
165
- tmp = obj;
166
- obj = pop_obj(next);
167
- if (task(p, tmp, arg) == -1)
168
- break;
169
- continue;
170
-
171
- is_array:
172
- /* test for loop-back. */
173
- tmp = OBJHEAD(past)->link;
174
- while (tmp) {
175
- if (obj == tmp)
176
- goto skip;
177
- tmp = OBJHEAD(tmp)->link;
178
- }
179
- /* add current object to the loop back stack */
180
- push_obj(past, obj);
181
-
182
- /* Add all array items to the stack. */
183
- resp_obj2arr(obj)->pos = resp_obj2arr(obj)->len;
184
- while (resp_obj2arr(obj)->pos != 0) {
185
- resp_obj2arr(obj)->pos--;
186
- if (!resp_obj2arr(obj)
187
- ->array[resp_obj2arr(obj)->pos]) /* fill any holes...? */
188
- resp_obj2arr(obj)->array[resp_obj2arr(obj)->pos] = resp_nil2obj();
189
- push_obj(next, resp_obj2arr(obj)->array[resp_obj2arr(obj)->pos]);
190
- }
191
- goto perform;
192
-
193
- skip:
194
- if (task != resp_dealloc_obj) {
195
- if (!err)
196
- err =
197
- resp_err2obj("ERR: infinit loopback detected. Can't process.", 46);
198
- obj = err;
199
- } else
200
- obj = resp_err2obj("ERR: infinit loopback detected. Can't process.", 46);
201
- goto perform;
202
- }
203
-
204
- /* cleaup */
205
- if (task != resp_dealloc_obj) {
206
- if (err)
207
- resp_dealloc_obj(NULL, err, NULL);
208
- while ((tmp = pop_obj(past)))
209
- ;
210
- }
211
- return count;
212
- }
213
-
214
- /* *****************************************************************************
215
- Object API
216
- ***************************************************************************** */
217
-
218
- /** Allocates an RESP NULL objcet. Remeber to free when done. */
219
- resp_object_s *resp_nil2obj(void) { return resp_alloc_obj(RESP_NULL, 0); }
220
-
221
- /** Allocates an RESP OK objcet. Remeber to free when done. */
222
- resp_object_s *resp_OK2obj(void) { return resp_alloc_obj(RESP_OK, 0); }
223
-
224
- /** Allocates an RESP Error objcet. Remeber to free when done. */
225
- resp_object_s *resp_err2obj(const void *msg, size_t len) {
226
- resp_string_s *o = (resp_string_s *)resp_alloc_obj(RESP_STRING, len);
227
- memcpy(o->string, msg, len);
228
- o->string[len] = 0;
229
- return (void *)o;
230
- }
231
-
232
- /** Allocates an RESP Number objcet. Remeber to free when done. */
233
- resp_object_s *resp_num2obj(uint64_t num) {
234
- return resp_alloc_obj(RESP_NUMBER, num);
235
- }
236
-
237
- /** Allocates an RESP String objcet. Remeber to free when done. */
238
- resp_object_s *resp_str2obj(const void *str, size_t len) {
239
- resp_string_s *o = (resp_string_s *)resp_alloc_obj(RESP_STRING, len);
240
- memcpy(o->string, str, len);
241
- o->string[len] = 0;
242
- return (void *)o;
243
- }
244
-
245
- /** Allocates an RESP Array objcet. Remeber to free when done. */
246
- resp_object_s *resp_arr2obj(int argc, resp_object_s *argv[]) {
247
- if (argc < 0)
248
- return NULL;
249
- resp_array_s *o = (resp_array_s *)resp_alloc_obj(RESP_ARRAY, argc);
250
- if (argv) {
251
- for (int i = 0; i < argc; i++) {
252
- o->array[i] = (resp_object_s *)argv[i];
253
- }
254
- }
255
- return (void *)o;
256
- }
257
-
258
- /** frees an object returned from the parser. */
259
- void resp_free_object(resp_object_s *obj) {
260
- resp_obj_each(NULL, obj, resp_dealloc_obj, NULL);
261
- }
262
-
263
- static int resp_dup_obj_task(resp_parser_pt p, resp_object_s *obj, void *arg) {
264
- spn_add(&OBJHEAD(obj)->ref, 1);
265
- return 0;
266
- (void)p;
267
- (void)arg;
268
- }
269
-
270
- /** Duplicates an object by increasing it's reference count. */
271
- resp_object_s *resp_dup_object(resp_object_s *obj) {
272
- resp_obj_each(NULL, obj, resp_dup_obj_task, NULL);
273
- return obj;
274
- }
275
-
276
- /* *****************************************************************************
277
- Formatter (RESP => Memory Buffer)
278
- ***************************************************************************** */
279
- struct resp_format_s {
280
- uint8_t *dest;
281
- size_t *size;
282
- size_t limit;
283
- uintptr_t multiplex_pubsub;
284
- int err;
285
- };
286
-
287
- static int resp_format_task(resp_parser_pt p, resp_object_s *obj, void *s_) {
288
- struct resp_format_s *s = s_;
289
-
290
- #define safe_write_eol() \
291
- if ((*s->size += 2) <= s->limit) { \
292
- *s->dest++ = '\r'; \
293
- *s->dest++ = '\n'; \
294
- }
295
- #define safe_write1(data) \
296
- if (++(*s->size) <= s->limit) { \
297
- *s->dest++ = (data); \
298
- }
299
- #define safe_write2(data, len) \
300
- do { \
301
- *s->size += (len); \
302
- if (*s->size <= s->limit) { \
303
- memcpy(s->dest, (data), (len)); \
304
- s->dest += (len); \
305
- } \
306
- } while (0)
307
- #define safe_write_i(i) \
308
- do { \
309
- int64_t t2 = (i); \
310
- if ((t2) < 0) { \
311
- safe_write1('-'); \
312
- t2 = (t2 * -1); \
313
- } \
314
- size_t len = t2 ? (((size_t)log10((double)(t2))) + 1) : 1; \
315
- *s->size += (len); \
316
- if (*s->size <= s->limit) { \
317
- int64_t t1 = len; \
318
- int64_t t3 = t2 / 10; \
319
- while (t1--) { \
320
- s->dest[t1] = '0' + (t2 - (t3 * 10)); \
321
- t2 = t3; \
322
- t3 = t3 / 10; \
323
- } \
324
- s->dest += len; \
325
- } \
326
- } while (0)
327
-
328
- switch (obj->type) {
329
- case RESP_ERR:
330
- safe_write1('-');
331
- safe_write2((resp_obj2str(obj)->string), (resp_obj2str(obj)->len));
332
- safe_write_eol();
333
- break;
334
- case RESP_NULL:
335
- safe_write2("$-1\r\n", (resp_obj2str(obj)->len));
336
- break;
337
- case RESP_OK:
338
- safe_write2("+OK\r\n", 5);
339
- break;
340
- case RESP_ARRAY:
341
- case RESP_PUBSUB:
342
- safe_write1('*');
343
- safe_write_i(resp_obj2arr(obj)->len);
344
- safe_write_eol();
345
- break;
346
- case RESP_STRING:
347
- safe_write1('$');
348
- safe_write_i(s->multiplex_pubsub ? (resp_obj2str(obj)->len + 1)
349
- : resp_obj2str(obj)->len);
350
- safe_write_eol();
351
- if (s->multiplex_pubsub) {
352
- s->multiplex_pubsub = 0;
353
- safe_write1('+');
354
- }
355
- safe_write2((resp_obj2str(obj)->string), (resp_obj2str(obj)->len));
356
- safe_write_eol();
357
- break;
358
- case RESP_NUMBER:
359
- safe_write1(':');
360
- safe_write_i((resp_obj2num(obj)->number));
361
- safe_write_eol();
362
- break;
363
- }
364
- return 0;
365
- (void)p;
366
- }
367
- #undef safe_write_eol
368
- #undef safe_write1
369
- #undef safe_write2
370
- #undef safe_write_i
371
-
372
- int resp_format(resp_parser_pt p, uint8_t *dest, size_t *size,
373
- resp_object_s *obj) {
374
- struct resp_format_s arg = {
375
- .dest = dest,
376
- .size = size,
377
- .limit = *size,
378
- .multiplex_pubsub =
379
- (p && p->multiplex_pubsub && obj->type == RESP_ARRAY &&
380
- ((resp_array_s *)obj)->len &&
381
- ((resp_array_s *)obj)->array[0]->type == RESP_STRING &&
382
- ((((resp_string_s *)((resp_array_s *)obj)->array[0])->string[0] ==
383
- 'm') ||
384
- (((resp_string_s *)((resp_array_s *)obj)->array[0])->string[0] ==
385
- 'M') ||
386
- (((resp_string_s *)((resp_array_s *)obj)->array[0])->string[0] ==
387
- '+')))};
388
- *size = 0;
389
- resp_obj_each(p, obj, resp_format_task, &arg);
390
- if (*arg.size < arg.limit) {
391
- *arg.dest = 0;
392
- return 0;
393
- }
394
- if (*arg.size == arg.limit)
395
- return 0;
396
- return -1;
397
- }
398
-
399
- /* *****************************************************************************
400
- Parser (Memory Buffer => RESP)
401
- ***************************************************************************** */
402
-
403
- /** create the parser */
404
- resp_parser_pt resp_parser_new(void) {
405
- resp_parser_s *p = malloc(sizeof(*p));
406
- *p = (resp_parser_s){.obj = NULL};
407
- return p;
408
- }
409
-
410
- static inline void reset_parser(resp_parser_pt p) {
411
- resp_object_s *tmp;
412
- while ((tmp = p->obj)) {
413
- p->obj = OBJHEAD(p->obj)->link;
414
- resp_free_object(tmp);
415
- }
416
- if (p->leftovers)
417
- free(p->leftovers);
418
- if (p->obj)
419
- resp_free_object(p->obj);
420
- *p = (resp_parser_s){.obj = NULL, .multiplex_pubsub = p->multiplex_pubsub};
421
- }
422
-
423
- /** free the parser and it's resources. */
424
- void resp_parser_destroy(resp_parser_pt p) {
425
- reset_parser(p);
426
- free(p);
427
- }
428
-
429
- void resp_parser_clear(resp_parser_pt p) {
430
- reset_parser(p);
431
- *p = (resp_parser_s){.obj = NULL};
432
- }
433
-
434
- /**
435
- * Feed the parser with data.
436
- *
437
- * Returns any fully parsed object / reply (often an array, but not always) or
438
- * NULL (needs more data / error).
439
- *
440
- * If an error occurs, the `pos` pointer is set to -1, otherwise it's updated
441
- * with the amount of data consumed.
442
- *
443
- * Partial consumption is possible when multiple replys were available in the
444
- * buffer. Otherwise the parser will consume the whole of the buffer.
445
- *
446
- */
447
- resp_object_s *resp_parser_feed(resp_parser_pt p, uint8_t *buf, size_t *len) {
448
- resp_object_s *tmp;
449
- uint8_t *pos = buf;
450
- uint8_t *eol = NULL;
451
- uint8_t *end = pos + (*len);
452
- int64_t num;
453
- uint8_t flag;
454
- restart:
455
-
456
- // while (pos < end && (*pos == '\r' || *pos == '\n'))
457
- // pos++; /* consume empty EOL markers. */
458
-
459
- if (p->missing && p->obj->type == RESP_STRING) {
460
- /* test for pub/sub duplexing extension */
461
- if (!p->has_first_object && p->multiplex_pubsub && pos[0] == '+' &&
462
- (pos[1] == '+' || pos[1] == 'm' || pos[1] == 'M')) {
463
- pos++;
464
- p->exclude_pubsub = 1;
465
- p->missing--;
466
- }
467
- p->has_first_object = 1;
468
- /* just fill the string with missing bytes */
469
- if (p->missing > (size_t)(end - pos)) {
470
- memcpy(resp_obj2str(p->obj)->string + resp_obj2str(p->obj)->len -
471
- p->missing,
472
- pos, (size_t)(end - pos));
473
- p->missing -= (size_t)(end - pos);
474
- pos = end;
475
- goto finish;
476
- } else {
477
- memcpy(resp_obj2str(p->obj)->string + resp_obj2str(p->obj)->len -
478
- p->missing,
479
- pos, p->missing);
480
- resp_obj2str(p->obj)->string[resp_obj2str(p->obj)->len] = 0; /* set NUL */
481
- pos += p->missing + 2; /* eat the EOL */
482
- /* set state to equal a freash, complete object */
483
- p->missing = 0;
484
- tmp = p->obj;
485
- p->obj = OBJHEAD(p->obj)->link;
486
- goto review;
487
- }
488
- }
489
-
490
- /* seek EOL */
491
- eol = pos;
492
- while (eol < end - 1) {
493
- if (eol[0] == '\r' && eol[1] == '\n')
494
- goto found_eol;
495
- eol++;
496
- }
497
-
498
- /* no EOL, we can't parse. */
499
- if (p->llen + (end - pos) >=
500
- 131072) { /* IMHO: simple objects are smaller than 128Kib. */
501
- REPORT("ERROR: (RESP parser) single line object too long. "
502
- "128Kib limit for simple strings, numbers exc'.\n");
503
- eol = NULL;
504
- goto error;
505
- }
506
- {
507
- void *tmp = realloc(p->leftovers, p->llen + (end - pos));
508
- if (!tmp) {
509
- fprintf(stderr, "ERROR: (RESP parser) Couldn't allocate memory.\n");
510
- goto error;
511
- }
512
- p->leftovers = (void *)tmp;
513
- memcpy(p->leftovers + p->llen, pos, end - pos);
514
- p->llen += (end - pos);
515
- }
516
- goto finish;
517
-
518
- found_eol:
519
-
520
- *eol = 0; /* mark with NUL */
521
-
522
- num = eol - pos - 1;
523
- /* route `pos` to p->leftovers, if data was fragmented. */
524
- if (p->leftovers) {
525
- void *tmp = realloc(p->leftovers, p->llen + num + 1);
526
- if (!tmp) {
527
- REPORT("ERROR: (RESP parser) Couldn't allocate memory.\n");
528
- goto error;
529
- }
530
- p->leftovers = (void *)tmp;
531
- memcpy(p->leftovers + p->llen, pos, num + 1); /* copy the NUL byte. */
532
- p->llen += (end - pos);
533
- pos = p->leftovers;
534
- }
535
-
536
- /* ** let's actually parse something ** put new objects in `tmp` ** */
537
- switch (*pos++) {
538
- case '*': /* Array */
539
- num = 0;
540
- while (*pos) {
541
- if (*pos < '0' || *pos > '9')
542
- goto error;
543
- num = (num * 10) + (*pos - '0');
544
- pos++;
545
- }
546
- tmp = resp_alloc_obj(RESP_ARRAY, num);
547
- p->missing = num;
548
- break;
549
-
550
- case '$': /* String */
551
- if (*pos == '-') {
552
- if (pos[1] == '1') {
553
- /* NULL Object */
554
- tmp = resp_alloc_obj(RESP_NULL, -1);
555
- p->missing = 0;
556
- } else {
557
- REPORT("ERROR: (RESP parser) Bulk String input error.\n");
558
- goto error;
559
- }
560
- } else {
561
- num = 0;
562
- while (*pos) {
563
- if (*pos < '0' || *pos > '9') {
564
- REPORT("ERROR: (RESP parser) Bulk String length error.\n");
565
- goto error;
566
- }
567
- num = (num * 10) + (*pos - '0');
568
- pos++;
569
- }
570
- tmp = resp_alloc_obj(RESP_STRING, num);
571
- p->missing = num;
572
- if (!num) {
573
- *eol = '\r';
574
- eol += 2;
575
- }
576
- }
577
- break;
578
-
579
- case '+': /* Simple String */
580
- case '-': /* Error String */
581
- p->has_first_object = 1;
582
- if (num == 2 && pos[0] == 'O' && pos[1] == 'K') {
583
- tmp = resp_alloc_obj(RESP_OK, 0);
584
- } else {
585
- tmp = resp_alloc_obj((pos[-1] == '+' ? RESP_STRING : RESP_ERR), num);
586
- memcpy(((resp_string_s *)tmp)->string, pos, num + 1); /* copy NUL */
587
- }
588
- p->missing = 0;
589
- break;
590
-
591
- case ':': /* number */
592
- p->has_first_object = 1;
593
- num = 0;
594
- flag = 0;
595
- if (*pos == '-') {
596
- flag = 1;
597
- pos++;
598
- }
599
- while (*pos) {
600
- if (*pos < '0' || *pos > '9') {
601
- REPORT("ERROR: (RESP parser) input error.\n");
602
- goto error;
603
- }
604
- num = (num * 10) + (*pos - '0');
605
- pos++;
606
- }
607
- if (flag)
608
- num = num * -1;
609
- tmp = resp_alloc_obj(RESP_NUMBER, num);
610
- p->missing = 0;
611
- break;
612
- default:
613
- REPORT("ERROR: (RESP Parser) input prefix unknown\n");
614
- goto error;
615
- }
616
- /* replace parsing marker */
617
- pos = eol + 2;
618
- /* un-effect the EOL */
619
- *eol = '\r';
620
- eol = NULL;
621
-
622
- /* clear the buffer used to handle fragmented transmissions. */
623
- if (p->leftovers) {
624
- free(p->leftovers);
625
- p->leftovers = NULL;
626
- p->llen = 0;
627
- }
628
-
629
- review:
630
- /* handle object rotation and nesting */
631
- if (p->missing) {
632
- /* tmp missing data: link and step into new object (nesting objects) */
633
- OBJHEAD(tmp)->link = p->obj;
634
- p->obj = tmp;
635
- tmp = NULL;
636
- goto restart;
637
- } else if (p->obj) {
638
- if (p->obj->type != RESP_ARRAY) {
639
- /* Nesting of objects can only be performed by RESP_ARRAY objects. */
640
- fprintf(stderr, "ERROR: (RESP Parser) internal error - "
641
- "objects can only be nested within arrays.\n");
642
- goto error;
643
- }
644
- resp_obj2arr(p->obj)->array[resp_obj2arr(p->obj)->pos++] = tmp;
645
- p->missing = resp_obj2arr(p->obj)->len - resp_obj2arr(p->obj)->pos;
646
- if (p->missing)
647
- goto restart; /* collect more objects. */
648
- /* un-nest */
649
- tmp = p->obj;
650
- p->obj = OBJHEAD(p->obj)->link;
651
- goto review;
652
- }
653
-
654
- /* tmp now holds the top-most object, it's missing no data, we're done */
655
-
656
- /* test for pub/sub semantics */
657
- if (!p->exclude_pubsub) {
658
- if (tmp->type == RESP_ARRAY && resp_obj2arr(tmp)->len == 3 &&
659
- resp_obj2arr(tmp)->array[0]->type == RESP_STRING &&
660
- resp_obj2arr(tmp)->array[1]->type == RESP_STRING &&
661
- resp_obj2arr(tmp)->array[2]->type == RESP_STRING &&
662
- resp_obj2str(resp_obj2arr(tmp)->array[0])->len == 7 &&
663
- !memcmp("message", resp_obj2str(resp_obj2arr(tmp)->array[0])->string,
664
- 7)) {
665
- /* PUB / SUB */
666
- tmp->type = RESP_PUBSUB;
667
- }
668
- }
669
- /* reset the parser, keeping it's state. */
670
- reset_parser(p);
671
- /* report how much the parser actually "ate" */
672
- *len = pos - buf;
673
- /* return the result. */
674
- return tmp;
675
-
676
- error:
677
- reset_parser(p);
678
- *len = 0;
679
- if (eol)
680
- *eol = '\r';
681
-
682
- finish:
683
- return NULL;
684
- }
685
-
686
- /* *****************************************************************************
687
- Tests
688
- ***************************************************************************** */
689
- #ifdef DEBUG
690
- #include <inttypes.h>
691
- #include <stdint.h>
692
- void resp_test(void) {
693
- uint8_t b_null[] = "$-1\r\n";
694
- uint8_t b_ok[] = "+OK\r\n";
695
- uint8_t b_num[] = ":13\r\n";
696
- uint8_t b_neg_num[] = ":-13\r\n";
697
- uint8_t b_err[] = "-ERR: or not :-)\r\n";
698
- uint8_t b_str[] = "$19\r\nthis is a string :)\r\n";
699
- uint8_t b_longer[] = "*8\r\n"
700
- ":1\r\n"
701
- ":2\r\n"
702
- ":3\r\n"
703
- ":4\r\n"
704
- ":-5794\r\n"
705
- "$6\r\n"
706
- "foobar\r\n"
707
- "$6\r\n"
708
- "barfoo\r\n"
709
- ":4\r\n";
710
- resp_parser_pt parser = resp_parser_new();
711
- size_t len;
712
-
713
- resp_object_s *obj;
714
-
715
- fprintf(stderr, "* OBJHEAD test %s\n",
716
- (OBJHEAD(((resp_objhead_s *)NULL) + 1) == NULL) ? "passed"
717
- : "FAILED");
718
-
719
- {
720
- obj = resp_OK2obj();
721
- resp_object_s *tmp = resp_nil2obj();
722
- push_obj(obj, tmp);
723
- fprintf(stderr, "* Push/Pop test: %s\n",
724
- (pop_obj(obj) == tmp && pop_obj(obj) == NULL) ? "ok." : "FAILED!");
725
- resp_free_object(obj);
726
- resp_free_object(tmp);
727
- }
728
-
729
- len = sizeof(b_null) - 1;
730
- obj = resp_parser_feed(parser, b_null, &len);
731
- if (obj && obj->type == RESP_NULL) {
732
- fprintf(stderr, "* NULL recognized, pos == %lu / %lu\n", len,
733
- sizeof(b_null) - 1);
734
- resp_free_object(obj);
735
- } else
736
- fprintf(stderr, "* NULL FAILED\n");
737
-
738
- len = sizeof(b_ok) - 1;
739
- obj = resp_parser_feed(parser, b_ok, &len);
740
- if (obj && obj->type == RESP_OK) {
741
- fprintf(stderr, "* OK recognized\n");
742
- resp_free_object(obj);
743
- } else
744
- fprintf(stderr, "* OK FAILED\n");
745
-
746
- len = sizeof(b_err) - 1;
747
- obj = resp_parser_feed(parser, b_err, &len);
748
- if (obj && obj->type == RESP_ERR) {
749
- fprintf(stderr, "* ERR / Simple String recognized: %s\n",
750
- resp_obj2str(obj)->string);
751
- resp_free_object(obj);
752
- } else
753
- fprintf(stderr, "* ERR / Simple String FAILED (type %d)\n",
754
- obj ? (int)obj->type : -1);
755
-
756
- len = sizeof(b_num) - 1;
757
- obj = resp_parser_feed(parser, b_num, &len);
758
- if (obj && obj->type == RESP_NUMBER) {
759
- fprintf(stderr, "* Number recognized: %lld\n", resp_obj2num(obj)->number);
760
- resp_free_object(obj);
761
- } else
762
- fprintf(stderr, "* Number FAILED\n");
763
-
764
- len = sizeof(b_neg_num) - 1;
765
- obj = resp_parser_feed(parser, b_neg_num, &len);
766
- if (obj && obj->type == RESP_NUMBER) {
767
- fprintf(stderr, "* Negative Number recognized: %lld\n",
768
- resp_obj2num(obj)->number);
769
- resp_free_object(obj);
770
- } else
771
- fprintf(stderr, "* Negative Number FAILED\n");
772
-
773
- len = sizeof(b_str) - 1;
774
- obj = resp_parser_feed(parser, b_str, &len);
775
- if (obj && obj->type == RESP_STRING) {
776
- fprintf(stderr, "* String recognized: %s\n", resp_obj2str(obj)->string);
777
- resp_free_object(obj);
778
- } else
779
- fprintf(stderr, "* String FAILED\n");
780
-
781
- {
782
- uint8_t empty_str[] = "$0\r\n\r\n";
783
- len = sizeof(empty_str) - 1;
784
- obj = resp_parser_feed(parser, empty_str, &len);
785
- if (obj && obj->type == RESP_STRING) {
786
- fprintf(stderr, "* Empty String recognized: %s\n",
787
- resp_obj2str(obj)->string);
788
- resp_free_object(obj);
789
- } else
790
- fprintf(stderr, "* Empty String FAILED\n");
791
- }
792
- len = sizeof(b_longer) - 1;
793
- obj = resp_parser_feed(parser, b_longer, &len);
794
- if (obj && obj->type == RESP_ARRAY) {
795
- fprintf(stderr, "* Array head recognized: (%lu)\n", resp_obj2arr(obj)->len);
796
- resp_object_s *tmp;
797
- for (size_t i = 0; i < resp_obj2arr(obj)->len; i++) {
798
- tmp = resp_obj2arr(obj)->array[i];
799
- if (resp_obj2arr(tmp)) {
800
- fprintf(stderr, "Item %lu is an .... array?!\n", i);
801
- }
802
- if (resp_obj2str(tmp)) {
803
- fprintf(stderr, "Item %lu is a String %s\n", i,
804
- resp_obj2str(tmp)->string);
805
- }
806
- if (resp_obj2num(tmp)) {
807
- fprintf(stderr, "Item %lu is a Number %" PRIi64 "\n", i,
808
- resp_obj2num(tmp)->number);
809
- }
810
- }
811
- {
812
- fprintf(stderr, "found %lu objects\n",
813
- resp_obj_each(NULL, obj, NULL, NULL));
814
- uint8_t buff[128] = {0};
815
- size_t len = 127;
816
- resp_format(NULL, buff, &len, obj);
817
- fprintf(stderr,
818
- "* In RESP format, it should take %lu bytes like so:\n%s\n", len,
819
- buff);
820
- }
821
- resp_free_object(obj);
822
- } else {
823
- fprintf(stderr, "* Array FAILED (type == %d)\n", obj ? (int)obj->type : -1);
824
- }
825
- {
826
- // uint8_t buff[128] = {0};
827
- // size_t len = 128;
828
- // resp_object_s *arr1 = resp_arr2obj(1, NULL);
829
- // resp_object_s *arr2 = resp_arr2obj(1, NULL);
830
- // resp_obj2arr(arr1)->array[0] = resp_dup_object(arr2);
831
- // resp_obj2arr(arr2)->array[0] = resp_dup_object(arr1);
832
- // resp_format(NULL, buff, &len, arr1);
833
- // fprintf(stderr, "* Loopback test:\n%s\n", buff);
834
- // resp_free_object(arr1);
835
- // resp_free_object(arr2);
836
- }
837
-
838
- resp_parser_destroy(parser);
839
- return;
840
- }
841
-
842
- #endif