1/*
2 * Copyright (c) 2003-2006 Niels Provos <provos@citi.umich.edu>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. The name of the author may not be used to endorse or promote products
14 *    derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#ifdef WIN32
29#include <winsock2.h>
30#include <windows.h>
31#endif
32
33#ifdef HAVE_CONFIG_H
34#include "config.h"
35#endif
36
37#include <sys/types.h>
38#include <sys/stat.h>
39#ifdef HAVE_SYS_TIME_H
40#include <sys/time.h>
41#endif
42#include <sys/queue.h>
43#ifndef WIN32
44#include <sys/socket.h>
45#include <signal.h>
46#include <unistd.h>
47#include <netdb.h>
48#endif
49#include <fcntl.h>
50#include <stdlib.h>
51#include <stdio.h>
52#include <string.h>
53#include <errno.h>
54#include <assert.h>
55
56#include "event.h"
57#include "evhttp.h"
58#include "log.h"
59#include "evrpc.h"
60
61#include "regress.gen.h"
62
63void rpc_suite(void);
64
65extern int test_ok;
66
67static struct evhttp *
68http_setup(short *pport)
69{
70	int i;
71	struct evhttp *myhttp;
72	short port = -1;
73
74	/* Try a few different ports */
75	for (i = 0; i < 50; ++i) {
76		myhttp = evhttp_start("127.0.0.1", 8080 + i);
77		if (myhttp != NULL) {
78			port = 8080 + i;
79			break;
80		}
81	}
82
83	if (port == -1)
84		event_errx(1, "Could not start web server");
85
86	*pport = port;
87	return (myhttp);
88}
89
90EVRPC_HEADER(Message, msg, kill);
91EVRPC_HEADER(NeverReply, msg, kill);
92
93EVRPC_GENERATE(Message, msg, kill);
94EVRPC_GENERATE(NeverReply, msg, kill);
95
96static int need_input_hook = 0;
97static int need_output_hook = 0;
98
99static void
100MessageCb(EVRPC_STRUCT(Message)* rpc, void *arg)
101{
102	struct kill* kill_reply = rpc->reply;
103
104	if (need_input_hook) {
105		struct evhttp_request* req = EVRPC_REQUEST_HTTP(rpc);
106		const char *header = evhttp_find_header(
107			req->input_headers, "X-Hook");
108		assert(strcmp(header, "input") == 0);
109	}
110
111	/* we just want to fill in some non-sense */
112	EVTAG_ASSIGN(kill_reply, weapon, "dagger");
113	EVTAG_ASSIGN(kill_reply, action, "wave around like an idiot");
114
115	/* no reply to the RPC */
116	EVRPC_REQUEST_DONE(rpc);
117}
118
119static EVRPC_STRUCT(NeverReply) *saved_rpc;
120
121static void
122NeverReplyCb(EVRPC_STRUCT(NeverReply)* rpc, void *arg)
123{
124	test_ok += 1;
125	saved_rpc = rpc;
126}
127
128static void
129rpc_setup(struct evhttp **phttp, short *pport, struct evrpc_base **pbase)
130{
131	short port;
132	struct evhttp *http = NULL;
133	struct evrpc_base *base = NULL;
134
135	http = http_setup(&port);
136	base = evrpc_init(http);
137
138	EVRPC_REGISTER(base, Message, msg, kill, MessageCb, NULL);
139	EVRPC_REGISTER(base, NeverReply, msg, kill, NeverReplyCb, NULL);
140
141	*phttp = http;
142	*pport = port;
143	*pbase = base;
144
145	need_input_hook = 0;
146	need_output_hook = 0;
147}
148
149static void
150rpc_teardown(struct evrpc_base *base)
151{
152	assert(EVRPC_UNREGISTER(base, Message) == 0);
153	assert(EVRPC_UNREGISTER(base, NeverReply) == 0);
154
155	evrpc_free(base);
156}
157
158static void
159rpc_postrequest_failure(struct evhttp_request *req, void *arg)
160{
161	if (req->response_code != HTTP_SERVUNAVAIL) {
162
163		fprintf(stderr, "FAILED (response code)\n");
164		exit(1);
165	}
166
167	test_ok = 1;
168	event_loopexit(NULL);
169}
170
171/*
172 * Test a malformed payload submitted as an RPC
173 */
174
175static void
176rpc_basic_test(void)
177{
178	short port;
179	struct evhttp *http = NULL;
180	struct evrpc_base *base = NULL;
181	struct evhttp_connection *evcon = NULL;
182	struct evhttp_request *req = NULL;
183
184	fprintf(stdout, "Testing Basic RPC Support: ");
185
186	rpc_setup(&http, &port, &base);
187
188	evcon = evhttp_connection_new("127.0.0.1", port);
189	if (evcon == NULL) {
190		fprintf(stdout, "FAILED\n");
191		exit(1);
192	}
193
194	/*
195	 * At this point, we want to schedule an HTTP POST request
196	 * server using our make request method.
197	 */
198
199	req = evhttp_request_new(rpc_postrequest_failure, NULL);
200	if (req == NULL) {
201		fprintf(stdout, "FAILED\n");
202		exit(1);
203	}
204
205	/* Add the information that we care about */
206	evhttp_add_header(req->output_headers, "Host", "somehost");
207	evbuffer_add_printf(req->output_buffer, "Some Nonsense");
208
209	if (evhttp_make_request(evcon, req,
210		EVHTTP_REQ_POST,
211		"/.rpc.Message") == -1) {
212		fprintf(stdout, "FAILED\n");
213		exit(1);
214	}
215
216	test_ok = 0;
217
218	event_dispatch();
219
220	evhttp_connection_free(evcon);
221
222	rpc_teardown(base);
223
224	if (test_ok != 1) {
225		fprintf(stdout, "FAILED\n");
226		exit(1);
227	}
228
229	fprintf(stdout, "OK\n");
230
231	evhttp_free(http);
232}
233
234static void
235rpc_postrequest_done(struct evhttp_request *req, void *arg)
236{
237	struct kill* kill_reply = NULL;
238
239	if (req->response_code != HTTP_OK) {
240
241		fprintf(stderr, "FAILED (response code)\n");
242		exit(1);
243	}
244
245	kill_reply = kill_new();
246
247	if ((kill_unmarshal(kill_reply, req->input_buffer)) == -1) {
248		fprintf(stderr, "FAILED (unmarshal)\n");
249		exit(1);
250	}
251
252	kill_free(kill_reply);
253
254	test_ok = 1;
255	event_loopexit(NULL);
256}
257
258static void
259rpc_basic_message(void)
260{
261	short port;
262	struct evhttp *http = NULL;
263	struct evrpc_base *base = NULL;
264	struct evhttp_connection *evcon = NULL;
265	struct evhttp_request *req = NULL;
266	struct msg *msg;
267
268	fprintf(stdout, "Testing Good RPC Post: ");
269
270	rpc_setup(&http, &port, &base);
271
272	evcon = evhttp_connection_new("127.0.0.1", port);
273	if (evcon == NULL) {
274		fprintf(stdout, "FAILED\n");
275		exit(1);
276	}
277
278	/*
279	 * At this point, we want to schedule an HTTP POST request
280	 * server using our make request method.
281	 */
282
283	req = evhttp_request_new(rpc_postrequest_done, NULL);
284	if (req == NULL) {
285		fprintf(stdout, "FAILED\n");
286		exit(1);
287	}
288
289	/* Add the information that we care about */
290	evhttp_add_header(req->output_headers, "Host", "somehost");
291
292	/* set up the basic message */
293	msg = msg_new();
294	EVTAG_ASSIGN(msg, from_name, "niels");
295	EVTAG_ASSIGN(msg, to_name, "tester");
296	msg_marshal(req->output_buffer, msg);
297	msg_free(msg);
298
299	if (evhttp_make_request(evcon, req,
300		EVHTTP_REQ_POST,
301		"/.rpc.Message") == -1) {
302		fprintf(stdout, "FAILED\n");
303		exit(1);
304	}
305
306	test_ok = 0;
307
308	event_dispatch();
309
310	evhttp_connection_free(evcon);
311
312	rpc_teardown(base);
313
314	if (test_ok != 1) {
315		fprintf(stdout, "FAILED\n");
316		exit(1);
317	}
318
319	fprintf(stdout, "OK\n");
320
321	evhttp_free(http);
322}
323
324static struct evrpc_pool *
325rpc_pool_with_connection(short port)
326{
327	struct evhttp_connection *evcon;
328	struct evrpc_pool *pool;
329
330	pool = evrpc_pool_new(NULL);
331	assert(pool != NULL);
332
333	evcon = evhttp_connection_new("127.0.0.1", port);
334	assert(evcon != NULL);
335
336	evrpc_pool_add_connection(pool, evcon);
337
338	return (pool);
339}
340
341static void
342GotKillCb(struct evrpc_status *status,
343    struct msg *msg, struct kill *kill, void *arg)
344{
345	char *weapon;
346	char *action;
347
348	if (need_output_hook) {
349		struct evhttp_request *req = status->http_req;
350		const char *header = evhttp_find_header(
351			req->input_headers, "X-Pool-Hook");
352		assert(strcmp(header, "ran") == 0);
353	}
354
355	if (status->error != EVRPC_STATUS_ERR_NONE)
356		goto done;
357
358	if (EVTAG_GET(kill, weapon, &weapon) == -1) {
359		fprintf(stderr, "get weapon\n");
360		goto done;
361	}
362	if (EVTAG_GET(kill, action, &action) == -1) {
363		fprintf(stderr, "get action\n");
364		goto done;
365	}
366
367	if (strcmp(weapon, "dagger"))
368		goto done;
369
370	if (strcmp(action, "wave around like an idiot"))
371		goto done;
372
373	test_ok += 1;
374
375done:
376	event_loopexit(NULL);
377}
378
379static void
380GotKillCbTwo(struct evrpc_status *status,
381    struct msg *msg, struct kill *kill, void *arg)
382{
383	char *weapon;
384	char *action;
385
386	if (status->error != EVRPC_STATUS_ERR_NONE)
387		goto done;
388
389	if (EVTAG_GET(kill, weapon, &weapon) == -1) {
390		fprintf(stderr, "get weapon\n");
391		goto done;
392	}
393	if (EVTAG_GET(kill, action, &action) == -1) {
394		fprintf(stderr, "get action\n");
395		goto done;
396	}
397
398	if (strcmp(weapon, "dagger"))
399		goto done;
400
401	if (strcmp(action, "wave around like an idiot"))
402		goto done;
403
404	test_ok += 1;
405
406done:
407	if (test_ok == 2)
408		event_loopexit(NULL);
409}
410
411static int
412rpc_hook_add_header(struct evhttp_request *req,
413    struct evbuffer *evbuf, void *arg)
414{
415	const char *hook_type = arg;
416	if (strcmp("input", hook_type) == 0)
417		evhttp_add_header(req->input_headers, "X-Hook", hook_type);
418	else
419		evhttp_add_header(req->output_headers, "X-Hook", hook_type);
420	return (0);
421}
422
423static int
424rpc_hook_remove_header(struct evhttp_request *req,
425    struct evbuffer *evbuf, void *arg)
426{
427	const char *header = evhttp_find_header(req->input_headers, "X-Hook");
428	assert(header != NULL);
429	assert(strcmp(header, arg) == 0);
430	evhttp_remove_header(req->input_headers, "X-Hook");
431	evhttp_add_header(req->input_headers, "X-Pool-Hook", "ran");
432
433	return (0);
434}
435
436static void
437rpc_basic_client(void)
438{
439	short port;
440	struct evhttp *http = NULL;
441	struct evrpc_base *base = NULL;
442	struct evrpc_pool *pool = NULL;
443	struct msg *msg;
444	struct kill *kill;
445
446	fprintf(stdout, "Testing RPC Client: ");
447
448	rpc_setup(&http, &port, &base);
449
450	need_input_hook = 1;
451	need_output_hook = 1;
452
453	assert(evrpc_add_hook(base, EVRPC_INPUT, rpc_hook_add_header, (void*)"input")
454	    != NULL);
455	assert(evrpc_add_hook(base, EVRPC_OUTPUT, rpc_hook_add_header, (void*)"output")
456	    != NULL);
457
458	pool = rpc_pool_with_connection(port);
459
460	assert(evrpc_add_hook(pool, EVRPC_INPUT, rpc_hook_remove_header, (void*)"output"));
461
462	/* set up the basic message */
463	msg = msg_new();
464	EVTAG_ASSIGN(msg, from_name, "niels");
465	EVTAG_ASSIGN(msg, to_name, "tester");
466
467	kill = kill_new();
468
469	EVRPC_MAKE_REQUEST(Message, pool, msg, kill,  GotKillCb, NULL);
470
471	test_ok = 0;
472
473	event_dispatch();
474
475	if (test_ok != 1) {
476		fprintf(stdout, "FAILED (1)\n");
477		exit(1);
478	}
479
480	/* we do it twice to make sure that reuse works correctly */
481	kill_clear(kill);
482
483	EVRPC_MAKE_REQUEST(Message, pool, msg, kill,  GotKillCb, NULL);
484
485	event_dispatch();
486
487	rpc_teardown(base);
488
489	if (test_ok != 2) {
490		fprintf(stdout, "FAILED (2)\n");
491		exit(1);
492	}
493
494	fprintf(stdout, "OK\n");
495
496	msg_free(msg);
497	kill_free(kill);
498
499	evrpc_pool_free(pool);
500	evhttp_free(http);
501}
502
503/*
504 * We are testing that the second requests gets send over the same
505 * connection after the first RPCs completes.
506 */
507static void
508rpc_basic_queued_client(void)
509{
510	short port;
511	struct evhttp *http = NULL;
512	struct evrpc_base *base = NULL;
513	struct evrpc_pool *pool = NULL;
514	struct msg *msg;
515	struct kill *kill_one, *kill_two;
516
517	fprintf(stdout, "Testing RPC (Queued) Client: ");
518
519	rpc_setup(&http, &port, &base);
520
521	pool = rpc_pool_with_connection(port);
522
523	/* set up the basic message */
524	msg = msg_new();
525	EVTAG_ASSIGN(msg, from_name, "niels");
526	EVTAG_ASSIGN(msg, to_name, "tester");
527
528	kill_one = kill_new();
529	kill_two = kill_new();
530
531	EVRPC_MAKE_REQUEST(Message, pool, msg, kill_one,  GotKillCbTwo, NULL);
532	EVRPC_MAKE_REQUEST(Message, pool, msg, kill_two,  GotKillCb, NULL);
533
534	test_ok = 0;
535
536	event_dispatch();
537
538	rpc_teardown(base);
539
540	if (test_ok != 2) {
541		fprintf(stdout, "FAILED (1)\n");
542		exit(1);
543	}
544
545	fprintf(stdout, "OK\n");
546
547	msg_free(msg);
548	kill_free(kill_one);
549	kill_free(kill_two);
550
551	evrpc_pool_free(pool);
552	evhttp_free(http);
553}
554
555static void
556GotErrorCb(struct evrpc_status *status,
557    struct msg *msg, struct kill *kill, void *arg)
558{
559	if (status->error != EVRPC_STATUS_ERR_TIMEOUT)
560		goto done;
561
562	/* should never be complete but just to check */
563	if (kill_complete(kill) == 0)
564		goto done;
565
566	test_ok += 1;
567
568done:
569	event_loopexit(NULL);
570}
571
572static void
573rpc_client_timeout(void)
574{
575	short port;
576	struct evhttp *http = NULL;
577	struct evrpc_base *base = NULL;
578	struct evrpc_pool *pool = NULL;
579	struct msg *msg;
580	struct kill *kill;
581
582	fprintf(stdout, "Testing RPC Client Timeout: ");
583
584	rpc_setup(&http, &port, &base);
585
586	pool = rpc_pool_with_connection(port);
587
588	/* set the timeout to 5 seconds */
589	evrpc_pool_set_timeout(pool, 5);
590
591	/* set up the basic message */
592	msg = msg_new();
593	EVTAG_ASSIGN(msg, from_name, "niels");
594	EVTAG_ASSIGN(msg, to_name, "tester");
595
596	kill = kill_new();
597
598	EVRPC_MAKE_REQUEST(NeverReply, pool, msg, kill, GotErrorCb, NULL);
599
600	test_ok = 0;
601
602	event_dispatch();
603
604	/* free the saved RPC structure up */
605	EVRPC_REQUEST_DONE(saved_rpc);
606
607	rpc_teardown(base);
608
609	if (test_ok != 2) {
610		fprintf(stdout, "FAILED (1)\n");
611		exit(1);
612	}
613
614	fprintf(stdout, "OK\n");
615
616	msg_free(msg);
617	kill_free(kill);
618
619	evrpc_pool_free(pool);
620	evhttp_free(http);
621}
622
623void
624rpc_suite(void)
625{
626	rpc_basic_test();
627	rpc_basic_message();
628	rpc_basic_client();
629	rpc_basic_queued_client();
630	rpc_client_timeout();
631}
632