Added SILC Thread Queue API
[crypto.git] / apps / silcmap / silcmap_client.c
1 /*
2
3   silcmap_client.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 2003 - 2004 Pekka Riikonen
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; version 2 of the License.
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17
18 */
19
20 #include "silcincludes.h"
21 #include "silcclient.h"
22 #include "silcmap.h"
23
24 /******* Map Client Routines *************************************************/
25
26 SILC_TASK_CALLBACK(silc_map_process_done)
27 {
28   SilcMap map = context;
29
30   /* Program stops */
31   silc_schedule_stop(map->client->schedule);
32 }
33
34 /* This function processes the data that was gathered from the server
35    and producess the outputs and the map. */
36
37 void silc_map_process_data(SilcMap map, SilcMapConnection mapconn)
38 {
39   SilcMapCommand cmd;
40   SilcMap ret_map;
41   SilcInt16 r, g, b, lr, lg, lb;
42   int i;
43
44   map->conn_num++;
45
46   SILC_LOG_DEBUG(("Processing the data from server (%d/%d)",
47                   map->conn_num, map->conns_num));
48
49   if (map->conn_num != map->conns_num)
50     return;
51
52   /* Load the map image to be processed */
53   silc_free(map->bitmap);
54   if (!map->loadmap.loadmap || !map->loadmap.filename) {
55     silc_schedule_task_add(map->client->schedule, 0,
56                            silc_map_process_done, map, 0, 1,
57                            SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
58     return;
59   }
60
61   if (!silc_map_load_ppm(map, map->loadmap.filename)) {
62     silc_schedule_task_add(map->client->schedule, 0,
63                            silc_map_process_done, map, 0, 1,
64                            SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
65     return;
66   }
67
68   /* Now process all received data one by one */
69   silc_dlist_start(map->conns);
70   while ((mapconn = silc_dlist_get(map->conns)) != SILC_LIST_END) {
71
72     /* Change colors according to server status */
73     silc_map_parse_color(mapconn->up_color, &r, &g, &b);
74     silc_map_parse_color(mapconn->up_text_color, &lr, &lg, &lb);
75     if (mapconn->down) {
76       silc_map_parse_color(mapconn->down_color, &r, &g, &b);
77       silc_map_parse_color(mapconn->down_text_color, &lr, &lg, &lb);
78     }
79
80     /* Execute the map commands */
81     silc_dlist_start(mapconn->commands);
82     while ((cmd = silc_dlist_get(mapconn->commands)) != SILC_LIST_END) {
83       if (cmd->alon && cmd->alat) {
84         cmd->x = silc_map_lon2x(map, cmd->alon);
85         cmd->y = silc_map_lat2y(map, cmd->alat);
86         if (cmd->blon && cmd->blat) {
87           cmd->x2 = silc_map_lon2x(map, cmd->blon);
88           cmd->y2 = silc_map_lat2y(map, cmd->blat);
89         }
90       }
91
92       if (cmd->cut) {
93         if (silc_map_cut(map, cmd->x, cmd->y, cmd->width,
94                          cmd->height, &ret_map)) {
95           silc_map_write_ppm(ret_map, cmd->filename);
96           silc_map_free(ret_map);
97         }
98         continue;
99       }
100
101       if (cmd->draw_line) {
102         if (cmd->color_set) {
103           r = cmd->r;
104           g = cmd->g;
105           b = cmd->b;
106         }
107         silc_map_draw_line(map, cmd->width, cmd->x, cmd->y, cmd->x2, cmd->y2,
108                            r, g, b);
109         continue;
110       }
111
112       if (cmd->draw_text) {
113         if (cmd->color_set) {
114           lr = cmd->r;
115           lg = cmd->g;
116           lb = cmd->b;
117         }
118         silc_map_draw_text(map, cmd->text, cmd->x, cmd->y, lr, lg, lb);
119         continue;
120       }
121
122       if (cmd->draw_circle) {
123         if (cmd->color_set) {
124           r = cmd->r;
125           g = cmd->g;
126           b = cmd->b;
127         }
128         if (cmd->lcolor_set) {
129           lr = cmd->lr;
130           lg = cmd->lg;
131           lb = cmd->lb;
132         }
133         silc_map_draw_circle(map, cmd->x, cmd->y, r, g, b,
134                              cmd->text, cmd->lposx, cmd->lposy, lr, lg, lb);
135         continue;
136       }
137
138       if (cmd->draw_rectangle) {
139         if (cmd->color_set) {
140           r = cmd->r;
141           g = cmd->g;
142           b = cmd->b;
143         }
144         if (cmd->lcolor_set) {
145           lr = cmd->lr;
146           lg = cmd->lg;
147           lb = cmd->lb;
148         }
149         silc_map_draw_rectangle(map, cmd->x, cmd->y, r, g, b,
150                                 cmd->text, cmd->lposx, cmd->lposy, lr, lg, lb);
151         continue;
152       }
153     }
154
155     /* Write the html data file */
156     if (map->writehtml.writehtml)
157       silc_map_writehtml(map, mapconn);
158
159     /* Write uptime reliability data */
160     if (map->writerel.writerel)
161       silc_map_writerel(map, mapconn);
162   }
163
164   SILC_LOG_DEBUG(("All connections processed"));
165
166   /* Produce output */
167   if (map->writemap.writemap)
168     silc_map_write_ppm(map, map->writemap.filename);
169   for (i = 0; i < map->cut_count; i++) {
170     if (map->cut[i].alon && map->cut[i].alat) {
171       map->cut[i].x = silc_map_lon2x(map, map->cut[i].alon);
172       map->cut[i].y = silc_map_lat2y(map, map->cut[i].alat);
173     }
174     if (silc_map_cut(map, map->cut[i].x, map->cut[i].y, map->cut[i].width,
175                      map->cut[i].height, &ret_map)) {
176       silc_map_write_ppm(ret_map, map->cut[i].filename);
177       silc_map_free(ret_map);
178     }
179   }
180
181   /* Write the HTML index file */
182   if (map->writehtml.writehtml)
183     silc_map_writehtml_index(map);
184
185   /* Write the HTML map file(s) */
186   silc_map_writemaphtml(map);
187
188   /* Write uptime reliability graph */
189   if (map->writerel.writerel)
190     silc_map_writerelhtml(map);
191
192   /* Schedule to stop */
193   silc_schedule_task_add(map->client->schedule, 0,
194                          silc_map_process_done, map, 0, 1,
195                          SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
196 }
197
198 /* Timeout callback to detect if server is down. */
199
200 SILC_TASK_CALLBACK(silc_map_connect_timeout)
201 {
202   SilcMapConnection mapconn = context;
203
204   SILC_LOG_DEBUG(("Connection timeout"));
205
206   silc_schedule_task_del_by_context(mapconn->map->client->schedule, mapconn);
207
208   /* The server is down. */
209   mapconn->down = TRUE;
210
211   /* Continue to produce the data and the map. */
212   silc_map_process_data(mapconn->map, mapconn);
213 }
214
215 /* Timeout callback to detect if server is down. */
216
217 SILC_TASK_CALLBACK(silc_map_data_timeout)
218 {
219   SilcMapConnection mapconn = context;
220
221   SILC_LOG_DEBUG(("data timeout"));
222
223   silc_schedule_task_del_by_context(mapconn->map->client->schedule, mapconn);
224
225   /* The server is down. */
226   mapconn->down = TRUE;
227
228   /* Close connection, we didn't get any data. */
229   SILC_LOG_DEBUG(("Closing connection to %s:%d", mapconn->conn->remote_host,
230                   mapconn->conn->remote_port));
231   silc_client_close_connection(mapconn->conn->client, mapconn->conn);
232
233   /* Continue to produce the data and the map. */
234   silc_map_process_data(mapconn->map, mapconn);
235 }
236
237 /* Close connection to server */
238
239 SILC_TASK_CALLBACK(silc_map_connect_close)
240 {
241   SilcMapConnection mapconn = context;
242
243   SILC_LOG_DEBUG(("Closing connection to %s:%d", mapconn->conn->remote_host,
244                   mapconn->conn->remote_port));
245
246   silc_client_close_connection(mapconn->conn->client, mapconn->conn);
247
248   /* Continue to produce the data and the map. */
249   silc_map_process_data(mapconn->map, mapconn);
250 }
251
252 /* Create connection to remote server to gather information about it. */
253
254 void silc_map_connect(SilcMap map, SilcMapConnection mapconn)
255 {
256   char *ip;
257
258   if (!mapconn->connect) {
259     silc_schedule_task_add(map->client->schedule, 0,
260                            silc_map_connect_timeout, mapconn, 0, 1,
261                            SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
262     return;
263   }
264
265   /* First configure IP is used to connect. */
266   silc_dlist_start(mapconn->ips);
267   ip = silc_dlist_get(mapconn->ips);
268
269   SILC_LOG_DEBUG(("Creating connection to server %s:%d", ip, mapconn->port));
270
271   /* Create connection.  We'll continue in the silc_connected after
272      connection is created. */
273   silc_client_connect_to_server(map->client, NULL,
274                                 mapconn->port, ip, mapconn);
275
276   /* Set connect timeout to detect if the server is down. */
277   silc_schedule_task_add(map->client->schedule, 0,
278                          silc_map_connect_timeout, mapconn,
279                          mapconn->connect_timeout, 0,
280                          SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
281 }
282
283
284 /******* SILC Client Operations **********************************************/
285
286 /* "say" client operation is a message from the client library to the
287    application.  It may include error messages or something else.  We
288    just dump them to screen. */
289
290 static void
291 silc_say(SilcClient client, SilcClientConnection conn,
292          SilcClientMessageType type, char *msg, ...)
293 {
294
295 }
296
297
298 /* Message for a channel. The `sender' is the sender of the message
299    The `channel' is the channel. The `message' is the message.  Note
300    that `message' maybe NULL.  The `flags' indicates message flags
301    and it is used to determine how the message can be interpreted
302    (like it may tell the message is multimedia message). */
303
304 static void
305 silc_channel_message(SilcClient client, SilcClientConnection conn,
306                      SilcClientEntry sender, SilcChannelEntry channel,
307                      SilcMessagePayload payload,
308                      SilcChannelPrivateKey key, SilcMessageFlags flags,
309                      const unsigned char *message,
310                      SilcUInt32 message_len)
311 {
312
313 }
314
315
316 /* Private message to the client. The `sender' is the sender of the
317    message. The message is `message'and maybe NULL.  The `flags'
318    indicates message flags  and it is used to determine how the message
319    can be interpreted (like it may tell the message is multimedia
320    message). */
321
322 static void
323 silc_private_message(SilcClient client, SilcClientConnection conn,
324                      SilcClientEntry sender, SilcMessagePayload payload,
325                      SilcMessageFlags flags,
326                      const unsigned char *message,
327                      SilcUInt32 message_len)
328 {
329
330 }
331
332
333 /* Notify message to the client. The notify arguments are sent in the
334    same order as servers sends them. The arguments are same as received
335    from the server except for ID's.  If ID is received application receives
336    the corresponding entry to the ID. For example, if Client ID is received
337    application receives SilcClientEntry.  Also, if the notify type is
338    for channel the channel entry is sent to application (even if server
339    does not send it because client library gets the channel entry from
340    the Channel ID in the packet's header). */
341
342 static void
343 silc_notify(SilcClient client, SilcClientConnection conn,
344             SilcNotifyType type, ...)
345 {
346
347 }
348
349
350 /* Command handler. This function is called always in the command function.
351    If error occurs it will be called as well. `conn' is the associated
352    client connection. `cmd_context' is the command context that was
353    originally sent to the command. `success' is FALSE if error occurred
354    during command. `command' is the command being processed. It must be
355    noted that this is not reply from server. This is merely called just
356    after application has called the command. Just to tell application
357    that the command really was processed. */
358
359 static void
360 silc_command(SilcClient client, SilcClientConnection conn,
361              SilcClientCommandContext cmd_context, bool success,
362              SilcCommand command, SilcStatus status)
363 {
364
365 }
366
367
368 /* Command reply handler. This function is called always in the command reply
369    function. If error occurs it will be called as well. Normal scenario
370    is that it will be called after the received command data has been parsed
371    and processed. The function is used to pass the received command data to
372    the application.
373
374    `conn' is the associated client connection. `cmd_payload' is the command
375    payload data received from server and it can be ignored. It is provided
376    if the application would like to re-parse the received command data,
377    however, it must be noted that the data is parsed already by the library
378    thus the payload can be ignored. `success' is FALSE if error occurred.
379    In this case arguments are not sent to the application. The `status' is
380    the command reply status server returned. The `command' is the command
381    reply being processed. The function has variable argument list and each
382    command defines the number and type of arguments it passes to the
383    application (on error they are not sent). */
384
385 static void
386 silc_command_reply(SilcClient client, SilcClientConnection conn,
387                    SilcCommandPayload cmd_payload, bool success,
388                    SilcCommand command, SilcStatus status, ...)
389 {
390   SilcMapConnection mapconn = conn->context;
391   va_list va;
392
393   /* If error occurred in client library with our command, print the error */
394   if (status != SILC_STATUS_OK)
395     fprintf(stderr, "COMMAND REPLY %s: %s\n",
396             silc_get_command_name(command),
397             silc_get_status_message(status));
398
399   if (!success)
400     return;
401
402   va_start(va, status);
403
404   switch (command) {
405   case SILC_COMMAND_STATS:
406     {
407       unsigned char *stats = va_arg(va, unsigned char *);
408       SilcUInt32 stats_len = va_arg(va, SilcUInt32);
409       SilcBufferStruct buf;
410
411       SILC_LOG_DEBUG(("STATS command reply from %s", conn->sock->hostname));
412
413       /* Get statistics structure */
414       silc_buffer_set(&buf, stats, stats_len);
415       silc_buffer_unformat(&buf,
416                            SILC_STR_UI_INT(&mapconn->data.starttime),
417                            SILC_STR_UI_INT(&mapconn->data.uptime),
418                            SILC_STR_UI_INT(&mapconn->data.clients),
419                            SILC_STR_UI_INT(&mapconn->data.channels),
420                            SILC_STR_UI_INT(&mapconn->data.server_ops),
421                            SILC_STR_UI_INT(&mapconn->data.router_ops),
422                            SILC_STR_UI_INT(&mapconn->data.cell_clients),
423                            SILC_STR_UI_INT(&mapconn->data.cell_channels),
424                            SILC_STR_UI_INT(&mapconn->data.cell_servers),
425                            SILC_STR_UI_INT(&mapconn->data.all_clients),
426                            SILC_STR_UI_INT(&mapconn->data.all_channels),
427                            SILC_STR_UI_INT(&mapconn->data.all_servers),
428                            SILC_STR_UI_INT(&mapconn->data.all_routers),
429                            SILC_STR_UI_INT(&mapconn->data.all_server_ops),
430                            SILC_STR_UI_INT(&mapconn->data.all_router_ops),
431                            SILC_STR_END);
432
433       mapconn->stats_received = TRUE;
434     }
435     break;
436
437   case SILC_COMMAND_MOTD:
438     {
439       char *motd = va_arg(va, char *);
440
441       SILC_LOG_DEBUG(("MOTD command reply"));
442
443       mapconn->data.motd = motd ? strdup(motd) : NULL;
444       mapconn->motd_received = motd ? TRUE : FALSE;
445     }
446     break;
447
448   default:
449     SILC_LOG_DEBUG(("Unsupported command reply"));
450     break;
451   };
452
453   va_end(va);
454
455   if (mapconn->motd && !mapconn->motd_received)
456     return;
457   if (!mapconn->stats_received)
458     return;
459
460   silc_schedule_task_del_by_context(client->schedule, mapconn);
461
462   /* All data is gathered, time to disconnect from the server. */
463   silc_schedule_task_add(client->schedule, 0,
464                          silc_map_connect_close, mapconn, 0, 1,
465                          SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
466 }
467
468
469 /* Called to indicate that connection was either successfully established
470    or connecting failed.  This is also the first time application receives
471    the SilcClientConnection objecet which it should save somewhere.
472    If the `success' is FALSE the application must always call the function
473    silc_client_close_connection. */
474
475 static void
476 silc_connected(SilcClient client, SilcClientConnection conn,
477                SilcClientConnectionStatus status)
478 {
479   SilcMapConnection mapconn = conn->context;
480   SilcMap map = mapconn->map;
481
482   silc_schedule_task_del_by_context(client->schedule, mapconn);
483
484   if (status != SILC_CLIENT_CONN_SUCCESS) {
485     fprintf(stderr, "Could not connect to server %s\n",
486                      conn->remote_host ? conn->remote_host : "");
487     silc_client_close_connection(client, conn);
488
489     /* Mark that this server is down. */
490     silc_schedule_task_add(map->client->schedule, 0,
491                            silc_map_connect_timeout, mapconn, 0, 1,
492                            SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
493     return;
494   }
495
496   if (mapconn->down) {
497     /* Already timeouted */
498     SILC_LOG_DEBUG(("Connection already timedout"));
499     silc_client_close_connection(client, conn);
500     return;
501   }
502
503   SILC_LOG_DEBUG(("Connected to server %s:%d", conn->remote_host,
504                   conn->remote_port));
505
506   mapconn->conn = conn;
507
508   /* Get statistics */
509   silc_client_command_call(client, conn, "STATS");
510
511   /* Get motd if requested */
512   if (mapconn->motd) {
513     char motd[256];
514     char *hostname;
515     silc_dlist_start(mapconn->hostnames);
516     hostname = silc_dlist_get(mapconn->hostnames);
517     memset(motd, 0, sizeof(motd));
518     silc_strncat(motd, sizeof(motd), "MOTD ", 5);
519     silc_strncat(motd, sizeof(motd), hostname, strlen(hostname));
520     silc_client_command_call(client, conn, motd);
521   }
522
523   /* Set data timeout to detect if the server is down. */
524   silc_schedule_task_add(map->client->schedule, 0,
525                          silc_map_data_timeout, mapconn,
526                          mapconn->connect_timeout, 0,
527                          SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
528 }
529
530
531 /* Called to indicate that connection was disconnected to the server.
532    The `status' may tell the reason of the disconnection, and if the
533    `message' is non-NULL it may include the disconnection message
534    received from server. */
535
536 static void
537 silc_disconnected(SilcClient client, SilcClientConnection conn,
538                   SilcStatus status, const char *message)
539 {
540   SilcMapConnection mapconn = conn->context;
541
542   silc_schedule_task_del_by_context(client->schedule, mapconn);
543
544   SILC_LOG_DEBUG(("Disconnected from server %s:%d", conn->remote_host,
545                   conn->remote_port));
546
547   /* Mark that this server is down. */
548   silc_schedule_task_add(map->client->schedule, 0,
549                          silc_map_connect_timeout, mapconn, 0, 1,
550                          SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
551
552   mapconn->conn = NULL;
553 }
554
555
556 /* Find authentication method and authentication data by hostname and
557    port. The hostname may be IP address as well. When the authentication
558    method has been resolved the `completion' callback with the found
559    authentication method and authentication data is called. The `conn'
560    may be NULL. */
561
562 static void
563 silc_get_auth_method(SilcClient client, SilcClientConnection conn,
564                      char *hostname, SilcUInt16 port,
565                      SilcGetAuthMeth completion,
566                      void *context)
567 {
568   /* No auth */
569   completion(TRUE, SILC_AUTH_NONE, NULL, 0, context);
570 }
571
572
573 /* Verifies received public key. The `conn_type' indicates which entity
574    (server, client etc.) has sent the public key. If user decides to trust
575    the application may save the key as trusted public key for later
576    use. The `completion' must be called after the public key has been
577    verified. */
578
579 static void
580 silc_verify_public_key(SilcClient client, SilcClientConnection conn,
581                        SilcSocketType conn_type, unsigned char *pk,
582                        SilcUInt32 pk_len, SilcSKEPKType pk_type,
583                        SilcVerifyPublicKey completion, void *context)
584 {
585   /* Accept all keys without verification */
586   completion(TRUE, context);
587 }
588
589
590 /* Ask (interact, that is) a passphrase from user. The passphrase is
591    returned to the library by calling the `completion' callback with
592    the `context'. The returned passphrase SHOULD be in UTF-8 encoded,
593    if not then the library will attempt to encode. */
594
595 static void
596 silc_ask_passphrase(SilcClient client, SilcClientConnection conn,
597                     SilcAskPassphrase completion, void *context)
598 {
599   completion(NULL, 0, context);
600 }
601
602
603 /* Notifies application that failure packet was received.  This is called
604    if there is some protocol active in the client.  The `protocol' is the
605    protocol context.  The `failure' is opaque pointer to the failure
606    indication.  Note, that the `failure' is protocol dependant and
607    application must explicitly cast it to correct type.  Usually `failure'
608    is 32 bit failure type (see protocol specs for all protocol failure
609    types). */
610
611 static void
612 silc_failure(SilcClient client, SilcClientConnection conn,
613              SilcProtocol protocol, void *failure)
614 {
615   fprintf(stderr, "Connecting failed (protocol failure)\n");
616 }
617
618
619 /* Asks whether the user would like to perform the key agreement protocol.
620    This is called after we have received an key agreement packet or an
621    reply to our key agreement packet. This returns TRUE if the user wants
622    the library to perform the key agreement protocol and FALSE if it is not
623    desired (application may start it later by calling the function
624    silc_client_perform_key_agreement). If TRUE is returned also the
625    `completion' and `context' arguments must be set by the application. */
626
627 static bool
628 silc_key_agreement(SilcClient client, SilcClientConnection conn,
629                    SilcClientEntry client_entry, const char *hostname,
630                    SilcUInt16 port, SilcKeyAgreementCallback *completion,
631                    void **context)
632 {
633   return FALSE;
634 }
635
636
637 /* Notifies application that file transfer protocol session is being
638    requested by the remote client indicated by the `client_entry' from
639    the `hostname' and `port'. The `session_id' is the file transfer
640    session and it can be used to either accept or reject the file
641    transfer request, by calling the silc_client_file_receive or
642    silc_client_file_close, respectively. */
643
644 static void
645 silc_ftp(SilcClient client, SilcClientConnection conn,
646          SilcClientEntry client_entry, SilcUInt32 session_id,
647          const char *hostname, SilcUInt16 port)
648 {
649
650 }
651
652
653 /* Delivers SILC session detachment data indicated by `detach_data' to the
654    application.  If application has issued SILC_COMMAND_DETACH command
655    the client session in the SILC network is not quit.  The client remains
656    in the network but is detached.  The detachment data may be used later
657    to resume the session in the SILC Network.  The appliation is
658    responsible of saving the `detach_data', to for example in a file.
659
660    The detachment data can be given as argument to the functions
661    silc_client_connect_to_server, or silc_client_add_connection when
662    creating connection to remote server, inside SilcClientConnectionParams
663    structure.  If it is provided the client library will attempt to resume
664    the session in the network.  After the connection is created
665    successfully, the application is responsible of setting the user
666    interface for user into the same state it was before detaching (showing
667    same channels, channel modes, etc).  It can do this by fetching the
668    information (like joined channels) from the client library. */
669
670 static void
671 silc_detach(SilcClient client, SilcClientConnection conn,
672             const unsigned char *detach_data, SilcUInt32 detach_data_len)
673 {
674
675 }
676
677 /* This structure and all the functions were taken from the
678    lib/silcclient/client_ops_example.c. */
679 SilcClientOperations silc_map_client_ops = {
680   silc_say,
681   silc_channel_message,
682   silc_private_message,
683   silc_notify,
684   silc_command,
685   silc_command_reply,
686   silc_connected,
687   silc_disconnected,
688   silc_get_auth_method,
689   silc_verify_public_key,
690   silc_ask_passphrase,
691   silc_failure,
692   silc_key_agreement,
693   silc_ftp,
694   silc_detach
695 };