diff --git a/.gitignore b/.gitignore index d2b3e444f6baef80d483dcf7091b7b21eead9d59..74c0e3c7cb4df06862f33ac10918252c035c8a44 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,5 @@ libwebsockets.pc build/ *.swp doc +/build2/ +/build3/ diff --git a/lib/context.c b/lib/context.c index c85a48167a2d3ebd412681cad4ba95d755c1188f..2ccfbe96efec7dec32b0b5b0be18910e94d113e3 100644 --- a/lib/context.c +++ b/lib/context.c @@ -1163,6 +1163,7 @@ lws_create_context(const struct lws_context_creation_info *info) lwsl_info("Using event loop: %s\n", context->event_loop_ops->name); #if defined(LWS_WITH_TLS) + time(&context->tls.last_cert_check_s); if (info->alpn) context->tls.alpn_default = info->alpn; else { @@ -1365,8 +1366,6 @@ lws_create_context(const struct lws_context_creation_info *info) if (!lws_check_opt(info->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS)) lws_plat_drop_app_privileges(info); - time(&context->last_cert_check_s); - /* expedite post-context init (eg, protocols) */ lws_cancel_service(context); @@ -1729,6 +1728,22 @@ static void lws_context_destroy3(struct lws_context *context) { struct lws_context **pcontext_finalize = context->pcontext_finalize; + struct lws_context_per_thread *pt; + int n; + + for (n = 0; n < context->count_threads; n++) { + pt = &context->pt[n]; + + if (context->event_loop_ops->destroy_pt) + context->event_loop_ops->destroy_pt(context, n); + + lws_free_set_NULL(context->pt[n].serv_buf); + +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + while (pt->http.ah_list) + _lws_destroy_ah(pt, pt->http.ah_list); +#endif + } lws_free(context); lwsl_info("%s: ctx %p freed\n", __func__, context); @@ -1746,8 +1761,9 @@ lws_context_destroy2(struct lws_context *context) { struct lws_vhost *vh = NULL, *vh1; #if defined(LWS_WITH_PEER_LIMITS) - uint32_t n; + uint32_t nu; #endif + int n; lwsl_info("%s: ctx %p\n", __func__, context); @@ -1780,9 +1796,9 @@ lws_context_destroy2(struct lws_context *context) lws_plat_context_late_destroy(context); #if defined(LWS_WITH_PEER_LIMITS) - for (n = 0; n < context->pl_hash_elements; n++) { + for (nu = 0; nu < context->pl_hash_elements; nu++) { lws_start_foreach_llp(struct lws_peer **, peer, - context->pl_hash_table[n]) { + context->pl_hash_table[nu]) { struct lws_peer *df = *peer; *peer = df->next; lws_free(df); @@ -1807,6 +1823,11 @@ lws_context_destroy2(struct lws_context *context) return; } + if (!context->pt[0].event_loop_foreign) + for (n = 0; n < context->count_threads; n++) + if (context->pt[n].inside_service) + return; + lws_context_destroy3(context); } @@ -1844,6 +1865,8 @@ lws_context_destroy(struct lws_context *context) } lwsl_info("%s: ctx %p: already being destroyed\n", __func__, context); + + lws_context_destroy3(context); return; } @@ -1891,20 +1914,6 @@ lws_context_destroy(struct lws_context *context) lws_pt_mutex_destroy(pt); } - for (n = 0; n < context->count_threads; n++) { - pt = &context->pt[n]; - - if (context->event_loop_ops->destroy_pt) - context->event_loop_ops->destroy_pt(context, n); - - lws_free_set_NULL(context->pt[n].serv_buf); - -#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) - while (pt->http.ah_list) - _lws_destroy_ah(pt, pt->http.ah_list); -#endif - } - /* * inform all the protocols that they are done and will have no more * callbacks. @@ -1944,10 +1953,5 @@ lws_context_destroy(struct lws_context *context) return; } - if (!context->pt[0].event_loop_foreign) - for (n = 0; n < context->count_threads; n++) - if (context->pt[n].inside_service) - return; - lws_context_destroy2(context); } diff --git a/lib/event-libs/libev/libev.c b/lib/event-libs/libev/libev.c index 746d57126c2ed0ee799596ac9b56b238d4dae697..d60072c0d43d756d7710a29d045bb5b73f4f1b37 100644 --- a/lib/event-libs/libev/libev.c +++ b/lib/event-libs/libev/libev.c @@ -1,7 +1,7 @@ /* * libwebsockets - small server side websockets and web server implementation * - * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -21,13 +21,66 @@ #include "private-libwebsockets.h" +static void +lws_ev_hrtimer_cb(struct ev_loop *loop, struct ev_timer *watcher, int revents) +{ + struct lws_context_per_thread *pt = + (struct lws_context_per_thread *)watcher->data; + lws_usec_t us; + + lws_pt_lock(pt, __func__); + us = __lws_hrtimer_service(pt); + if (us != LWS_HRTIMER_NOWAIT) { + ev_timer_set(&pt->ev.hrtimer, ((float)us) / 1000000.0, 0); + ev_timer_start(pt->ev.io_loop, &pt->ev.hrtimer); + } + lws_pt_unlock(pt); +} + +static void +lws_ev_idle_cb(struct ev_loop *loop, struct ev_idle *handle, int revents) +{ + struct lws_context_per_thread *pt = lws_container_of(handle, + struct lws_context_per_thread, ev.idle); + lws_usec_t us; + + lws_service_do_ripe_rxflow(pt); + + /* + * is there anybody with pending stuff that needs service forcing? + */ + if (!lws_service_adjust_timeout(pt->context, 1, pt->tid)) { + /* -1 timeout means just do forced service */ + _lws_plat_service_tsi(pt->context, -1, pt->tid); + /* still somebody left who wants forced service? */ + if (!lws_service_adjust_timeout(pt->context, 1, pt->tid)) + /* yes... come back again later */ + return; + } + + /* account for hrtimer */ + + lws_pt_lock(pt, __func__); + us = __lws_hrtimer_service(pt); + if (us != LWS_HRTIMER_NOWAIT) { + ev_timer_set(&pt->ev.hrtimer, ((float)us) / 1000000.0, 0); + ev_timer_start(pt->ev.io_loop, &pt->ev.hrtimer); + } + lws_pt_unlock(pt); + + /* there is nobody who needs service forcing, shut down idle */ + ev_idle_stop(loop, handle); +} + static void lws_accept_cb(struct ev_loop *loop, struct ev_io *watcher, int revents) { + struct lws_context_per_thread *pt; struct lws_io_watcher *lws_io = lws_container_of(watcher, struct lws_io_watcher, ev.watcher); struct lws_context *context = lws_io->context; struct lws_pollfd eventfd; + struct lws *wsi; if (revents & EV_ERROR) return; @@ -45,7 +98,12 @@ lws_accept_cb(struct ev_loop *loop, struct ev_io *watcher, int revents) eventfd.revents |= LWS_POLLOUT; } - lws_service_fd(context, &eventfd); + wsi = wsi_from_fd(context, watcher->fd); + pt = &context->pt[(int)wsi->tsi]; + + lws_service_fd_tsi(context, &eventfd, (int)wsi->tsi); + + ev_idle_start(pt->ev.io_loop, &pt->ev.idle); } LWS_VISIBLE void @@ -64,6 +122,7 @@ lws_ev_sigint_cb(struct ev_loop *loop, struct ev_signal *watcher, int revents) static int elops_init_pt_ev(struct lws_context *context, void *_loop, int tsi) { + struct lws_context_per_thread *pt = &context->pt[tsi]; struct ev_signal *w_sigint = &context->pt[tsi].w_sigint.ev.watcher; struct lws_vhost *vh = context->vhost_list; const char *backend_name; @@ -84,7 +143,7 @@ elops_init_pt_ev(struct lws_context *context, void *_loop, int tsi) return -1; } - context->pt[tsi].ev.io_loop = loop; + pt->ev.io_loop = loop; /* * Initialize the accept w_accept with all the listening sockets @@ -138,6 +197,11 @@ elops_init_pt_ev(struct lws_context *context, void *_loop, int tsi) lwsl_info(" libev backend: %s\n", backend_name); (void)backend_name; + ev_timer_init(&pt->ev.hrtimer, lws_ev_hrtimer_cb, 0, 0); + pt->ev.hrtimer.data = pt; + + ev_idle_init(&pt->ev.idle, lws_ev_idle_cb); + return status; } @@ -147,17 +211,22 @@ elops_destroy_pt_ev(struct lws_context *context, int tsi) struct lws_context_per_thread *pt = &context->pt[tsi]; struct lws_vhost *vh = context->vhost_list; - if (!pt->ev.io_loop) - return; - while (vh) { if (vh->lserv_wsi) ev_io_stop(pt->ev.io_loop, &vh->w_accept.ev.watcher); vh = vh->vhost_next; } - if (!pt->event_loop_foreign) + + /* static assets */ + + ev_timer_stop(pt->ev.io_loop, &pt->ev.hrtimer); + ev_idle_stop(pt->ev.io_loop, &pt->ev.idle); + + if (!pt->event_loop_foreign) { ev_signal_stop(pt->ev.io_loop, &pt->w_sigint.ev.watcher); + ev_loop_destroy(pt->ev.io_loop); + } } static int @@ -226,7 +295,7 @@ static int elops_destroy_context2_ev(struct lws_context *context) { struct lws_context_per_thread *pt; - int n, m, internal = 0; + int n, m; lwsl_debug("%s\n", __func__); @@ -240,7 +309,6 @@ elops_destroy_context2_ev(struct lws_context *context) if (pt->event_loop_foreign || !pt->ev.io_loop) continue; - internal = 1; if (!context->finalize_destroy_after_internal_loops_stopped) { ev_break(pt->ev.io_loop, EVBREAK_ONE); continue; @@ -252,7 +320,7 @@ elops_destroy_context2_ev(struct lws_context *context) ev_loop_destroy(pt->ev.io_loop); } - return internal; + return 0; } static int @@ -281,6 +349,15 @@ elops_init_vhost_listen_wsi_ev(struct lws *wsi) return 0; } +static void +elops_destroy_wsi_ev(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + + ev_io_stop(pt->ev.io_loop, &wsi->w_read.ev.watcher); + ev_io_stop(pt->ev.io_loop, &wsi->w_write.ev.watcher); +} + struct lws_event_loop_ops event_loop_ops_ev = { /* name */ "libev", /* init_context */ elops_init_context_ev, @@ -295,7 +372,7 @@ struct lws_event_loop_ops event_loop_ops_ev = { /* io */ elops_io_ev, /* run_pt */ elops_run_pt_ev, /* destroy_pt */ elops_destroy_pt_ev, - /* destroy wsi */ NULL, + /* destroy wsi */ elops_destroy_wsi_ev, /* periodic_events_available */ 0, }; diff --git a/lib/event-libs/libev/private.h b/lib/event-libs/libev/private.h index 54e1b124e240afb6a7d7eb8fa5ce501fce7179f3..52de727a2eebb3ee28ba01772423311dde9c55e0 100644 --- a/lib/event-libs/libev/private.h +++ b/lib/event-libs/libev/private.h @@ -23,9 +23,19 @@ #include <ev.h> +#define LWS_EV_REFCOUNT_STATIC_HANDLE_NEW(_x, _ctx) \ + { (_x)->data = _ctx; \ + _ctx->count_event_loop_static_asset_handles++; } +#define LWS_EV_REFCOUNT_STATIC_HANDLE_TO_CONTEXT(_x) \ + ((struct lws_context *)(_x)->data))) +#define LWS_EV_REFCOUNT_STATIC_HANDLE_DESTROYED(_x) \ + (--(LWS_UV_REFCOUNT_STATIC_HANDLE_TO_CONTEXT(_x)-> \ + count_event_loop_static_asset_handles)) + struct lws_pt_eventlibs_libev { struct ev_loop *io_loop; struct ev_timer hrtimer; + struct ev_idle idle; }; struct lws_io_watcher_libev { diff --git a/lib/event-libs/libevent/libevent.c b/lib/event-libs/libevent/libevent.c index 43dda05c5ee7d2b8a81820c9c355fe66b60ca669..5cb47263a359cb70989016c2060efea34c6da004 100644 --- a/lib/event-libs/libevent/libevent.c +++ b/lib/event-libs/libevent/libevent.c @@ -21,24 +21,83 @@ #include "private-libwebsockets.h" +static void +lws_event_hrtimer_cb(int fd, short event, void *p) +{ + struct lws_context_per_thread *pt = (struct lws_context_per_thread *)p; + struct timeval tv; + lws_usec_t us; + + lws_pt_lock(pt, __func__); + us = __lws_hrtimer_service(pt); + if (us != LWS_HRTIMER_NOWAIT) { + tv.tv_sec = us / 1000000; + tv.tv_usec = us - (tv.tv_sec * 1000000); + evtimer_add(pt->event.hrtimer, &tv); + } + lws_pt_unlock(pt); +} + +static void +lws_event_idle_timer_cb(int fd, short event, void *p) +{ + struct lws_context_per_thread *pt = (struct lws_context_per_thread *)p; + struct timeval tv; + lws_usec_t us; + + lws_service_do_ripe_rxflow(pt); + + /* + * is there anybody with pending stuff that needs service forcing? + */ + if (!lws_service_adjust_timeout(pt->context, 1, pt->tid)) { + /* -1 timeout means just do forced service */ + _lws_plat_service_tsi(pt->context, -1, pt->tid); + /* still somebody left who wants forced service? */ + if (!lws_service_adjust_timeout(pt->context, 1, pt->tid)) { + /* yes... come back again later */ + + tv.tv_sec = 0; + tv.tv_usec = 1000; + evtimer_add(pt->event.idle_timer, &tv); + + return; + } + } + + /* account for hrtimer */ + + lws_pt_lock(pt, __func__); + us = __lws_hrtimer_service(pt); + if (us != LWS_HRTIMER_NOWAIT) { + tv.tv_sec = us / 1000000; + tv.tv_usec = us - (tv.tv_sec * 1000000); + evtimer_add(pt->event.hrtimer, &tv); + } + lws_pt_unlock(pt); +} + static void lws_event_cb(evutil_socket_t sock_fd, short revents, void *ctx) { struct lws_io_watcher *lws_io = (struct lws_io_watcher *)ctx; struct lws_context *context = lws_io->context; + struct lws_context_per_thread *pt; struct lws_pollfd eventfd; + struct timeval tv; + struct lws *wsi; if (revents & EV_TIMEOUT) return; /* !!! EV_CLOSED doesn't exist in libevent2 */ - #if LIBEVENT_VERSION_NUMBER < 0x02000000 +#if LIBEVENT_VERSION_NUMBER < 0x02000000 if (revents & EV_CLOSED) { event_del(lws_io->event.watcher); event_free(lws_io->event.watcher); return; } - #endif +#endif eventfd.fd = sock_fd; eventfd.events = 0; @@ -52,7 +111,16 @@ lws_event_cb(evutil_socket_t sock_fd, short revents, void *ctx) eventfd.revents |= LWS_POLLOUT; } - lws_service_fd(context, &eventfd); + wsi = wsi_from_fd(context, sock_fd); + pt = &context->pt[(int)wsi->tsi]; + + lws_service_fd_tsi(context, &eventfd, wsi->tsi); + + /* set the idle timer for 1ms ahead */ + + tv.tv_sec = 0; + tv.tv_usec = 1000; + evtimer_add(pt->event.idle_timer, &tv); } LWS_VISIBLE void @@ -77,6 +145,7 @@ elops_init_pt_event(struct lws_context *context, void *_loop, int tsi) { struct lws_vhost *vh = context->vhost_list; struct event_base *loop = (struct event_base *)_loop; + struct lws_context_per_thread *pt = &context->pt[tsi]; lwsl_info("%s: loop %p\n", __func__, _loop); @@ -91,7 +160,7 @@ elops_init_pt_event(struct lws_context *context, void *_loop, int tsi) return -1; } - context->pt[tsi].event.io_loop = loop; + pt->event.io_loop = loop; /* * Initialize all events with the listening sockets @@ -110,13 +179,22 @@ elops_init_pt_event(struct lws_context *context, void *_loop, int tsi) vh = vh->vhost_next; } + /* static event loop objects */ + + pt->event.hrtimer = event_new(loop, -1, EV_PERSIST, + lws_event_hrtimer_cb, pt); + + pt->event.idle_timer = event_new(loop, -1, EV_PERSIST, + lws_event_idle_timer_cb, pt); + /* Register the signal watcher unless it's a foreign loop */ - if (context->pt[tsi].event_loop_foreign) + + if (pt->event_loop_foreign) return 0; - context->pt[tsi].w_sigint.event.watcher = evsignal_new(loop, SIGINT, - lws_event_sigint_cb, &context->pt[tsi]); - event_add(context->pt[tsi].w_sigint.event.watcher, NULL); + pt->w_sigint.event.watcher = evsignal_new(loop, SIGINT, + lws_event_sigint_cb, pt); + event_add(pt->w_sigint.event.watcher, NULL); return 0; } @@ -217,8 +295,15 @@ elops_destroy_pt_event(struct lws_context *context, int tsi) vh = vh->vhost_next; } - if (!pt->event_loop_foreign) + event_free(pt->event.hrtimer); + event_free(pt->event.idle_timer); + + if (!pt->event_loop_foreign) { + event_del(pt->w_sigint.event.watcher); event_free(pt->w_sigint.event.watcher); + + event_base_free(pt->event.io_loop); + } } static void @@ -227,10 +312,10 @@ elops_destroy_wsi_event(struct lws *wsi) if (!wsi) return; - if(wsi->w_read.event.watcher) + if (wsi->w_read.event.watcher) event_free(wsi->w_read.event.watcher); - if(wsi->w_write.event.watcher) + if (wsi->w_write.event.watcher) event_free(wsi->w_write.event.watcher); } @@ -271,7 +356,7 @@ static int elops_destroy_context2_event(struct lws_context *context) { struct lws_context_per_thread *pt; - int n, m, internal = 0; + int n, m; lwsl_debug("%s\n", __func__); @@ -285,7 +370,6 @@ elops_destroy_context2_event(struct lws_context *context) if (pt->event_loop_foreign || !pt->event.io_loop) continue; - internal = 1; if (!context->finalize_destroy_after_internal_loops_stopped) { event_base_loopexit(pt->event.io_loop, NULL); continue; @@ -305,7 +389,7 @@ elops_destroy_context2_event(struct lws_context *context) } - return internal; + return 0; } struct lws_event_loop_ops event_loop_ops_event = { diff --git a/lib/event-libs/libevent/private.h b/lib/event-libs/libevent/private.h index 347c7fe5459b8d4090583693d425bc03a6699298..1c2d3607cb987d2f1a898b180ecfba83be0bef0c 100644 --- a/lib/event-libs/libevent/private.h +++ b/lib/event-libs/libevent/private.h @@ -26,6 +26,7 @@ struct lws_pt_eventlibs_libevent { struct event_base *io_loop; struct event *hrtimer; + struct event *idle_timer; }; struct lws_io_watcher_libevent { diff --git a/lib/event-libs/libuv/libuv.c b/lib/event-libs/libuv/libuv.c index ecc7e2dec90ea2eb58279bcb11ff1403ad7b8278..ee948b64135c23b7628db3bbd34b59536485002b 100644 --- a/lib/event-libs/libuv/libuv.c +++ b/lib/event-libs/libuv/libuv.c @@ -1,7 +1,7 @@ /* * libwebsockets - small server side websockets and web server implementation * - * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -116,11 +116,7 @@ lws_io_cb(uv_poll_t *watcher, int status, int revents) eventfd.revents |= LWS_POLLOUT; } } - lws_service_fd(context, &eventfd); - - lws_pt_lock(pt, __func__); - __lws_hrtimer_service(pt); - lws_pt_unlock(pt); + lws_service_fd_tsi(context, &eventfd, wsi->tsi); uv_idle_start(&pt->uv.idle, lws_uv_idle); } @@ -526,7 +522,8 @@ elops_destroy_context1_uv(struct lws_context *context) UV_RUN_NOWAIT))) ; if (m) - lwsl_err("%s: tsi %d: failed to close everything\n", __func__, n); + lwsl_err("%s: tsi %d: not all closed\n", + __func__, n); } } @@ -891,8 +888,9 @@ lws_libuv_closewsi(uv_handle_t* handle) vh = vh->vhost_next; } - if (context->pt[0].event_loop_foreign) { - lwsl_info("%s: calling lws_context_destroy2\n", __func__); + if (!context->count_event_loop_static_asset_handles && + context->pt[0].event_loop_foreign) { + lwsl_info("%s: call lws_context_destroy2\n", __func__); lws_context_destroy2(context); } } diff --git a/lib/event-libs/libuv/private.h b/lib/event-libs/libuv/private.h index 173340e354542ea95579ee075fcc3c14265096a2..cf8f71045653207b4fbc9c96768d4fea8618f630 100644 --- a/lib/event-libs/libuv/private.h +++ b/lib/event-libs/libuv/private.h @@ -24,6 +24,11 @@ #include <uv.h> /* + * libuv's async destroy cb means that asking to close something doesn't mean + * you can destroy it or parent things until after the close completes. + * + * So we must reference-count creation and close completions with libuv. + * * All "static" (per-pt or per-context) uv handles must * * - have their .data set to point to the context diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index 4ff58781dd08dcfcc78ac4009f3cb136be8ab43f..9f60bd6067c7deb1a20e357b2dcb53583b785c93 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -757,7 +757,6 @@ struct lws_peer { struct lws_context { time_t last_timeout_check_s; time_t last_ws_ping_pong_check_s; - time_t last_cert_check_s; time_t time_up; time_t time_discontiguity; time_t time_fixup; diff --git a/lib/service.c b/lib/service.c index 8cf8c934bd607785ea9a8cee8d75cfdfb3c62faa..a08506fbf9c2954379eeab9b1fc1c612b7530dcd 100644 --- a/lib/service.c +++ b/lib/service.c @@ -554,9 +554,6 @@ lws_service_periodic_checks(struct lws_context *context, struct lws *wsi; int timed_out = 0; time_t now; -#if defined(LWS_WITH_TLS) - int n = 0; -#endif #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) struct allocated_headers *ah; int m; @@ -810,12 +807,10 @@ lws_service_periodic_checks(struct lws_context *context, /* * Phase 6: check the remaining cert lifetime daily */ -#if defined(LWS_WITH_TLS) - n = lws_compare_time_t(context, now, context->last_cert_check_s); - if ((!context->last_cert_check_s || n > (24 * 60 * 60)) && - !lws_tls_check_all_cert_lifetimes(context)) - context->last_cert_check_s = now; -#endif + + if (context->tls_ops && + context->tls_ops->periodic_housekeeping) + context->tls_ops->periodic_housekeeping(context, now); return timed_out; } @@ -869,7 +864,8 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, } #if defined(LWS_WITH_TLS) - if (lwsi_state(wsi) == LRS_SHUTDOWN && lws_is_ssl(wsi) && wsi->tls.ssl) { + if (lwsi_state(wsi) == LRS_SHUTDOWN && + lws_is_ssl(wsi) && wsi->tls.ssl) { switch (__lws_tls_shutdown(wsi)) { case LWS_SSL_CAPABLE_DONE: case LWS_SSL_CAPABLE_ERROR: @@ -927,6 +923,10 @@ handled: #endif pollfd->revents = 0; + lws_pt_lock(pt, __func__); + __lws_hrtimer_service(pt); + lws_pt_unlock(pt); + return 0; } diff --git a/lib/tls/mbedtls/ssl.c b/lib/tls/mbedtls/ssl.c index 44eb06144ff3a5413d0d2a9f819b4fc327cb9e6e..b024dfa66a71d49625ea05c76bb8b015e88025a2 100644 --- a/lib/tls/mbedtls/ssl.c +++ b/lib/tls/mbedtls/ssl.c @@ -501,7 +501,20 @@ tops_fake_POLLIN_for_buffered_mbedtls(struct lws_context_per_thread *pt) return lws_tls_fake_POLLIN_for_buffered(pt); } +static int +tops_periodic_housekeeping_mbedtls(struct lws_context *context, time_t now) +{ + int n; + + n = lws_compare_time_t(context, now, context->tls.last_cert_check_s); + if ((!context->tls.last_cert_check_s || n > (24 * 60 * 60)) && + !lws_tls_check_all_cert_lifetimes(context)) + context->tls.last_cert_check_s = now; + + return 0; +} + const struct lws_tls_ops tls_ops_mbedtls = { /* fake_POLLIN_for_buffered */ tops_fake_POLLIN_for_buffered_mbedtls, - + /* periodic_housekeeping */ tops_periodic_housekeeping_mbedtls, }; diff --git a/lib/tls/openssl/ssl.c b/lib/tls/openssl/ssl.c index d92ea781a3f3e4dad0ce7d108125e742903fd7bc..ec43770c405464b313bf2515098bc620e0b9dbf1 100644 --- a/lib/tls/openssl/ssl.c +++ b/lib/tls/openssl/ssl.c @@ -689,7 +689,21 @@ tops_fake_POLLIN_for_buffered_openssl(struct lws_context_per_thread *pt) return lws_tls_fake_POLLIN_for_buffered(pt); } +static int +tops_periodic_housekeeping_openssl(struct lws_context *context, time_t now) +{ + int n; + + n = lws_compare_time_t(context, now, context->tls.last_cert_check_s); + if ((!context->tls.last_cert_check_s || n > (24 * 60 * 60)) && + !lws_tls_check_all_cert_lifetimes(context)) + context->tls.last_cert_check_s = now; + + return 0; +} + const struct lws_tls_ops tls_ops_openssl = { /* fake_POLLIN_for_buffered */ tops_fake_POLLIN_for_buffered_openssl, + /* periodic_housekeeping */ tops_periodic_housekeeping_openssl, }; diff --git a/lib/tls/private.h b/lib/tls/private.h index 20f9bd36982ffe508596387de946a4adc4814840..5c6beeb9e8eac75aad0a50a2ff9a8164133c4cf7 100644 --- a/lib/tls/private.h +++ b/lib/tls/private.h @@ -93,6 +93,7 @@ struct lws_context_per_thread; struct lws_tls_ops { int (*fake_POLLIN_for_buffered)(struct lws_context_per_thread *pt); + int (*periodic_housekeeping)(struct lws_context *context, time_t now); }; #if defined(LWS_WITH_TLS) @@ -110,6 +111,7 @@ extern const struct lws_tls_ops tls_ops_openssl, tls_ops_mbedtls; struct lws_context_tls { char alpn_discovered[32]; const char *alpn_default; + time_t last_cert_check_s; }; struct lws_pt_tls { diff --git a/lib/tls/tls-server.c b/lib/tls/tls-server.c index 01e4ca63b7724030ba43f5e2586172213d80c5c1..19549a5d5d2d8fd3a523258a6a02c64ef4209af9 100644 --- a/lib/tls/tls-server.c +++ b/lib/tls/tls-server.c @@ -214,7 +214,7 @@ lws_server_socket_service_ssl(struct lws *wsi, lws_sockfd_type accept_fd) lws_gate_accepts(context, 0); #if defined(LWS_WITH_STATS) - context->updated = 1; + context->updated = 1; #endif /* * we are not accepted yet, but we need to enter ourselves diff --git a/minimal-examples/http-server/README.md b/minimal-examples/http-server/README.md index d9ea25f330b44b5f58625f3ae3e10f61f5eb9b78..1adb6943ec356ec15ea7c9044d8ec0fcca7d3bf5 100644 --- a/minimal-examples/http-server/README.md +++ b/minimal-examples/http-server/README.md @@ -3,6 +3,7 @@ minimal-http-server-basicauth|Shows how to protect a mount using a password file and basic auth minimal-http-server-dynamic|Serves both static and dynamically generated http content minimal-http-server-eventlib-foreign|Demonstrates integrating lws with a foreign event library +minimal-http-server-eventlib-demos|Using the demo plugins with event libraries minimal-http-server-eventlib|Same as minimal-http-server but works with a supported event library minimal-http-server-form-get|Process a GET form minimal-http-server-form-post-file|Process a multipart POST form with file transfer diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-demos/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-eventlib-demos/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..593d6871606f697d691b744cf9eda3f4e74d94e0 --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-eventlib-demos/CMakeLists.txt @@ -0,0 +1,79 @@ +cmake_minimum_required(VERSION 2.8) +include(CheckCSourceCompiles) + +set(SAMP lws-minimal-http-server-eventlib-demos) +set(SRCS minimal-http-server-eventlib-demos.c) + +# If we are being built as part of lws, confirm current build config supports +# reqconfig, else skip building ourselves. +# +# If we are being built externally, confirm installed lws was configured to +# support reqconfig, else error out with a helpful message about the problem. +# +MACRO(require_lws_config reqconfig _val result) + + if (DEFINED ${reqconfig}) + if (${reqconfig}) + set (rq 1) + else() + set (rq 0) + endif() + else() + set(rq 0) + endif() + + if (${_val} EQUAL ${rq}) + set(SAME 1) + else() + set(SAME 0) + endif() + + if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME}) + if (${_val}) + message("${SAMP}: skipping as lws being built without ${reqconfig}") + else() + message("${SAMP}: skipping as lws built with ${reqconfig}") + endif() + set(${result} 0) + else() + if (LWS_WITH_MINIMAL_EXAMPLES) + set(MET ${SAME}) + else() + CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig}) + if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig}) + set(HAS_${reqconfig} 0) + else() + set(HAS_${reqconfig} 1) + endif() + if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val})) + set(MET 1) + else() + set(MET 0) + endif() + endif() + if (NOT MET) + if (${_val}) + message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}") + else() + message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project") + endif() + endif() + + endif() +ENDMACRO() + +set(requirements 1) +require_lws_config(LWS_ROLE_H1 1 requirements) +require_lws_config(LWS_ROLE_WS 1 requirements) +require_lws_config(LWS_WITHOUT_SERVER 0 requirements) + +if (requirements) + add_executable(${SAMP} ${SRCS}) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared) + add_dependencies(${SAMP} websockets_shared) + else() + target_link_libraries(${SAMP} websockets) + endif() +endif() diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-demos/README.md b/minimal-examples/http-server/minimal-http-server-eventlib-demos/README.md new file mode 100644 index 0000000000000000000000000000000000000000..90720e4cda634f860836e0485e762b2027c506c8 --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-eventlib-demos/README.md @@ -0,0 +1,30 @@ +# lws minimal http server eventlib demos + +This demonstrates a slightly more complex demo that can use +any of the event loops (it defaults to poll) + +It uses statically included plugins to provide the lws test server functions + +Commandline option|Meaning +---|--- +-d <loglevel>|Debug verbosity in decimal, eg, -d15 +--uv|Use the libuv event library (lws must have been configured with `-DLWS_WITH_LIBUV=1`) +--event|Use the libevent library (lws must have been configured with `-DLWS_WITH_LIBEVENT=1`) +--ev|Use the libev event library (lws must have been configured with `-DLWS_WITH_LIBEV=1`) + +## build + +``` + $ cmake . && make +``` + +## usage + +``` + $ ./lws-minimal-http-server-eventlib-demos +[2018/03/04 09:30:02:7986] USER: LWS minimal http server-eventlib-demos | visit http://localhost:7681 +[2018/03/04 09:30:02:7986] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 on +``` + +Visit http://localhost:7681 + diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-demos/localhost-100y.cert b/minimal-examples/http-server/minimal-http-server-eventlib-demos/localhost-100y.cert new file mode 100644 index 0000000000000000000000000000000000000000..6f372db40ad29f5bc1e0463078d48f2bd8d5c60c --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-eventlib-demos/localhost-100y.cert @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +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 +e2Hl/7M0c/lbZ4FvXgARlex2rkgS0Ka06HE= +-----END CERTIFICATE----- diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-demos/localhost-100y.key b/minimal-examples/http-server/minimal-http-server-eventlib-demos/localhost-100y.key new file mode 100644 index 0000000000000000000000000000000000000000..148f8598ee1b61bcdfd35bba618fb010d8772f39 --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-eventlib-demos/localhost-100y.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +fBrpEY1IATtPq1taBZZogRqI3rOkkPk= +-----END PRIVATE KEY----- diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-demos/minimal-http-server-eventlib-demos.c b/minimal-examples/http-server/minimal-http-server-eventlib-demos/minimal-http-server-eventlib-demos.c new file mode 100644 index 0000000000000000000000000000000000000000..f624fa519685152ae0c05ba7b929542cc3c49b06 --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-eventlib-demos/minimal-http-server-eventlib-demos.c @@ -0,0 +1,186 @@ +/* + * lws-minimal-http-server-eventlib + * + * Copyright (C) 2018 Andy Green <andy@warmcat.com> + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * This demonstrates a minimal http[s] server that can work with any of the + * supported event loop backends, or the default poll() one. + * + * To keep it simple, it serves stuff from the subdirectory + * "./mount-origin" of the directory it was started in. + * You can change that by changing mount.origin below. + */ + +#include <libwebsockets.h> +#include <string.h> +#include <signal.h> + +#define LWS_PLUGIN_STATIC +#include "../../../plugins/protocol_lws_mirror.c" +#include "../../../plugins/protocol_lws_status.c" +#include "../../../plugins/protocol_dumb_increment.c" +#include "../../../plugins/protocol_post_demo.c" + +static struct lws_context *context; + +static struct lws_protocols protocols[] = { + /* first protocol must always be HTTP handler */ + + { "http-only", lws_callback_http_dummy, 0, 0, }, + LWS_PLUGIN_PROTOCOL_DUMB_INCREMENT, + LWS_PLUGIN_PROTOCOL_MIRROR, + LWS_PLUGIN_PROTOCOL_LWS_STATUS, + LWS_PLUGIN_PROTOCOL_POST_DEMO, + { NULL, NULL, 0, 0 } /* terminator */ +}; + +/* + * mount handlers for sections of the URL space + */ + +static const struct lws_http_mount mount_ziptest = { + NULL, /* linked-list pointer to next*/ + "/ziptest", /* mountpoint in URL namespace on this vhost */ + "candide.zip", /* handler */ + NULL, /* default filename if none given */ + NULL, + NULL, + NULL, + NULL, + 0, + 0, + 0, + 0, + 0, + 0, + LWSMPRO_FILE, /* origin points to a callback */ + 8, /* strlen("/ziptest"), ie length of the mountpoint */ + NULL, + + { NULL, NULL } // sentinel +}; + +static const struct lws_http_mount mount_post = { + (struct lws_http_mount *)&mount_ziptest, /* linked-list pointer to next*/ + "/formtest", /* mountpoint in URL namespace on this vhost */ + "protocol-post-demo", /* handler */ + NULL, /* default filename if none given */ + NULL, + NULL, + NULL, + NULL, + 0, + 0, + 0, + 0, + 0, + 0, + LWSMPRO_CALLBACK, /* origin points to a callback */ + 9, /* strlen("/formtest"), ie length of the mountpoint */ + NULL, + + { NULL, NULL } // sentinel +}; + + +static const struct lws_http_mount mount = { + /* .mount_next */ &mount_post, /* linked-list "next" */ + /* .mountpoint */ "/", /* mountpoint URL */ + /* .origin */ "./mount-origin", /* serve from dir */ + /* .def */ "test.html", /* default filename */ + /* .protocol */ NULL, + /* .cgienv */ NULL, + /* .extra_mimetypes */ NULL, + /* .interpret */ NULL, + /* .cgi_timeout */ 0, + /* .cache_max_age */ 0, + /* .auth_mask */ 0, + /* .cache_reusable */ 0, + /* .cache_revalidate */ 0, + /* .cache_intermediaries */ 0, + /* .origin_protocol */ LWSMPRO_FILE, /* files in a dir */ + /* .mountpoint_len */ 1, /* char count */ + /* .basic_auth_login_file */ NULL, +}; + +void signal_cb(void *handle, int signum) +{ + lwsl_err("%s: signal %d\n", __func__, signum); + + switch (signum) { + case SIGTERM: + case SIGINT: + break; + default: + + break; + } + lws_context_destroy(context); +} + +void sigint_handler(int sig) +{ + signal_cb(NULL, sig); +} + +int main(int argc, const char **argv) +{ + struct lws_context_creation_info info; + const char *p; + int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE + /* for LLL_ verbosity above NOTICE to be built into lws, + * lws must have been configured and built with + * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ + /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ + /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ + /* | LLL_DEBUG */; + + if ((p = lws_cmdline_option(argc, argv, "-d"))) + logs = atoi(p); + + lws_set_log_level(logs, NULL); + lwsl_user("LWS minimal http server eventlib | visit http://localhost:7681\n"); + lwsl_user(" [-s (ssl)] [--uv (libuv)] [--ev (libev)] [--event (libevent)]\n"); + + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + info.port = 7681; + info.mounts = &mount; + info.error_document_404 = "/404.html"; + info.pcontext = &context; + info.protocols = protocols; + info.signal_cb = signal_cb; + + if (lws_cmdline_option(argc, argv, "-s")) { + info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + info.ssl_cert_filepath = "localhost-100y.cert"; + info.ssl_private_key_filepath = "localhost-100y.key"; + } + + if (lws_cmdline_option(argc, argv, "--uv")) + info.options |= LWS_SERVER_OPTION_LIBUV; + else + if (lws_cmdline_option(argc, argv, "--event")) + info.options |= LWS_SERVER_OPTION_LIBEVENT; + else + if (lws_cmdline_option(argc, argv, "--ev")) + info.options |= LWS_SERVER_OPTION_LIBEV; + else + signal(SIGINT, sigint_handler); + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + while (!lws_service(context, 0)) + ; + + lwsl_info("calling external context destroy\n"); + lws_context_destroy(context); + + return 0; +} diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/404.html b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/404.html new file mode 100644 index 0000000000000000000000000000000000000000..1f7ae66e8b080611f28f8c0e56238e53cc5b72fb --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/404.html @@ -0,0 +1,9 @@ +<meta charset="UTF-8"> +<html> + <body> + <img src="libwebsockets.org-logo.png"><br> + <h1>404</h1> + Sorry, that file doesn't exist. + </body> +</html> + diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/candide.zip b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/candide.zip new file mode 100644 index 0000000000000000000000000000000000000000..82a66199a701657e3b4819f36e9fe18f2379a886 Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/candide.zip differ diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/favicon.ico b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c0cc2e3dff34012ba3d4a7848a7ed17579788ec5 Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/favicon.ico differ diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/http2.png b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/http2.png new file mode 100644 index 0000000000000000000000000000000000000000..b4129e73b7ad5a5aa5696e8958a968ba780a6798 Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/http2.png differ diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/leaf.jpg b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/leaf.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1a3f46b59df8ee245d02416287387022e432462f Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/leaf.jpg differ diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/libwebsockets.org-logo.png b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/libwebsockets.org-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..2060a10c936a0959f2a5c3a6b7fa60ac324f1a95 Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/libwebsockets.org-logo.png differ diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/lws-common.js b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/lws-common.js new file mode 100644 index 0000000000000000000000000000000000000000..f0c5b92f27c0d38cc23607d0ed1c47ede93e3375 --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/lws-common.js @@ -0,0 +1,398 @@ +/* + * This section around grayOut came from here: + * http://www.codingforums.com/archive/index.php/t-151720.html + * Assumed public domain + * + * Init like this in your main html script, this also reapplies the gray + * + * lws_gray_out(true,{'zindex':'499'}); + * + * To remove the gray + * + * lws_gray_out(false); + * + */ + +function lws_gray_out(vis, options) { + + var options = options || {}; + var zindex = options.zindex || 50; + var opacity = options.opacity || 70; + var opaque = (opacity / 100); + var bgcolor = options.bgcolor || '#000000'; + var dark = document.getElementById('darkenScreenObject'); + + if (!dark) { + var tbody = document.getElementsByTagName("body")[0]; + var tnode = document.createElement('div'); + tnode.style.position = 'absolute'; + tnode.style.top = '0px'; + tnode.style.left = '0px'; + tnode.style.overflow = 'hidden'; + tnode.style.display ='none'; + tnode.id = 'darkenScreenObject'; + tbody.appendChild(tnode); + dark = document.getElementById('darkenScreenObject'); + } + if (vis) { + dark.style.opacity = opaque; + dark.style.MozOpacity = opaque; + // dark.style.filter ='alpha(opacity='+opacity+')'; + dark.style.zIndex = zindex; + dark.style.backgroundColor = bgcolor; + dark.style.width = gsize(1); + dark.style.height = gsize(0); + dark.style.display ='block'; + addEvent(window, "resize", + function() { + dark.style.height = gsize(0); + dark.style.width = gsize(1); + } + ); + } else { + dark.style.display = 'none'; + removeEvent(window, "resize", + function() { + dark.style.height = gsize(0); + dark.style.width = gsize(1); + } + ); + } +} + +function gsize(ptype) +{ + var h = document.compatMode == 'CSS1Compat' && + !window.opera ? + document.documentElement.clientHeight : + document.body.clientHeight; + var w = document.compatMode == 'CSS1Compat' && + !window.opera ? + document.documentElement.clientWidth : + document.body.clientWidth; + if (document.body && + (document.body.scrollWidth || document.body.scrollHeight)) { + var pageWidth = (w > (t = document.body.scrollWidth)) ? + ("" + w + "px") : ("" + (t) + "px"); + var pageHeight = (h > (t = document.body.scrollHeight)) ? + ("" + h + "px") : ("" + (t) + "px"); + } else if (document.body.offsetWidth) { + var pageWidth = (w > (t = document.body.offsetWidth)) ? + ("" + w + "px") : ("" + (t) + "px"); + var pageHeight =(h > (t = document.body.offsetHeight)) ? + ("" + h + "px") : ("" + (t) + "px"); + } else { + var pageWidth = '100%'; + var pageHeight = '100%'; + } + return (ptype == 1) ? pageWidth : pageHeight; +} + +function addEvent( obj, type, fn ) { + if ( obj.attachEvent ) { + obj['e' + type + fn] = fn; + obj[type+fn] = function() { obj['e' + type+fn]( window.event );} + obj.attachEvent('on' + type, obj[type + fn]); + } else + obj.addEventListener(type, fn, false); +} + +function removeEvent( obj, type, fn ) { + if ( obj.detachEvent ) { + obj.detachEvent('on' + type, obj[type + fn]); + obj[type + fn] = null; + } else + obj.removeEventListener(type, fn, false); +} + +/* + * end of grayOut related stuff + */ + +/* + * lws-meta helpers + */ + +var lws_meta_cmd = { + OPEN_SUBCHANNEL: 0x41, + /**< Client requests to open new subchannel + */ + OPEN_RESULT: 0x42, + /**< Result of client request to open new subchannel */ + CLOSE_NOT: 0x43, + CLOSE_RQ: 0x44, + /**< client requests to close a subchannel */ + WRITE: 0x45, + /**< connection writes something to specific channel index */ + RX: 0x46, +}; + +function new_ws(urlpath, protocol) +{ + if (typeof MozWebSocket != "undefined") + return new MozWebSocket(urlpath, protocol); + + return new WebSocket(urlpath, protocol); +} + +function lws_meta_ws() { + var real; + + var channel_id_to_child; + var pending_children; + var active_children; +} + +function lws_meta_ws_child() { + var onopen; + var onmessage; + var onclose; + + var channel_id; + + var subprotocol; + var suburl; + var cookie; + + var extensions; + + var parent; +} + +lws_meta_ws_child.prototype.send = function(data) +{ + + if (typeof data == "string") { + data = String.fromCharCode(lws_meta_cmd.WRITE) + + String.fromCharCode(this.channel_id) + + data; + + return this.parent.real.send(data); + } + + { + + var ab = new Uint8Array(data.length + 2); + + ab[0] = lws_meta_cmd.WRITE; + ab[1] = this.channel_id; + ab.set(data, 2); + + return this.parent.real.send(ab); + } +} + +lws_meta_ws_child.prototype.close = function(close_code, close_string) +{ + var pkt = new Uint8Array(129), m = 0, pkt1; + + pkt[m++] = lws_meta_cmd.CLOSE_RQ; + pkt[m++] = this.channel_id; + + pkt[m++] = close_string.length + 0x20; + + pkt[m++] = close_code / 256; + pkt[m++] = close_code % 256; + + for (i = 0; i < close_string.length; i++) + pkt[m++] = close_string.charCodeAt(i); + + pkt1 = new Uint8Array(m); + for (n = 0; n < m; n++) + pkt1[n] = pkt[n]; + + this.parent.real.send(pkt1.buffer); +} + +/* make a real ws connection using lws_meta*/ +lws_meta_ws.prototype.new_parent = function(urlpath) +{ + var n, i, m = 0, pkt1; + + this.ordinal = 1; + this.pending_children = []; + this.active_children = []; + this.real = new_ws(urlpath, "lws-meta"); + + this.real.binaryType = 'arraybuffer'; + this.real.myparent = this; + + this.real.onopen = function() { + pkt = new Uint8Array(1024); + var n, i, m = 0, pkt1; + console.log("real open - pending children " + this.myparent.pending_children.length); + for (n = 0; n < this.myparent.pending_children.length; n++) { + + var p = this.myparent.pending_children[n]; + + pkt[m++] = lws_meta_cmd.OPEN_SUBCHANNEL; + for (i = 0; i < p.subprotocol.length; i++) + pkt[m++] = p.subprotocol.charCodeAt(i); + pkt[m++] = 0; + for (i = 0; i < p.suburl.length; i++) + pkt[m++] = p.suburl.charCodeAt(i); + pkt[m++] = 0; + for (i = 0; i < p.cookie.length; i++) + pkt[m++] = p.cookie.charCodeAt(i); + pkt[m++] = 0; + } + + pkt1 = new Uint8Array(m); + for (n = 0; n < m; n++) + pkt1[n] = pkt[n]; + + console.log(this.myparent.pending_children[0].subprotocol); + console.log(pkt1); + + this.send(pkt1.buffer); + } + + + this.real.onmessage = function(msg) { + + if (typeof msg.data != "string") { + var ba = new Uint8Array(msg.data), n = 0; + + while (n < ba.length) { + + switch (ba[n++]) { + case lws_meta_cmd.OPEN_RESULT: + { + var m = 0, cookie = "", protocol = "", ch = 0; + var ws = this.myparent; + /* cookie NUL + * channel index + 0x20 + * protocol NUL + */ + while (ba[n]) + cookie = cookie + String.fromCharCode(ba[n++]); + n++; + ch = ba[n++]; + + while (ba[n]) + protocol = protocol + String.fromCharCode(ba[n++]); + + console.log("open result " + cookie + " " + protocol + " " + ch + " pending len " + ws.pending_children.length); + + for (m = 0; m < ws.pending_children.length; m++) { + if (ws.pending_children[m].cookie == cookie) { + var newchild = ws.pending_children[m]; + + /* found it */ + ws.pending_children[m].channel_id = ch; + /* add to active children array */ + ws.active_children.push(ws.pending_children[m]); + /* remove from pending children array */ + ws.pending_children.splice(m, 1); + + newchild.parent = ws; + newchild.extensions = this.extensions; + + newchild.onopen(); + + console.log("made active " + cookie); + break; + } + } + break; + } + + case lws_meta_cmd.CLOSE_NOT: + { + var code = 0, str = "", ch = 0, m, le; + var ba = new Uint8Array(msg.data); + /* + * BYTE: channel + * BYTE: MSB status code + * BYTE: LSB status code + * BYTES: rest of message is close status string + */ + + ch = ba[n++]; + le = ba[n++] - 0x20; + code = ba[n++] * 256; + code += ba[n++]; + + while (le--) + str += String.fromCharCode(ba[n++]); + + console.log("channel id " + ch + " code " + code + " str " + str + " len " + str.length); + + for (m = 0; m < this.myparent.active_children.length; m++) + if (this.myparent.active_children[m].channel_id == ch) { + var child = this.myparent.active_children[m]; + var ms = new CloseEvent("close", { code:code, reason:str } ); + + /* reply with close ack */ + this.send(msg.data); + + if (child.onclose) + child.onclose(ms); + + this.myparent.active_children.splice(m, 1); + break; + } + + } + } // switch + } + } else { + if (msg.data.charCodeAt(0) == lws_meta_cmd.WRITE ) { + var ch = msg.data.charCodeAt(1), m, ms; + var ws = this.myparent, ms; + + for (m = 0; m < ws.active_children.length; m++) { + if (ws.active_children[m].channel_id == ch) { + ms = new MessageEvent("WebSocket", { data: msg.data.substr(2, msg.data.length - 2) } ); + if (ws.active_children[m].onmessage) + ws.active_children[m].onmessage(ms); + break; + } + } + } + } + } + this.real.onclose = function() { + var ws = this.myparent, m; + for (m = 0; m < ws.active_children.length; m++) { + var child = ws.active_children[m]; + var ms = new CloseEvent("close", { code:1000, reason:"parent closed" } ); + + if (child.onclose) + child.onclose(ms); + } + } + +} + + + +/* make a child connection using existing lws_meta real ws connection */ +lws_meta_ws.prototype.new_ws = function(suburl, protocol) +{ + var ch = new lws_meta_ws_child(); + + ch.suburl = suburl; + ch.subprotocol = protocol; + ch.cookie = "C" + this.ordinal++; + + this.pending_children.push(ch); + + if (this.real.readyState == 1) + this.real.onopen(); + + return ch; +} + + +/* + * end of lws-meta helpers + */ + +function lws_san(s) +{ + if (s.search("<") != -1) + return "invalid string"; + + return s; +} diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/test.html b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/test.html new file mode 100644 index 0000000000000000000000000000000000000000..91c6dc2e38c439b606d461125577925489fbf325 --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/test.html @@ -0,0 +1,858 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset=utf-8 http-equiv="Content-Language" content="en"/> + <script src="/lws-common.js"></script> + <title>Minimal Websocket test app</title> +<style type="text/css"> + span.title { font-size:18pt; font-family: Arial; font-weight:normal; text-align:center; color:#000000; } + .browser { font-size:12pt; font-family: Arial; font-weight:normal; text-align:center; color:#ffff00; vertical-align:middle; text-align:center; background:#d0b070; padding:12px; -webkit-border-radius:10px; border-radius:10px;} + .group2 { vertical-align:middle; + text-align:center; + background:#f0f0e0; + padding:12px; + -webkit-border-radius:10px; + border-radius:10px; } + .explain { vertical-align:middle; + text-align:center; + background:#f0f0c0; padding:12px; + -webkit-border-radius:10px; + border-radius:10px; + color:#404000; } + td.wsstatus { vertical-align:middle; width:200px; height:50px; + text-align:center; + background:#f0f0c0; padding:6px; + -webkit-border-radius:8px; + border-radius:8px; + color:#404000; } + .tdform { vertical-align:middle; width:200px; height:50px; + text-align:center; + background:#f0f0d0; padding:6px; + -webkit-border-radius:8px; + margin:10px; + border-radius:8px; + border: 1px solid black; + border-collapse: collapse;font-size:18pt; font-family: Arial; font-weight:normal; text-align:center; color:#000000; + color:#404000; } + + td.l { vertical-align:middle; + text-align:center; + background:#d0d0b0; + padding:3px; + -webkit-border-radius:3px; + border-radius:3px; } + .content { vertical-align:top; text-align:center; background:#fffff0; padding:12px; -webkit-border-radius:10px; border-radius:10px; } + .canvas { vertical-align:top; text-align:center; background:#efefd0; padding:12px; -webkit-border-radius:10px; border-radius:10px; } +.tabs { + position: relative; + min-height: 750px; /* This part sucks */ + clear: both; + margin: 25px 0; +} +.tab { + float: left; +} +.tab label { + background: #eee; + padding: 10px; + border: 1px solid #ccc; + margin-left: -1px; + position: relative; + left: 1px; +} +.tab [type=radio] { + display: none; +} +.content { + position: absolute; + top: 28px; + left: 0; + background: white; + right: 0; + bottom: 0; + padding: 20px; + border: 1px solid #ccc; +} +[type=radio]:checked ~ label { + background: white; + border-bottom: 1px solid white; + z-index: 2; +} +[type=radio]:checked ~ label ~ .content { + z-index: 1; +} +</style> +</head> + +<body> +<header></header> +<article> + +<table><tr><td> + +<table width=600px> + <tr> + <td valign=middle align=center> + <a href="https://libwebsockets.org"> + <img src="libwebsockets.org-logo.png"></a></td><td> + <section class="browser"> + <div id=brow>...</div></section> + </td> + <td width="64" height="64" id="wstransport"></td> + <td width="64" height="64" id="transport"></td> + </tr> + +</table> +</td></tr> +<tr><td colspan=2 align=center> +Click <a href="leaf.jpg" target="_blank">Here</a> to +have the test server send a big picture by http. +</td></tr> +<tr><td colspan=2> +<div class="tabs"> + + <div class="tab"> + <input type="radio" id="tab-1" name="tab-group-1" checked> + <label for="tab-1">Dumb Increment Demo</label> + + <div class="content"> + <div id="dumb" class="group2"> + <table> + <tr> + <td id=wsdi_statustd align=center class="wsstatus"> + <span id=wsdi_status>Websocket connection not initialized</span></td> + <td><span class="title">dumb increment-protocol</span></td> + </tr> + <tr> + <td class="explain" colspan=2> +The incrementing number is coming from the server at 20Hz and is individual for +each connection to the server... try opening a second browser window. +<br/><br/> +The button sends a message over the websocket link to ask the server +to zero just this connection's number. + </td> + </tr> + <tr> + <td align=center><div id=number style="font-size:120%;"> </div></td> + <td align=center> + <input type=button id=offset value="Reset counter"> + <input type=button id=junk value="Send junk"> + </td> + </tr> + </table> + </div> + </div> + </div> + + <div class="tab"> + <input type="radio" id="tab-2" name="tab-group-1"> + <label for="tab-2">Mirror Demo</label> + + <div class="content"> + <div id="mirror" class="group2"> + <table> + <tr> + <td colspan=1 id=wslm_statustd align=center class="wsstatus"> + <span id=wslm_status>Websocket connection not initialized</span> + </td> + <td> + <span class="title">lws-mirror-protocol</span> + </td> + </tr> + <tr> + <td colspan=2> + <div class="explain"> +Use the mouse to draw on the canvas below -- all other browser windows open +on this page see your drawing in realtime and you can see any of theirs as +well. +<br/><br/> +The lws-mirror protocol doesn't interpret what is being sent to it, it just +re-sends it to every other websocket it has a connection with using that +protocol, including the guy who sent the packet. +<br/><br/> +<b>libwebsockets-test-client</b> joins in by spamming circles on to this shared canvas when +run. + </div> + </td> + </tr> + <tr> + <td colspan=2>Drawing color: + <select id="color"> + <option value=#000000>Black</option> + <option value=#0000ff>Blue</option> + <option value=#20ff20>Green</option> + <option value=#802020>Dark Red</option> + </select> + </tr> + <tr> + <td colspan=2 width=500 height=320> + <div id="wslm_drawing" style="background:white"></div> + </td> + </tr> + </table> + </div> + </div> + </div> + + <div class="tab"> + <input type="radio" id="tab-3" name="tab-group-1"> + <label for="tab-3">Close Testing</label> + + <div class="content"> +<div id="ot" class="group2"> + <table> + <tr> + <td> + + </td></tr> + <tr><td id=ot_statustd align=center class="wsstatus"> + <span id=ot_status>Websocket connection not initialized</span> + </td> + <td colspan=2><span class="title">Open and close testing</span></td> + </tr> + <tr> +<td class="explain" colspan=3 style="padding:3px"> +To help with open and close testing, you can open and close a connection by hand using + the buttons.<br> + "<b>Close</b>" closes the connection from the browser with code 3000 + and reason 'Bye!".<br> + "<b>Request Server Close</b>" sends a message asking the server to +initiate the close, which it does with code 1001 and reason "Seeya". +</td></tr> + <tr> + <td align=center><input type=button id=ot_open_btn value="Open"></td> + <td align=center><input type=button id=ot_close_btn disabled value="Close" ></td> + <td align=center><input type=button id=ot_req_close_btn disabled value="Request Server Close" ></td> + </tr> + +</table> + +</div> + </div> + </div> + + <div class="tab"> + <input type="radio" id="tab-4" name="tab-group-1"> + <label for="tab-4">Server info</label> + + <div class="content"> +<div id="ot" class="group2"> + <table> + <tr> + <td id=s_statustd align=center class="wsstatus"> + <div id=s_status>Websocket connection not initialized</div> + </td> + <td colspan=1> +<span class="title">Server Info</span> <input type=button id=pmd value="Test pmd"> + + </td> + </tr><tr> +<td class="explain" colspan=2> +This information is sent by the server over a ws[s] link and updated live +whenever the information changes server-side. +</td></tr> + <tr> + <td align=center colspan=2><div id=servinfo></div></td> + </tr> + <tr> + <td align=center colspan=2><div id=conninfo style="border: solid 2px #e0d040; padding: 4px; width: 500px; height:350px; overflow: auto;"></div></td> + </tr> +</table> +</div> + </div> + </div> + + <div class="tab"> + <input type="radio" id="tab-5" name="tab-group-1"> + <label for="tab-5">POST</label> + + <div class="content"> +<div id="ot" class="group2"> + <table width=100%> + <tr> + <td colspan=1> +<span class="title">POST Form testing</span> + </td> + </tr><tr> +<td class="explain" colspan=2> +This tests POST handling in lws. +</td></tr> + <tr> + <td align=center colspan=2 class=tdform><div id=postinfo style=form> + FORM 1: send with urlencoded POST body args<br> + <form action="formtest" method="post"> + <span style="font-size:12pt;">Some text: </span> + <input type="text" name="text" value="Give me some text"><br> + <input type="submit" name="send" value="Send the form"> + </form> + </div></td> + </tr> + +<script nonce="lwscaro"> +function check_file() +{ + var f = document.getElementById('file').files[0]; + var max_len = 100000; + var dis = 0; + + if (f) { + if (f.size >= max_len) { + dis = 1; + document.getElementById('file_info').innerHTML = + "<span style=\"color:red;font-weight:bold\">File larger than "+max_len+"</span>"; + } else + document.getElementById('file_info').innerHTML = + "File length "+f.size; + } else + dis = 1; + + document.getElementById('upload').disabled = dis; +} +</script> + + <tr> + <td align=center colspan=2 class=tdform><div id=postinfo style=form> + FORM 2: send with multipart/form-data<br> + (can handle file upload, test limited to 100KB)<br> + <form name=multipart action="formtest" method="post" enctype="multipart/form-data"> + <span style="font-size:12pt;">Some text: </span> + <input type="text" name="text" value="Give me some text"> +<br> + <input type="file" name="file" id="file" size="20"> <span id=file_info style="font-size:12pt;"></span><br> + <input type="submit" id="upload" name="upload" disabled=1 value="Upload"> + </form> + </div></td> + </tr> + +</table> +</div> + </div> + </div> + +</div> +</td></tr></table> + +Looking for support? <a href="https://libwebsockets.org">https://libwebsockets.org</a>, <a href="https://github.com/warmcat/libwebsockets">https://github.com/warmcat/libwebsockets</a></a><br/> +Join the mailing list: <a href="https://libwebsockets.org/mailman/listinfo/libwebsockets">https://libwebsockets.org/mailman/listinfo/libwebsockets</a> + +</article> + +<script nonce="lwscaro"> + +document.getElementById('file').onchange = check_file; +document.getElementById('offset').onclick = reset; +document.getElementById('junk').onclick = junk; +document.getElementById('color').onclick = update_color; +document.getElementById('ot_open_btn').onclick = ot_open; +document.getElementById('ot_close_btn').onclick = ot_close; +document.getElementById('ot_req_close_btn').onclick = ot_req_close; +document.getElementById('pmd').onclick = on_pmd; + +/* + * We display untrusted stuff in html context... reject anything + * that has HTML stuff in it + */ + +function san(s) +{ + if (s.search("<") != -1) + return "invalid string"; + + return s; +} + +lws_gray_out(true,{'zindex':'499'}); + +/* BrowserDetect came from http://www.quirksmode.org/js/detect.html */ + +var BrowserDetect = { + init: function () { + this.browser = this.searchString(this.dataBrowser) || "An unknown browser"; + this.version = this.searchVersion(navigator.userAgent) + || this.searchVersion(navigator.appVersion) + || "an unknown version"; + this.OS = this.searchString(this.dataOS) || "an unknown OS"; + }, + searchString: function (data) { + for (var i=0;i<data.length;i++) { + var dataString = data[i].string; + var dataProp = data[i].prop; + this.versionSearchString = data[i].versionSearch || data[i].identity; + if (dataString) { + if (dataString.indexOf(data[i].subString) != -1) + return data[i].identity; + } + else if (dataProp) + return data[i].identity; + } + }, + searchVersion: function (dataString) { + var index = dataString.indexOf(this.versionSearchString); + if (index == -1) return; + return parseFloat(dataString.substring(index+this.versionSearchString.length+1)); + }, + dataBrowser: [ + { + string: navigator.userAgent, + subString: "Chrome", + identity: "Chrome" + }, + { string: navigator.userAgent, + subString: "OmniWeb", + versionSearch: "OmniWeb/", + identity: "OmniWeb" + }, + { + string: navigator.vendor, + subString: "Apple", + identity: "Safari", + versionSearch: "Version" + }, + { + prop: window.opera, + identity: "Opera", + versionSearch: "Version" + }, + { + string: navigator.vendor, + subString: "iCab", + identity: "iCab" + }, + { + string: navigator.vendor, + subString: "KDE", + identity: "Konqueror" + }, + { + string: navigator.userAgent, + subString: "Firefox", + identity: "Firefox" + }, + { + string: navigator.vendor, + subString: "Camino", + identity: "Camino" + }, + { // for newer Netscapes (6+) + string: navigator.userAgent, + subString: "Netscape", + identity: "Netscape" + }, + { + string: navigator.userAgent, + subString: "MSIE", + identity: "Explorer", + versionSearch: "MSIE" + }, + { + string: navigator.userAgent, + subString: "Gecko", + identity: "Mozilla", + versionSearch: "rv" + }, + { // for older Netscapes (4-) + string: navigator.userAgent, + subString: "Mozilla", + identity: "Netscape", + versionSearch: "Mozilla" + } + ], + dataOS : [ + { + string: navigator.platform, + subString: "Win", + identity: "Windows" + }, + { + string: navigator.platform, + subString: "Mac", + identity: "Mac" + }, + { + string: navigator.userAgent, + subString: "iPhone", + identity: "iPhone/iPod" + }, + { + string: navigator.platform, + subString: "Linux", + identity: "Linux" + } + ] + +}; +BrowserDetect.init(); + +document.getElementById("brow").textContent = " " + BrowserDetect.browser + " " + + BrowserDetect.version +" " + BrowserDetect.OS +" "; + + var pos = 0; + +function get_appropriate_ws_url(extra_url) +{ + var pcol; + var u = document.URL; + + /* + * We open the websocket encrypted if this page came on an + * https:// url itself, otherwise unencrypted + */ + + if (u.substring(0, 5) == "https") { + pcol = "wss://"; + u = u.substr(8); + } else { + pcol = "ws://"; + if (u.substring(0, 4) == "http") + u = u.substr(7); + } + + u = u.split('/'); + + /* + "/xxx" bit is for IE10 workaround */ + + return pcol + u[0] + "/" + extra_url; +} + +var params = {}; + +if (location.search) { + var parts = location.search.substring(1).split('&'); + + for (var i = 0; i < parts.length; i++) { + var nv = parts[i].split('='); + if (!nv[0]) continue; + params[nv[0]] = nv[1] || true; + } +} +window.onload = function() { +var transport_protocol = ""; + +if ( performance && performance.timing.nextHopProtocol ) { + transport_protocol = performance.timing.nextHopProtocol; +} else if ( window.chrome && window.chrome.loadTimes ) { + transport_protocol = window.chrome.loadTimes().connectionInfo; +} else { + + var p = performance.getEntriesByType("resource"); + for (var i=0; i < p.length; i++) { +var value = "nextHopProtocol" in p[i]; + if (value) + transport_protocol = p[i].nextHopProtocol; + } + } + + console.log("transport protocol " + transport_protocol); + + if (transport_protocol == "h2") + document.getElementById("transport").innerHTML = "<img src=\"./http2.png\">"; +} + +var mirror_name = ""; +if (params.mirror) + mirror_name = params.mirror; + + console.log(mirror_name); + + +/* + * if using lws-meta to carry the other ws connections, declare the + * parent connection object and start its connection to the server. + * + * These helpers are defined in lws-common.js + */ + +var use_lws_meta = 0, lws_meta; + +if (use_lws_meta) { + lws_meta = new lws_meta_ws(); + lws_meta.new_parent(get_appropriate_ws_url("?mirror=" + mirror_name)); +} + + +document.getElementById("number").textContent = get_appropriate_ws_url(mirror_name); + +/* dumb increment protocol */ + + /* + * to connect via an lws-meta connection, start the connection using + * lws_meta.new_ws(). To connect by independent connection, start + * the connection using just new_ws() + */ + + var socket_di; + + if (use_lws_meta) + socket_di = lws_meta.new_ws("", "dumb-increment-protocol"); + else + socket_di = new_ws(get_appropriate_ws_url(""), "dumb-increment-protocol"); + + try { + socket_di.onopen = function() { + document.getElementById("wsdi_statustd").style.backgroundColor = "#40ff40"; + document.getElementById("wsdi_status").innerHTML = + " <b>websocket connection opened</b><br>" + + san(socket_di.extensions); + } + + socket_di.onmessage =function got_packet(msg) { + document.getElementById("number").textContent = msg.data + "\n"; + } + + socket_di.onclose = function(){ + document.getElementById("wsdi_statustd").style.backgroundColor = "#ff4040"; + document.getElementById("wsdi_status").textContent = " websocket connection CLOSED "; + } + } catch(exception) { + alert('<p>Error' + exception); + } + + var socket_status, jso, s; + + if (use_lws_meta) + socket_status = lws_meta.new_ws("", "lws-status"); + else + socket_status = new_ws(get_appropriate_ws_url(""), "lws-status"); + + try { + socket_status.onopen = function() { + document.getElementById("s_statustd").style.backgroundColor = "#40ff40"; + document.getElementById("s_status").innerHTML = + " <b>websocket connection opened</b><br>" + + san(socket_status.extensions); + } + + socket_status.onmessage =function got_packet(msg) { + var s; + + console.log(msg.data); + + jso = JSON.parse(msg.data); + + if (jso.wss_over_h2 == "1") + document.getElementById("wstransport").innerHTML = "<img src=\"./wss-over-h2.png\">"; + + document.getElementById("servinfo").innerHTML = + "<table><tr><td class=l>Build info</td><td>"+ + san(jso.version) + "</td></tr>" + + "<tr><td class=l>Server info</td><td>" + + san(jso.hostname) + "</td></tr>" + + "</table>"; + s="<table>"; + var n; + for (n = 0; n < jso.conns.length; n++) { + var d = new Date(parseInt(jso.conns[n].time) * 1000); + + s = s + "<tr><td class=l>client " + (n + 1) + + "</td><td><b>" + san(jso.conns[n].peer) + + "</b><br>" + san(d.toString()) + + "<br>" + san(jso.conns[n].ua) + + "</td></tr>"; + } + s = s + "</table>"; + + document.getElementById("conninfo").innerHTML = s; + } + + socket_status.onclose = function(){ + document.getElementById("s_statustd").style.backgroundColor = "#ff4040"; + document.getElementById("s_status").textContent = " websocket connection CLOSED "; + } + } catch(exception) { + alert('<p>Error' + exception); + } + +function reset() { + socket_di.send("reset\n"); +} + + +function junk() { + for(var word = ''; word.length < 9000; word += 'a'){} + socket_di.send(word); +} + +function on_pmd() { + socket_status.send("{ \"RequestType\":\"DDoS\", \"blob\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAJbElEQVR4Xu2af4xUVxXHv+e9O0OhIEibgrRlF8rOG6CWKiumrUlTo6byj6mmNFta2DdQsRJJrP5j+0cnjSVp1BoSU1yzO28LEeL6h7GJNhpJNa2CBQ1gd9k3QPlRBU2LLV0W2p173zFvZvbtzOybmbd7J9DEd/+befece97nfe+95/4gxEWLAGlZx8aIAWqKIAYYA9QkoGkeKzAGqElA0zxWYAxQk4CmeazAGKAmAU3zWIExQE0CmuaxAmOAmgQ0zWMFxgA1CWiaxwqMAWoS0DRvrQJ3ujeb17Hrx6SuzLwV32x7VzO+1pj3HJprJue8BcBQCSONRzr+2RrHaO2OtHDc1wDcUwqOj0s7nWpVoDp+hOMeA5Au+zggbesuHX+Vti1VoOh3j4BxRxEf8xmVSbe3KlAdP2bOPUmEpaXviiGZsVbq+IsBxgCnpp9YgVPjNan2RwagmXMfUpnUAEAc9k6Rx8AHB0zzy3euU5nUXk02RXMzl+9Ssw8PYN06FeYvOkAmM5dfpzLWL6LGFXkSMR13jIAEmKXMpBM6AIXjeih+BVLqTEcSWfJ/T71ks4bZ9vAYgU1/epC2ZegAFLnhAogEM0tV5x1r/UcGKBw3UJ1sTyVwH8laZ5EU2DO4WCTFmXFbOTbnemxZdHnq9AAMvDVTjF4ObKVS7di8IvA97jOSArNsiLZ8oGBpW5HYRKrkB1IFcGwkiS2dhWkB7B1qE6Z5uiUAew7NEsk5o4EveEtgLw98TwngwIApRlcFoogBVnzdSArUAWg6+T4QS3U69Xi98eiqK7A4vnX1+YsldWbPJmSzk8fJa6FAP67FXTv9sVLZqU1k9rvHibGsmKQzBlTGeih0gqgcA69CFxaO+zKA+4uxEF6W3dbaSXFdA4Bmzt1DhK5SLHycTMd9n4A5pT9oSNqp0GXO1VagcFx/cphZhnZF2tasjwJAkRs+CqJPFvEBIzHA8a8ScQyMAdbbTGgJwAY7FS3LA3edXCyUbJoHitzwKIhK3Zb5ssykr5/Uha9BHigc9whQ3nHyu7DIDe8A0TY/OI/xtJexngnP5ocLRCRAkLLbas1KhOGps6lE2MxvOO6TBvBsMS7Pe8rbtHz7pLhavRLpdwtgCDCkzIS/o9Gff8pg/n55EtlRSqT7h+8B0xhs62AYvHJlf524QZ3duzs0pfCT7aj7gQNsmqPHH1V2qr9+ewBePHYXIICNHfsb1TNzw7Y6e343svdNWh35dpHyQL9iKUV5VGVSu+qt94tx7DrZCa8wA93pP0deiTR80fLDyACjOGthncgAp9FmqwG+CsbnynHkpW1Z04ip5Sai3x0CY3kpU+P9sjt9d6saaSlA9JybJWaMXARDyRFjLrZ1fNiqQLX8DAwmxai4CCAhzblzsWFhsH7W8lvM8eOiRSAGqIUvVqAmvhhgDFCbgKaDqY+B2ayBBV2fQJJnwzQlSL6HjcsvhMaRHUwG/2dXjjWMtXdwPjBjPryCARKXIcQ7sJd8EOn9fFuTbkCBCDOMUZw69596SXWVv4HB2bicXAClBARdwqm95+stEurFER1gz6GESM45DGBFmLNJW+C5E7cKUmfH68pkoh3rl046r8CL7s3CQ+hdFQY2KNvaXS94o8/\" }"); + socket_status.send("{ \"RequestType\":\"SendImage\", \"RequestID\":\"283463389\", \"toType\":\"toUser\", \"toID\":\"1036\", \"fileType\":\"image/jpeg\", \"blob\":\"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAGqAoADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9U6KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigArlviX43Pw78H3fipdHl1RreSGJLSKQRtK0kqxgBiDjlvSuprhvjNpmoav4INnpljPdznU9Ok8qCMu2xbuJmbA5wFBJPYA0AL4r+K2j+G/hxH8Rra0k1G3ureOezto3CPOXXcFBwcHbk9O1bmg+L9H12wW7juEimSygvrmAtlreOVCy7jjngN+VeOal4L8Vz3Wv8Ag99DvG0Hw5b317pEixMUupLpS0UUf94w5ZMD1FWdLfW/CFxqcF34S126l1vwxpttZfZdPllU3EUMitFIyqREcuOXIHXninYD0y8+KPgCwNkt34ntU/tCGO4tyA7BopPuOSBhQc8bsVYXxtpEc+tHULm2tbPRVheW5afIKyAkEjHy9OOTnNeEHQNX0bw/pTQaH4l0/W38NabbNCNFkvbS/kjgUeTMqrmF1bKncVx1rZ1Lwz4tuV1bVL3wpdSJDf8Ah+/urCGBmFzFAxaeOIf8tNv90ZzgDvRYD2fw34w8NeLoppvDurRXqwNslChlZD1GVYAj8q0rq9tLIRtd3CRCVxGhc4BY9Bn3rlfBl/Z61reqazp/g660q3kjhiW8u7SS0muyoOR5UiqwVc4DEc89q2fFQSTRprVtFk1VrjESWyjhmPQs38AHXdkYx60gNivPb74uwWXhPW/EX/CPzyXmjal/ZQ05Zh5k85ZFjCtjjcZFxxXXeGdO1LSdDtNP1bUPtt1CmJJvXngZ74HGcDOM4FeYXfhPXm+Nq28elXDeG7yaHXbi68s+SLmGORRGW6bi7RNjr8hoA6XU/i3p1pYeHr+w043a+INPk1NMziMQwIiNuY7T/fA/OtS8+J3gnSntLbWtftbO6u4IpxESzhA4BG5gMKPc4ryPSvBHipIvGdheaDei20HT7jRtB/ct/pMLzSSK0Yx8w8t4k47oaj8XaLqVglzJpmjeIbfV7rRLSAWw0WS/sdUdIiBFJsX9yQSVJZhgHPanYD1B/jD4PsfEWseHNdvo9Nm0q7itUeRiwn8yGOQN8q/IP3m3k9R1ra8ceLoPBfgzVfGTWpvYdMs3vPKSQL5qqucBsHGfXFebDwpq15YfE681LwvIuoaotuLdRbl/NK2EAKxHHzgSBxx3Bre+IukavqHwA1TRbPTbq41GXw75C2scTNM0vkAbAgGS2eMYzmgDt9c1xNF0CfXXiRlhjWQo8mwckDlsHHX0rMf4leCYNUg0O78QW0OoT+WogO4gO+Nql8bQTkYBIrjfFvjJfFvgjUfDeleE/Fq3s1qgQXHh68gQlXTI3PGBnr3rjdc0rWtM1K8Ph3QdeXVbq6tJW0250iS50/UGUIPN89V2wEYOSzfKVziiwHsPhL4k+GfGer6zoujXLvcaLcfZ5gyMA3yg7gSOmTj8K6qvPfh9aT6V418b2d3ot5bG81NL22uDZusE0Jt4l+WXGwkMrZUHI9K9CpAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUVma34m8OeGvsf/CQ67YaZ/aNyllafa7hIvtFw/3Yk3EbnPZRyaANOiisbQfGfhHxTc39l4a8T6XqtxpUohvorO7jme1kOcLIFJKHg8HHQ0AbNFZ2u+ItA8MWa6j4j1qx0u1eVIFmvLhYUMjnCoGYgbieg6mi08R+H7/VrnQbLW7G41KzjSa4s47hWmhjb7rOgOVB7EjmgDRoorO1/wAQ6D4V0uXXPE2tWOladAVEt3ezrDChZgq5diAMkgDnqRQBo0UgORkUtABRVbUtT07RrCfVNWvreys7VDJPcTyCOONB1ZmPAHuaNO1LT9XsYNT0q9gvLO5QSQzwSB45EPRlYcEe4oAs0VT1fWNJ8P6Zcazrup2un2FonmT3V1KsUUS+rMxAA9zU9tc297bRXlnPHPBOiyxSxsGV0YZDAjggggg0AS0UUmRnGeaAFooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooqrqGpWumQ+dcsQCcKBySaaTk7ITairstUVz3/AAmdh/z63H/jv+NH/CZ2H/Prcf8Ajv8AjW31ar/KZfWKfc6Giue/4TOw/wCfW4/8d/xo/wCEzsP+fW4/8d/xo+rVf5Q+sU+50NFc9/wmdh/z63H/AI7/AI0f8JnYf8+tx/47/jR9Wq/yh9Yp9zoaK57/AITOw/59bj/x3/GtDTNcstVLJBuWRRkow5xUyoVIK8kVGtCTsmaNFFFZGgUUUUAFFFFABXin7YfhS78TfAbxBeaUCup+HVj16ykUZaOS1cSkr7lVYfjXtdVtRsLXVLC50y+hWW3u4nhljboyMMEH8DQBxNz8UNOb4IH4tQyrHaz6AurREH7peEMg/wC+mAr52/Zu8OXnwh+KfhZtT3xn4seHLi9u1JO3+0Y5DcA/UxMwA9qxNG1PUX+COl/s0vcNJrFt46m8ITg9Xs7aYzE/TyvK9sGvaf2ptPTwl4J8LfEvS4WB+HGuWGolYx8xsg4imT6eU7E/SgBn7Qif8Jp8Ufhb8KU/eQTanJ4h1KP0gtgBG3/fbH8q6zw7rXw8j+M/jO3stIltPENlpdrNq2oSSnypbfLbAATgbcEk4rmPh40fj39pfx146jdZ7DwtYWnhrT5lOV8woJ7gA/70gH4Vm6DDpFx+0b8XLfX7mO30yXwzZpeTSSCNY4T5gdix4UAZ57UAbEH7S2r67YyeJfAvwY8TeIfDCFzHq0UkcX2iNTgyRRN8zrwcHIzXK/tQ+P8Aw78Sf2QNf8X+Grh3sriWzVllXZJDIt5EHjcdmB6irvg3wv8AtE/BnwxZ6B4BPhj4ieEbGEDSkkujZXv2Xqi7yDG+FxghuRiuE+Mvivwf4x/Y78djw34Tl8MXVtq8EOs6TKuJLe+a7hMhPJB3cEEcGgD2LU/2htUjt5tY8J/CHxL4j8O2u7fq9u0cccqL96SFG+aROCQeMjpXo/gHx34f+JPhSx8Y+GLhpbC/Qsu9drowOGRh2YHgitXTNNsdM0q10mxtkitLaBIIoVGFWNVwFA9McV4l+xt8vw28QQrwkPjDV0Reyr5q8D86AO2/aF/5Ij40/wCwRN/KpPgF/wAkX8G/9giD/wBBqP8AaF/5Ij40/wCwRN/KpPgF/wAkX8G/9giD/wBBoAn+Nt74T074UeJb7xzpcupaDDZFr+0ico8se5flBBBHOO9Y3iH4w+Gfh/4c8Jabo3h/UtV1HxDZxLoWh2QDTyRLCG5ZjhVVcZY1D+1X/wAm8eOv+wWf/Q1rktd+Hep+Lrb4aeLPh5440vRvHnhbQIns7W+Hmw3NrNbxrKskaneFPHzAH9aAOn8P/H+ZvF2neC/iJ8OtY8GX2tFk0uW6lSe3unUZKCRcbWx2IryTxZ8WPF1p+1VodxD8JvFM/wBj0TULaOxjeLddruT9+g3Y2jHfnmuq174jeNPDus+HbL9o/wCEumHSm1SKHT/EWk3v2m3t71uI2eMhZI89Bwa1vEP/ACeL4Q/7FbUv/Qo6APcrOeS6tIbmW3e3eWNXaKT70ZIztOO46VNRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABXKeNSfNtRnjax/UV1dcp41/wBda/7rfzFdOD/jI58V/CZzVeear8Q9V0r4tWHg6e3g/sa8tlDT4PmR3D58vJ6YYgL9WFeh15/4t+HOo+IdY1fVrW/t4JLnTYobB23boLuKQSRyHA+6GVTxzxX0eF9lzNVdrf0/keFiPacqdPe5Qb4sXMPxG1fRbiGIeH9Js2Zp1UmWSdCBJjnGFZtv1U11Ws/ELw1oKeZqc8sajS5NX3bMj7OhQHv97Mi8Vx8fwf1MWcEMmq2rTto89reTYbMt7LIZXlAx90uSfXmn/wDCAePdSujfaxceHo3h8PzaNDAvmTxu7PEdz7lX5SI8EDkZ4zXVKGFk009Fo/Pz+ZzxniIp3Wr/AK/A2n+JNneQwG3t77S5jfQWzx39n8zrIpK7drYwcdcnGOlWrP4m+H77V49LtrXUmimuTaRX/wBm/wBFeYdUD5znIxnGM965DTvhN4lW4M00+n6fafbbW5XT4Lya4iTyg+91aRQQW3gbeny1veGPCXjXw+bTQFvdK/sKyvJLlZlLm6ljZy4jKFdo+ZuWDHipqU8Mk+V/j/V/QcJ4htcyO/rV8LkjWoAD1D5/75NZVavhf/kNwfR//QTXk1v4cvRnp0f4kfU7uiiivBPZCiiigAooooAKKKKAPEbP9mu3tf2kLv47HX1ayngLx6P5JxFetFHG9wGzjJEYzxnnrXqHjzwlZePfBWueC9RIW31uwnsJGIztEiFdw9xnNb1FAHl/7O/wZl+B/wAPh4Tv9eGt6ncXk99f6j5Rj+0SyOSCQSTwu1eT2p0HwVtp/iH428Wa3qEV7pfjPSItIn0/yirLGoYPl88hgxHAFenUUAeC+G/hh+0R8OdFh8D+CPiV4fvvD9kgttNm1nS3lvLK3UYSPcjqsm0YA3L0Apus/suyan8E/EXw0HjBpNc8VX8Wp6prdzb7vNuFmRziNSMLhNoGeM175RQAijaoX0GK8/8Agp8L7j4TeG9T0G51iPUm1DW73VhIkJjCLOwITBJyRjr3r0GigDm/iP4Tl8d+BNc8HQ3q2b6vZvarOybxGW7lQRn868h8L/CT9qDwj4e0/wAM6R8dPC62WmQLbwB/C+5gijjJMnNfQVFAHhmsfCL46eNfBviXwX8QPi1oWo2euaebSE2ugfZzBIWB3kh8sMDGOK0PGHwM128bwh4o8B+MF0Pxh4Q09NNivHtvNtr238tUkimiJ5VtoI5yp6GvY6KAPDL/AOEXxa+Juo6Snxj8ZaIdB0i9i1AaVolg8Qu54jmNpZJHZtoPO1cVr/FL4ReMvEPxB8PfFD4c+MLLRNa0O1uLGSO+sftMFzBNjIIDKQRtGCDXrlFAENmt0lpCl9KklwsaiV0XarPjkgdhntU1FFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFZ+r6RDq0IR2KOnKOO1aFFVGTg+aO4pRUlZnKf8IVN/wBBBP8Av2f8aP8AhCpv+ggn/fs/411dFb/XK3cw+q0uxyn/AAhU3/QQT/v2f8aP+EKm/wCggn/fs/411dFH1yt3D6rS7HKf8IVN/wBBBP8Av2f8aP8AhCpv+ggn/fs/411dFH1yt3D6rS7HKf8ACFTf9BBP+/Z/xrT0bw7FpUhuHl82XGAcYArYoqZ4mrNcrZUcPTg7pBRRRWBsFFFFABRRRQAVFcPcRqDbwLK2eQX24H5GpaKAKX2jVP8AoGx/+BH/ANjR9o1T/oGx/wDgR/8AY1dooApfaNU/6Bsf/gR/9jR9o1T/AKBsf/gR/wDY1dooApfaNU/6Bsf/AIEf/Y0faNU/6Bsf/gR/9jV2igCl9o1T/oGx/wDgR/8AY0faNU/6Bsf/AIEf/Y1dooApfaNU/wCgbH/4Ef8A2NH2jVP+gbH/AOBH/wBjV2igCl9o1T/oGx/+BH/2NH2jVP8AoGx/+BH/ANjV2igCl9o1T/oGx/8AgR/9jR9o1T/oGx/+BH/2NXaKAKX2jVP+gbH/AOBH/wBjR9o1T/oGx/8AgR/9jV2igCl9o1T/AKBsf/gR/wDY0faNU/6Bsf8A4Ef/AGNXaKAKX2jVP+gbH/4Ef/Y0faNU/wCgbH/4Ef8A2NXaKAKX2jVP+gbH/wCBH/2NH2jVP+gbH/4Ef/Y1dooApfaNU/6Bsf8A4Ef/AGNH2jVP+gbH/wCBH/2NXaKAKX2jVP8AoGx/+BH/ANjR9o1T/oGx/wDgR/8AY1dooApfaNU/6Bsf/gR/9jR9o1T/AKBsf/gR/wDY1dooApfaNU/6Bsf/AIEf/Y0faNU/6Bsf/gR/9jV2igCl9o1T/oGx/wDgR/8AY0faNU/6Bsf/AIEf/Y1dooApfaNU/wCgbH/4Ef8A2NH2jVP+gbH/AOBH/wBjV2igCl9o1T/oGx/+BH/2NH2jVP8AoGx/+BH/ANjV2igCl9o1T/oGx/8AgR/9jR9o1T/oGx/+BH/2NXaKAKX2jVP+gbH/AOBH/wBjR9o1T/oGx/8AgR/9jV2igCl9o1T/AKBsf/gR/wDY0faNU/6Bsf8A4Ef/AGNXaKAKX2jVP+gbH/4Ef/Y0faNU/wCgbH/4Ef8A2NXaKAKX2jVP+gbH/wCBH/2NH2jVP+gbH/4Ef/Y1dooApfaNU/6Bsf8A4Ef/AGNH2jVP+gbH/wCBH/2NXaKAKX2jVP8AoGx/+BH/ANjR9o1T/oGx/wDgR/8AY1dooApfaNU/6Bsf/gR/9jR9o1T/AKBsf/gR/wDY1dooApfaNU/6Bsf/AIEf/Y0faNU/6Bsf/gR/9jV2igCl9o1T/oGx/wDgR/8AY0faNU/6Bsf/AIEf/Y1dooApfaNU/wCgbH/4Ef8A2NH2jVP+gbH/AOBH/wBjV2igCl9o1T/oGx/+BH/2NH2jVP8AoGx/+BH/ANjV2igCl9o1T/oGx/8AgR/9jR9o1T/oGx/+BH/2NXaKAKX2jVP+gbH/AOBH/wBjR9o1T/oGx/8AgR/9jV2igCl9o1T/AKBsf/gR/wDY0faNU/6Bsf8A4Ef/AGNXaKAKX2jVP+gbH/4Ef/Y0faNU/wCgbH/4Ef8A2NXaKAKX2jVP+gbH/wCBH/2NH2jVP+gbH/4Ef/Y1dooApfaNU/6Bsf8A4Ef/AGNOSfUWdRJp8aqSMsJ84HrjFW6KACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACioZry0t/8AX3Mcf+8wFUZvEekxcfaN5/2VJosBqUVz03jC3GRBau3+8QKozeLb9/8AVRRxj86fKwOvorg5tc1Sb7124HoOKjtdUvba4WcXDtgjILZBFPlA9AoooqQCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACori5gtYzLcSqijualrkPFsztfpAWO1EBA9zTSuBqzeK9NjyIxJIfYYFUpvGMh/497NR/vtn+Vc3RV8qA1pvE+rS/dlWMf7Kj+tUZtRv7j/XXcrD0LnFV6KdkAUUUUAFFFFABQOtFA60AelDpS0g6UtZAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFcb4r/5Cv/bJf612Vcb4r/5Cv/bJf61UdwMaiiirAKKKVVZztVSSewFACUVch0fU5/8AV2UuD3K4H61eh8KanJ/rDFF/vNn+VF0Bi0V08Pg5BzPeE+yrV2HwxpUX3o3kP+01LmQHF0DrXb3ui6b9ilCWqIVQkEdQRXEDrQncD0odKWkHSlrMAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigArjfFf8AyFf+2S/1rsq43xX/AMhX/tkv9aqO4FfSNGl1UuVkEaJwWIzzW7D4RsU5lmlkPpnApvg//jzn/wCuv9BW/Q27gZ8Og6TD92zRv9/5v51djhihG2KJUHooxT6KkAooooAKKKKAIbz/AI9Jv+ubfyrzoda9FvP+PSb/AK5t/KvOh1q4gelDpS0g6UtQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVxviv8A5Cv/AGyX+tdlXG+K/wDkK/8AbJf61UdwNPwf/wAec3/XX+grfrA8H/8AHnN/11/oK36T3AKKKKQBRRRQAUUUUAQ3n/HpN/1zb+VedDrXot5/x6Tf9c2/lXnQ61cQPSh0paQdKWoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK43xX/yFf+2S/wBa7KuN8V/8hX/tkv8AWqjuBp+D/wDjzm/66/0Fb9YHg/8A485v+uv9BW/Se4BRRRSAKKKKACiiigCG8/49Jv8Arm38q86HWvRbz/j0m/65t/KvOh1q4gelDpS0g6UtQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVxviv/AJCv/bJf612Vcb4r/wCQr/2yX+tVHcDT8H/8ec3/AF1/oK36wPB//HnN/wBdf6Ct+k9wCiiikAUUVDc3dtZp5lzMsY7ZPX6UATUViy+K9MQ4QSye4XH86j/4S+w/59p/yH+NOzA2Lw4tJs/882/lXnQ610GqeKBdW7W9pCyBxhmbGce1c+OtXFWA9KHSlpB0pazAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiioLy9t7GEz3MgVR09SfQUAT0Vy9z4vkJItLYAdi55/Kqh8Vaqe8Q+iVXKwOzri/FDq+qttOdqKp+vNNfxNqrqV81Fz3C81mO7yOXdizMcknvTirAdV4P/AOPOb/rr/QVv1geD/wDjzm/66/0Fb9S9wCiiikBXv7tLG1kuX/gHA9TXBXd3PeztPO5Zj09h6Cuq8WFhpqgdDIM/rXH1cUAUUUVQBQOtFS2sTT3MUKjJdwv5mgD0UdKWkpayAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACuH8QX73l+67j5cR2qP5mu4rzzUI2ivZ43HIc/zqo7gV6KKKsAooooA6zweP8AQpj/ANNf6Ct+snwzbNb6WjMMGUl/w7fpWtWb3AKKKKQFXU7IX9lJbHgsMqfQ1wU8EttK0MyFWU4INej1UvtLstQXFzECezDgiqTsB5/RXUyeDoScxXjKPQrmmr4OT+K+P4J/9eq5kBzFdH4Y0h/MGo3CFQv+rB7n1rSs/DWnWrB2VpWHQv0/KtUAAYAwBUuXYBaKKKkAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKK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RGJ2jIDLzkMM9wQCPpXM6V8IfDuk614c1uC7vGm8N2ktrAjsu2YuCDJIMcuNzYIx9413VFAHBn4VfY2a48PeMNY0m6a+vr1pYhFIr/apmldGjkRkIDMdpIyB3qfwl8K9E8IajZ6rY3t3LPa2dzaMZSuJTPKkjyMABhtyDgYGCeK7WigDC8N+EbHwzoM3h+0uZ5YZp7qcvJjcDPK8jDgAYBcgewFc6vwc0NbW1tU1fVI/sel3WlRSRSiOQJPKspkDKAQ6soxjj1Brv6KAPPf+FQwX76jdeJvFWp6xe39gNNW5ljgiaGAMWG0RIqk7jnLA10Wg+F73RZ7SWfxVqmoJa2IsvJn8tY5CGBEpVFA3gDbxgYzxmugooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD//Z\"}") + socket_status.send("{ \"RequestType\":\"SendImage\", \"RequestID\":\"788346414\", \"toType\":\"toUser\", \"toID\":\"1036\", \"fileType\":\"image/jpeg\", \"blob\":\"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAHgAoADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9U6KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAopNy/wB4fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/AHh+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv8AeH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/wB4fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/AHh+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv8AeH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/wB4fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/AHh+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv8AeH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/wB4fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv94fnRuX+8PzoAxst6mky3940UmRQA7J9TRk+ppKKAAFh/EaCWP8RoooAXJ9TRk+ppKKADL/AN40Zf8AvGkwKMCgA3N/eNG5v7xoI70Ad6AF3Mf4iPxpMsP4j+dHSlxmgBCWH8R/OlBb+8fzpCM96OgoAVWb+8fzoLN6n86OBTSc0AOy3940Zb+8aTg0YFACkv8A3jRlv7xpNvvSYx3oAXLn+I0Ev/eNAwOc0YBNABubHU/nSbm/vH86VaMc5oATc394/nTt3+0aTb70baAAl8/eP50uW/vGk20baADc3940B27k0babQA/Lf3jRlv7xpMAdTRgHpQA4lj/EaTLf3jTSMUvFAC5b+8aMt/eNJjHNGAelAC5b+8aMt/eNJxQSD3oAXLf3jRlv7xpNvvRjHegBSW/vGgFv7xpuB60YHrQA7Lf3jRlv7xpAQO9IMDvQA7Lf3jRlv7xpuM96XbQAuW/vGjLf3jSfLR8tAC5b+8aAW/vGkAFJgetADst/eNGW/vGk2+9IAPWgB5LH+I0mW/vGmkYpeKAFy3940Zb+8aTGOaMA9KAFy3940Zb+8aTigkHvQAuW/vGjLf3jSbfejGO9ACkt/eNALf3jTcD1owPWgB2Wz940mWzjcaP4aMDFAC5b+8aPm/vGmjA70pGaAF+b+8aPm/vGm4HrRgetADst/eNALf3jTcD1pQPegBQX/vGjLf3jSAClOO9AAS2fvGgFs/eNJgYzRgYzQApLf3jQS3940hA9aQjHegB2W/vGjLf3jTcD1owPWgB1FFFABRRRQAUUUUAGc0UnzUtACZFGRRk+lGT6UALSZx2paMigApMr1paTJ9KAA57UDPelpPmoAMCjAoyKMigA+Wj5aMn0oyfSgBcYowKKKAEwKCAO1Lz6UhJoAMg0ZApfwo/CgBMCjApAT0FOoATAowKWigBMCjgUZPpRk+lAAeDmlpDycUtACfNSgGkzntRz9KADn1oOR3oyBQSD3oAXoKRTnqaXp1ozmgAooooAKKT5qPmoAXPGaQEmlBzScCgAwfWjB9aMijIoADntQM96BnvQc9qAFpP4qXPOKOM0AITilHNHtRQAnzUoBpM57Uc/SgA59aDkd6MgUEg96AF6CkU56ml6daM5oAKKKKACik+aj5qADqOKFpQc0ZFACEZpQMUgPrS0AIc9qBnvQc9qBnvQAtJto3UtACE54FKOKAc0ZxQAUUm72o3e1AAc9qTBFKCaDntQAtFITigHNACg5oJxQBigjNAATQPeikyKAFoozijOaACg57UUUAJgUv4Ugz3paAEwKMCl59KOfSgAooooATgUGl59KKADgUmRS0mT6UALRwKKTJ9KADg0vAoOR2pMn0oACfalyKTJ9KUnFABRjFFGRQAcdBzQeODwPSuN+IXxa8EfDLTW1DxZq8NrGASFJyx/Ac189aj/AMFFvhRaXTQ2K/aVU4D/ADCgD635AyRj3oPTI596+evh3+218HvHl8mlx6wlrdyHARwcZ+pr362vrW8tFvbW4SSJ13KynIIoAm45/hPal6ngY968W179q34XeHfGjeBtQ1YDUldY/L2Hqa9lgnS6hSdPuSorg+xGRQA89KWkPSloAQDFLRRQAc+lI1LnFITxQANSjgUUUAIc8sB16ilHp+leZ/Fz4++Bfgy1jH4xvxbPqG7yvlJzt+ldN8PfH+hfErw1beKfDtx59ldZ8t8Yzg4oA6XHt06UuOpHJpMkHPf0rB8ceM9H8A+HLnxNrkvlWVqAZG69aAN4nC5xk96Mc4HXtXlvwq/aJ8AfF2+ubHwjqQmltSBINpHX616iB6jPvQA7A60UUhGaAA57UtIc9qWgABzSYpQMUZoAQ9KWkPSloAQDFLRRQAc+lI1LnFITxQANSjiiigAooooAMYoPNIc9qWgAHSiijPOKACiiigBPmo+ag57Upz2oATrQDmjpQBigBaKKKAEBz2paQYzSk4oAQnFKeKQjNKeaAEY0ueM0YzQelAAcnvR+NJxnOaOtAC4zRSEZpaACiiigAooooAKKBzRQAUUUUAH40UHHeigAopMCjAoAX8aPxpDjvScetADufWjn1pDjvQMdqADB9aMH1oyKMigA59aXn1pDjvSkjvQAgyc4rmfiR40sfh94O1LxPqMgWO0iJBJ744/Wum6jBr5Z/wCChOuXen/Bi6s7aRkW5xvIPo1AHxBdah8R/wBrj4vnTo7u4aGe4ZQoY7Y0B/LpX2j4Q/4J4/CnTdESDX4ft96yAyS5K849K8x/4Ji+FbSX/hIvEVzErzr5XkuRyvXNfoGAoHAz70Afl5+1T+x3cfBy2HjPwJPM2nodzxxAgxc8c9TXuP7Anxn1/wAV+Hb3wT4j8+STTY8RSSggnIPrX2B4h8N6N4p02TStbtEubab78bDg1z/hv4V+BfBBnu/DWix2csincy9+KAPzF+M8m39rm4ZjhRdxdT06V+j+rfHf4X+BrCytPEfii3t5fs8YKr838I9K/MH9qI6j/wANIawulyFblp40jYDueK+h/CP7Auv+OvDCeJfG3jO4i1O6hDrEyltvHHf0oA+1vBnxW8B+P4xJ4X1+C6B7AgH8utdaCBkk5B6e1fjday+NP2avjpHoEGq3BksrqOMqWO11cgdM46Gv0i/aI+Nn/Cqvg5/wk0L41G6tIzAueSWUZP60Ad74w+MHw+8CKW8SeIbe229VDBiPwFZ3hX4+/CzxpdCz8PeKLeeU8YY7f51+dfwU+A/j39qnVr3xb4p8Tz2tk8m4SsxIcEnjGe1bHx6/ZA8SfAnSR448G+LJruCyIeXYSu054780Afp7G6yrvQh1PRgeKz9a8RaN4dtGvdX1CG0iQZLO4Gfzr5i/Yq/aIu/iN4GvNK8S3e/UdDiy0jHlxg/0FfLPx9+LPjv9oP4vt4A8MXs6WInMMMUTEdPvHIoA+/JP2pfgjHfGwk8YwifO3btOM/WvQ/D/AIr0DxVbC70LU4LqMjIKOCfyr4bsv+CbUkvhcXdx41mGpvF5m3YchsZxnNeS/BzWfiz+zv8AGhPC98uoXGmpOIrhSrMrg8Kc84oA9N/4KiFheeEDuwczYHr0r6E/YZOf2fNAYntJwe3zV86f8FN5zcDwRc7cNMkjn2yAa2vhh+0h4f8Agn+y3owa5SbWJ45RBbhuc7utAH2jrXxE8HeHbr7Hq+u20Ew6ozjNeTftU69pev8A7P2v32lXAmhZF+ZenWvjP4OfCf4lftUeO38ceKdQu4tIabe77yFK54AANfX/AO0f4N0rwN+zTq+g6OhjhtokVcsWJOeetAHy/wD8E6NWsNI8R+Ib7VLyO2hjZSWdgB39a+1Zf2nfgvFqR0mTxhCtyG2425GfrX5UfAnwN45+JnimTwV4TvJrW3vpMXUkf8IBr6s8Xf8ABOO7sfC8moaL4xmn1OCIyFdpBY4yec0Afd+i6/pHiG0F9o2oRXMDAEFGBq+8ixxtJKQEHcnFflj+yj8a/GPwg+K8fw78V3s7afLMYZklYnZjgdfU19O/tw/tEX/w08I2eieGLny9Q1qM7ZF6oMD+hoA9y8VfHn4XeDJWt9f8UW8MgOMKd38qd4T+Ovwy8bTeToHii3mc8BWIX+dfnz8AP2Q/F3x7sH8b+L/EN1Z2t0S0buWbfzzxnioPj/8AsneMv2ereLxr4T8Q3V5ZW7b5HQlAnPHGeaAP1Gmu7eC3N08gMaruLA8Yrmbb4p+BLu8TT4fEVs1ySVEe8A5/Ovn/APYt+Ok/xm8DXXhjxROZb+yjEUjE8sDx/KvmD9rj4NeKfgj8QI/G/hm5vP7Knm85cSMRHg5PfuaAP1Ha4hihNzJIqx7dxYngCuWh+KngWe7+wQ+ILd7gttCKwJJ/CvhLxj+3edT+BltoemztH4luofJlYdUxx+oq5+wr+z7rXiPUh8VfGU939n3mSzSSRsSEk7uM0AfoYjh1DL0IBFOPtTY0WNAi9FAApSaAFOT3opOBRkGgBaKKKAAmijIoJxQAmD60YPrRkUZFAC/jQOKQAUvAoATPGaM8ZoPSg9KAFooooAAc0E4oooAKKM5ooATgUvBpODS8CgAopMijIoAWiiigBM84paT+KjHOaAAUZ5xS0e9ABRRRQAUUUUAJ81HzUmB60YHrQAvzUfNSYHrRgetAClvSgZ70tJkUALRSA5oJxQAtI1GRQT2oAGoPShqD0oAAcmj+KgHn60fxUAB7YoPSgnmgnigAB4znrXzR+3v4TvPEHwS1C7so2lktApCqOeWr6XyNpBXkdKy/Evh+x8T6Jd6HqcKvFdxlCp5HSgD8+f8Agml4/sNH1vXPB+ozrHPfmNbdWOOVzmv0ZJ6gYr8mPjT8BviX+zf8Qz4u8JJcPYrMZra5hUngnJGB+VereEv+Ckuv6XoyWPiXwXNdXUKBfOZypcgemKAPsT9oX4sR/B/4dX3ilNjXUQHkxFgCxzivF/2Vf2pfFvx31nVLLUtNMNvZr97dkcg18ffFn41fFX9qjxFbaLY6Rdw2Uj7YrRVJVQfVsV94/slfs/j4LeAWfU4h/a9/FuuOORxxQB8MfGlFk/a4uFcAj7ZD1/Cv1f0dVTRbJEGB9mj/APQBX5UfGWyvH/a2uHWznK/a4fmEZI7d6/VjSfl0ezXv9mj/APQRQB+U/wC2QAP2oJ8Lg/bLbt7ivb/+Ch73q/DXwWsDN5JtB5uOn3VxXi/7Y1jezftNzSRWk7j7ZbfMsZI6r3r7e/aK+DUvxf8AgfDpmnW+7Uraxia3Hf7oJ/lQB8Ufs/eG/wBrDUvBFvP8JrnGjHOxVKZHPfPNdx4q+FX7c3ifRbjR/EtwJrCcASI7RqPzrhPgN+0J45/Zf1a78I+JvDt1PYb9vlEECPB7HHeu5+Nf7dPib4laGfCXgHw7dWcl5hWmj3E5zx2oA2P2afgB8T/hND4l1XxBbRwQ3FuxPlzq+fkPYGvKP2NTbP8AtMRPqpUuLqfbv79fWvrb9jb4Z+OrHwXfav8AEW5nkl1iLEcMpJ2jBH9a+Tfjp8LfHv7OvxjPjnw7p85sTOZraWJSQRnLdKAP1aBVUBBBGPyFeb618Qvg1ZeJX0jVbywGrKyqytEpbcenNfJw/wCCj8p8IG1bwfMdX8nys7jndjGeleU/s7/Df4h/H74zr478QxXkVgJ/NuJX3KMdVGKAPRf+CoLo7eDnh6MsxXHcYGK+VdI+C/xL8TfD6bx3a2E8mjaeAyHdkcnnAr6u/wCCmunXIbwXb20EswgWRPkQnoAO1e7/ALHPh201j9mrS9I1awGy5ilR1dMHknrQB5F/wT7+PdlNp/8Awq7W3igurc7bbIAL85NfQX7YRB+A2vt1BROfxr8+/jp8MvFP7N/xmXXvDkVwtq9x5ttJEhIIzlhxX1r45+KsPxf/AGTdR1eCGUXawRrPEUO4EHFAHj//AATVOlDxTr3nbBdl18vPXvnFfovPsMTBz8m07s9xivxa+Cni/wCIPwo8Uv448PaReTW9lJm4QIwyCcelfVnij/go7cXPhOW10nwjPDqs8Xl5ycqcYJxigDwj9oQRp+1tfroG3aLy32lOnUZrr/29/tv/AAmvhtrzd5XlQ7M9Purmsz9lb4Q+MfjV8YY/Hviayn/s+GYzXM0qkbs8r1r6l/bh/Z51H4leDrPXPDFoZNQ0OM7Y1HMg4/kBQB67+zCbD/hTegiwKeWYjkrjk1T/AGsf7OPwR13+0dhTyxgN65r4c/Z7/a68W/AnTj4H8WeHrq7sbViibwy+Wc89uai/aF/ay8X/AB/ij8FeEvD11a2M7BHCBm83J47cUAdD/wAE2zc/8J9riwqRbiQbj2xzivq79sXxF4E0j4TalB4yjhla4TFvG33ic9u9cN+x78G5PgT8Or3xj4xgMF5eRCaRCMlcAkfnXyf8cPHnjX9qD4vw+G9Ftbs6atx5dvFsYKFzhjQB876W9tZ67bajfWbyaeLoOFIIBTf6/Sv2n+Avinwh4q+G+kah4N8lbIQhfKTqpAAOfxr5/wDHv7E3h6b4EW+haPZr/bunW5lS4A+ZifmIr54/ZA+NXiT4JfECTwL4qhu00u4n8ly0bERkHA7dzQB+poPpSDOeaitLqG+tormE/LKgcfQjNS8YwaADvR3o70d6AAnFHQUEZpaAAc80HnikJxS4xQAU3JpQc0EZoAU8UgNKeaQCgA3UZFHy0fLQAZFGRR8tHy0AGQaMgUny0fLQAuRRn3pDtxSjaaADvR3o70d6ADn1oOR3oIHrRgHpQAEkDrQARzRjPNAAoACcUtITiloAQDvS0UUAJ3o70d6O9AC0UUGgBAeaCOc0mBig4xQDVg5zkUvPrQMdKBjPFJK/UNWHA4ozQwxQRTt5hdCE0ueKRqXHFHzC6BaAecYoFIetO6DQUkjvQcgdaQ896OtK6DQXIHSjIoA4pB1o1DUM0ZoHWlajUNRaMUh6YoWlqBS1PRdM1u2az1OyhuYmGCsiA/zrzLUv2V/gjrF013f+DIWlY5JDkD9K9ZODgmg4DYLZFPUZyHhL4SfD7wOAPDvh62tj2YoGI/E115A/1eARjFKeRjHSj5cZzk+lOzE7HJXnwr8DX+sHXbzQoJL1iG84gZyK6tFVVWNQAEGAPQU8YIwBSdyCdxPalqLc5TWvhd4I17UjrOraFBPeFg3mMBnI6V1EUccUaQxgeWi7QuO1PHXnj0o3EfL2FFhnGeLPg98PPG0vmeI/DltPJ/eChT+grO8O/AL4U+FrsX2jeFreKVTkFhu/nXooOBn0oJzyOlFgGRxRQxpHDGqIvACjAFZ+u+GtE8SWr2OuWEN1E4xh0BxWkOufXtRz0zg0rtB7zPK3/Zf+Cz3f2tvBkPmk7t27jP0r0LQvDei+GrNNP0TT4bWKMYARAP1rSIz06+tKD2LZI600nILM57xR4C8LeMjC3iPS4rtrfOwuM4zWlouh6X4c0+PStHtVt7SP7iKOBV7nlg2c0vG0ZGCKAOf8TeBPCvjARf8ACQaPDeeVnbvUZGarWXw08GaZpU+i2mjRR2VxjzIh0P4V1HGN4GCetB9hgetAHJad8KvAWmW1xaWfh21SO5H71SgOf0rmpv2Zvgxc3IvJfCEAk3bg2e/0r1IEgbs5IoXptxwaAMzQfDWheGLJdP0OwhtYIhgBEAzWk6LIrCVAynjBGQaXg8E5pM8H+8egoA4PxN8C/hj4vuTda74Xt5ZW5LKoX+VL4X+B/wAMfB9yLnQfC9vFKvIZlDY/MV3mcDBPTtS8AdeT2p2sw0XQpalpOn6rZNpl9CJLZxhkAwMVzuifCjwJ4d1AappPh+3guUJKOFBPPWuu7YB4WjqcjqaSTb0YIR1V1MTKCMYxXGXvwe+Heo6k2qXXh23a5dg28KAcjvXaZ6ZOcUDjhjgetF0O5Fb20NpAlvAMKgCgegFSnOQc0ZA+ajjv3ouguLRSAYpScUm3HYQhOKWjIFFHqAhGaWk3Uuad0AUUgOaDzTugFHFNPWnZpo60XQCnpQOlLRRdAIM96Dk0ZFGRS0QW1uGD60YPrRkUZFK6YO973A9KAMUtJupgAOaCcUZA4oPSgBc0mD60ZAoyKADJ9KMn0oyKMigANABpcZpuB60ALg+tGD60ZFGRQAvPrRRkUUAJg+tGD60YFGBQAHPagjNGBQCACc9KG1b3gbSXvBjvnHvRjuR+NeX/ABB/aE8BfD+8bTr7UUe7X70YycfiK4cftneAedqcDqea46mMoU92cVTMMNSdnI+iM/7X6UZ/2v0r54/4bO8Bdk/nR/w2d4B/ufzqf7QofzELNMK/tH0OcHvQMDvXzv8A8NneAv7n86P+GzvAX9z+dH9oYf8AmD+1ML/MfRHPrRz6187/APDZ3gP+5/Oj/hs7wH/c/nR/aFD+YP7Uwv8AMfRB3HvSYP8AEa+eD+2d4C7J/Omy/tpeAIo8uvT61rTxtGbsmH9qYX+Y+icn1oHHevmj/huj4c/3P50f8Nz/AA6/ufzr2FleKkrqI/7Uw38x9L4PpQQT2r5nP7c/w4/55/zo/wCG6Phx3jP60/7KxX8of2phv5j6ZNA4/wD118zf8N0fDf8A55n9aP8Ahuj4b/8APM/rR/ZWK/lF/aWH/mPpgcUdTXzP/wAN0fDf/nmf1o/4bn+HHaM/rT/srFfyh/aeG/mPpnnOB0pAMn6V8zn9uf4c8/uzx9asWf7bnw6vrlLaKP5n+tH9lYr+Ul5thIK8pH0jn/apPoa8NH7WHgkgEJ/Oj/hq/wAFdk/nT/snFP7Bxf6yZbF2lUPc8H1owfWvDP8Ahq/wX/d/nR/w1f4L/u/zo/sjF/yh/rLlv/Pw9zz/ALX6UZ9/0rwz/hrDwV/c/nR/w1f4K/ufzpf2Ti/5Q/1ly3/n4e5k+9A69a8M/wCGsPBX9z+dH/DV/gofwfzprKMU/sjXEuXJfGe5kkKTnk0AgMQK8MP7V/gv72z+dXNL/ae8Iatex2FvH+8f61FTLMVBX5S6Gf4CtKymez5HrRn/AGv0rhB8VtNIBFu2D9aX/haunf8APu361wNW0Z7SaaujusH1owfWuE/4Wrp3/Pu360f8LV07/n3b9aBnd5/2v0oz/tfpXCf8LV07/n3b9aP+Fq6d/wA+7frQB3fXvSH1zXC/8LV07/n3b9aP+Fq6d/z7t+tAHdHjk85pQDjrxXC/8LV0wHi3bH41R1T41aJpVv8AabqEhR9axxFanhoe2qOyNKVGVeXLBHpBDY4NAz3NePf8NI+Ev7v86P8AhpHwlj7v868KXFWWN6VLHpvIsdNXjA9hyfSjn0rx7/hpHwn/AHf50n/DSXhP+7/Oj/WrK/8An4P+wcd/IexfhRz6V49/w0n4U/u/zo/4aT8J+n86P9asr/5+B/YWO/kPYefSgZHavHv+GkvCfp/Oj/hpLwp6fzprivLLXdQP7Ax/8h7CB37mgnHJPSvHz+0l4U/u9PrWN4j/AGtvBHhu3FxdD5W+taUeJMtxMuWNQqPD+Pe0D3nPtRn2/WvmA/t2/Dj+7/Ok/wCG7fhx/cP616Sx9D+Y2/1WzP8A59M+oM+360Z9v1r5f/4bu+HH9w/rR/w3d8OP7h/Wj6/Q/mD/AFWzT/n0z6gz7frRn2/Wvl//AIbu+HB/gP60f8N3fDj+4f1p/XqH8wf6rZp/z6Z9QZpMj0r5g/4bt+HH9w/rSj9u34b/ANw/rS+v0P5h/wCq2af8+mfTxzwc8GlIGeelfMcP7dfw2aRUYbVY8nnivZfhx8X/AAZ8TrP7V4Z1RLgoBuToR+dXTxdGo7JnLisix+DhzVoWR3GBRnFIDmlxmult9DyNluIRmgDFAIoJxQAfNR81Hy0fLQApz2oGe9J8tHy0AAOe1BOO1G6jdQAZ5xR/FS55xSfxUALRz6UnzUc+tAC0UUUAFFFFACcHLetY3i6+k07w7f3kJ+ZIWI/I1skk4OOBXPeP9p8I6mB3gb+RqKvwMyry9x+h+ZHiPUr7Xtcu9Sv5WkkkmcfMc9CapfZMZIA5q3sBvLknn9/Jj/vo1YWMAZ71+e1qj52flderLnkjM+yn+7R9kI6CtTyh6UeUPSsXOSdkY+0ktDK+yn+7S/ZT6VqeUPSjyhjOKfPJD9pK9rmX9k9qPsp9K1PKHpR5Q9KFO60H7WWzMo2xGeKo6pBttJDjtXRGNcEkVl62irYyYHQV6GVyviYJ9wjVu7HExQbh071L9m5HFS2Y+Q8d6thAa/pXDQjKlGy6IcqjUtWUPsgoFoK0PKo8ut/ZQWpPtJt6Gf8AZBR9kHpWh5dHl0eyXQftGt2Z5tKPsnPStDy6PLpuiJVuhnG268Ve0CDbq0P1pzR8ZqxoqY1WH60vZR7GGJqP2TPSoLYsi8dqn+xHd0q1Yx5iX6VdEIHOKtU0fn9WvJS3Mj7H7Uv2L2rW8kelHkj0o9mk0kiPrGm5k/YvQUfYj6VriIelHlewodKKshfWJRd4syPsXtR9i9q1vJHpR5I9KPZK6G8RJK9zHNlz0rc8CwbPFFnx/FUTQgdK0vByAeKLPA/jrz8ypWw0rHs5BXlLHU031Pqe00xXhjIQcqO3tVj+x1I/1Y/KtbS4Fa2iyP4F/lWkLZcdK/LJ/Ez+jKXwL0OY/sdP+eQ/Kj+x0/55D8q6j7MPQUfZl9BUlnL/ANjp/wA8h+VH9jp/zyH5V1H2ZfQUfZh6CgDl/wCx0/55D8qP7HT/AJ5D8q6j7MPQUfZh6CgDljpA/wCeYx9K4H4tWSw+HnOwD8PevZZLdQM4ry741RKvhtyPUfzr5/idtZbUPWyTXGwR4BHaZHAqUWRx0rQtIlKjirawJ6V/M9SvKMnqftlOlHkWhifYj6UGxz2rb8lPSgQIe1R9Zk1uV7GOljE+xcfdo+w/7Nbfkp6UeSnpU/Wp7Jh7GLaujE+xe1H2I+lbnkL6UeQvpVfWJ8urGqMXJJGEbJvSvNfjNBs0mH8a9kkhXB4ryj42pjSocep/nXs5DXlLGQVzowlH98rnhywbuop32fBq1EgqXy81+tqbaPs44eLitCh9nGelH2cDtWh5Q9KPL9qfPbqX9VjfYz/s6+lH2celaHlD+7R5Y9KTqX0QfVoJNWM/7MB2pfs6+lX/ACx/dpPLX0oc7ISw0exnmADOT16V7T+yX4n1TQ/i3pem21w/kXjMJV3ccDjivJWjHOF6da9F/ZsG340+Hzjje+fyrrwVR+1jqeDxHhYf2fUuuh+qcZLRo3qoNPOccVFBnyY/9wfyqT5q+6Xwn8vTilKXqLQBimg4pQc1RAfxUNRjnNH8VABkUuRRz6Uc+lACZFGRRk+lHPpQAHpQOlLRQAUUUUAFFFFABRRRQAh6Vzvj/wD5FLUv+uDfyNdEOc1zvxA/5FLUv+uDfyNZVvgZlX/hy9D8yf8Al5uP+u8n/oRqyOlVx/x9XH/XeT/0I1aAr86qfGz8nr/HL1A9RluvQ0AHgbcepz1o7EYz6V6P8Lvgn4i+JQlns42htYlyZcZyadKlOtLlp7ioYedafLT1Z5zgnIxmun+H/wAP9b+IGsw6VpVs5RmHmNjhRW7pfwR8YXvjM+E202RXjkAd8cBfX8q+0/AHw/8ACvwd8K+dKYklSPdNO3UnFelg8ulXqc1TRI9fL8oliJ81TRLc+Zvi5+zTN4H8PRa3pdyJljXNxkYwa8AKkEgr0OGr3r9oH493XjO7m0DRJCumxkqWH8deDHnG7qefrXPmHsvaWo7HLmaoKrbDidjWRrufsMn0rXI4NZGu/wDHjJ9KrKrPFwXmcCepylkBtH1NXF6VTs/uD8auL0r+n8M7YeK8kKau7sXoQSeOwpOnbHtS4J4ByB0969Z+Fn7PHiz4oabcarZwNDbwrujcj79KtXpYWHNVZpRoVMTK1M8mABbIruvhP8J/EHxR12HTdMt38ncPOl28KK2vB37PvjXxH43bwpNp0sIgkAmkI4Uf/qr738G+DPBfwI8Fb5DDB5EW6aZgMsa8TM86jh48lDWTPZy7KJV3zV9Io+Pfj/8Asv3Pwy0iLxDpFwJrJFHng8bD/XmvnVjghvzFe9ftG/tD6h8TtSl0XSnMejwsVAB4krwckkliceld+VLEOhzYndnDmUqCq8uH2Qh6VNo//ITi+tQnpU+jf8hOH616LR5GJ/gs9Z08fukq+DVHT/8AUp9KvDvVxR+d1viF6YK9uvtR1BP3g3X2o5+WvRPBnwW8ReMdGn1eCMxxqu6IY+/61jiMRTw0eao7GuDwNbHVHToRuzzvAHAGAK7T4cfDXVfH2pi3ghZLYEeZLjgCr3gr4P8AiDxJ4jOk3No8Mdu+JnI7V9X6bp/hn4VeGOTFBHAmST1Jrw81zqNL93h9ZM+u4f4XliZe3xq5acT5l+LnwYn8CRrqFk4ltSBu7bT/AFrycAZGOlel/F74sX3j3UmtYnKWEbEKg715qMngngdK9PK/b/V08R8R4GffVPrklgvgQ1hxitHweP8Aip7PP9+s9q0fB/Piiz/36WaL/ZpJmnDzisfTXW59jaT/AMe0X+4v8q1FrL0n/j2i/wBxf5VqDpX5RP4mf0lS+Begc8DGcdqD97nr29qGIXndhQCSfSvCfH37W3gHwJ42tPB9xcpIXcpPID/qj2qSz3bv0+prxn9oL9ozw58F9Ckle4S41KUfuIA3JP8ASs745ftQ+Efh14OXUtNv4ry+vYibaNG7kf8A16/O1U+IX7SHxCABnu7i8l99qLn8hxQB+hP7MX7TVl8arB7G/iaDU7cncuCQwJ45+lfQGSTnHTrXjf7O37PehfBfw6kcSh9WuEU3E2OSa9lOeMNn8KAGS/dryz43f8i5J+H869Tl+7Xlnxt/5FyT6j+dfP8AE7Tyyoevkf8AvsDxW05UfSrvQVStBhV+lXB0r+X63xP1P3Ck7U0GB/hS8HqKFUllK9+MV3WmfCTxBqXh+TWkjYEDdHFjlh3roweW4jMZNYdXsYYnG0MIlKu7XOF2jcAOQf1rufh18M7/AMY3PmyQtHZp1cjrVj4d/C3VPE2pbr+FobWFvm3DrX0BqGpeH/h1oG3EcSxJgKOpNfa8OcJqSeLzBWprv1Pl864hcGsNgdZv8D5x+I3w9n8GXow++3f7prjM/wAIHPb2rqPHnji/8Y6m9zKSLdSfLT0Fct83ds4r5LOvqzxklgvgPosqWIeGj9Z+LqNkHymvJfjeP+JVDj3/AJ1605yDXk3xw50mH6n+dbcO6Y6ET2cIl7eJ4zF0qeoYugqev2B7H29NqMUg4+8TgUnQYIx71JDDLcTLDCpZ2IUL6k17Rbfss+ObnwL/AMJgsDl9m8W+3kitaeHlVWhw47M8Nl0l9Zmo32PFApY/J1PQV7v+z9+zVrHxRn/tPVIXt9LQH5mX73HH61e/Z5/Zm1jx7rSal4jspLfTLaTL7xjfg/8A1q+zvHPjnwT8B/BXlRiCHyItsUS4yxxXr4HLo8nta2x8FxPxhUVRYDKveqy6rofn18dPgxqHwi8QiwkkElpOT5MnHzAdeO1eY46DORXc/Fn4qa38U/EUmrapKRAHPkx/3BXC8keory8T7P2j9nsfdZOsX9Sh9dd6ltRH6V6H+zf/AMlq0D/ff+VeeP0r0P8AZv8A+S16B/vP/KrwS/fROTiPXL6nofqfbHMMf+4KkJxUdt/qI/8AdH8qlr71fCfyrW+J+oUgHNLRVGYhGe9KRmiigApPmpaT5qAFPtSDPelPtSDPegBaMiijAoATrR0paKACiiigAooooAMVzvxAA/4RLUuf+WDfyNdDj3rnviAMeEtSP/TBv5Gsq2kGY19KTPzJH/H1cf8AXeT/ANCNWR1FV1/4+rn/AK7yf+hGrA7V+d1fjZ+UV9arFP6Gvoz9mr462/hMxeEteVFs3O2ObAG3/GvnPHG3OPUUqM8Tb0Yqy8hga0wteeHmpwNcJip4OoqkT9RZ9S8M2FhJ4ldrdUZN5m4yRj1r4z+Pvx+v/Gd5LoegzNHpsRKsVb79eb3XxX8Y3vhmPwrPqsn2KIYxnkj61xuSSWLEnv716eLzV4iHJT0PYx+ePE0/Z0lbuKxLEsxJJ9aKQUA5rxNz53fcQ96ydd/48ZPpWse9ZOu/8eMn0r0Mq/3uHqVHc5Sz+4KuLVSy+4PqatrX9PYbWhG/ZClJ82gobDAgYAORX1v+yl+0ZZ6EsPgfxN5cUBIWGUgDFfJBA4+bOf0p0c8tvIkkLFGQ5BBwc1jj8DTzCl7OR04HFTwVX2iP1y1bW/CfhnTJvFVw9rFGyeY0wAy3HFfAn7Rf7ReqfEnVZdH0e4eLSIWKqqnHmV57rfxi8ca94atvCmoavLJZW4KhemR9a4jcxADHNeNluQrCy9rX1fQ9bMM6liV7Ojog5PJPWgZ70c0gz3r6bW10fP2V7MG61Po//ITh+tQN1qfR/wDkJw/WkzDE/wAFnrdgcxJ9BV/PSqFgMRJ9BV/0qon53W+MVSVYSDqpyPevpH4BfGWyihh8K63sjI+WKQ8Bq+bQOPX29KfBPJbyLNBKUKHII4INcOY4Cnj6bpz3PTyXN6mT4lVobdT9BNa1vw74Z06bW53hhTbuLgD5vSvkL4s/FvUfHWoyW9vM0dhGxEag9a5zXPiJ4m1/TLfS9Qv3eCEFQOma5gf7TV5eVZEsHP2lfWXQ+gz/AIulmUVRw/ux6hnNFFFfSHw71dxDWh4OP/FT2n+/Wea0PB3/ACM9p/v15uaf7tI93h7/AJGEPU+x9J/49ov9xf5VqLWXpP8Ax7Rf7i/yrUWvyifxM/pKl8C9Bs8azRPC4yJFKt9CK/PX9sD9ljV9H1G5+IPhKOW4tpG8yaMEkx/jX6Gjhsk9aq6lplpq1nLY6hAs8MylWVhkEVJZ+MngnwV43+KviK28MWSXVy6vsO8kiMd+tfp9+z1+zz4d+Dfh6FRbRy6pMgM9wV5zXVeBfgt4E+Ht/d6j4d0aK3mum3O/U59s13g65z06UAAAPNAwe1LSAY70ANk/1Zryv42f8i3J+H869Uk/1Zryv42f8i3J+H86+f4n/wCRZUPXyP8A32B4ta/dH0q6eoqla/dH0q6eor+Xq/xM/caX8NDomMUqP3Qgivo74RfFCx1i0j0PVCkdyg2qT0YV83H72c/Sp7O9ubC4jubSUxOhySK9zh7PquR1lUirxe6PJznKaebUeSWjWzPsbxJ4k0Pwfpcl6/lJxkBcDJr5b8deOdQ8X6k8s07CIE+XGOgFUfEPi7WfEpRdSu2kSMYVegFYoO3kHr39K9niXi6ebr2ND3YHnZJw5HLf3tb3ph0FA5FIOuKOp4r4Y+pW42Toa8m+N/8AyCYfqa9ak6V5L8cP+QVF9TXucPf79A6MJ/GR41D92phyKhh+7Uw4r9etdWZ9vT1SZa02/m028hv7fG+FwwyM5wa/Q39mv9obQfH+jw+GtcaGHUIkCbWxh/6V+dJycnOCa0dB1/VPDmpRappFy8E8JyCprvwWM+pvyPm+JuHKfENDlbtNbM/Ur4n/ABP8H/CDw3Les0ETlSYokABY/hX5vfFf4seIfijr8+p6lcP5Ic+VDu4UVn+OviX4q+IN0s/iHUZJ/KUCNScDp6VynGPQ1vjsyeI92HwnmcK8H08kXtsR71TuLRRRXk+h92rX0GSdK9D/AGbv+S16B/vP/KvPJfu16H+zd/yWrQP99/5V2YP+NE8LiPTL6iXY/VG34gj/ANwfyqSo4P8AUx/7g/lUlfexfuo/lOprNt92FAGKCcUVRmJjnNB6UtFACHpS0h6UtABRRRQAUUUUAFFFFABRRRQAUUUUAGMsR61zvxAwPCGp5/54N/I10OTuA9Ky/E2ntquh3lggy0sLAfXBqKq5oMyrq9Nn5eJzdXBzx50n/oRqyvI54Bq/4q8N6l4U8RXuk6payRPHMxUlTggknrWasqbfvZPpX53WpyjN6H5Xiaco1XoS0UzzV9aTzU9RWXJNdDD2c+iJKTB9ab5qetIZU7mhwl0QOnN7okoqMyoO4pfNX1/Wjkl2Dkl2HHpWVrv/AB5S/StEypjiszXHU2UmPSvRyqLWLhddSowl2OUs87B9aueuTVKzYBOfWrXmJzzX9M4acPZRbfRBKEnokSYPrRg+tM80daXzV9a256b1TJ5Ki0aHUUzzEHejzEPWhVV3B02ug/B9aMH1pvmrSeatNVIN2uHsprWw5j2qxo3Opw896qmRc1Y0ZgdVi+tJzj3McTCXsXoeuaef3S89hV8YwOazrB1ES5PYVd81OOaqM49z88rUanN8LJKKZ5y+opfMT+8Kp1ILVsyWHqb8rHUUzzk9R+dHmp6inzR6MPYztqmPopnnJ6j86POT1H50c8e4OjU/lY5ulaPg8Z8T2f8Av1lGZDxkVp+D2U+J7PB/jrzcznF4eVme5w/TmsfBtM+yNJ/49ov9xf5VqD2NYulX1oLeMGZPuD+IelaI1Cz7zx/99CvyqfxM/o+l8CLLUo+tVf7Qs/8AnvH/AN9Cg6hZ9p4/++hUllnB9aMH1qr/AGhZ/wDPeP8A76o/tCz/AOe8f/fVAFoYoOKrf2hZf890/wC+hR/aFl/z3T/voUATy/dxmvK/jdn/AIRyTnuP516W+oWewkTx/wDfQrzD403EEvh6QJIrHjgHPevA4mTeW1Ej1skaWNg2eN2h4H0q4elUbWRNo57Va82PH3hX8w1qNTmfus/b6dSHs1qSUH61H5sfrR5sfZh+dZOjWbvys09pB63Hk89aUnio/MT+8KPNTGMj86FRqr7LEqkU7XHnpQtM8yP1A/GgSx+o/On7Gp/KxKpC+4rk7SDXk3xw/wCQVF9T/OvV3ljKk5FeTfG8g6XDj1P869zh6lNY6LaZ0YWpD2y1PHIcham5x1qCJuKl3DGK/XeSUldI+2hXpctnJfePopu8elIXHalyN6NF+2pS2kvvHUtM3il3ijkklZIft6ezkvvHUU3eKTeKfI3okL29GWikr+oPggdh6V6J+zfx8a9Ay3LO/wCHFedFwScjPpXt/wCyR4B1rX/ihY67HZyCz09iZJGXA5HFdmChL2sdD5zijF06WXT95bH6S25P2eL/AHR/KpabEu2NU/ugCnGvu0vdR/LtWXNNpAelIBigHNLTICiiigAooooAKRaWigAopcH0NGD6GgBo6UtAVsdDS4PoaAEowKXB9DRg+hoASilwfQ0bT6GgBoPFID+tP2N/dP5Umw56Gl7z0YWvuch4u+Ffgfxud3iLRI7lu7A7T+YrlG/Zb+C+Qf8AhFj/AN/mr1raR2NG3sQR+FYywtOW6OWeEoTeqPJf+GW/gvjJ8Lf+R2o/4Zb+C2M/8It/5HavWtr47/lRtOO/5VH1Sl/KT9Rw/Y8lH7LnwWH/ADK//kZqP+GW/gt/0K//AJGavW9rf3T+VG1v7p/Kj6pS/lD6jh/5TyQ/sufBU/8AMrn/AL/NR/wy78F/+hW/8jtXrWxvQ/lRhvf8qHhKP8ofUcP/ACnkv/DLnwXzz4WP/f8Aakk/ZX+Ckow3hXI/67NXrZR+pB/KlKsRwD+VXDD0ou6QfUcP/KeM/wDDI3wJ/wChR/8AI7Uf8Mi/Ar/oUv8AyO1eyhWx0P5UYbP3TXorG10rKTH9Rw/8p4z/AMMi/Av/AKFL/wAjtS/8Mi/Ar/oUv/I7V7LsPoaNh9DT+u4j+Zh9Rw/8p41/wyL8Cv8AoUv/ACO1H/DI3wK/6FL/AMjtXsuG9D+VGG9DR9exH8zD6jh/5Txr/hkb4Ff9Cj/5Hag/si/Ar/oUf/I7V7LsPoaNh9DR9dxH8zD6jh/5Txo/sjfAwAf8UlwP+m7VLbfsn/A+1mE9t4Sww/6btXsAQg5IODQVPoQPpR9er/zMmWX0JKzieZ/8M6fCYdPD2P8Atq1H/DO3wn/6F8/9/Wr0zBHY0YJ7H8qP7Qr/AMzOZ5LgnvBHmZ/Z1+FA/wCZfP8A39agfs6/Cg/8y+f+/rV6YVbPQ/lQA3ofyp/X6/8AMw/sTBfyI8z/AOGdvhP/ANC8f+/rUf8ADO3wn/6F4/8Af1q9M5/umjn+6aX16v8AzMP7EwX/AD7R5of2dPhT/wBC8f8Av81N/wCGdPhV/wBC8f8Av61emkE/wGk2n+4aax9f+Zg8jwT+wjzT/hnP4UFiP+EePP8A01arFj8Avhhp1yt3Z6BslTofNNeiEEjaSQfpS7Tx8pI7molja8tHJmlPKMHSd1BXOVHwz8I9rBx/21NH/CtPCX/PjJ/39aup2n+6aNp/umuZu56KVlY5b/hWnhH/AJ8ZP+/rUf8ACtPCP/PjJ/39aup2n+6aNh7KaBnLf8K08Jf8+En/AH9aj/hWnhL/AJ8JP+/rV1JDd0NADdkNAHLH4a+Ev+fCQ/8AbVqT/hWnhP8A58JP+/zV1O0/3DRtP9w0Acv/AMK08In5Rp8mP+uzVXvfhP4Hvo/Ju9LZl95WNdkEI4OfypMMOCpIrKrRjiI8s1oVCq6UrxPPj8Cvhz/DouP+2ho/4UV8Of8AoDH/AL+GvQQpH8JoCE/wGvLWQZfJ3nTR3vM8Sl7s39558fgV8Ou2jH/v6aT/AIUV8O/+gN/5FNehFTn7po2kdFNP+wMv/wCfaJ/tTF/zv7zz7/hRfw7/AOgMf+/rUn/Civh3/wBAb/yK1eh4/wBk0Y/2TR/YGX/8+0P+1MX/ADv7zz0/Ar4ddtGP/f00n/Ci/h0P+YL/AORGr0LaT2NJtP8AdNC4fwFv4aB5pi/53955/wD8KL+HWMHRj/38NZ2sfs2/CfXIfJ1PQPNT08xq9SKnGMGja4GApAqqWTYKjK6poazXFr7b+88U/wCGP/gX28J/+Rmo/wCGP/gZ/wBCn/5Hava9r/5FG1/Su76rSX2TX+2sb/z8f3s8UP7IHwK7+Ff/ACM1A/ZA+BX/AEKn/kZq9rCnupoKHspo+q0v5Q/trHf8/X97PFP+GPvgX/0Kf/kZqP8Ahj/4Gf8AQp/+R2r2sq3YGja/p+lH1Wl/KH9tY7/n6/vZ4mf2QPgX28K/+RmpR+x/8CyP+RU/8jNXtW1v7hpdpHRTR9Vpdg/trHf8/H97PF4v2QvgdG4b/hE+VOR++avSvCvgnw54LshYeH9MjtYlGPlHP51vBTkkgk+lCq3J2EVUcPGHQwrZlisQuWrNteoCil2n+6fyo2n0NbX6HBs7oTpRS7T6GjafQ0AJSYFO2n0NGD6GgBM4oo2k9jS7T6GgBKM5pdp9DSbSOxoA2tq/3R+VG1f7o/KlooATav8AdH5UbV/uj8qWigBNq/3R+VG1f7o/KlooATav90flRtX+6PypaKAGlR2UflSbB6CnfjSHPehXFZMNqjsPypML/dFKtLkdKA2EwvoKMJ6D8qNvvRt96Bi/L6CjC+gpNvvRt96A0FwPQUbV/uj8qTHvRt96QC4X0FJhfQUbfejb70wDC/3R+VGF/uj8qXHSjHSgBML/AHR+VGF/uj8qXHSjHSkGgmF/uj8qML/dH5UuOlGOlAaCYX+6Pyowv90flS46UY96YaBtX+6PypNo/uj8qdSZFAtRNq/3RRgDHyilyKTbS1GLtX+6Pyo2r/dH5UYo20C0Dav90flRtX+6Pyo20baA0Dav90flRtX+6PypNvvS7aY9g2r/AHR+VN2+wp9FK7Fa4m1f7o/Kjav90flS0UxibV/uj8qNq/3R+VLRQAm1f7o/Kjav90flS0UAJtX+6Pyo2r/dH5UtFACbV7gflSYT0H5U6kyKV2hbjSF7AUYHoKfRRa4xuxfQUbF9BTqKYDdi+go2L6D8qdRQAzavt+VKFX0FKDmlpagN2p3Ao2r/AHRTqTFMWobV/uj8qNq/3R+VGKNtINA2r/dH5UbV/uj8qNtG2gNA2r/dH5UbV/uj8qNtG2gNA2r/AHR+VG1f7o/KjbRtoDQQKOuBS7V9BR+NLRqwGkKP4RS7V/uj8qWm7fejYYu1f7o/Kjav90flS0UwE2r/AHR+VG1f7o/KlooATav90flRtX+6PypaKAE2r/dH5UbV/uj8qWigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/9k=\"}") +} + +var socket_ot; + +function ot_open() { + if (use_lws_meta) + socket_ot = lws_meta.new_ws(get_appropriate_ws_url(""), "dumb-increment-protocol"); + else + socket_ot = new_ws(get_appropriate_ws_url(""), "dumb-increment-protocol"); + + console.log("ot_open"); + + try { + socket_ot.onopen = function() { + document.getElementById("ot_statustd").style.backgroundColor = "#40ff40"; + document.getElementById("ot_status").innerHTML = " <b>websocket connection opened</b><br>" + san(socket_di.extensions); + document.getElementById("ot_open_btn").disabled = true; + document.getElementById("ot_close_btn").disabled = false; + document.getElementById("ot_req_close_btn").disabled = false; + console.log("ot_open.onopen"); + } + + socket_ot.onclose = function(e){ + document.getElementById("ot_statustd").style.backgroundColor = "#ff4040"; + document.getElementById("ot_status").textContent = " websocket connection CLOSED, code: " + e.code + + ", reason: " + e.reason; + document.getElementById("ot_open_btn").disabled = false; + document.getElementById("ot_close_btn").disabled = true; + document.getElementById("ot_req_close_btn").disabled = true; + } + } catch(exception) { + alert('<p>Error' + exception); + } +} + +/* browser will close the ws in a controlled way */ +function ot_close() { + socket_ot.close(3000, "Bye!"); +} + +/* we ask the server to close the ws in a controlled way */ +function ot_req_close() { + socket_ot.send("closeme\n"); +} + +/* lws-mirror protocol */ + + var down = 0; + var no_last = 1; + var last_x = 0, last_y = 0; + var ctx; + var socket_lm; + var color = "#000000"; + var pending = ""; + var lm_timer; + + if (use_lws_meta) + socket_lm = lws_meta.new_ws(get_appropriate_ws_url("?mirror=" + mirror_name), + "lws-mirror-protocol"); + else + socket_lm = new_ws(get_appropriate_ws_url("?mirror=" + mirror_name), + "lws-mirror-protocol"); + try { + socket_lm.onopen = function() { + document.getElementById("wslm_statustd").style.backgroundColor = "#40ff40"; + document.getElementById("wslm_status").innerHTML = + " <b>websocket connection opened</b><br>" + + san(socket_lm.extensions); + lws_gray_out(false); + } + + socket_lm.onmessage =function got_packet(msg) { + j = msg.data.split(';'); + f = 0; + while (f < j.length - 1) { + i = j[f].split(' '); + if (i[0] == 'd') { + ctx.strokeStyle = i[1]; + ctx.beginPath(); + ctx.moveTo(+(i[2]), +(i[3])); + ctx.lineTo(+(i[4]), +(i[5])); + ctx.stroke(); + } + if (i[0] == 'c') { + ctx.strokeStyle = i[1]; + ctx.beginPath(); + ctx.arc(+(i[2]), +(i[3]), +(i[4]), 0, Math.PI*2, true); + ctx.stroke(); + } + + f++; + } + } + + socket_lm.onclose = function(){ + document.getElementById("wslm_statustd").style.backgroundColor = "#ff4040"; + document.getElementById("wslm_status").textContent = " websocket connection CLOSED "; + lws_gray_out(true,{'zindex':'499'}); + } + } catch(exception) { + alert('<p>Error' + exception); + } + + var canvas = document.createElement('canvas'); + canvas.height = 300; + canvas.width = 480; + ctx = canvas.getContext("2d"); + + document.getElementById('wslm_drawing').appendChild(canvas); + + canvas.addEventListener('mousemove', ev_mousemove, false); + canvas.addEventListener('mousedown', ev_mousedown, false); + canvas.addEventListener('mouseup', ev_mouseup, false); + + offsetX = offsetY = 0; + element = canvas; + if (element.offsetParent) { + do { + offsetX += element.offsetLeft; + offsetY += element.offsetTop; + } while ((element = element.offsetParent)); + } + +function update_color() { + color = document.getElementById("color").value; +} + +function ev_mousedown (ev) { + down = 1; +} + +function ev_mouseup(ev) { + down = 0; + no_last = 1; +} + +function lm_timer_handler(ev) { + socket_lm.send(pending); + pending=""; +} + +function ev_mousemove (ev) { + var x, y; + + if (ev.offsetX) { + x = ev.offsetX; + y = ev.offsetY; + } else { + x = ev.layerX - offsetX; + y = ev.layerY - offsetY; + + } + + if (!down) + return; + if (no_last) { + no_last = 0; + last_x = x; + last_y = y; + return; + } + pending = pending + "d " + color + " " + last_x + " " + last_y + + " " + x + ' ' + y + ';'; + + if (pending.length > 400) { + socket_lm.send(pending); + clearTimeout(lm_timer); + pending = ""; + } else + lm_timer = setTimeout(lm_timer_handler, 1); + + last_x = x; + last_y = y; +} + + +</script> + +</body> +</html> diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/wss-over-h2.png b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/wss-over-h2.png new file mode 100644 index 0000000000000000000000000000000000000000..1a62d8327f688a51276cc420128dd54ddfeaba62 Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/wss-over-h2.png differ diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-foreign/README.md b/minimal-examples/http-server/minimal-http-server-eventlib-foreign/README.md index a663d6e81eae02d4090bdfd133c1822d762d513d..4c21fa1a916c4d415f578df59e436a7a7340b45f 100644 --- a/minimal-examples/http-server/minimal-http-server-eventlib-foreign/README.md +++ b/minimal-examples/http-server/minimal-http-server-eventlib-foreign/README.md @@ -41,8 +41,8 @@ exits itself. ## usage ``` - $ ./lws-minimal-http-server-libuv-foreign -[2018/03/29 12:19:31:3480] USER: LWS minimal http server libuv + foreign loop | visit http://localhost:7681 + $ ./lws-minimal-http-server-eventlib-foreign +[2018/03/29 12:19:31:3480] USER: LWS minimal http server eventlib + foreign loop | visit http://localhost:7681 [2018/03/29 12:19:31:3724] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 off [2018/03/29 12:19:31:3804] NOTICE: Using foreign event loop... [2018/03/29 12:19:31:3938] USER: Foreign 1Hz timer diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-foreign/minimal-http-server-eventlib-foreign.c b/minimal-examples/http-server/minimal-http-server-eventlib-foreign/minimal-http-server-eventlib-foreign.c index 3e807d9c894668489556160a59290a639523e039..f31b7bd28aa41d31a4b5ee65db942b2df03ab480 100644 --- a/minimal-examples/http-server/minimal-http-server-eventlib-foreign/minimal-http-server-eventlib-foreign.c +++ b/minimal-examples/http-server/minimal-http-server-eventlib-foreign/minimal-http-server-eventlib-foreign.c @@ -337,7 +337,7 @@ int main(int argc, const char **argv) logs = atoi(p); lws_set_log_level(logs, NULL); - lwsl_user("LWS minimal http server libuv + foreign loop |" + lwsl_user("LWS minimal http server eventlib + foreign loop |" " visit http://localhost:7681\n"); /* @@ -383,8 +383,7 @@ int main(int argc, const char **argv) lwsl_user("\n"); lwsl_user(" Finally close only the timer and signalhandler and\n"); lwsl_user(" exit the loop cleanly\n"); - - lwsl_notice("%s\n", info.ssl_cert_filepath); + lwsl_user("\n"); /* foreign loop specific startup and run */ diff --git a/minimal-examples/http-server/minimal-http-server-eventlib/README.md b/minimal-examples/http-server/minimal-http-server-eventlib/README.md index 873e2502da2c55cc7620cb032fee9938bf91f8b7..ecfb733cf96c90495d6e25247382bd13f74cb046 100644 --- a/minimal-examples/http-server/minimal-http-server-eventlib/README.md +++ b/minimal-examples/http-server/minimal-http-server-eventlib/README.md @@ -1,4 +1,13 @@ -# lws minimal http server libuv +# lws minimal http server eventlib + +This demonstrates a minimal http server that can use any of the event libraries + +Commandline option|Meaning +---|--- +-d <loglevel>|Debug verbosity in decimal, eg, -d15 +--uv|Use the libuv event library (lws must have been configured with `-DLWS_WITH_LIBUV=1`) +--event|Use the libevent library (lws must have been configured with `-DLWS_WITH_LIBEVENT=1`) +--ev|Use the libev event library (lws must have been configured with `-DLWS_WITH_LIBEV=1`) ## build @@ -9,8 +18,8 @@ ## usage ``` - $ ./lws-minimal-http-server-libuv -[2018/03/04 09:30:02:7986] USER: LWS minimal http server-libuv | visit http://localhost:7681 + $ ./lws-minimal-http-server-eventlib +[2018/03/04 09:30:02:7986] USER: LWS minimal http server-eventlib | visit http://localhost:7681 [2018/03/04 09:30:02:7986] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 on ```