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