blob: 382629b6b3a7fc0d7728fd6510b72d77d20e2991 [file] [log] [blame]
Tomas Cejkad340dbf2013-03-24 20:36:57 +01001/*
2 * libwebsockets-test-server - libwebsockets test implementation
3 *
4 * Copyright (C) 2010-2011 Andy Green <andy@warmcat.com>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation:
9 * version 2.1 of the License.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 * MA 02110-1301 USA
20 */
21#include <stdio.h>
22#include <stdlib.h>
23#include <unistd.h>
24#include <getopt.h>
25#include <string.h>
26#include <sys/time.h>
27#include <sys/stat.h>
28#include <fcntl.h>
29#include <assert.h>
30#include "libwebsockets.h"
31#include "notification_module.h"
32
33#ifndef TEST_NOTIFICATION_SERVER
34#include <httpd.h>
35#include <http_log.h>
36#endif
37
38#if defined(TEST_NOTIFICATION_SERVER) || defined(WITH_NOTIFICATIONS)
39static int close_testing;
40static int max_poll_elements;
41
42static struct pollfd *pollfds;
43static int *fd_lookup;
44static int count_pollfds;
45static int force_exit = 0;
46static struct libwebsocket_context *context = NULL;
47static server_rec *http_server = NULL;
48
49/*
50 * This demo server shows how to use libwebsockets for one or more
51 * websocket protocols in the same server
52 *
53 * It defines the following websocket protocols:
54 *
55 * dumb-increment-protocol: once the socket is opened, an incrementing
56 * ascii string is sent down it every 50ms.
57 * If you send "reset\n" on the websocket, then
58 * the incrementing number is reset to 0.
59 *
60 * lws-mirror-protocol: copies any received packet to every connection also
61 * using this protocol, including the sender
62 */
63
64enum demo_protocols {
65 /* always first */
66 PROTOCOL_HTTP = 0,
67
68 PROTOCOL_NOTIFICATION,
69
70 /* always last */
71 DEMO_PROTOCOL_COUNT
72};
73
74
75#define LOCAL_RESOURCE_PATH "."
76char *resource_path = LOCAL_RESOURCE_PATH;
77
78/*
79 * We take a strict whitelist approach to stop ../ attacks
80 */
81
82struct serveable {
83 const char *urlpath;
84 const char *mimetype;
85};
86
87static const struct serveable whitelist[] = {
88 { "/favicon.ico", "image/x-icon" },
89 { "/libwebsockets.org-logo.png", "image/png" },
90
91 /* last one is the default served if no match */
92 { "/test.html", "text/html" },
93};
94
95struct per_session_data__http {
96 int fd;
97};
98
99/* this protocol server (always the first one) just knows how to do HTTP */
100
101static int callback_http(struct libwebsocket_context *context,
102 struct libwebsocket *wsi,
103 enum libwebsocket_callback_reasons reason, void *user,
104 void *in, size_t len)
105{
106 char client_name[128];
107 char client_ip[128];
108 char buf[256];
109 int n, m;
110 unsigned char *p;
111 static unsigned char buffer[4096];
112 struct stat stat_buf;
113 struct per_session_data__http *pss = (struct per_session_data__http *)user;
114 int fd = (int)(long)in;
115
116 switch (reason) {
117 case LWS_CALLBACK_HTTP:
118
119 /* check for the "send a big file by hand" example case */
120
121 if (!strcmp((const char *)in, "/leaf.jpg")) {
122 char leaf_path[1024];
123 snprintf(leaf_path, sizeof(leaf_path), "%s/leaf.jpg", resource_path);
124
125 /* well, let's demonstrate how to send the hard way */
126
127 p = buffer;
128
129 pss->fd = open(leaf_path, O_RDONLY);
130
131 if (pss->fd < 0)
132 return -1;
133
134 fstat(pss->fd, &stat_buf);
135
136 /*
137 * we will send a big jpeg file, but it could be
138 * anything. Set the Content-Type: appropriately
139 * so the browser knows what to do with it.
140 */
141
142 p += sprintf((char *)p,
143 "HTTP/1.0 200 OK\x0d\x0a"
144 "Server: libwebsockets\x0d\x0a"
145 "Content-Type: image/jpeg\x0d\x0a"
146 "Content-Length: %u\x0d\x0a\x0d\x0a",
147 (unsigned int)stat_buf.st_size);
148
149 /*
150 * send the http headers...
151 * this won't block since it's the first payload sent
152 * on the connection since it was established
153 * (too small for partial)
154 */
155
156 n = libwebsocket_write(wsi, buffer,
157 p - buffer, LWS_WRITE_HTTP);
158
159 if (n < 0) {
160 close(pss->fd);
161 return -1;
162 }
163 /*
164 * book us a LWS_CALLBACK_HTTP_WRITEABLE callback
165 */
166 libwebsocket_callback_on_writable(context, wsi);
167 break;
168 }
169
170 /* if not, send a file the easy way */
171
172 for (n = 0; n < (sizeof(whitelist) / sizeof(whitelist[0]) - 1); n++)
173 if (in && strcmp((const char *)in, whitelist[n].urlpath) == 0)
174 break;
175
176 sprintf(buf, "%s%s", resource_path, whitelist[n].urlpath);
177
178 if (libwebsockets_serve_http_file(context, wsi, buf, whitelist[n].mimetype))
179 return -1; /* through completion or error, close the socket */
180
181 /*
182 * notice that the sending of the file completes asynchronously,
183 * we'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when
184 * it's done
185 */
186
187 break;
188
189 case LWS_CALLBACK_HTTP_FILE_COMPLETION:
190// lwsl_info("LWS_CALLBACK_HTTP_FILE_COMPLETION seen\n");
191 /* kill the connection after we sent one file */
192 return -1;
193
194 case LWS_CALLBACK_HTTP_WRITEABLE:
195 /*
196 * we can send more of whatever it is we were sending
197 */
198
199 do {
200 n = read(pss->fd, buffer, sizeof buffer);
201 /* problem reading, close conn */
202 if (n < 0)
203 goto bail;
204 /* sent it all, close conn */
205 if (n == 0)
206 goto bail;
207 /*
208 * because it's HTTP and not websocket, don't need to take
209 * care about pre and postamble
210 */
211 m = libwebsocket_write(wsi, buffer, n, LWS_WRITE_HTTP);
212 if (m < 0)
213 /* write failed, close conn */
214 goto bail;
215 if (m != n)
216 /* partial write, adjust */
217 lseek(pss->fd, m - n, SEEK_CUR);
218
219 } while (!lws_send_pipe_choked(wsi));
220 libwebsocket_callback_on_writable(context, wsi);
221 break;
222
223bail:
224 close(pss->fd);
225 return -1;
226
227 /*
228 * callback for confirming to continue with client IP appear in
229 * protocol 0 callback since no websocket protocol has been agreed
230 * yet. You can just ignore this if you won't filter on client IP
231 * since the default uhandled callback return is 0 meaning let the
232 * connection continue.
233 */
234
235 case LWS_CALLBACK_FILTER_NETWORK_CONNECTION:
236 libwebsockets_get_peer_addresses(context, wsi, (int)(long)in, client_name,
237 sizeof(client_name), client_ip, sizeof(client_ip));
238
239 fprintf(stderr, "Received network connect from %s (%s)\n",
240 client_name, client_ip);
241 /* if we returned non-zero from here, we kill the connection */
242 break;
243
244 /*
245 * callbacks for managing the external poll() array appear in
246 * protocol 0 callback
247 */
248
249 case LWS_CALLBACK_ADD_POLL_FD:
250
251 if (count_pollfds >= max_poll_elements) {
252 lwsl_err("LWS_CALLBACK_ADD_POLL_FD: too many sockets to track\n");
253 return 1;
254 }
255
256 fd_lookup[fd] = count_pollfds;
257 pollfds[count_pollfds].fd = fd;
258 pollfds[count_pollfds].events = (int)(long)len;
259 pollfds[count_pollfds++].revents = 0;
260 break;
261
262 case LWS_CALLBACK_DEL_POLL_FD:
263 if (!--count_pollfds)
264 break;
265 m = fd_lookup[fd];
266 /* have the last guy take up the vacant slot */
267 pollfds[m] = pollfds[count_pollfds];
268 fd_lookup[pollfds[count_pollfds].fd] = m;
269 break;
270
271 case LWS_CALLBACK_SET_MODE_POLL_FD:
272 pollfds[fd_lookup[fd]].events |= (int)(long)len;
273 break;
274
275 case LWS_CALLBACK_CLEAR_MODE_POLL_FD:
276 pollfds[fd_lookup[fd]].events &= ~(int)(long)len;
277 break;
278
279 default:
280 break;
281 }
282
283 return 0;
284}
285
286/*
287 * this is just an example of parsing handshake headers, you don't need this
288 * in your code unless you will filter allowing connections by the header
289 * content
290 */
291
292static void
293dump_handshake_info(struct libwebsocket *wsi)
294{
295 int n;
296 static const char *token_names[WSI_TOKEN_COUNT] = {
297 /*[WSI_TOKEN_GET_URI] =*/ "GET URI",
298 /*[WSI_TOKEN_HOST] =*/ "Host",
299 /*[WSI_TOKEN_CONNECTION] =*/ "Connection",
300 /*[WSI_TOKEN_KEY1] =*/ "key 1",
301 /*[WSI_TOKEN_KEY2] =*/ "key 2",
302 /*[WSI_TOKEN_PROTOCOL] =*/ "Protocol",
303 /*[WSI_TOKEN_UPGRADE] =*/ "Upgrade",
304 /*[WSI_TOKEN_ORIGIN] =*/ "Origin",
305 /*[WSI_TOKEN_DRAFT] =*/ "Draft",
306 /*[WSI_TOKEN_CHALLENGE] =*/ "Challenge",
307
308 /* new for 04 */
309 /*[WSI_TOKEN_KEY] =*/ "Key",
310 /*[WSI_TOKEN_VERSION] =*/ "Version",
311 /*[WSI_TOKEN_SWORIGIN] =*/ "Sworigin",
312
313 /* new for 05 */
314 /*[WSI_TOKEN_EXTENSIONS] =*/ "Extensions",
315
316 /* client receives these */
317 /*[WSI_TOKEN_ACCEPT] =*/ "Accept",
318 /*[WSI_TOKEN_NONCE] =*/ "Nonce",
319 /*[WSI_TOKEN_HTTP] =*/ "Http",
320 /*[WSI_TOKEN_MUXURL] =*/ "MuxURL",
321 };
322 char buf[256];
323
324 for (n = 0; n < WSI_TOKEN_COUNT; n++) {
325 if (!lws_hdr_total_length(wsi, n))
326 continue;
327
328 lws_hdr_copy(wsi, buf, sizeof buf, n);
329
330 fprintf(stderr, " %s = %s\n", token_names[n], buf);
331 }
332}
333
334/* dumb_increment protocol */
335
336/*
337 * one of these is auto-created for each connection and a pointer to the
338 * appropriate instance is passed to the callback in the user parameter
339 *
340 * for this example protocol we use it to individualize the count for each
341 * connection.
342 */
343
344struct per_session_data__dumb_increment {
345 int number;
346};
347
348//static void get_client_notification()
349//{
350// /* get non-exclusive (read) access to sessions_list (conns) */
351// if (pthread_rwlock_rdlock (&session_lock) != 0) {
352// ap_log_error (APLOG_MARK, APLOG_ERR, 0, server, "Error while locking rwlock: %d (%s)", errno, strerror(errno));
353// return NULL;
354// }
355// /* get session where send the RPC */
356// locked_session = (struct session_with_mutex *)apr_hash_get(conns, session_key, APR_HASH_KEY_STRING);
357// if (locked_session != NULL) {
358// session = locked_session->session;
359// }
360// if (session != NULL) {
361// /* get exclusive access to session */
362// if (pthread_mutex_lock(&locked_session->lock) != 0) {
363// /* unlock before returning error */
364// if (pthread_rwlock_unlock (&session_lock) != 0) {
365// ap_log_error (APLOG_MARK, APLOG_ERR, 0, server, "Error while locking rwlock: %d (%s)", errno, strerror(errno));
366// return NULL;
367// }
368// return NULL;
369// }
370// /* send the request and get the reply */
371// msgt = nc_session_send_recv(session, rpc, &reply);
372//
373// /* first release exclusive lock for this session */
374// pthread_mutex_unlock(&locked_session->lock);
375// /* end of critical section */
376// if (pthread_rwlock_unlock (&session_lock) != 0) {
377// ap_log_error (APLOG_MARK, APLOG_ERR, 0, server, "Error while unlocking rwlock: %d (%s)", errno, strerror(errno));
378// return (NULL);
379// }
380//
381// /* process the result of the operation */
382// switch (msgt) {
383// case NC_MSG_UNKNOWN:
384// if (nc_session_get_status(session) != NC_SESSION_STATUS_WORKING) {
385// ap_log_error(APLOG_MARK, APLOG_ERR, 0, server, "mod_netconf: receiving rpc-reply failed");
386// netconf_close(server, conns, session_key);
387// return (NULL);
388// }
389// /* no break */
390// case NC_MSG_NONE:
391// /* there is error handled by callback */
392// return (NULL);
393// break;
394// case NC_MSG_REPLY:
395// switch (replyt = nc_reply_get_type(reply)) {
396// case NC_REPLY_DATA:
397// if ((data = nc_reply_get_data (reply)) == NULL) {
398// ap_log_error(APLOG_MARK, APLOG_ERR, 0, server, "mod_netconf: no data from reply");
399// data = NULL;
400// }
401// break;
402// default:
403// ap_log_error(APLOG_MARK, APLOG_ERR, 0, server, "mod_netconf: unexpected rpc-reply (%d)", replyt);
404// data = NULL;
405// break;
406// }
407// break;
408// default:
409// ap_log_error(APLOG_MARK, APLOG_ERR, 0, server, "mod_netconf: unexpected reply message received (%d)", msgt);
410// data = NULL;
411// break;
412// }
413// nc_reply_free(reply);
414// return (data);
415// } else {
416// /* release lock on failure */
417// if (pthread_rwlock_unlock (&session_lock) != 0) {
418// ap_log_error (APLOG_MARK, APLOG_ERR, 0, server, "Error while unlocking rwlock: %d (%s)", errno, strerror(errno));
419// }
420// ap_log_error(APLOG_MARK, APLOG_ERR, 0, server, "Unknown session to process.");
421// return (NULL);
422// }
423//}
424
425static int callback_notification(struct libwebsocket_context *context,
426 struct libwebsocket *wsi,
427 enum libwebsocket_callback_reasons reason,
428 void *user, void *in, size_t len)
429{
430 int isnotif = 0;
431 int n, m;
432 unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + 512 + LWS_SEND_BUFFER_POST_PADDING];
433 unsigned char *p = &buf[LWS_SEND_BUFFER_PRE_PADDING];
434 struct per_session_data__dumb_increment *pss = (struct per_session_data__dumb_increment *)user;
435 #ifndef TEST_NOTIFICATION_SERVER
436 if (http_server != NULL) {
437 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "libwebsockets callback_notification");
438 }
439 #endif
440
441 switch (reason) {
442
443 case LWS_CALLBACK_ESTABLISHED:
444 lwsl_info("callback_notification: LWS_CALLBACK_ESTABLISHED\n");
445 pss->number = 0;
446 break;
447
448 case LWS_CALLBACK_SERVER_WRITEABLE:
449// get_client_notification();
450 n = sprintf((char *)p, "not%d\n", pss->number++);
451 m = libwebsocket_write(wsi, p, n, LWS_WRITE_TEXT);
452 if (m < n) {
453 lwsl_err("ERROR %d writing to di socket\n", n);
454 return -1;
455 }
456 if (close_testing && pss->number == 50) {
457 lwsl_info("close tesing limit, closing\n");
458 return -1;
459 }
460 break;
461
462 case LWS_CALLBACK_RECEIVE:
463// fprintf(stderr, "rx %d\n", (int)len);
464 if (len < 6)
465 break;
466 if (strcmp((const char *)in, "reset\n") == 0)
467 pss->number = 0;
468 break;
469 /*
470 * this just demonstrates how to use the protocol filter. If you won't
471 * study and reject connections based on header content, you don't need
472 * to handle this callback
473 */
474
475 case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION:
476 //dump_handshake_info(wsi);
477 /* you could return non-zero here and kill the connection */
478 break;
479
480 default:
481 break;
482 }
483
484 return 0;
485}
486
487/* list of supported protocols and callbacks */
488
489static struct libwebsocket_protocols protocols[] = {
490 /* first protocol must always be HTTP handler */
491
492 {
493 "http-only", /* name */
494 callback_http, /* callback */
495 sizeof (struct per_session_data__http), /* per_session_data_size */
496 0, /* max frame size / rx buffer */
497 },
498 {
499 "notification-protocol",
500 callback_notification,
501 sizeof(struct per_session_data__dumb_increment),
502 10,
503 },
504 { NULL, NULL, 0, 0 } /* terminator */
505};
506
507
508int notification_init(apr_pool_t * pool, server_rec * server)
509{
510 char cert_path[1024];
511 char key_path[1024];
512 int use_ssl = 0;
513 struct lws_context_creation_info info;
514 int opts = 0;
515 char interface_name[128] = "";
516 const char *iface = NULL;
517 int debug_level = 7;
518
519 memset(&info, 0, sizeof info);
520 info.port = NOTIFICATION_SERVER_PORT;
521
522 /* tell the library what debug level to emit and to send it to syslog */
523 lws_set_log_level(debug_level, lwsl_emit_syslog);
524
525 #ifndef TEST_NOTIFICATION_SERVER
526 if (server != NULL) {
527 http_server = server;
528 ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, http_server, "Initialization of libwebsocket");
529 }
530 #endif
531 lwsl_notice("libwebsockets test server - "
532 "(C) Copyright 2010-2013 Andy Green <andy@warmcat.com> - "
533 "licensed under LGPL2.1\n");
534 max_poll_elements = getdtablesize();
535 pollfds = malloc(max_poll_elements * sizeof (struct pollfd));
536 fd_lookup = malloc(max_poll_elements * sizeof (int));
537 if (pollfds == NULL || fd_lookup == NULL) {
538 lwsl_err("Out of memory pollfds=%d\n", max_poll_elements);
539 return -1;
540 }
541
542 info.iface = iface;
543 info.protocols = protocols;
544
545 //snprintf(cert_path, sizeof(cert_path), "%s/libwebsockets-test-server.pem", resource_path);
546 //snprintf(key_path, sizeof(cert_path), "%s/libwebsockets-test-server.key.pem", resource_path);
547
548 //info.ssl_cert_filepath = cert_path;
549 //info.ssl_private_key_filepath = key_path;
550
551 info.gid = -1;
552 info.uid = -1;
553 info.options = opts;
554
555 /* create server */
556 context = libwebsocket_create_context(&info);
557 if (context == NULL) {
558 lwsl_err("libwebsocket init failed\n");
559 return -1;
560 }
561}
562
563void notification_close()
564{
565 libwebsocket_context_destroy(context);
566
567 lwsl_notice("libwebsockets-test-server exited cleanly\n");
568}
569
570/**
571 * \brief send notification if any
572 * \return < 0 on error
573 */
574int notification_handle()
575{
576 static struct timeval tv;
577 static unsigned int olds = 0;
578 int n = 0;
579
580 gettimeofday(&tv, NULL);
581
582 /*
583 * This provokes the LWS_CALLBACK_SERVER_WRITEABLE for every
584 * live websocket connection using the DUMB_INCREMENT protocol,
585 * as soon as it can take more packets (usually immediately)
586 */
587
588 if (((unsigned int)tv.tv_sec - olds) > 0) {
589 libwebsocket_callback_on_writable_all_protocol(&protocols[PROTOCOL_NOTIFICATION]);
590 olds = tv.tv_sec;
591 }
592
593
594 /*
595 * this represents an existing server's single poll action
596 * which also includes libwebsocket sockets
597 */
598
599 n = poll(pollfds, count_pollfds, 50);
600 if (n < 0)
601 return n;
602
603
604 if (n) {
605 for (n = 0; n < count_pollfds; n++) {
606 if (pollfds[n].revents) {
607 /*
608 * returns immediately if the fd does not
609 * match anything under libwebsockets
610 * control
611 */
612 if (libwebsocket_service_fd(context, &pollfds[n]) < 0) {
613 return 1;
614 }
615 }
616 }
617 }
618 return 0;
619}
620
621#endif
622
623
624#ifndef WITH_NOTIFICATIONS
625#ifdef TEST_NOTIFICATION_SERVER
626int main(int argc, char **argv)
627{
628// char cert_path[1024];
629// char key_path[1024];
630// int n = 0;
631// int use_ssl = 0;
632// struct libwebsocket_context *context;
633// int opts = 0;
634// char interface_name[128] = "";
635// const char *iface = NULL;
636// unsigned int oldus = 0;
637// unsigned int olds = 0;
638// struct lws_context_creation_info info;
639//
640// int debug_level = 7;
641//
642// memset(&info, 0, sizeof info);
643// info.port = 8081;
644//
645// /* tell the library what debug level to emit and to send it to syslog */
646// lws_set_log_level(debug_level, lwsl_emit_syslog);
647//
648// lwsl_notice("libwebsockets test server - "
649// "(C) Copyright 2010-2013 Andy Green <andy@warmcat.com> - "
650// "licensed under LGPL2.1\n");
651// max_poll_elements = getdtablesize();
652// pollfds = malloc(max_poll_elements * sizeof (struct pollfd));
653// fd_lookup = malloc(max_poll_elements * sizeof (int));
654// if (pollfds == NULL || fd_lookup == NULL) {
655// lwsl_err("Out of memory pollfds=%d\n", max_poll_elements);
656// return -1;
657// }
658//
659// info.iface = iface;
660// info.protocols = protocols;
661//
662// //snprintf(cert_path, sizeof(cert_path), "%s/libwebsockets-test-server.pem", resource_path);
663// //snprintf(key_path, sizeof(cert_path), "%s/libwebsockets-test-server.key.pem", resource_path);
664//
665// //info.ssl_cert_filepath = cert_path;
666// //info.ssl_private_key_filepath = key_path;
667// //
668// info.gid = -1;
669// info.uid = -1;
670// info.options = opts;
671//
672// /* create server */
673// context = libwebsocket_create_context(&info);
674// if (context == NULL) {
675// lwsl_err("libwebsocket init failed\n");
676// return -1;
677// }
678//
679// n = 0;
680// while (n >= 0 && !force_exit) {
681// struct timeval tv;
682//
683// gettimeofday(&tv, NULL);
684//
685// /*
686// * This provokes the LWS_CALLBACK_SERVER_WRITEABLE for every
687// * live websocket connection using the DUMB_INCREMENT protocol,
688// * as soon as it can take more packets (usually immediately)
689// */
690//
691// if (((unsigned int)tv.tv_sec - olds) > 0) {
692// libwebsocket_callback_on_writable_all_protocol(&protocols[PROTOCOL_NOTIFICATION]);
693// olds = tv.tv_sec;
694// }
695//
696//
697// /*
698// * this represents an existing server's single poll action
699// * which also includes libwebsocket sockets
700// */
701//
702// n = poll(pollfds, count_pollfds, 50);
703// if (n < 0)
704// continue;
705//
706//
707// if (n) {
708// for (n = 0; n < count_pollfds; n++) {
709// if (pollfds[n].revents) {
710// /*
711// * returns immediately if the fd does not
712// * match anything under libwebsockets
713// * control
714// */
715// if (libwebsocket_service_fd(context, &pollfds[n]) < 0) {
716// break;
717// }
718// }
719// }
720// }
721// }
722//
723// libwebsocket_context_destroy(context);
724//
725// lwsl_notice("libwebsockets-test-server exited cleanly\n");
726//
727// return 0;
728 if (notification_init(NULL, NULL) == -1) {
729 fprintf(stderr, "Error during initialization\n");
730 return 1;
731 }
732 while (!force_exit) {
733 notification_handle();
734 }
735 notification_close();
736}
737#endif
738#endif