• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * lws-api-test-lws_sequencer
3  *
4  * Written in 2019 by Andy Green <andy@warmcat.com>
5  *
6  * This file is made available under the Creative Commons CC0 1.0
7  * Universal Public Domain Dedication.
8  *
9  * This api test uses the lws_sequencer api to make five http client requests
10  * to libwebsockets.org in sequence, from inside the event loop.  The fourth
11  * fourth http client request is directed to port 22 where it stalls
12  * triggering the lws_sequencer timeout flow.  The fifth is given a nonexistant
13  * dns name and is expected to fail.
14  */
15 
16 #include <libwebsockets.h>
17 
18 #include <signal.h>
19 
20 static int interrupted, test_good = 0;
21 
22 enum {
23 	SEQ1,
24 	SEQ2,
25 	SEQ3_404,
26 	SEQ4_TIMEOUT,		/* we expect to timeout */
27 	SEQ5_BAD_ADDRESS	/* we expect the connection to fail */
28 };
29 
30 /*
31  * This is the user defined struct whose space is allocated along with the
32  * sequencer when that is created.
33  *
34  * You'd put everything your sequencer needs to do its job in here.
35  */
36 
37 struct myseq {
38 	struct lws_vhost	*vhost;
39 	struct lws		*cwsi;	/* client wsi for current step if any */
40 
41 	int			state;	/* which test we're on */
42 	int			http_resp;
43 };
44 
45 /* sequencer messages specific to this sequencer */
46 
47 enum {
48 	SEQ_MSG_CLIENT_FAILED = LWSSEQ_USER_BASE,
49 	SEQ_MSG_CLIENT_DONE,
50 };
51 
52 /* this is the sequence of GETs we will do */
53 
54 static const char *url_paths[] = {
55 	"https://libwebsockets.org/index.html",
56 	"https://libwebsockets.org/lws.css",
57 	"https://libwebsockets.org/404.html",
58 	"https://libwebsockets.org:22",		/* this causes us to time out */
59 	"https://doesntexist.invalid/"		/* fail early in connect */
60 };
61 
62 
63 static void
sigint_handler(int sig)64 sigint_handler(int sig)
65 {
66 	interrupted = 1;
67 }
68 
69 /*
70  * This is the sequencer-aware http protocol handler.  It monitors the client
71  * http action and queues messages for the sequencer when something definitive
72  * happens.
73  */
74 
75 static int
callback_http(struct lws * wsi,enum lws_callback_reasons reason,void * user,void * in,size_t len)76 callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
77 	      void *in, size_t len)
78 {
79 	struct myseq *s = (struct myseq *)user;
80 	int seq_msg = SEQ_MSG_CLIENT_FAILED;
81 
82 	switch (reason) {
83 
84 	/* because we are protocols[0] ... */
85 	case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
86 		lwsl_notice("CLIENT_CONNECTION_ERROR: %s\n",
87 			 in ? (char *)in : "(null)");
88 		goto notify;
89 
90 	case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
91 		if (!s)
92 			return 1;
93 		s->http_resp = (int)lws_http_client_http_response(wsi);
94 		lwsl_info("Connected with server response: %d\n", s->http_resp);
95 		break;
96 
97 	/* chunks of chunked content, with header removed */
98 	case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
99 		lwsl_info("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int)len);
100 #if 0  /* enable to dump the html */
101 		{
102 			const char *p = in;
103 
104 			while (len--)
105 				if (*p < 0x7f)
106 					putchar(*p++);
107 				else
108 					putchar('.');
109 		}
110 #endif
111 		return 0; /* don't passthru */
112 
113 	/* uninterpreted http content */
114 	case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
115 		{
116 			char buffer[1024 + LWS_PRE];
117 			char *px = buffer + LWS_PRE;
118 			int lenx = sizeof(buffer) - LWS_PRE;
119 
120 			if (lws_http_client_read(wsi, &px, &lenx) < 0)
121 				return -1;
122 		}
123 		return 0; /* don't passthru */
124 
125 	case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
126 		lwsl_notice("LWS_CALLBACK_COMPLETED_CLIENT_HTTP: wsi %p\n",
127 			    wsi);
128 		if (!s)
129 			return 1;
130 		/*
131 		 * We got a definitive transaction completion
132 		 */
133 		seq_msg = SEQ_MSG_CLIENT_DONE;
134 		goto notify;
135 
136 	case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
137 		lwsl_info("LWS_CALLBACK_CLOSED_CLIENT_HTTP\n");
138 		if (!s)
139 			return 1;
140 
141 		lwsl_user("%s: wsi %p: seq failed at CLOSED_CLIENT_HTTP\n",
142 			  __func__, wsi);
143 		goto notify;
144 
145 	default:
146 		break;
147 	}
148 
149 	return lws_callback_http_dummy(wsi, reason, user, in, len);
150 
151 notify:
152 	/*
153 	 * We only inform the sequencer of a definitive outcome for our step.
154 	 *
155 	 * So once we have informed it, we detach ourselves from the sequencer
156 	 * and the sequencer from ourselves.  Wsi may want to live on but after
157 	 * we got our result and moved on to the next test or completed, the
158 	 * sequencer doesn't want to hear from it again.
159 	 */
160 	if (!s)
161 		return 1;
162 
163 	lws_set_wsi_user(wsi, NULL);
164 	s->cwsi = NULL;
165 	lws_seq_queue_event(lws_seq_from_user(s), seq_msg,
166 				  NULL, NULL);
167 
168 	return 0;
169 }
170 
171 static const struct lws_protocols protocols[] = {
172 	{ "seq-test-http", callback_http, 0, 0, 0, NULL, 0 },
173 	LWS_PROTOCOL_LIST_TERM
174 };
175 
176 
177 static int
sequencer_start_client(struct myseq * s)178 sequencer_start_client(struct myseq *s)
179 {
180 	struct lws_client_connect_info i;
181 	const char *prot, *path1;
182 	char uri[128], path[128];
183 	int n;
184 
185 	lws_strncpy(uri, url_paths[s->state], sizeof(uri));
186 
187 	memset(&i, 0, sizeof i);
188 	i.context = lws_seq_get_context(lws_seq_from_user(s));
189 
190 	if (lws_parse_uri(uri, &prot, &i.address, &i.port, &path1)) {
191 		lwsl_err("%s: uri error %s\n", __func__, uri);
192 	}
193 
194 	if (!strcmp(prot, "https"))
195 		i.ssl_connection = LCCSCF_USE_SSL;
196 
197 	path[0] = '/';
198 	n = 1;
199 	if (path1[0] == '/')
200 		n = 0;
201 	lws_strncpy(&path[n], path1, sizeof(path) - 1);
202 
203 	i.path = path;
204 	i.host = i.address;
205 	i.origin = i.address;
206 	i.method = "GET";
207 	i.vhost = s->vhost;
208 	i.userdata = s;
209 
210 	i.protocol = protocols[0].name;
211 	i.local_protocol_name = protocols[0].name;
212 	i.pwsi = &s->cwsi;
213 
214 	if (!lws_client_connect_via_info(&i)) {
215 		lwsl_notice("%s: connecting to %s://%s:%d%s failed\n",
216 			    __func__, prot, i.address, i.port, path);
217 
218 		/* we couldn't even get started with the client connection */
219 
220 		lws_seq_queue_event(lws_seq_from_user(s),
221 				    (lws_seq_events_t)SEQ_MSG_CLIENT_FAILED, NULL, NULL);
222 
223 		return 1;
224 	}
225 
226 	lws_seq_timeout_us(lws_seq_from_user(s), 3 * LWS_US_PER_SEC);
227 
228 	lwsl_notice("%s: wsi %p: connecting to %s://%s:%d%s\n", __func__,
229 		    s->cwsi, prot, i.address, i.port, path);
230 
231 	return 0;
232 }
233 
234 /*
235  * The sequencer callback handles queued sequencer messages in the order they
236  * were queued.  The messages are presented from the event loop thread context
237  * even if they were queued from a different thread.
238  */
239 
240 static lws_seq_cb_return_t
sequencer_cb(struct lws_sequencer * seq,void * user,int event,void * data,void * aux)241 sequencer_cb(struct lws_sequencer *seq, void *user, int event,
242 	     void *data, void *aux)
243 {
244 	struct myseq *s = (struct myseq *)user;
245 
246 	switch ((int)event) {
247 	case LWSSEQ_CREATED: /* our sequencer just got started */
248 		s->state = SEQ1;  /* first thing we'll do is the first url */
249 		goto step;
250 
251 	case LWSSEQ_DESTROYED:
252 		/*
253 		 * This sequencer is about to be destroyed.  If we have any
254 		 * other assets in play, detach them from us.
255 		 */
256 		if (s->cwsi)
257 			lws_set_wsi_user(s->cwsi, NULL);
258 
259 		interrupted = 1;
260 		break;
261 
262 	case LWSSEQ_TIMED_OUT: /* current step timed out */
263 		if (s->state == SEQ4_TIMEOUT) {
264 			lwsl_user("%s: test %d got expected timeout\n",
265 				  __func__, s->state);
266 			goto done;
267 		}
268 		lwsl_user("%s: seq timed out at step %d\n", __func__, s->state);
269 		return LWSSEQ_RET_DESTROY;
270 
271 	case SEQ_MSG_CLIENT_FAILED:
272 		if (s->state == SEQ5_BAD_ADDRESS) {
273 			/*
274 			 * in this specific case, we expect to fail
275 			 */
276 			lwsl_user("%s: test %d failed as expected\n",
277 				  __func__, s->state);
278 			goto done;
279 		}
280 
281 		lwsl_user("%s: seq failed at step %d\n", __func__, s->state);
282 
283 		return LWSSEQ_RET_DESTROY;
284 
285 	case SEQ_MSG_CLIENT_DONE:
286 		if (s->state >= SEQ4_TIMEOUT) {
287 			/*
288 			 * In these specific cases, done would be a failure,
289 			 * we expected to timeout or fail
290 			 */
291 			lwsl_user("%s: seq failed at step %d\n", __func__,
292 				  s->state);
293 
294 			return LWSSEQ_RET_DESTROY;
295 		}
296 		lwsl_user("%s: seq done step %d (resp %d)\n", __func__,
297 			  s->state, s->http_resp);
298 
299 done:
300 		lws_seq_timeout_us(lws_seq_from_user(s), LWSSEQTO_NONE);
301 		s->state++;
302 		if (s->state == LWS_ARRAY_SIZE(url_paths)) {
303 			/* the sequence has completed */
304 			lwsl_user("%s: sequence completed OK\n", __func__);
305 
306 			test_good = 1;
307 
308 			return LWSSEQ_RET_DESTROY;
309 		}
310 
311 step:
312 		sequencer_start_client(s);
313 		break;
314 	default:
315 		break;
316 	}
317 
318 	return LWSSEQ_RET_CONTINUE;
319 }
320 
321 int
main(int argc,const char ** argv)322 main(int argc, const char **argv)
323 {
324 	int n = 1, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
325 	struct lws_context_creation_info info;
326 	struct lws_context *context;
327 	struct lws_sequencer *seq;
328 	struct lws_vhost *vh;
329 	lws_seq_info_t i;
330 	struct myseq *s;
331 	const char *p;
332 
333 	/* the normal lws init */
334 
335 	signal(SIGINT, sigint_handler);
336 
337 	if ((p = lws_cmdline_option(argc, argv, "-d")))
338 		logs = atoi(p);
339 
340 	lws_set_log_level(logs, NULL);
341 	lwsl_user("LWS API selftest: lws_sequencer\n");
342 
343 	memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
344 	info.port = CONTEXT_PORT_NO_LISTEN;
345 	info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
346 		       LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
347 	info.protocols = protocols;
348 
349 #if defined(LWS_WITH_MBEDTLS) || defined(USE_WOLFSSL)
350 	/*
351 	 * OpenSSL uses the system trust store.  mbedTLS has to be told which
352 	 * CA to trust explicitly.
353 	 */
354 	info.client_ssl_ca_filepath = "./libwebsockets.org.cer";
355 #endif
356 
357 	context = lws_create_context(&info);
358 	if (!context) {
359 		lwsl_err("lws init failed\n");
360 		return 1;
361 	}
362 
363 	vh = lws_create_vhost(context, &info);
364 	if (!vh) {
365 		lwsl_err("Failed to create first vhost\n");
366 		goto bail1;
367 	}
368 
369 	/*
370 	 * Create the sequencer... when the event loop starts, it will
371 	 * receive the LWSSEQ_CREATED callback
372 	 */
373 
374 	memset(&i, 0, sizeof(i));
375 	i.context = context;
376 	i.user_size = sizeof(struct myseq);
377 	i.puser = (void **)&s;
378 	i.cb = sequencer_cb;
379 	i.name = "seq";
380 
381 	seq = lws_seq_create(&i);
382 	if (!seq) {
383 		lwsl_err("%s: unable to create sequencer\n", __func__);
384 		goto bail1;
385 	}
386 	s->vhost = vh;
387 
388 	/* the usual lws event loop */
389 
390 	while (n >= 0 && !interrupted)
391 		n = lws_service(context, 0);
392 
393 bail1:
394 	lwsl_user("Completed: %s\n", !test_good ? "FAIL" : "PASS");
395 
396 	lws_context_destroy(context);
397 
398 	return !test_good;
399 }
400