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
20static int interrupted, test_good = 0;
21
22enum {
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
37struct 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
47enum {
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
54static 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
63static void
64sigint_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
75static int
76callback_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
151notify:
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
171static const struct lws_protocols protocols[] = {
172	{ "seq-test-http", callback_http, 0, 0, 0, NULL, 0 },
173	LWS_PROTOCOL_LIST_TERM
174};
175
176
177static int
178sequencer_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
240static lws_seq_cb_return_t
241sequencer_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
299done:
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
311step:
312		sequencer_start_client(s);
313		break;
314	default:
315		break;
316	}
317
318	return LWSSEQ_RET_CONTINUE;
319}
320
321int
322main(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
393bail1:
394	lwsl_user("Completed: %s\n", !test_good ? "FAIL" : "PASS");
395
396	lws_context_destroy(context);
397
398	return !test_good;
399}
400