792566d586c590a7cfa9935aacea7cc8473b70b9
[olsrd.git] / src / link_set.c
1 /*
2  * OLSR ad-hoc routing table management protocol
3  * Copyright (C) 2003 Andreas T√łnnesen (andreto@ifi.uio.no)
4  *
5  * This file is part of olsrd-unik.
6  *
7  * olsrd-unik is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * olsrd-unik is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with olsrd-unik; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  *
21  */
22
23
24 /*
25  * Link sensing database for the OLSR routing daemon
26  */
27
28 #include "link_set.h"
29 #include "hysteresis.h"
30 #include "mid_set.h"
31 #include "mpr.h"
32 #include "olsr.h"
33 #include "scheduler.h"
34
35 #include "link_layer.h"
36
37 void
38 olsr_init_link_set()
39 {
40
41   /* Timers */
42   olsr_init_timer((olsr_u32_t) (neighbor_hold_time*1000), &hold_time_neighbor);
43   olsr_init_timer((olsr_u32_t) (neighbor_hold_time_nw*1000), &hold_time_neighbor_nw);
44
45   olsr_register_timeout_function(&olsr_time_out_link_set);
46   if(use_hysteresis)
47     {
48       olsr_register_timeout_function(&olsr_time_out_hysteresis);
49     }
50   link_set = NULL;
51 }
52
53
54
55 /**
56  * Get the status of a link. The status is based upon different
57  * timeouts in the link entry.
58  *
59  *@param remote address of the remote interface
60  *
61  *@return the link status of the link
62  */
63 int
64 lookup_link_status(struct link_entry *entry)
65 {
66
67   if(entry == NULL || link_set == NULL)
68     {
69       return UNSPEC_LINK;
70     }
71
72   /*
73    * Hysteresis
74    */
75   if(use_hysteresis)
76     {
77       /*
78         if L_LOST_LINK_time is not expired, the link is advertised
79         with a link type of LOST_LINK.
80       */
81       if(!TIMED_OUT(&entry->L_LOST_LINK_time))
82         return LOST_LINK;
83       /*
84         otherwise, if L_LOST_LINK_time is expired and L_link_pending
85         is set to "true", the link SHOULD NOT be advertised at all;
86       */
87       if(entry->L_link_pending == 1)
88         {
89 #ifdef DEBUG
90           olsr_printf(3, "HYST[%s]: Setting to HIDE\n", olsr_ip_to_string(&entry->neighbor_iface_addr));
91 #endif
92           return HIDE_LINK;
93         }
94       /*
95         otherwise, if L_LOST_LINK_time is expired and L_link_pending
96         is set to "false", the link is advertised as described
97         previously in section 6.
98       */
99     }
100
101   if(!TIMED_OUT(&entry->SYM_time))
102     return SYM_LINK;
103
104   if(!TIMED_OUT(&entry->ASYM_time))
105     return ASYM_LINK;
106
107   return LOST_LINK;
108
109
110 }
111
112
113
114
115
116
117 /**
118  *Find the "best" link status to a
119  *neighbor
120  *
121  *@param address the address to check for
122  *
123  *@return SYM_LINK if a symmetric link exists 0 if not
124  */
125 int
126 get_neighbor_status(union olsr_ip_addr *address)
127 {
128   union olsr_ip_addr *main_addr;
129   struct addresses   *aliases;
130   struct link_entry  *link;
131   struct interface   *ifs;
132
133   //printf("GET_NEIGHBOR_STATUS\n");
134
135   /* Find main address */
136   if(!(main_addr = mid_lookup_main_addr(address)))
137     main_addr = address;
138
139   //printf("\tmain: %s\n", olsr_ip_to_string(main_addr));
140
141   /* Loop trough local interfaces to check all possebilities */
142   for(ifs = ifnet; ifs != NULL; ifs = ifs->int_next)
143     {
144       //printf("\tChecking %s->", olsr_ip_to_string(&ifs->ip_addr));
145       //printf("%s : ", olsr_ip_to_string(main_addr)); 
146       if((link = lookup_link_entry(main_addr, &ifs->ip_addr)) != NULL)
147         {
148           //printf("%d\n", lookup_link_status(link));
149           if(lookup_link_status(link) == SYM_LINK)
150             return SYM_LINK;
151         }
152       /* Get aliases */
153       for(aliases = mid_lookup_aliases(main_addr);
154           aliases != NULL;
155           aliases = aliases->next)
156         {
157           //printf("\tChecking %s->", olsr_ip_to_string(&ifs->ip_addr));
158           //printf("%s : ", olsr_ip_to_string(&aliases->address)); 
159           if((link = lookup_link_entry(&aliases->address, &ifs->ip_addr)) != NULL)
160             {
161               //printf("%d\n", lookup_link_status(link));
162
163               if(lookup_link_status(link) == SYM_LINK)
164                 return SYM_LINK;
165             }
166         }
167     }
168   
169   return 0;
170 }
171
172
173
174
175 /**
176  *Get the remote interface address to use as nexthop
177  *to reach the remote host.
178  *
179  *@param address the address of the remote host
180  *@return the nexthop address to use. Returns the pointer
181  *passed as arg 1 if nothing is found(if no MID is registered).
182  */
183 union olsr_ip_addr *
184 get_neighbor_nexthop(union olsr_ip_addr *address)
185 {
186   union olsr_ip_addr *main_addr;
187   struct addresses   *aliases;
188   struct link_entry  *link;
189   struct interface   *ifs;
190
191   //printf("GET_NEIGHBOR_NEXTHOP\n");
192
193   /* Find main address */
194   if(!(main_addr = mid_lookup_main_addr(address)))
195     main_addr = address;
196
197   //printf("\tmain: %s\n", olsr_ip_to_string(main_addr));
198
199   /* Loop trough local interfaces to check all possebilities */
200   for(ifs = ifnet; ifs != NULL; ifs = ifs->int_next)
201     {
202       //printf("\tChecking %s->", olsr_ip_to_string(&ifs->ip_addr));
203       //printf("%s : ", olsr_ip_to_string(main_addr)); 
204       if((link = lookup_link_entry(main_addr, &ifs->ip_addr)) != NULL)
205         {
206           //printf("%d\n", lookup_link_status(link));
207           if(lookup_link_status(link) == SYM_LINK)
208             return main_addr;
209         }
210       /* Get aliases */
211       for(aliases = mid_lookup_aliases(main_addr);
212           aliases != NULL;
213           aliases = aliases->next)
214         {
215           //printf("\tChecking %s->", olsr_ip_to_string(&ifs->ip_addr));
216           //printf("%s : ", olsr_ip_to_string(&aliases->address)); 
217           if((link = lookup_link_entry(&aliases->address, &ifs->ip_addr)) != NULL)
218             {
219               //printf("%d\n", lookup_link_status(link));
220
221               if(lookup_link_status(link) == SYM_LINK)
222                 return &aliases->address;
223             }
224         }
225     }
226   
227   /* This shoud only happen if not MID addresses for the
228    * multi-homed remote host are registered yet
229    */
230   return address;
231 }
232
233
234
235
236 /**
237  *Get the interface to use when setting up
238  *a route to a neighbor. The interface with
239  *the lowest metric is used.
240  *
241  *As this function is only called by the route calculation
242  *functions it is considered that the caller is responsible
243  *for making sure the neighbor is symmetric.
244  *Due to experiences of route calculaition queryig for interfaces
245  *when no links with a valid SYM time is avalibe, the function
246  *will return a possible interface with an expired SYM time
247  *if no SYM links were discovered.
248  *
249  *@param address of the neighbor - does not have to
250  *be the main address
251  *
252  *@return a interface struct representing the interface to use
253  */
254 struct interface *
255 get_interface_link_set(union olsr_ip_addr *remote)
256 {
257   struct link_entry *tmp_link_set;
258   union olsr_ip_addr *remote_addr;
259   struct interface *if_to_use, *tmp_if, *backup_if;
260
261   if_to_use = NULL;
262   backup_if = NULL;
263
264   if(remote == NULL || link_set == NULL)
265     {
266       olsr_printf(1, "Get interface: not sane request or empty link set!\n");
267       return NULL;
268     }
269
270   /* Check for main address of address */
271   if((remote_addr = mid_lookup_main_addr(remote)) == NULL)
272     remote_addr = remote;
273
274   tmp_link_set = link_set;
275   
276   while(tmp_link_set)
277     {
278       //printf("Checking %s vs ", olsr_ip_to_string(&tmp_link_set->neighbor_iface_addr));
279       //printf("%s\n", olsr_ip_to_string(addr));
280       
281       if(COMP_IP(remote_addr, &tmp_link_set->neighbor->neighbor_main_addr) ||
282          COMP_IP(remote_addr, &tmp_link_set->neighbor_iface_addr))
283         {
284
285           tmp_if = if_ifwithaddr(&tmp_link_set->local_iface_addr);
286
287           /* Must be symmetric link! */
288           if(!TIMED_OUT(&tmp_link_set->SYM_time))
289             {
290               if((if_to_use == NULL) || (if_to_use->int_metric > tmp_if->int_metric))
291                 if_to_use = tmp_if;
292             }
293           /* Backup solution in case the links have timed out */
294           else
295             {
296               if((if_to_use == NULL) && ((backup_if == NULL) || (backup_if->int_metric > tmp_if->int_metric)))
297                 {
298                   backup_if = tmp_if;
299                 }
300             }
301         }
302       
303       tmp_link_set = tmp_link_set->next;
304     }
305   
306   /* Not found */
307   if(if_to_use == NULL)
308     return backup_if;
309   
310   return if_to_use;
311 }
312
313
314
315 /**
316  *Nothing mysterious here.
317  *Adding a new link entry to the link set.
318  *
319  *@param local the local IP address
320  *@param remote the remote IP address
321  *@param remote_main teh remote nodes main address
322  *@param vtime the validity time of the entry
323  *@param htime the HELLO interval of the remote node
324  */
325
326 struct link_entry *
327 add_new_entry(union olsr_ip_addr *local, union olsr_ip_addr *remote, union olsr_ip_addr *remote_main, double vtime, double htime)
328 {
329   struct link_entry *tmp_link_set, *new_link;
330   struct neighbor_entry *neighbor;
331 #ifndef WIN32
332   struct interface *local_if;
333 #endif
334
335   tmp_link_set = link_set;
336
337   while(tmp_link_set)
338     {
339       if(COMP_IP(remote, &tmp_link_set->neighbor_iface_addr))
340         return tmp_link_set;
341       tmp_link_set = tmp_link_set->next;
342     }
343
344   /*
345    * if there exists no link tuple with
346    * L_neighbor_iface_addr == Source Address
347    */
348
349 #ifdef DEBUG
350   olsr_printf(3, "Adding %s to link set\n", olsr_ip_to_string(remote));
351 #endif
352
353   /* a new tuple is created with... */
354
355   new_link = olsr_malloc(sizeof(struct link_entry), "new link entry");
356
357   /*
358    * L_local_iface_addr = Address of the interface
359    * which received the HELLO message
360    */
361   //printf("\tLocal IF: %s\n", olsr_ip_to_string(local));
362   COPY_IP(&new_link->local_iface_addr, local);
363   /* L_neighbor_iface_addr = Source Address */
364   COPY_IP(&new_link->neighbor_iface_addr, remote);
365
366   /* L_SYM_time            = current time - 1 (expired) */
367   new_link->SYM_time = now;
368   /* Subtract 1 */
369   new_link->SYM_time.tv_sec -= 1;
370
371   /* L_time = current time + validity time */
372   olsr_get_timestamp((olsr_u32_t) vtime*1000, &new_link->time);
373
374
375   /* HYSTERESIS */
376   if(use_hysteresis)
377     {
378       new_link->L_link_pending = 1;
379       olsr_get_timestamp((olsr_u32_t) vtime*1000, &new_link->L_LOST_LINK_time);
380       olsr_get_timestamp((olsr_u32_t) htime*1500, &new_link->hello_timeout);
381       new_link->last_htime = htime;
382       new_link->olsr_seqno = 0;
383       new_link->L_link_quality = 0;
384     }
385   /* Add to queue */
386   new_link->next = link_set;
387   link_set = new_link;
388
389
390   /*
391    * Create the neighbor entry
392    */
393
394   /* Neighbor MUST exist! */
395   if(NULL == (neighbor = olsr_lookup_neighbor_table(remote_main)))
396     {
397       neighbor = olsr_insert_neighbor_table(remote_main);
398       /* Copy the main address */
399       COPY_IP(&neighbor->neighbor_main_addr, remote_main);
400 #ifdef DEBUG
401       olsr_printf(3, "ADDING NEW NEIGHBOR ENTRY %s FROM LINK SET\n", olsr_ip_to_string(remote_main));
402 #endif
403     }
404
405   neighbor->linkcount++;
406
407
408   new_link->neighbor = neighbor;
409
410   if(!COMP_IP(remote, remote_main))
411     {
412       /* Add MID alias if not already registered */
413       /* This is kind of sketchy... and not specified
414        * in the RFC. We can only guess a vtime.
415        * We'll go for one that is hopefully long
416        * enough in most cases. 20 seconds
417        */
418       olsr_printf(1, "Adding MID alias main %s ", olsr_ip_to_string(remote_main));
419       olsr_printf(1, "-> %s based on HELLO\n\n", olsr_ip_to_string(remote));
420       insert_mid_alias(remote_main, remote, 20.0);
421     }
422
423   /* Add to link-layer spy list */
424 #ifndef WIN32
425   if(llinfo)
426     {
427       local_if = if_ifwithaddr(local);
428       
429       olsr_printf(1, "Adding %s to spylist of interface %s\n", olsr_ip_to_string(remote), local_if->int_name);
430
431       if((local_if != NULL) && (add_spy_node(remote, local_if->int_name)))
432         new_link->spy_activated = 1;
433     }
434 #endif
435
436   return link_set;
437 }
438
439
440 /**
441  *Lookup the status of a link.
442  *
443  *@param int_addr address of the remote interface
444  *
445  *@return 1 of the link is symmertic 0 if not
446  */
447
448 int
449 check_neighbor_link(union olsr_ip_addr *int_addr)
450 {
451   struct link_entry *tmp_link_set;
452
453   tmp_link_set = link_set;
454
455   while(tmp_link_set)
456     {
457       if(COMP_IP(int_addr, &tmp_link_set->neighbor_iface_addr))
458         return lookup_link_status(tmp_link_set);
459       tmp_link_set = tmp_link_set->next;
460     }
461   return UNSPEC_LINK;
462 }
463
464
465 /**
466  *Lookup a link entry
467  *
468  *@param remote the remote interface address
469  *@param local the local interface address
470  *
471  *@return the link entry if found, NULL if not
472  */
473 struct link_entry *
474 lookup_link_entry(union olsr_ip_addr *remote, union olsr_ip_addr *local)
475 {
476   struct link_entry *tmp_link_set;
477
478   tmp_link_set = link_set;
479
480   while(tmp_link_set)
481     {
482       if(COMP_IP(remote, &tmp_link_set->neighbor_iface_addr) &&
483          COMP_IP(local, &tmp_link_set->local_iface_addr))
484         return tmp_link_set;
485       tmp_link_set = tmp_link_set->next;
486     }
487   return NULL;
488
489 }
490
491
492
493
494
495
496
497 /**
498  *Update a link entry. This is the "main entrypoint" in
499  *the link-sensing. This function is calles from the HELLO
500  *parser function.
501  *It makes sure a entry is updated or created.
502  *
503  *@param local the local IP address
504  *@param remote the remote IP address
505  *@param message the HELLO message
506  *@param in_if the interface on which this HELLO was received
507  *
508  *@return the link_entry struct describing this link entry
509  */
510 struct link_entry *
511 update_link_entry(union olsr_ip_addr *local, union olsr_ip_addr *remote, struct hello_message *message, struct interface *in_if)
512 {
513   int status;
514   struct link_entry *entry;
515 #ifndef WIN32
516   struct interface *local_if;
517 #endif
518
519   /* Time out entries */
520   //timeout_link_set();
521
522   /* Add if not registered */
523   entry = add_new_entry(local, remote, &message->source_addr, message->vtime, message->htime);
524
525   /* Update link layer info */
526   /* Add to link-layer spy list */
527 #ifndef WIN32
528   if(llinfo && !entry->spy_activated)
529     {
530       local_if = if_ifwithaddr(local);
531       
532       olsr_printf(1, "Adding %s to spylist of interface %s\n", olsr_ip_to_string(remote), local_if->int_name);
533
534       if((local_if != NULL) && (add_spy_node(remote, local_if->int_name)))
535         entry->spy_activated = 1;
536     }
537 #endif
538
539   /* Update ASYM_time */
540   //printf("Vtime is %f\n", message->vtime);
541   /* L_ASYM_time = current time + validity time */
542   olsr_get_timestamp((olsr_u32_t) (message->vtime*1000), &entry->ASYM_time);
543
544
545   status = check_link_status(message);
546
547   //printf("Status %d\n", status);
548
549   switch(status)
550     {
551     case(LOST_LINK):
552       /* L_SYM_time = current time - 1 (i.e., expired) */
553       entry->SYM_time = now;
554       entry->SYM_time.tv_sec -= 1;
555
556       break;
557     case(SYM_LINK):
558     case(ASYM_LINK):
559       /* L_SYM_time = current time + validity time */
560       //printf("updating SYM time for %s\n", olsr_ip_to_string(remote));
561       olsr_get_timestamp((olsr_u32_t) (message->vtime*1000), &entry->SYM_time);
562         //timeradd(&now, &tmp_timer, &entry->SYM_time);
563
564       /* L_time = L_SYM_time + NEIGHB_HOLD_TIME */
565       if(in_if->is_wireless)
566         timeradd(&entry->SYM_time, &hold_time_neighbor, &entry->time);
567       else
568         timeradd(&entry->SYM_time, &hold_time_neighbor_nw, &entry->time);
569
570       break;
571     default:;
572     }
573
574   /* L_time = max(L_time, L_ASYM_time) */
575   if(timercmp(&entry->time, &entry->ASYM_time, <))
576     entry->time = entry->ASYM_time;
577
578
579   /*
580   printf("Updating link LOCAL: %s ", olsr_ip_to_string(local));
581   printf("REMOTE: %s\n", olsr_ip_to_string(remote));
582   printf("VTIME: %f ", message->vtime);
583   printf("STATUS: %d\n", status);
584   */
585
586   /* Update hysteresis values */
587   if(use_hysteresis)
588     olsr_process_hysteresis(entry);
589
590   /* update neighbor status */
591   /* Return link status */
592   //status = lookup_link_status(entry);
593   /* UPDATED ! */
594   status = get_neighbor_status(remote);
595
596   /* Update neighbor */
597   update_neighbor_status(entry->neighbor, status);
598   //update_neighbor_status(entry->neighbor);
599
600   return entry;  
601 }
602
603
604 /**
605  * Fuction that updates all registered pointers to
606  * one neighbor entry with another pointer
607  * Used by MID updates.
608  *
609  *@old the pointer to replace
610  *@new the pointer to use instead of "old"
611  *
612  *@return the number of entries updated
613  */
614 int
615 replace_neighbor_link_set(struct neighbor_entry *old,
616                           struct neighbor_entry *new)
617 {
618   struct link_entry *tmp_link_set, *last_link_entry;
619   int retval;
620
621   retval = 0;
622
623   if(link_set == NULL)
624     return retval;
625       
626   tmp_link_set = link_set;
627   last_link_entry = NULL;
628
629   while(tmp_link_set)
630     {
631
632       if(tmp_link_set->neighbor == old)
633         {
634           tmp_link_set->neighbor = new;
635           retval++;
636         }
637       tmp_link_set = tmp_link_set->next;
638     }
639
640   return retval;
641
642 }
643
644
645 /**
646  *Checks the link status to a neighbor by
647  *looking in a received HELLO message.
648  *
649  *@param message the HELLO message to check
650  *
651  *@return the link status
652  */
653 int
654 check_link_status(struct hello_message *message)
655 {
656
657   struct hello_neighbor  *neighbors;
658   struct interface *ifd;
659
660   neighbors = message->neighbors;
661   
662   while(neighbors!=NULL)
663     {  
664       //printf("(linkstatus)Checking %s ",olsr_ip_to_string(&neighbors->address));
665       //printf("against %s\n",olsr_ip_to_string(&main_addr));
666
667       /* Check all interfaces */          
668       for (ifd = ifnet; ifd ; ifd = ifd->int_next) 
669         {
670           if(COMP_IP(&neighbors->address, &ifd->ip_addr))
671             {
672               //printf("ok");
673               return neighbors->link;
674             }
675         }
676
677       neighbors = neighbors->next; 
678     }
679
680
681   return UNSPEC_LINK;
682 }
683
684
685 /**
686  *Time out the link set. In other words, the link
687  *set is traversed and all non-valid entries are
688  *deleted.
689  *
690  */
691 void
692 olsr_time_out_link_set()
693 {
694
695   struct link_entry *tmp_link_set, *last_link_entry;
696
697
698   if(link_set == NULL)
699     return;
700       
701   tmp_link_set = link_set;
702   last_link_entry = NULL;
703
704   while(tmp_link_set)
705     {
706
707       if(TIMED_OUT(&tmp_link_set->time))
708         {
709           if(last_link_entry != NULL)
710             {
711               last_link_entry->next = tmp_link_set->next;
712
713               /* Delete neighbor entry */
714               if(tmp_link_set->neighbor->linkcount == 1)
715                 olsr_delete_neighbor_table(&tmp_link_set->neighbor->neighbor_main_addr);
716               else
717                 tmp_link_set->neighbor->linkcount--;
718
719               //olsr_delete_neighbor_if_no_link(&tmp_link_set->neighbor->neighbor_main_addr);
720               changes_neighborhood = UP;
721
722               free(tmp_link_set);
723               tmp_link_set = last_link_entry;
724             }
725           else
726             {
727               link_set = tmp_link_set->next; /* CHANGED */
728
729               /* Delete neighbor entry */
730               if(tmp_link_set->neighbor->linkcount == 1)
731                 olsr_delete_neighbor_table(&tmp_link_set->neighbor->neighbor_main_addr);
732               else
733                 tmp_link_set->neighbor->linkcount--;
734               //olsr_delete_neighbor_if_no_link(&tmp_link_set->neighbor->neighbor_main_addr);
735
736               changes_neighborhood = UP;
737
738               free(tmp_link_set);
739               tmp_link_set = link_set;
740               continue;
741             }       
742         }
743       
744       last_link_entry = tmp_link_set;
745       tmp_link_set = tmp_link_set->next;
746     }
747
748   return;
749 }
750
751
752
753
754 /**
755  *Updates links that we have not received
756  *HELLO from in expected time according to 
757  *hysteresis.
758  *
759  *@return nada
760  */
761 void
762 olsr_time_out_hysteresis()
763 {
764
765   struct link_entry *tmp_link_set;
766   int status;
767
768   if(link_set == NULL)
769     return;
770       
771   tmp_link_set = link_set;
772
773   while(tmp_link_set)
774     {
775       if(TIMED_OUT(&tmp_link_set->hello_timeout))
776         {
777           tmp_link_set->L_link_quality = olsr_hyst_calc_instability(tmp_link_set->L_link_quality);
778           olsr_printf(1, "HYST[%s] HELLO timeout %0.3f\n", olsr_ip_to_string(&tmp_link_set->neighbor_iface_addr), tmp_link_set->L_link_quality);
779           /* Update hello_timeout - NO SLACK THIS TIME */
780           olsr_get_timestamp((olsr_u32_t) tmp_link_set->last_htime*1000, &tmp_link_set->hello_timeout);
781
782           /* Recalculate status */
783           /* Update hysteresis values */
784           olsr_process_hysteresis(tmp_link_set);
785           
786           /* update neighbor status */
787           //status = lookup_link_status(tmp_link_set);
788           /* UPDATED ! */
789           status = get_neighbor_status(&tmp_link_set->neighbor_iface_addr);
790
791
792           /* Update neighbor */
793           update_neighbor_status(tmp_link_set->neighbor, status);
794           //update_neighbor_status(tmp_link_set->neighbor);
795
796           /* Update seqno - not mentioned in the RFC... kind of a hack.. */
797           tmp_link_set->olsr_seqno++;
798         }
799       tmp_link_set = tmp_link_set->next;
800     }
801
802   return;
803 }
804