BadVPN – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 /**
2 * @file sys_request_client.c
3 * @author Ambroz Bizjak <ambrop7@gmail.com>
4 *
5 * @section LICENSE
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of the author nor the
15 * names of its contributors may be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 *
29 * @section DESCRIPTION
30 *
31 * Synopsis:
32 * sys.request_client(string connect_addr)
33 *
34 * Description:
35 * Connects to a request server (sys.request_server()).
36 * Goes up when the connection, and dies with error when it is broken.
37 * When requested to die, dies immediately, breaking the connection.
38 *
39 * The connect address should be in the same format as for the socket module.
40 * In particular, it must be in one of the following forms:
41 * - {"tcp", {"ipv4", ipv4_address, port_number}},
42 * - {"tcp", {"ipv6", ipv6_address, port_number}},
43 * - {"unix", socket_path}.
44 *
45 * Synopsis:
46 * sys.request_client::request(request_data, string reply_handler, string finished_handler, list args)
47 *
48 * Description:
49 * Sends a request to the server and dispatches replies to the provided handlers.
50 *
51 * The 'request_data' argument is sent as part of the request and is used by the server
52 * to determine what to do with the request.
53 *
54 * When a reply is received, a new template process is created from 'reply_handler' to process the
55 * reply. This process can access the reply data sent by the server using '_reply.data'.
56 * Similarly, if the server finishes the request, a process is created from 'finished_handler'.
57 * In both cases, the process can access objects as seen from the request statement via "_caller".
58 * Termination of these processes is initiated immediately after they completes. They are created
59 * synchronously - if a reply or a finished message arrives before a previous process is has
60 * finished, it is queued. Once the finished message has been processed by 'finished_handler', no
61 * more processes will be created.
62 *
63 * When the request statement is requested to terminate, it initiates termination of the current
64 * handler process and waits for it to terminate (if any is running), and then dies.
65 * If the corresponding client statement dies after being requested to die, or as a result of
66 * an error, the request statement will not react to this. It will dispatch any pending messages
67 * and then proceed to do nothing. In this case, if a finished message was not received, it will
68 * not be dispatched.
69 *
70 * The request statement may however die at any time due to errors. In this case, it will
71 * initiate termination of the current process and wait for it to terminate (if any) before dying.
72 *
73 * The request protocol and the server allow the client the abort requests at any time, and to
74 * have the client notified only after the request has been completely aborted (i.e. the handler
75 * process of sys.request_server() has deinitialized completely). This client implementation will
76 * automatically request abortion of active requests when the request statement is requested
77 * to die. However, the request statement will not wait for the abortion to finish before dying.
78 * This means, for instance, that if you initialize a request statement right after having
79 * deinitiazed it, the requests may overlap on the server side.
80 */
81  
82 #include <stdlib.h>
83 #include <string.h>
84 #include <inttypes.h>
85 #include <limits.h>
86  
87 #include <misc/offset.h>
88 #include <structure/LinkedList0.h>
89 #include <structure/LinkedList1.h>
90 #include <ncd/extra/NCDRequestClient.h>
91 #include <ncd/extra/address_utils.h>
92  
93 #include <ncd/module_common.h>
94  
95 #include <generated/blog_channel_ncd_sys_request_client.h>
96  
97 #define CSTATE_CONNECTING 1
98 #define CSTATE_CONNECTED 2
99  
100 #define RRSTATE_SENDING_REQUEST 1
101 #define RRSTATE_READY 2
102 #define RRSTATE_GONE_BAD 3
103 #define RRSTATE_GONE_GOOD 4
104  
105 #define RPSTATE_NONE 1
106 #define RPSTATE_WORKING 2
107 #define RPSTATE_TERMINATING 3
108  
109 #define RDSTATE_NONE 1
110 #define RDSTATE_DYING 2
111 #define RDSTATE_DYING_ERROR 3
112  
113 struct instance {
114 NCDModuleInst *i;
115 NCDRequestClient client;
116 LinkedList0 requests_list;
117 int state;
118 };
119  
120 struct request_instance {
121 NCDModuleInst *i;
122 NCDValRef reply_handler;
123 NCDValRef finished_handler;
124 NCDValRef args;
125 struct instance *client;
126 NCDRequestClientRequest request;
127 LinkedList0Node requests_list_node;
128 LinkedList1 replies_list;
129 NCDModuleProcess process;
130 int process_is_finished;
131 NCDValMem process_reply_mem;
132 NCDValRef process_reply_data;
133 int rstate;
134 int pstate;
135 int dstate;
136 };
137  
138 struct reply {
139 LinkedList1Node replies_list_node;
140 NCDValMem mem;
141 NCDValRef val;
142 };
143  
144 static void client_handler_error (struct instance *o);
145 static void client_handler_connected (struct instance *o);
146 static void request_handler_sent (struct request_instance *o);
147 static void request_handler_reply (struct request_instance *o, NCDValMem reply_mem, NCDValRef reply_value);
148 static void request_handler_finished (struct request_instance *o, int is_error);
149 static void request_process_handler_event (NCDModuleProcess *process, int event);
150 static int request_process_func_getspecialobj (NCDModuleProcess *process, NCD_string_id_t name, NCDObject *out_object);
151 static int request_process_caller_obj_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object);
152 static int request_process_reply_obj_func_getvar (const NCDObject *obj, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out);
153 static void request_gone (struct request_instance *o, int is_bad);
154 static void request_terminate_process (struct request_instance *o);
155 static void request_die (struct request_instance *o, int is_error);
156 static void request_free_reply (struct request_instance *o, struct reply *r, int have_value);
157 static int request_init_reply_process (struct request_instance *o, NCDValMem reply_mem, NCDValSafeRef reply_data);
158 static int request_init_finished_process (struct request_instance *o);
159 static void instance_free (struct instance *o, int with_error);
160 static void request_instance_free (struct request_instance *o, int with_error);
161  
162 enum {STRING_REPLY, STRING_DATA};
163  
164 static const char *strings[] = {
165 "_reply", "data", NULL
166 };
167  
168 static void client_handler_error (struct instance *o)
169 {
170 ModuleLog(o->i, BLOG_ERROR, "client error");
171  
172 // free instance
173 instance_free(o, 1);
174 }
175  
176 static void client_handler_connected (struct instance *o)
177 {
178 ASSERT(o->state == CSTATE_CONNECTING)
179  
180 // signal up
181 NCDModuleInst_Backend_Up(o->i);
182  
183 // set state connected
184 o->state = CSTATE_CONNECTED;
185 }
186  
187 static void request_handler_sent (struct request_instance *o)
188 {
189 ASSERT(o->rstate == RRSTATE_SENDING_REQUEST)
190  
191 // signal up
192 NCDModuleInst_Backend_Up(o->i);
193  
194 // set rstate ready
195 o->rstate = RRSTATE_READY;
196 }
197  
198 static void request_handler_reply (struct request_instance *o, NCDValMem reply_mem, NCDValRef reply_value)
199 {
200 ASSERT(o->rstate == RRSTATE_READY)
201  
202 // queue reply if process is running
203 if (o->pstate != RPSTATE_NONE) {
204 struct reply *r = malloc(sizeof(*r));
205 if (!r) {
206 ModuleLog(o->i, BLOG_ERROR, "malloc failed");
207 goto fail1;
208 }
209 r->mem = reply_mem;
210 r->val = NCDVal_Moved(&r->mem, reply_value);
211 LinkedList1_Append(&o->replies_list, &r->replies_list_node);
212 return;
213 }
214  
215 // start reply process
216 if (!request_init_reply_process(o, reply_mem, NCDVal_ToSafe(reply_value))) {
217 goto fail1;
218 }
219  
220 return;
221  
222 fail1:
223 NCDValMem_Free(&reply_mem);
224 request_die(o, 1);
225 }
226  
227 static void request_handler_finished (struct request_instance *o, int is_error)
228 {
229 ASSERT(o->rstate == RRSTATE_SENDING_REQUEST || o->rstate == RRSTATE_READY)
230 ASSERT(is_error || o->rstate == RRSTATE_READY)
231  
232 if (is_error) {
233 ModuleLog(o->i, BLOG_ERROR, "received error reply");
234 goto fail;
235 }
236  
237 // request gone good
238 request_gone(o, 0);
239  
240 // start process for reporting finished, if possible
241 if (o->pstate == RPSTATE_NONE) {
242 if (!request_init_finished_process(o)) {
243 goto fail;
244 }
245 }
246  
247 return;
248  
249 fail:
250 request_die(o, 1);
251 }
252  
253 static void request_process_handler_event (NCDModuleProcess *process, int event)
254 {
255 struct request_instance *o = UPPER_OBJECT(process, struct request_instance, process);
256 ASSERT(o->pstate != RPSTATE_NONE)
257  
258 switch (event) {
259 case NCDMODULEPROCESS_EVENT_UP: {
260 ASSERT(o->pstate == RPSTATE_WORKING)
261  
262 // request process termination
263 request_terminate_process(o);
264 } break;
265  
266 case NCDMODULEPROCESS_EVENT_DOWN: {
267 ASSERT(0)
268 } break;
269  
270 case NCDMODULEPROCESS_EVENT_TERMINATED: {
271 ASSERT(o->pstate == RPSTATE_TERMINATING)
272 ASSERT(o->rstate != RRSTATE_SENDING_REQUEST)
273  
274 // free process
275 NCDModuleProcess_Free(&o->process);
276  
277 // free reply data
278 if (!o->process_is_finished) {
279 NCDValMem_Free(&o->process_reply_mem);
280 }
281  
282 // set process state none
283 o->pstate = RPSTATE_NONE;
284  
285 // die finally if requested
286 if (o->dstate == RDSTATE_DYING || o->dstate == RDSTATE_DYING_ERROR) {
287 request_instance_free(o, o->dstate == RDSTATE_DYING_ERROR);
288 return;
289 }
290  
291 if (!LinkedList1_IsEmpty(&o->replies_list)) {
292 // get first reply
293 struct reply *r = UPPER_OBJECT(LinkedList1_GetFirst(&o->replies_list), struct reply, replies_list_node);
294  
295 // start reply process
296 if (!request_init_reply_process(o, r->mem, NCDVal_ToSafe(r->val))) {
297 goto fail;
298 }
299  
300 // free reply
301 request_free_reply(o, r, 0);
302 }
303 else if (o->rstate == RRSTATE_GONE_GOOD && !o->process_is_finished) {
304 // start process for reporting finished
305 if (!request_init_finished_process(o)) {
306 goto fail;
307 }
308 }
309  
310 return;
311  
312 fail:
313 request_die(o, 1);
314 } break;
315 }
316 }
317  
318 static int request_process_func_getspecialobj (NCDModuleProcess *process, NCD_string_id_t name, NCDObject *out_object)
319 {
320 struct request_instance *o = UPPER_OBJECT(process, struct request_instance, process);
321 ASSERT(o->pstate != RPSTATE_NONE)
322  
323 if (name == NCD_STRING_CALLER) {
324 *out_object = NCDObject_Build(-1, o, NCDObject_no_getvar, request_process_caller_obj_func_getobj);
325 return 1;
326 }
327  
328 if (!o->process_is_finished && name == ModuleString(o->i, STRING_REPLY)) {
329 *out_object = NCDObject_Build(-1, o, request_process_reply_obj_func_getvar, NCDObject_no_getobj);
330 return 1;
331 }
332  
333 return 0;
334 }
335  
336 static int request_process_caller_obj_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object)
337 {
338 struct request_instance *o = NCDObject_DataPtr(obj);
339 ASSERT(o->pstate != RPSTATE_NONE)
340  
341 return NCDModuleInst_Backend_GetObj(o->i, name, out_object);
342 }
343  
344 static int request_process_reply_obj_func_getvar (const NCDObject *obj, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
345 {
346 struct request_instance *o = NCDObject_DataPtr(obj);
347 ASSERT(o->pstate != RPSTATE_NONE)
348 ASSERT(!o->process_is_finished)
349  
350 if (name == ModuleString(o->i, STRING_DATA)) {
351 *out = NCDVal_NewCopy(mem, o->process_reply_data);
352 return 1;
353 }
354  
355 return 0;
356 }
357  
358 static void request_gone (struct request_instance *o, int is_bad)
359 {
360 ASSERT(o->rstate != RRSTATE_GONE_BAD)
361 ASSERT(o->rstate != RRSTATE_GONE_GOOD)
362  
363 // remove from requests list
364 LinkedList0_Remove(&o->client->requests_list, &o->requests_list_node);
365  
366 // free request
367 NCDRequestClientRequest_Free(&o->request);
368  
369 // set state over
370 o->rstate = (is_bad ? RRSTATE_GONE_BAD : RRSTATE_GONE_GOOD);
371 }
372  
373 static void request_terminate_process (struct request_instance *o)
374 {
375 ASSERT(o->pstate == RPSTATE_WORKING)
376  
377 // request process termination
378 NCDModuleProcess_Terminate(&o->process);
379  
380 // set process state terminating
381 o->pstate = RPSTATE_TERMINATING;
382 }
383  
384 static void request_die (struct request_instance *o, int is_error)
385 {
386 // if we have no process, die right away, else we have to wait for process to terminate
387 if (o->pstate == RPSTATE_NONE) {
388 request_instance_free(o, is_error);
389 return;
390 }
391  
392 // release request
393 if (o->rstate != RRSTATE_GONE_BAD && o->rstate != RRSTATE_GONE_GOOD) {
394 request_gone(o, 1);
395 }
396  
397 // initiate process termination, if needed
398 if (o->pstate != RPSTATE_TERMINATING) {
399 request_terminate_process(o);
400 }
401  
402 // set dstate
403 o->dstate = (is_error ? RDSTATE_DYING_ERROR : RDSTATE_DYING);
404 }
405  
406 static void request_free_reply (struct request_instance *o, struct reply *r, int have_value)
407 {
408 // remove from replies list
409 LinkedList1_Remove(&o->replies_list, &r->replies_list_node);
410  
411 // free value
412 if (have_value) {
413 NCDValMem_Free(&r->mem);
414 }
415  
416 // free structure
417 free(r);
418 }
419  
420 static int request_init_reply_process (struct request_instance *o, NCDValMem reply_mem, NCDValSafeRef reply_data)
421 {
422 ASSERT(o->pstate == RPSTATE_NONE)
423  
424 // set parameters
425 o->process_is_finished = 0;
426 o->process_reply_mem = reply_mem;
427 o->process_reply_data = NCDVal_FromSafe(&o->process_reply_mem, reply_data);
428  
429 // init process
430 if (!NCDModuleProcess_InitValue(&o->process, o->i, o->reply_handler, o->args, request_process_handler_event)) {
431 ModuleLog(o->i, BLOG_ERROR, "NCDModuleProcess_Init failed");
432 goto fail0;
433 }
434  
435 // set special objects function
436 NCDModuleProcess_SetSpecialFuncs(&o->process, request_process_func_getspecialobj);
437  
438 // set process state working
439 o->pstate = RPSTATE_WORKING;
440 return 1;
441  
442 fail0:
443 return 0;
444 }
445  
446 static int request_init_finished_process (struct request_instance *o)
447 {
448 ASSERT(o->pstate == RPSTATE_NONE)
449  
450 // set parameters
451 o->process_is_finished = 1;
452  
453 // init process
454 if (!NCDModuleProcess_InitValue(&o->process, o->i, o->finished_handler, o->args, request_process_handler_event)) {
455 ModuleLog(o->i, BLOG_ERROR, "NCDModuleProcess_Init failed");
456 goto fail0;
457 }
458  
459 // set special objects function
460 NCDModuleProcess_SetSpecialFuncs(&o->process, request_process_func_getspecialobj);
461  
462 // set process state working
463 o->pstate = RPSTATE_WORKING;
464 return 1;
465  
466 fail0:
467 return 0;
468 }
469  
470 static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
471 {
472 struct instance *o = vo;
473 o->i = i;
474  
475 // check arguments
476 NCDValRef connect_addr_arg;
477 if (!NCDVal_ListRead(params->args, 1, &connect_addr_arg)) {
478 ModuleLog(o->i, BLOG_ERROR, "wrong arity");
479 goto fail0;
480 }
481  
482 // read connect address
483 struct BConnection_addr addr;
484 if (!ncd_read_bconnection_addr(connect_addr_arg, &addr)) {
485 ModuleLog(o->i, BLOG_ERROR, "wrong connect address");
486 goto fail0;
487 }
488  
489 // init client
490 if (!NCDRequestClient_Init(&o->client, addr, i->params->iparams->reactor, i->params->iparams->string_index, o,
491 (NCDRequestClient_handler_error)client_handler_error,
492 (NCDRequestClient_handler_connected)client_handler_connected)) {
493 ModuleLog(o->i, BLOG_ERROR, "NCDRequestClient_Init failed");
494 goto fail0;
495 }
496  
497 // init requests list
498 LinkedList0_Init(&o->requests_list);
499  
500 // set state connecting
501 o->state = CSTATE_CONNECTING;
502 return;
503  
504 fail0:
505 NCDModuleInst_Backend_DeadError(i);
506 }
507  
508 static void instance_free (struct instance *o, int with_error)
509 {
510 // deal with requests
511 LinkedList0Node *ln;
512 while (ln = LinkedList0_GetFirst(&o->requests_list)) {
513 struct request_instance *req = UPPER_OBJECT(ln, struct request_instance, requests_list_node);
514 request_gone(req, 1);
515 }
516  
517 // free client
518 NCDRequestClient_Free(&o->client);
519  
520 if (with_error) {
521 NCDModuleInst_Backend_DeadError(o->i);
522 } else {
523 NCDModuleInst_Backend_Dead(o->i);
524 }
525 }
526  
527 static void func_die (void *vo)
528 {
529 struct instance *o = vo;
530  
531 instance_free(o, 0);
532 }
533  
534 static void request_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
535 {
536 struct request_instance *o = vo;
537 o->i = i;
538  
539 // check arguments
540 NCDValRef request_data_arg;
541 NCDValRef reply_handler_arg;
542 NCDValRef finished_handler_arg;
543 NCDValRef args_arg;
544 if (!NCDVal_ListRead(params->args, 4, &request_data_arg, &reply_handler_arg, &finished_handler_arg, &args_arg)) {
545 ModuleLog(o->i, BLOG_ERROR, "wrong arity");
546 goto fail0;
547 }
548 if (!NCDVal_IsString(reply_handler_arg) || !NCDVal_IsString(finished_handler_arg) ||
549 !NCDVal_IsList(args_arg)
550 ) {
551 ModuleLog(o->i, BLOG_ERROR, "wrong type");
552 goto fail0;
553 }
554 o->reply_handler = reply_handler_arg;
555 o->finished_handler = finished_handler_arg;
556 o->args = args_arg;
557  
558 // get client
559 struct instance *client = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
560 o->client = client;
561  
562 // check client state
563 if (client->state != CSTATE_CONNECTED) {
564 ModuleLog(o->i, BLOG_ERROR, "client is not connected");
565 goto fail0;
566 }
567  
568 // init request
569 if (!NCDRequestClientRequest_Init(&o->request, &client->client, request_data_arg, o,
570 (NCDRequestClientRequest_handler_sent)request_handler_sent,
571 (NCDRequestClientRequest_handler_reply)request_handler_reply,
572 (NCDRequestClientRequest_handler_finished)request_handler_finished)) {
573 ModuleLog(o->i, BLOG_ERROR, "NCDRequestClientRequest_Init failed");
574 goto fail0;
575 }
576  
577 // add to requests list
578 LinkedList0_Prepend(&client->requests_list, &o->requests_list_node);
579  
580 // init replies list
581 LinkedList1_Init(&o->replies_list);
582  
583 // set state
584 o->rstate = RRSTATE_SENDING_REQUEST;
585 o->pstate = RPSTATE_NONE;
586 o->dstate = RDSTATE_NONE;
587 return;
588  
589 fail0:
590 NCDModuleInst_Backend_DeadError(i);
591 }
592  
593 static void request_instance_free (struct request_instance *o, int with_error)
594 {
595 ASSERT(o->pstate == RPSTATE_NONE)
596  
597 // free replies
598 LinkedList1Node *ln;
599 while (ln = LinkedList1_GetFirst(&o->replies_list)) {
600 struct reply *r = UPPER_OBJECT(ln, struct reply, replies_list_node);
601 request_free_reply(o, r, 1);
602 }
603  
604 // release request
605 if (o->rstate != RRSTATE_GONE_BAD && o->rstate != RRSTATE_GONE_GOOD) {
606 request_gone(o, 1);
607 }
608  
609 if (with_error) {
610 NCDModuleInst_Backend_DeadError(o->i);
611 } else {
612 NCDModuleInst_Backend_Dead(o->i);
613 }
614 }
615  
616 static void request_func_die (void *vo)
617 {
618 struct request_instance *o = vo;
619  
620 request_die(o, 0);
621 }
622  
623 static struct NCDModule modules[] = {
624 {
625 .type = "sys.request_client",
626 .func_new2 = func_new,
627 .func_die = func_die,
628 .alloc_size = sizeof(struct instance)
629 }, {
630 .type = "sys.request_client::request",
631 .func_new2 = request_func_new,
632 .func_die = request_func_die,
633 .alloc_size = sizeof(struct request_instance)
634 }, {
635 .type = NULL
636 }
637 };
638  
639 const struct NCDModuleGroup ncdmodule_sys_request_client = {
640 .modules = modules,
641 .strings = strings
642 };