• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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