1 /*
2 * lws-minimal-secure-streams
3 *
4 * Written in 2010-2020 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 *
10 * This demonstrates a minimal http client using secure streams api.
11 *
12 * It visits https://warmcat.com/ and receives the html page there.
13 *
14 * This example is built two different ways from the same source... one includes
15 * the policy everything needed to fulfil the stream directly. The other -client
16 * variant has no policy itself and some other minor init changes, and connects
17 * to the -proxy example to actually get the connection done.
18 *
19 * In the -client build case, the example does not even init the tls libraries
20 * since the proxy part will take care of all that.
21 */
22
23 #include <libwebsockets.h>
24 #include <string.h>
25 #include <signal.h>
26
27 // #define FORCE_OS_TRUST_STORE
28
29 /*
30 * uncomment to force network traffic through 127.0.0.1:1080
31 *
32 * On your local machine, you can run a SOCKS5 proxy like this
33 *
34 * $ ssh -N -D 0.0.0.0:1080 localhost -v
35 *
36 * If enabled, this also fetches a remote policy that also
37 * specifies that all traffic should go through the remote
38 * proxy.
39 */
40 // #define VIA_LOCALHOST_SOCKS
41
42 static int interrupted, bad = 1, force_cpd_fail_portal,
43 force_cpd_fail_no_internet, test_respmap, test_blob, test_ots;
44 static unsigned int timeout_ms = 3000;
45 static lws_state_notify_link_t nl;
46
47 /*
48 * If the -proxy app is fulfilling our connection, then we don't need to have
49 * the policy in the client.
50 *
51 * When we build with LWS_SS_USE_SSPC, the apis hook up to a proxy process over
52 * a Unix Domain Socket. To test that, you need to separately run the
53 * ./lws-minimal-secure-streams-proxy test app on the same machine.
54 */
55
56 #if !defined(LWS_SS_USE_SSPC)
57 static const char * const default_ss_policy =
58 "{"
59 "\"release\":" "\"01234567\","
60 "\"product\":" "\"myproduct\","
61 "\"schema-version\":" "1,"
62 #if defined(VIA_LOCALHOST_SOCKS)
63 "\"via-socks5\":" "\"127.0.0.1:1080\","
64 #endif
65
66 "\"retry\": [" /* named backoff / retry strategies */
67 "{\"default\": {"
68 "\"backoff\": [" "1000,"
69 "2000,"
70 "3000,"
71 "5000,"
72 "10000"
73 "],"
74 "\"conceal\":" "5,"
75 "\"jitterpc\":" "20,"
76 "\"svalidping\":" "30,"
77 "\"svalidhup\":" "35"
78 "}}"
79 "],"
80 "\"certs\": [" /* named individual certificates in BASE64 DER */
81 /*
82 * Let's Encrypt certs for warmcat.com / libwebsockets.org
83 *
84 * We fetch the real policy from there using SS and switch to
85 * using that.
86 */
87 #if !defined(FORCE_OS_TRUST_STORE)
88 "{\"isrg_root_x1\": \""
89 "MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw"
90 "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh"
91 "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4"
92 "WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu"
93 "ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY"
94 "MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc"
95 "h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+"
96 "0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U"
97 "A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW"
98 "T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH"
99 "B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC"
100 "B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv"
101 "KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn"
102 "OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn"
103 "jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw"
104 "qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI"
105 "rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV"
106 "HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq"
107 "hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL"
108 "ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ"
109 "3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK"
110 "NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5"
111 "ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur"
112 "TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC"
113 "jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc"
114 "oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq"
115 "4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA"
116 "mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d"
117 "emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc="
118 "\"}"
119 #endif
120 "],"
121 "\"trust_stores\": [" /* named cert chains */
122 #if !defined(FORCE_OS_TRUST_STORE)
123 "{"
124 "\"name\": \"le_via_isrg\","
125 "\"stack\": ["
126 "\"isrg_root_x1\""
127 "]"
128 "}"
129 #endif
130 "],"
131 "\"s\": ["
132 /*
133 * "fetch_policy" decides from where the real policy
134 * will be fetched, if present. Otherwise the initial
135 * policy is treated as the whole, hardcoded, policy.
136 */
137 "{\"fetch_policy\": {"
138 "\"endpoint\":" "\"warmcat.com\","
139 "\"port\":" "443,"
140 "\"protocol\":" "\"h1\","
141 "\"http_method\":" "\"GET\","
142 #if defined(VIA_LOCALHOST_SOCKS)
143 "\"http_url\":" "\"policy/minimal-proxy-socks.json\","
144 #else
145 "\"http_url\":" "\"policy/minimal-proxy-v4.2-v2.json\","
146 #endif
147 "\"tls\":" "true,"
148 "\"opportunistic\":" "true,"
149 #if !defined(FORCE_OS_TRUST_STORE)
150 "\"tls_trust_store\":" "\"le_via_isrg\","
151 #endif
152 "\"retry\":" "\"default\""
153 "}},{"
154 /*
155 * "captive_portal_detect" describes
156 * what to do in order to check if the path to
157 * the Internet is being interrupted by a
158 * captive portal. If there's a larger policy
159 * fetched from elsewhere, it should also include
160 * this since it needs to be done at least after
161 * every DHCP acquisition
162 */
163 "\"captive_portal_detect\": {"
164 "\"endpoint\": \"connectivitycheck.android.com\","
165 "\"http_url\": \"generate_204\","
166 "\"port\": 80,"
167 "\"protocol\": \"h1\","
168 "\"http_method\": \"GET\","
169 "\"opportunistic\": true,"
170 "\"http_expect\": 204,"
171 "\"http_fail_redirect\": true"
172 "}}"
173 "]}"
174 ;
175
176 #endif
177
178 typedef struct myss {
179 struct lws_ss_handle *ss;
180 void *opaque_data;
181 /* ... application specific state ... */
182 lws_sorted_usec_list_t sul;
183 size_t amt;
184
185 struct lws_genhash_ctx hash_ctx;
186 } myss_t;
187
188 #if !defined(LWS_SS_USE_SSPC)
189
190 static const char *canned_root_token_payload =
191 "grant_type=refresh_token"
192 "&refresh_token=Atzr|IwEBIJedGXjDqsU_vMxykqOMg"
193 "SHfYe3CPcedueWEMWSDMaDnEmiW8RlR1Kns7Cb4B-TOSnqp7ifVsY4BMY2B8tpHfO39XP"
194 "zfu9HapGjTR458IyHX44FE71pWJkGZ79uVBpljP4sazJuk8XS3Oe_yLnm_DIO6fU1nU3Y"
195 "0flYmsOiOAQE_gRk_pdlmEtHnpMA-9rLw3mkY5L89Ty9kUygBsiFaYatouROhbsTn8-jW"
196 "k1zZLUDpT6ICtBXSnrCIg0pUbZevPFhTwdXd6eX-u4rq0W-XaDvPWFO7au-iPb4Zk5eZE"
197 "iX6sissYrtNmuEXc2uHu7MnQO1hHCaTdIO2CANVumf-PHSD8xseamyh04sLV5JgFzY45S"
198 "KvKMajiUZuLkMokOx86rjC2Hdkx5DO7G-dbG1ufBDG-N79pFMSs7Ck5pc283IdLoJkCQc"
199 "AGvTX8o8I29QqkcGou-9TKhOJmpX8As94T61ok0UqqEKPJ7RhfQHHYdCtsdwxgvfVr9qI"
200 "xL_hDCcTho8opCVX-6QhJHl6SQFlTw13"
201 "&client_id="
202 "amzn1.application-oa2-client.4823334c434b4190a2b5a42c07938a2d";
203
204 #endif
205
206 /* secure streams payload interface */
207
208 static const uint8_t expected_blob_hash[] = {
209 0xed, 0x57, 0x20, 0xc1, 0x68, 0x30, 0x81, 0x0e,
210 0x58, 0x29, 0xdf, 0xb9, 0xb6, 0x6c, 0x96, 0xb2,
211 0xe2, 0x4e, 0xfc, 0x4f, 0x93, 0xaa, 0x5e, 0x38,
212 0xc7, 0xff, 0x41, 0x50, 0xd3, 0x1c, 0xfb, 0xbf
213 };
214
215 static lws_ss_state_return_t
myss_rx(void * userobj,const uint8_t * buf,size_t len,int flags)216 myss_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
217 {
218 myss_t *m = (myss_t *)userobj;
219 const char *md_srv = "not set", *md_test = "not set";
220 size_t md_srv_len = 7, md_test_len = 7;
221
222 if (flags & LWSSS_FLAG_PERF_JSON)
223 return LWSSSSRET_OK;
224
225 if (test_blob) {
226
227 if (flags & LWSSS_FLAG_SOM) {
228 if (lws_genhash_init(&m->hash_ctx, LWS_GENHASH_TYPE_SHA256))
229 lwsl_err("%s: hash init failed\n", __func__);
230 m->amt = 0;
231 }
232
233 if (lws_genhash_update(&m->hash_ctx, buf, len))
234 lwsl_err("%s: hash failed\n", __func__);
235
236 if ((m->amt + len) / 102400 != (m->amt / 102400)) {
237
238 lwsl_user("%s: blob test: rx %uKiB\n", __func__,
239 (unsigned int)((m->amt + len) / 1024));
240 /*
241 * Let's make it hard for client to keep up with onward
242 * server, delay 50ms after every 100K received, so we
243 * are forcing the flow control action at the proxy
244 */
245 usleep(50000);
246 }
247
248 m->amt += len;
249
250 if (flags & LWSSS_FLAG_EOM) {
251 uint8_t digest[32];
252 lws_genhash_destroy(&m->hash_ctx, digest);
253
254 if (!memcmp(expected_blob_hash, digest, 32)) {
255 lwsl_user("%s: SHA256 match\n", __func__);
256 bad = 0;
257 }
258
259 interrupted = 1;
260 }
261
262 return LWSSSSRET_OK;
263 }
264
265 lws_ss_get_metadata(m->ss, "srv", (const void **)&md_srv, &md_srv_len);
266 lws_ss_get_metadata(m->ss, "test", (const void **)&md_test, &md_test_len);
267
268 lwsl_user("%s: len %d, flags: %d, srv: %.*s, test: %.*s\n", __func__,
269 (int)len, flags, (int)md_srv_len, md_srv,
270 (int)md_test_len, md_test);
271 lwsl_hexdump_info(buf, len);
272
273 /*
274 * If we received the whole message, for our example it means
275 * we are done.
276 */
277 if (flags & LWSSS_FLAG_EOM) {
278 bad = 0;
279 interrupted = 1;
280 }
281
282 return LWSSSSRET_OK;
283 }
284
285 static lws_ss_state_return_t
myss_tx(void * userobj,lws_ss_tx_ordinal_t ord,uint8_t * buf,size_t * len,int * flags)286 myss_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len,
287 int *flags)
288 {
289 //myss_t *m = (myss_t *)userobj;
290
291 /* in this example, we don't send stuff */
292
293 return LWSSSSRET_TX_DONT_SEND;
294 }
295
296 static lws_ss_state_return_t
myss_state(void * userobj,void * sh,lws_ss_constate_t state,lws_ss_tx_ordinal_t ack)297 myss_state(void *userobj, void *sh, lws_ss_constate_t state,
298 lws_ss_tx_ordinal_t ack)
299 {
300 myss_t *m = (myss_t *)userobj;
301
302 lwsl_user("%s: %s (%d), ord 0x%x\n", __func__,
303 lws_ss_state_name((int)state), state, (unsigned int)ack);
304
305 switch (state) {
306 case LWSSSCS_CREATING:
307 return lws_ss_client_connect(m->ss);
308
309 case LWSSSCS_CONNECTING:
310 lws_ss_start_timeout(m->ss, timeout_ms);
311
312 if (!test_blob) {
313 if (lws_ss_set_metadata(m->ss, "uptag", "myuptag123", 10))
314 /* can fail, eg due to OOM, retry later if so */
315 return LWSSSSRET_DISCONNECT_ME;
316
317 if (lws_ss_set_metadata(m->ss, "ctype", "myctype", 7))
318 /* can fail, eg due to OOM, retry later if so */
319 return LWSSSSRET_DISCONNECT_ME;
320 }
321 break;
322
323 case LWSSSCS_ALL_RETRIES_FAILED:
324 /* if we're out of retries, we want to close the app and FAIL */
325 interrupted = 1;
326 bad = 2;
327 break;
328
329 case LWSSSCS_QOS_ACK_REMOTE:
330 lwsl_notice("%s: LWSSSCS_QOS_ACK_REMOTE\n", __func__);
331 break;
332
333 case LWSSSCS_TIMEOUT:
334 lwsl_notice("%s: LWSSSCS_TIMEOUT\n", __func__);
335 /* if we're out of time */
336 interrupted = 1;
337 bad = 3;
338 break;
339
340 case LWSSSCS_USER_BASE:
341 lwsl_notice("%s: LWSSSCS_USER_BASE\n", __func__);
342 break;
343
344 default:
345 break;
346 }
347
348 return LWSSSSRET_OK;
349 }
350
351 static int
app_system_state_nf(lws_state_manager_t * mgr,lws_state_notify_link_t * link,int current,int target)352 app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link,
353 int current, int target)
354 {
355 struct lws_context *context = lws_system_context_from_system_mgr(mgr);
356 #if !defined(LWS_SS_USE_SSPC)
357
358 lws_system_blob_t *ab = lws_system_get_blob(context,
359 LWS_SYSBLOB_TYPE_AUTH, 1 /* AUTH_IDX_ROOT */);
360 size_t size;
361 #endif
362
363 /*
364 * For the things we care about, let's notice if we are trying to get
365 * past them when we haven't solved them yet, and make the system
366 * state wait while we trigger the dependent action.
367 */
368 switch (target) {
369
370 #if !defined(LWS_SS_USE_SSPC)
371
372 /*
373 * The proxy takes responsibility for this stuff if we get things
374 * done through that
375 */
376
377 case LWS_SYSTATE_INITIALIZED: /* overlay on the hardcoded policy */
378 case LWS_SYSTATE_POLICY_VALID: /* overlay on the loaded policy */
379
380 if (target != current)
381 break;
382
383 if (force_cpd_fail_portal)
384
385 /* this makes it look like we're behind a captive portal
386 * because the overriden address does a redirect */
387
388 lws_ss_policy_overlay(context,
389 "{\"s\": [{\"captive_portal_detect\": {"
390 "\"endpoint\": \"google.com\","
391 "\"http_url\": \"/\","
392 "\"port\": 80"
393 "}}]}");
394
395 if (force_cpd_fail_no_internet)
396
397 /* this looks like no internet, because the overridden
398 * port doesn't have anything that will connect to us */
399
400 lws_ss_policy_overlay(context,
401 "{\"s\": [{\"captive_portal_detect\": {"
402 "\"endpoint\": \"warmcat.com\","
403 "\"http_url\": \"/\","
404 "\"port\": 999"
405 "}}]}");
406 break;
407
408 case LWS_SYSTATE_REGISTERED:
409 size = lws_system_blob_get_size(ab);
410 if (size)
411 break;
412
413 /* let's register our canned root token so auth can use it */
414 lws_system_blob_direct_set(ab,
415 (const uint8_t *)canned_root_token_payload,
416 strlen(canned_root_token_payload));
417 break;
418
419 #endif
420
421 case LWS_SYSTATE_OPERATIONAL:
422 if (current == LWS_SYSTATE_OPERATIONAL) {
423 lws_ss_info_t ssi;
424
425 /* We're making an outgoing secure stream ourselves */
426
427 memset(&ssi, 0, sizeof(ssi));
428 ssi.handle_offset = offsetof(myss_t, ss);
429 ssi.opaque_user_data_offset = offsetof(myss_t,
430 opaque_data);
431 ssi.rx = myss_rx;
432 ssi.tx = myss_tx;
433 ssi.state = myss_state;
434 ssi.user_alloc = sizeof(myss_t);
435 ssi.streamtype = test_ots ? "mintest-ots" :
436 (test_blob ? "bulkproxflow" :
437 (test_respmap ? "respmap" : "mintest"));
438
439 if (lws_ss_create(context, 0, &ssi, NULL, NULL,
440 NULL, NULL)) {
441 lwsl_err("%s: failed to create secure stream\n",
442 __func__);
443 return -1;
444 }
445 }
446 break;
447 }
448
449 return 0;
450 }
451
452 static lws_state_notify_link_t * const app_notifier_list[] = {
453 &nl, NULL
454 };
455
456 #if defined(LWS_WITH_SYS_METRICS)
457
458 static int
my_metric_report(lws_metric_pub_t * mp)459 my_metric_report(lws_metric_pub_t *mp)
460 {
461 lws_metric_bucket_t *sub = mp->u.hist.head;
462 char buf[192];
463
464 do {
465 if (lws_metrics_format(mp, &sub, buf, sizeof(buf)))
466 lwsl_user("%s: %s\n", __func__, buf);
467 } while ((mp->flags & LWSMTFL_REPORT_HIST) && sub);
468
469 /* 0 = leave metric to accumulate, 1 = reset the metric */
470
471 return 1;
472 }
473
474 static const lws_system_ops_t system_ops = {
475 .metric_report = my_metric_report,
476 };
477
478 #endif
479
480 static void
sigint_handler(int sig)481 sigint_handler(int sig)
482 {
483 interrupted = 1;
484 }
485
main(int argc,const char ** argv)486 int main(int argc, const char **argv)
487 {
488 struct lws_context_creation_info info;
489 struct lws_context *context;
490 int n = 0, expected = 0;
491 const char *p;
492
493 signal(SIGINT, sigint_handler);
494
495 memset(&info, 0, sizeof info);
496 lws_cmdline_option_handle_builtin(argc, argv, &info);
497
498 lwsl_user("LWS secure streams test client [-d<verb>]\n");
499
500 /* these options are mutually exclusive if given */
501
502 if (lws_cmdline_option(argc, argv, "--force-portal"))
503 force_cpd_fail_portal = 1;
504
505 if (lws_cmdline_option(argc, argv, "--force-no-internet"))
506 force_cpd_fail_no_internet = 1;
507
508 if (lws_cmdline_option(argc, argv, "--respmap"))
509 test_respmap = 1;
510
511 if (lws_cmdline_option(argc, argv, "--ots"))
512 /*
513 * Use a streamtype that relies on the OS trust store for
514 * validation
515 */
516 test_ots = 1;
517
518 if ((p = lws_cmdline_option(argc, argv, "--timeout_ms")))
519 timeout_ms = (unsigned int)atoi(p);
520
521 if (lws_cmdline_option(argc, argv, "--blob")) {
522 test_blob = 1;
523 if (timeout_ms == 3000)
524 /*
525 * Don't use default 3s, we're going to be a lot
526 * slower
527 */
528 timeout_ms = 60000;
529 }
530
531 info.fd_limit_per_thread = 1 + 6 + 1;
532 info.port = CONTEXT_PORT_NO_LISTEN;
533 #if defined(LWS_SS_USE_SSPC)
534 info.protocols = lws_sspc_protocols;
535 {
536 const char *p;
537
538 /* connect to ssproxy via UDS by default, else via
539 * tcp connection to this port */
540 if ((p = lws_cmdline_option(argc, argv, "-p")))
541 info.ss_proxy_port = (uint16_t)atoi(p);
542
543 /* UDS "proxy.ss.lws" in abstract namespace, else this socket
544 * path; when -p given this can specify the network interface
545 * to bind to */
546 if ((p = lws_cmdline_option(argc, argv, "-i")))
547 info.ss_proxy_bind = p;
548
549 /* if -p given, -a specifies the proxy address to connect to */
550 if ((p = lws_cmdline_option(argc, argv, "-a")))
551 info.ss_proxy_address = p;
552 }
553 #else
554 info.pss_policies_json = default_ss_policy;
555 info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
556 LWS_SERVER_OPTION_H2_JUST_FIX_WINDOW_UPDATE_OVERFLOW |
557 LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
558 #endif
559
560 #if defined(LWS_WITH_MBEDTLS)
561
562 /* uncomment to force mbedtls to load a system trust store like
563 * openssl does
564 *
565 * info.mbedtls_client_preload_filepath =
566 * "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem";
567 */
568 #endif
569
570 /* integrate us with lws system state management when context created */
571
572 nl.name = "app";
573 nl.notify_cb = app_system_state_nf;
574 info.register_notifier_list = app_notifier_list;
575
576
577 #if defined(LWS_WITH_SYS_METRICS)
578 info.system_ops = &system_ops;
579 info.metrics_prefix = "ssmex";
580 #endif
581
582 /* create the context */
583
584 context = lws_create_context(&info);
585 if (!context) {
586 lwsl_err("lws init failed\n");
587 goto bail;
588 }
589
590 #if !defined(LWS_SS_USE_SSPC)
591 /*
592 * If we're being a proxied client, the proxy does all this
593 */
594
595 /*
596 * Set the related lws_system blobs
597 *
598 * ...direct_set() sets a pointer, so the thing pointed to has to have
599 * a suitable lifetime, eg, something that already exists on the heap or
600 * a const string in .rodata like this
601 */
602
603 lws_system_blob_direct_set(lws_system_get_blob(context,
604 LWS_SYSBLOB_TYPE_DEVICE_SERIAL, 0),
605 (const uint8_t *)"SN12345678", 10);
606 lws_system_blob_direct_set(lws_system_get_blob(context,
607 LWS_SYSBLOB_TYPE_DEVICE_FW_VERSION, 0),
608 (const uint8_t *)"v0.01", 5);
609
610 /*
611 * ..._heap_append() appends to a buflist kind of arrangement on heap,
612 * just one block is fine, otherwise it will concatenate the fragments
613 * in the order they were appended (and take care of freeing them at
614 * context destroy time). ..._heap_empty() is also available to remove
615 * everything that was already allocated.
616 *
617 * Here we use _heap_append() just so it's tested as well as direct set.
618 */
619
620 lws_system_blob_heap_append(lws_system_get_blob(context,
621 LWS_SYSBLOB_TYPE_DEVICE_TYPE, 0),
622 (const uint8_t *)"spacerocket", 11);
623 #endif
624
625 /* the event loop */
626
627 while (n >= 0 && !interrupted)
628 n = lws_service(context, 0);
629
630 lws_context_destroy(context);
631
632 bail:
633 if ((p = lws_cmdline_option(argc, argv, "--expected-exit")))
634 expected = atoi(p);
635
636 if (bad == expected) {
637 lwsl_user("Completed: OK (seen expected %d)\n", expected);
638 return 0;
639 } else
640 lwsl_err("Completed: failed: exit %d, expected %d\n", bad, expected);
641
642 return 1;
643 }
644