| /* |
| * lws-api-test-lws_sequencer |
| * |
| * Written in 2019 by Andy Green <andy@warmcat.com> |
| * |
| * This file is made available under the Creative Commons CC0 1.0 |
| * Universal Public Domain Dedication. |
| * |
| * This api test uses the lws_sequencer api to make five http client requests |
| * to libwebsockets.org in sequence, from inside the event loop. The fourth |
| * fourth http client request is directed to port 22 where it stalls |
| * triggering the lws_sequencer timeout flow. The fifth is given a nonexistant |
| * dns name and is expected to fail. |
| */ |
| |
| #include <libwebsockets.h> |
| |
| #include <signal.h> |
| |
| static int interrupted, test_good = 0; |
| |
| enum { |
| SEQ1, |
| SEQ2, |
| SEQ3_404, |
| SEQ4_TIMEOUT, /* we expect to timeout */ |
| SEQ5_BAD_ADDRESS /* we expect the connection to fail */ |
| }; |
| |
| /* |
| * This is the user defined struct whose space is allocated along with the |
| * sequencer when that is created. |
| * |
| * You'd put everything your sequencer needs to do its job in here. |
| */ |
| |
| struct myseq { |
| struct lws_vhost *vhost; |
| struct lws *cwsi; /* client wsi for current step if any */ |
| |
| int state; /* which test we're on */ |
| int http_resp; |
| }; |
| |
| /* sequencer messages specific to this sequencer */ |
| |
| enum { |
| SEQ_MSG_CLIENT_FAILED = LWSSEQ_USER_BASE, |
| SEQ_MSG_CLIENT_DONE, |
| }; |
| |
| /* this is the sequence of GETs we will do */ |
| |
| static const char *url_paths[] = { |
| "https://libwebsockets.org/index.html", |
| "https://libwebsockets.org/lws.css", |
| "https://libwebsockets.org/404.html", |
| "https://libwebsockets.org:22", /* this causes us to time out */ |
| "https://doesntexist.invalid/" /* fail early in connect */ |
| }; |
| |
| |
| static void |
| sigint_handler(int sig) |
| { |
| interrupted = 1; |
| } |
| |
| /* |
| * This is the sequencer-aware http protocol handler. It monitors the client |
| * http action and queues messages for the sequencer when something definitive |
| * happens. |
| */ |
| |
| static int |
| callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, |
| void *in, size_t len) |
| { |
| struct myseq *s = (struct myseq *)user; |
| int seq_msg = SEQ_MSG_CLIENT_FAILED; |
| |
| switch (reason) { |
| |
| /* because we are protocols[0] ... */ |
| case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: |
| lwsl_notice("CLIENT_CONNECTION_ERROR: %s\n", |
| in ? (char *)in : "(null)"); |
| goto notify; |
| |
| case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: |
| if (!s) |
| return 1; |
| s->http_resp = (int)lws_http_client_http_response(wsi); |
| lwsl_info("Connected with server response: %d\n", s->http_resp); |
| break; |
| |
| /* chunks of chunked content, with header removed */ |
| case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: |
| lwsl_info("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int)len); |
| #if 0 /* enable to dump the html */ |
| { |
| const char *p = in; |
| |
| while (len--) |
| if (*p < 0x7f) |
| putchar(*p++); |
| else |
| putchar('.'); |
| } |
| #endif |
| return 0; /* don't passthru */ |
| |
| /* uninterpreted http content */ |
| case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: |
| { |
| char buffer[1024 + LWS_PRE]; |
| char *px = buffer + LWS_PRE; |
| int lenx = sizeof(buffer) - LWS_PRE; |
| |
| if (lws_http_client_read(wsi, &px, &lenx) < 0) |
| return -1; |
| } |
| return 0; /* don't passthru */ |
| |
| case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: |
| lwsl_notice("LWS_CALLBACK_COMPLETED_CLIENT_HTTP: wsi %p\n", |
| wsi); |
| if (!s) |
| return 1; |
| /* |
| * We got a definitive transaction completion |
| */ |
| seq_msg = SEQ_MSG_CLIENT_DONE; |
| goto notify; |
| |
| case LWS_CALLBACK_CLOSED_CLIENT_HTTP: |
| lwsl_info("LWS_CALLBACK_CLOSED_CLIENT_HTTP\n"); |
| if (!s) |
| return 1; |
| |
| lwsl_user("%s: wsi %p: seq failed at CLOSED_CLIENT_HTTP\n", |
| __func__, wsi); |
| goto notify; |
| |
| default: |
| break; |
| } |
| |
| return lws_callback_http_dummy(wsi, reason, user, in, len); |
| |
| notify: |
| /* |
| * We only inform the sequencer of a definitive outcome for our step. |
| * |
| * So once we have informed it, we detach ourselves from the sequencer |
| * and the sequencer from ourselves. Wsi may want to live on but after |
| * we got our result and moved on to the next test or completed, the |
| * sequencer doesn't want to hear from it again. |
| */ |
| if (!s) |
| return 1; |
| |
| lws_set_wsi_user(wsi, NULL); |
| s->cwsi = NULL; |
| lws_seq_queue_event(lws_seq_from_user(s), seq_msg, |
| NULL, NULL); |
| |
| return 0; |
| } |
| |
| static const struct lws_protocols protocols[] = { |
| { "seq-test-http", callback_http, 0, 0, 0, NULL, 0 }, |
| LWS_PROTOCOL_LIST_TERM |
| }; |
| |
| |
| static int |
| sequencer_start_client(struct myseq *s) |
| { |
| struct lws_client_connect_info i; |
| const char *prot, *path1; |
| char uri[128], path[128]; |
| int n; |
| |
| lws_strncpy(uri, url_paths[s->state], sizeof(uri)); |
| |
| memset(&i, 0, sizeof i); |
| i.context = lws_seq_get_context(lws_seq_from_user(s)); |
| |
| if (lws_parse_uri(uri, &prot, &i.address, &i.port, &path1)) { |
| lwsl_err("%s: uri error %s\n", __func__, uri); |
| } |
| |
| if (!strcmp(prot, "https")) |
| i.ssl_connection = LCCSCF_USE_SSL; |
| |
| path[0] = '/'; |
| n = 1; |
| if (path1[0] == '/') |
| n = 0; |
| lws_strncpy(&path[n], path1, sizeof(path) - 1); |
| |
| i.path = path; |
| i.host = i.address; |
| i.origin = i.address; |
| i.method = "GET"; |
| i.vhost = s->vhost; |
| i.userdata = s; |
| |
| i.protocol = protocols[0].name; |
| i.local_protocol_name = protocols[0].name; |
| i.pwsi = &s->cwsi; |
| |
| if (!lws_client_connect_via_info(&i)) { |
| lwsl_notice("%s: connecting to %s://%s:%d%s failed\n", |
| __func__, prot, i.address, i.port, path); |
| |
| /* we couldn't even get started with the client connection */ |
| |
| lws_seq_queue_event(lws_seq_from_user(s), |
| (lws_seq_events_t)SEQ_MSG_CLIENT_FAILED, NULL, NULL); |
| |
| return 1; |
| } |
| |
| lws_seq_timeout_us(lws_seq_from_user(s), 3 * LWS_US_PER_SEC); |
| |
| lwsl_notice("%s: wsi %p: connecting to %s://%s:%d%s\n", __func__, |
| s->cwsi, prot, i.address, i.port, path); |
| |
| return 0; |
| } |
| |
| /* |
| * The sequencer callback handles queued sequencer messages in the order they |
| * were queued. The messages are presented from the event loop thread context |
| * even if they were queued from a different thread. |
| */ |
| |
| static lws_seq_cb_return_t |
| sequencer_cb(struct lws_sequencer *seq, void *user, int event, |
| void *data, void *aux) |
| { |
| struct myseq *s = (struct myseq *)user; |
| |
| switch ((int)event) { |
| case LWSSEQ_CREATED: /* our sequencer just got started */ |
| s->state = SEQ1; /* first thing we'll do is the first url */ |
| goto step; |
| |
| case LWSSEQ_DESTROYED: |
| /* |
| * This sequencer is about to be destroyed. If we have any |
| * other assets in play, detach them from us. |
| */ |
| if (s->cwsi) |
| lws_set_wsi_user(s->cwsi, NULL); |
| |
| interrupted = 1; |
| break; |
| |
| case LWSSEQ_TIMED_OUT: /* current step timed out */ |
| if (s->state == SEQ4_TIMEOUT) { |
| lwsl_user("%s: test %d got expected timeout\n", |
| __func__, s->state); |
| goto done; |
| } |
| lwsl_user("%s: seq timed out at step %d\n", __func__, s->state); |
| return LWSSEQ_RET_DESTROY; |
| |
| case SEQ_MSG_CLIENT_FAILED: |
| if (s->state == SEQ5_BAD_ADDRESS) { |
| /* |
| * in this specific case, we expect to fail |
| */ |
| lwsl_user("%s: test %d failed as expected\n", |
| __func__, s->state); |
| goto done; |
| } |
| |
| lwsl_user("%s: seq failed at step %d\n", __func__, s->state); |
| |
| return LWSSEQ_RET_DESTROY; |
| |
| case SEQ_MSG_CLIENT_DONE: |
| if (s->state >= SEQ4_TIMEOUT) { |
| /* |
| * In these specific cases, done would be a failure, |
| * we expected to timeout or fail |
| */ |
| lwsl_user("%s: seq failed at step %d\n", __func__, |
| s->state); |
| |
| return LWSSEQ_RET_DESTROY; |
| } |
| lwsl_user("%s: seq done step %d (resp %d)\n", __func__, |
| s->state, s->http_resp); |
| |
| done: |
| lws_seq_timeout_us(lws_seq_from_user(s), LWSSEQTO_NONE); |
| s->state++; |
| if (s->state == LWS_ARRAY_SIZE(url_paths)) { |
| /* the sequence has completed */ |
| lwsl_user("%s: sequence completed OK\n", __func__); |
| |
| test_good = 1; |
| |
| return LWSSEQ_RET_DESTROY; |
| } |
| |
| step: |
| sequencer_start_client(s); |
| break; |
| default: |
| break; |
| } |
| |
| return LWSSEQ_RET_CONTINUE; |
| } |
| |
| int |
| main(int argc, const char **argv) |
| { |
| int n = 1, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; |
| struct lws_context_creation_info info; |
| struct lws_context *context; |
| struct lws_sequencer *seq; |
| struct lws_vhost *vh; |
| lws_seq_info_t i; |
| struct myseq *s; |
| const char *p; |
| |
| /* the normal lws init */ |
| |
| signal(SIGINT, sigint_handler); |
| |
| if ((p = lws_cmdline_option(argc, argv, "-d"))) |
| logs = atoi(p); |
| |
| lws_set_log_level(logs, NULL); |
| lwsl_user("LWS API selftest: lws_sequencer\n"); |
| |
| memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ |
| info.port = CONTEXT_PORT_NO_LISTEN; |
| info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | |
| LWS_SERVER_OPTION_EXPLICIT_VHOSTS; |
| info.protocols = protocols; |
| |
| #if defined(LWS_WITH_MBEDTLS) || defined(USE_WOLFSSL) |
| /* |
| * OpenSSL uses the system trust store. mbedTLS has to be told which |
| * CA to trust explicitly. |
| */ |
| info.client_ssl_ca_filepath = "./libwebsockets.org.cer"; |
| #endif |
| |
| context = lws_create_context(&info); |
| if (!context) { |
| lwsl_err("lws init failed\n"); |
| return 1; |
| } |
| |
| vh = lws_create_vhost(context, &info); |
| if (!vh) { |
| lwsl_err("Failed to create first vhost\n"); |
| goto bail1; |
| } |
| |
| /* |
| * Create the sequencer... when the event loop starts, it will |
| * receive the LWSSEQ_CREATED callback |
| */ |
| |
| memset(&i, 0, sizeof(i)); |
| i.context = context; |
| i.user_size = sizeof(struct myseq); |
| i.puser = (void **)&s; |
| i.cb = sequencer_cb; |
| i.name = "seq"; |
| |
| seq = lws_seq_create(&i); |
| if (!seq) { |
| lwsl_err("%s: unable to create sequencer\n", __func__); |
| goto bail1; |
| } |
| s->vhost = vh; |
| |
| /* the usual lws event loop */ |
| |
| while (n >= 0 && !interrupted) |
| n = lws_service(context, 0); |
| |
| bail1: |
| lwsl_user("Completed: %s\n", !test_good ? "FAIL" : "PASS"); |
| |
| lws_context_destroy(context); |
| |
| return !test_good; |
| } |