Imported Upstream version 3.2.0
[platform/upstream/libwebsockets.git] / minimal-examples / api-tests / api-test-lws_sequencer / main.c
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
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
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 = 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, },
173         { NULL, NULL, 0, 0 }
174 };
175
176
177 static int
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                                     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
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
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         lws_seq_t *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)
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 }