]> git.proxmox.com Git - mirror_frr.git/blob - ospfclient/ospf_apiclient.c
lib: migrate to new memory-type handling
[mirror_frr.git] / ospfclient / ospf_apiclient.c
1 /*
2 * Client side of OSPF API.
3 * Copyright (C) 2001, 2002, 2003 Ralph Keller
4 *
5 * This file is part of GNU Zebra.
6 *
7 * GNU Zebra is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published
9 * by the Free Software Foundation; either version 2, or (at your
10 * option) any later version.
11 *
12 * GNU Zebra is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with GNU Zebra; see the file COPYING. If not, write to the
19 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 * Boston, MA 02111-1307, USA.
21 */
22
23 #include <zebra.h>
24
25 #include <lib/version.h>
26 #include "getopt.h"
27 #include "thread.h"
28 #include "prefix.h"
29 #include "linklist.h"
30 #include "if.h"
31 #include "vector.h"
32 #include "vty.h"
33 #include "command.h"
34 #include "filter.h"
35 #include "stream.h"
36 #include "log.h"
37 #include "memory.h"
38
39 #include "ospfd/ospfd.h"
40 #include "ospfd/ospf_interface.h"
41 #include "ospfd/ospf_asbr.h"
42 #include "ospfd/ospf_lsa.h"
43 #include "ospfd/ospf_opaque.h"
44 #include "ospfd/ospf_lsdb.h"
45 #include "ospfd/ospf_neighbor.h"
46 #include "ospfd/ospf_dump.h"
47 #include "ospfd/ospf_zebra.h"
48 #include "ospfd/ospf_api.h"
49
50 #include "ospf_apiclient.h"
51
52 DEFINE_MTYPE_STATIC(OSPFD, OSPF_APICLIENT, "OSPF-API client")
53
54 /* Backlog for listen */
55 #define BACKLOG 5
56
57 /* -----------------------------------------------------------
58 * Forward declarations
59 * -----------------------------------------------------------
60 */
61
62 void ospf_apiclient_handle_reply (struct ospf_apiclient *oclient,
63 struct msg *msg);
64 void ospf_apiclient_handle_update_notify (struct ospf_apiclient *oclient,
65 struct msg *msg);
66 void ospf_apiclient_handle_delete_notify (struct ospf_apiclient *oclient,
67 struct msg *msg);
68
69 /* -----------------------------------------------------------
70 * Initialization
71 * -----------------------------------------------------------
72 */
73
74 static unsigned short
75 ospf_apiclient_getport (void)
76 {
77 struct servent *sp = getservbyname ("ospfapi", "tcp");
78
79 return sp ? ntohs (sp->s_port) : OSPF_API_SYNC_PORT;
80 }
81
82 /* -----------------------------------------------------------
83 * Followings are functions for connection management
84 * -----------------------------------------------------------
85 */
86
87 struct ospf_apiclient *
88 ospf_apiclient_connect (char *host, int syncport)
89 {
90 struct sockaddr_in myaddr_sync;
91 struct sockaddr_in myaddr_async;
92 struct sockaddr_in peeraddr;
93 struct hostent *hp;
94 struct ospf_apiclient *new;
95 int size = 0;
96 unsigned int peeraddrlen;
97 int async_server_sock;
98 int fd1, fd2;
99 int ret;
100 int on = 1;
101
102 /* There are two connections between the client and the server.
103 First the client opens a connection for synchronous requests/replies
104 to the server. The server will accept this connection and
105 as a reaction open a reverse connection channel for
106 asynchronous messages. */
107
108 async_server_sock = socket (AF_INET, SOCK_STREAM, 0);
109 if (async_server_sock < 0)
110 {
111 fprintf (stderr,
112 "ospf_apiclient_connect: creating async socket failed\n");
113 return NULL;
114 }
115
116 /* Prepare socket for asynchronous messages */
117 /* Initialize async address structure */
118 memset (&myaddr_async, 0, sizeof (struct sockaddr_in));
119 myaddr_async.sin_family = AF_INET;
120 myaddr_async.sin_addr.s_addr = htonl (INADDR_ANY);
121 myaddr_async.sin_port = htons (syncport+1);
122 size = sizeof (struct sockaddr_in);
123 #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN
124 myaddr_async.sin_len = size;
125 #endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */
126
127 /* This is a server socket, reuse addr and port */
128 ret = setsockopt (async_server_sock, SOL_SOCKET,
129 SO_REUSEADDR, (void *) &on, sizeof (on));
130 if (ret < 0)
131 {
132 fprintf (stderr, "ospf_apiclient_connect: SO_REUSEADDR failed\n");
133 close (async_server_sock);
134 return NULL;
135 }
136
137 #ifdef SO_REUSEPORT
138 ret = setsockopt (async_server_sock, SOL_SOCKET, SO_REUSEPORT,
139 (void *) &on, sizeof (on));
140 if (ret < 0)
141 {
142 fprintf (stderr, "ospf_apiclient_connect: SO_REUSEPORT failed\n");
143 close (async_server_sock);
144 return NULL;
145 }
146 #endif /* SO_REUSEPORT */
147
148 /* Bind socket to address structure */
149 ret = bind (async_server_sock, (struct sockaddr *) &myaddr_async, size);
150 if (ret < 0)
151 {
152 fprintf (stderr, "ospf_apiclient_connect: bind async socket failed\n");
153 close (async_server_sock);
154 return NULL;
155 }
156
157 /* Wait for reverse channel connection establishment from server */
158 ret = listen (async_server_sock, BACKLOG);
159 if (ret < 0)
160 {
161 fprintf (stderr, "ospf_apiclient_connect: listen: %s\n", safe_strerror (errno));
162 close (async_server_sock);
163 return NULL;
164 }
165
166 /* Make connection for synchronous requests and connect to server */
167 /* Resolve address of server */
168 hp = gethostbyname (host);
169 if (!hp)
170 {
171 fprintf (stderr, "ospf_apiclient_connect: no such host %s\n", host);
172 close (async_server_sock);
173 return NULL;
174 }
175
176 fd1 = socket (AF_INET, SOCK_STREAM, 0);
177 if (fd1 < 0)
178 {
179 fprintf (stderr,
180 "ospf_apiclient_connect: creating sync socket failed\n");
181 return NULL;
182 }
183
184
185 /* Reuse addr and port */
186 ret = setsockopt (fd1, SOL_SOCKET,
187 SO_REUSEADDR, (void *) &on, sizeof (on));
188 if (ret < 0)
189 {
190 fprintf (stderr, "ospf_apiclient_connect: SO_REUSEADDR failed\n");
191 close (fd1);
192 return NULL;
193 }
194
195 #ifdef SO_REUSEPORT
196 ret = setsockopt (fd1, SOL_SOCKET, SO_REUSEPORT,
197 (void *) &on, sizeof (on));
198 if (ret < 0)
199 {
200 fprintf (stderr, "ospf_apiclient_connect: SO_REUSEPORT failed\n");
201 close (fd1);
202 return NULL;
203 }
204 #endif /* SO_REUSEPORT */
205
206
207 /* Bind sync socket to address structure. This is needed since we
208 want the sync port number on a fixed port number. The reverse
209 async channel will be at this port+1 */
210
211 memset (&myaddr_sync, 0, sizeof (struct sockaddr_in));
212 myaddr_sync.sin_family = AF_INET;
213 myaddr_sync.sin_port = htons (syncport);
214 #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN
215 myaddr_sync.sin_len = sizeof (struct sockaddr_in);
216 #endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */
217
218 ret = bind (fd1, (struct sockaddr *) &myaddr_sync, size);
219 if (ret < 0)
220 {
221 fprintf (stderr, "ospf_apiclient_connect: bind sync socket failed\n");
222 close (fd1);
223 return NULL;
224 }
225
226 /* Prepare address structure for connect */
227 memcpy (&myaddr_sync.sin_addr, hp->h_addr, hp->h_length);
228 myaddr_sync.sin_family = AF_INET;
229 myaddr_sync.sin_port = htons(ospf_apiclient_getport ());
230 #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN
231 myaddr_sync.sin_len = sizeof (struct sockaddr_in);
232 #endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */
233
234 /* Now establish synchronous channel with OSPF daemon */
235 ret = connect (fd1, (struct sockaddr *) &myaddr_sync,
236 sizeof (struct sockaddr_in));
237 if (ret < 0)
238 {
239 fprintf (stderr, "ospf_apiclient_connect: sync connect failed\n");
240 close (async_server_sock);
241 close (fd1);
242 return NULL;
243 }
244
245 /* Accept reverse connection */
246 peeraddrlen = sizeof (struct sockaddr_in);
247 memset (&peeraddr, 0, peeraddrlen);
248
249 fd2 =
250 accept (async_server_sock, (struct sockaddr *) &peeraddr, &peeraddrlen);
251 if (fd2 < 0)
252 {
253 fprintf (stderr, "ospf_apiclient_connect: accept async failed\n");
254 close (async_server_sock);
255 close (fd1);
256 return NULL;
257 }
258
259 /* Server socket is not needed anymore since we are not accepting more
260 connections */
261 close (async_server_sock);
262
263 /* Create new client-side instance */
264 new = XCALLOC (MTYPE_OSPF_APICLIENT, sizeof (struct ospf_apiclient));
265
266 /* Initialize socket descriptors for sync and async channels */
267 new->fd_sync = fd1;
268 new->fd_async = fd2;
269
270 return new;
271 }
272
273 int
274 ospf_apiclient_close (struct ospf_apiclient *oclient)
275 {
276
277 if (oclient->fd_sync >= 0)
278 {
279 close (oclient->fd_sync);
280 }
281
282 if (oclient->fd_async >= 0)
283 {
284 close (oclient->fd_async);
285 }
286
287 /* Free client structure */
288 XFREE (MTYPE_OSPF_APICLIENT, oclient);
289 return 0;
290 }
291
292 /* -----------------------------------------------------------
293 * Followings are functions to send a request to OSPFd
294 * -----------------------------------------------------------
295 */
296
297 /* Send synchronous request, wait for reply */
298 static int
299 ospf_apiclient_send_request (struct ospf_apiclient *oclient, struct msg *msg)
300 {
301 u_int32_t reqseq;
302 struct msg_reply *msgreply;
303 int rc;
304
305 /* NB: Given "msg" is freed inside this function. */
306
307 /* Remember the sequence number of the request */
308 reqseq = ntohl (msg->hdr.msgseq);
309
310 /* Write message to OSPFd */
311 rc = msg_write (oclient->fd_sync, msg);
312 msg_free (msg);
313
314 if (rc < 0)
315 {
316 return -1;
317 }
318
319 /* Wait for reply *//* NB: New "msg" is allocated by "msg_read()". */
320 msg = msg_read (oclient->fd_sync);
321 if (!msg)
322 return -1;
323
324 assert (msg->hdr.msgtype == MSG_REPLY);
325 assert (ntohl (msg->hdr.msgseq) == reqseq);
326
327 msgreply = (struct msg_reply *) STREAM_DATA (msg->s);
328 rc = msgreply->errcode;
329 msg_free (msg);
330
331 return rc;
332 }
333
334
335 /* -----------------------------------------------------------
336 * Helper functions
337 * -----------------------------------------------------------
338 */
339
340 static u_int32_t
341 ospf_apiclient_get_seqnr (void)
342 {
343 static u_int32_t seqnr = MIN_SEQ;
344 u_int32_t tmp;
345
346 tmp = seqnr;
347 /* Increment sequence number */
348 if (seqnr < MAX_SEQ)
349 {
350 seqnr++;
351 }
352 else
353 {
354 seqnr = MIN_SEQ;
355 }
356 return tmp;
357 }
358
359 /* -----------------------------------------------------------
360 * API to access OSPF daemon by client applications.
361 * -----------------------------------------------------------
362 */
363
364 /*
365 * Synchronous request to register opaque type.
366 */
367 int
368 ospf_apiclient_register_opaque_type (struct ospf_apiclient *cl,
369 u_char ltype, u_char otype)
370 {
371 struct msg *msg;
372 int rc;
373
374 /* just put 1 as a sequence number. */
375 msg = new_msg_register_opaque_type (ospf_apiclient_get_seqnr (),
376 ltype, otype);
377 if (!msg)
378 {
379 fprintf (stderr, "new_msg_register_opaque_type failed\n");
380 return -1;
381 }
382
383 rc = ospf_apiclient_send_request (cl, msg);
384 return rc;
385 }
386
387 /*
388 * Synchronous request to synchronize with OSPF's LSDB.
389 * Two steps required: register_event in order to get
390 * dynamic updates and LSDB_Sync.
391 */
392 int
393 ospf_apiclient_sync_lsdb (struct ospf_apiclient *oclient)
394 {
395 struct msg *msg;
396 int rc;
397 struct lsa_filter_type filter;
398
399 filter.typemask = 0xFFFF; /* all LSAs */
400 filter.origin = ANY_ORIGIN;
401 filter.num_areas = 0; /* all Areas. */
402
403 msg = new_msg_register_event (ospf_apiclient_get_seqnr (), &filter);
404 if (!msg)
405 {
406 fprintf (stderr, "new_msg_register_event failed\n");
407 return -1;
408 }
409 rc = ospf_apiclient_send_request (oclient, msg);
410
411 if (rc != 0)
412 goto out;
413
414 msg = new_msg_sync_lsdb (ospf_apiclient_get_seqnr (), &filter);
415 if (!msg)
416 {
417 fprintf (stderr, "new_msg_sync_lsdb failed\n");
418 return -1;
419 }
420 rc = ospf_apiclient_send_request (oclient, msg);
421
422 out:
423 return rc;
424 }
425
426 /*
427 * Synchronous request to originate or update an LSA.
428 */
429
430 int
431 ospf_apiclient_lsa_originate (struct ospf_apiclient *oclient,
432 struct in_addr ifaddr,
433 struct in_addr area_id,
434 u_char lsa_type,
435 u_char opaque_type, u_int32_t opaque_id,
436 void *opaquedata, int opaquelen)
437 {
438 struct msg *msg;
439 int rc;
440 u_char buf[OSPF_MAX_LSA_SIZE];
441 struct lsa_header *lsah;
442 u_int32_t tmp;
443
444
445 /* We can only originate opaque LSAs */
446 if (!IS_OPAQUE_LSA (lsa_type))
447 {
448 fprintf (stderr, "Cannot originate non-opaque LSA type %d\n", lsa_type);
449 return OSPF_API_ILLEGALLSATYPE;
450 }
451
452 /* Make a new LSA from parameters */
453 lsah = (struct lsa_header *) buf;
454 lsah->ls_age = 0;
455 lsah->options = 0;
456 lsah->type = lsa_type;
457
458 tmp = SET_OPAQUE_LSID (opaque_type, opaque_id);
459 lsah->id.s_addr = htonl (tmp);
460 lsah->adv_router.s_addr = 0;
461 lsah->ls_seqnum = 0;
462 lsah->checksum = 0;
463 lsah->length = htons (sizeof (struct lsa_header) + opaquelen);
464
465 memcpy (((u_char *) lsah) + sizeof (struct lsa_header), opaquedata,
466 opaquelen);
467
468 msg = new_msg_originate_request (ospf_apiclient_get_seqnr (),
469 ifaddr, area_id, lsah);
470 if (!msg)
471 {
472 fprintf (stderr, "new_msg_originate_request failed\n");
473 return OSPF_API_NOMEMORY;
474 }
475
476 rc = ospf_apiclient_send_request (oclient, msg);
477 return rc;
478 }
479
480 int
481 ospf_apiclient_lsa_delete (struct ospf_apiclient *oclient,
482 struct in_addr area_id, u_char lsa_type,
483 u_char opaque_type, u_int32_t opaque_id)
484 {
485 struct msg *msg;
486 int rc;
487
488 /* Only opaque LSA can be deleted */
489 if (!IS_OPAQUE_LSA (lsa_type))
490 {
491 fprintf (stderr, "Cannot delete non-opaque LSA type %d\n", lsa_type);
492 return OSPF_API_ILLEGALLSATYPE;
493 }
494
495 /* opaque_id is in host byte order and will be converted
496 * to network byte order by new_msg_delete_request */
497 msg = new_msg_delete_request (ospf_apiclient_get_seqnr (),
498 area_id, lsa_type, opaque_type, opaque_id);
499
500 rc = ospf_apiclient_send_request (oclient, msg);
501 return rc;
502 }
503
504 /* -----------------------------------------------------------
505 * Followings are handlers for messages from OSPF daemon
506 * -----------------------------------------------------------
507 */
508
509 static void
510 ospf_apiclient_handle_ready (struct ospf_apiclient *oclient, struct msg *msg)
511 {
512 struct msg_ready_notify *r;
513 r = (struct msg_ready_notify *) STREAM_DATA (msg->s);
514
515 /* Invoke registered callback function. */
516 if (oclient->ready_notify)
517 {
518 (oclient->ready_notify) (r->lsa_type, r->opaque_type, r->addr);
519 }
520 }
521
522 static void
523 ospf_apiclient_handle_new_if (struct ospf_apiclient *oclient, struct msg *msg)
524 {
525 struct msg_new_if *n;
526 n = (struct msg_new_if *) STREAM_DATA (msg->s);
527
528 /* Invoke registered callback function. */
529 if (oclient->new_if)
530 {
531 (oclient->new_if) (n->ifaddr, n->area_id);
532 }
533 }
534
535 static void
536 ospf_apiclient_handle_del_if (struct ospf_apiclient *oclient, struct msg *msg)
537 {
538 struct msg_del_if *d;
539 d = (struct msg_del_if *) STREAM_DATA (msg->s);
540
541 /* Invoke registered callback function. */
542 if (oclient->del_if)
543 {
544 (oclient->del_if) (d->ifaddr);
545 }
546 }
547
548 static void
549 ospf_apiclient_handle_ism_change (struct ospf_apiclient *oclient,
550 struct msg *msg)
551 {
552 struct msg_ism_change *m;
553 m = (struct msg_ism_change *) STREAM_DATA (msg->s);
554
555 /* Invoke registered callback function. */
556 if (oclient->ism_change)
557 {
558 (oclient->ism_change) (m->ifaddr, m->area_id, m->status);
559 }
560
561 }
562
563 static void
564 ospf_apiclient_handle_nsm_change (struct ospf_apiclient *oclient,
565 struct msg *msg)
566 {
567 struct msg_nsm_change *m;
568 m = (struct msg_nsm_change *) STREAM_DATA (msg->s);
569
570 /* Invoke registered callback function. */
571 if (oclient->nsm_change)
572 {
573 (oclient->nsm_change) (m->ifaddr, m->nbraddr, m->router_id, m->status);
574 }
575 }
576
577 static void
578 ospf_apiclient_handle_lsa_update (struct ospf_apiclient *oclient,
579 struct msg *msg)
580 {
581 struct msg_lsa_change_notify *cn;
582 struct lsa_header *lsa;
583 int lsalen;
584
585 cn = (struct msg_lsa_change_notify *) STREAM_DATA (msg->s);
586
587 /* Extract LSA from message */
588 lsalen = ntohs (cn->data.length);
589 lsa = XMALLOC (MTYPE_OSPF_APICLIENT, lsalen);
590 if (!lsa)
591 {
592 fprintf (stderr, "LSA update: Cannot allocate memory for LSA\n");
593 return;
594 }
595 memcpy (lsa, &(cn->data), lsalen);
596
597 /* Invoke registered update callback function */
598 if (oclient->update_notify)
599 {
600 (oclient->update_notify) (cn->ifaddr, cn->area_id,
601 cn->is_self_originated, lsa);
602 }
603
604 /* free memory allocated by ospf apiclient library */
605 XFREE (MTYPE_OSPF_APICLIENT, lsa);
606 }
607
608 static void
609 ospf_apiclient_handle_lsa_delete (struct ospf_apiclient *oclient,
610 struct msg *msg)
611 {
612 struct msg_lsa_change_notify *cn;
613 struct lsa_header *lsa;
614 int lsalen;
615
616 cn = (struct msg_lsa_change_notify *) STREAM_DATA (msg->s);
617
618 /* Extract LSA from message */
619 lsalen = ntohs (cn->data.length);
620 lsa = XMALLOC (MTYPE_OSPF_APICLIENT, lsalen);
621 if (!lsa)
622 {
623 fprintf (stderr, "LSA delete: Cannot allocate memory for LSA\n");
624 return;
625 }
626 memcpy (lsa, &(cn->data), lsalen);
627
628 /* Invoke registered update callback function */
629 if (oclient->delete_notify)
630 {
631 (oclient->delete_notify) (cn->ifaddr, cn->area_id,
632 cn->is_self_originated, lsa);
633 }
634
635 /* free memory allocated by ospf apiclient library */
636 XFREE (MTYPE_OSPF_APICLIENT, lsa);
637 }
638
639 static void
640 ospf_apiclient_msghandle (struct ospf_apiclient *oclient, struct msg *msg)
641 {
642 /* Call message handler function. */
643 switch (msg->hdr.msgtype)
644 {
645 case MSG_READY_NOTIFY:
646 ospf_apiclient_handle_ready (oclient, msg);
647 break;
648 case MSG_NEW_IF:
649 ospf_apiclient_handle_new_if (oclient, msg);
650 break;
651 case MSG_DEL_IF:
652 ospf_apiclient_handle_del_if (oclient, msg);
653 break;
654 case MSG_ISM_CHANGE:
655 ospf_apiclient_handle_ism_change (oclient, msg);
656 break;
657 case MSG_NSM_CHANGE:
658 ospf_apiclient_handle_nsm_change (oclient, msg);
659 break;
660 case MSG_LSA_UPDATE_NOTIFY:
661 ospf_apiclient_handle_lsa_update (oclient, msg);
662 break;
663 case MSG_LSA_DELETE_NOTIFY:
664 ospf_apiclient_handle_lsa_delete (oclient, msg);
665 break;
666 default:
667 fprintf (stderr, "ospf_apiclient_read: Unknown message type: %d\n",
668 msg->hdr.msgtype);
669 break;
670 }
671 }
672
673 /* -----------------------------------------------------------
674 * Callback handler registration
675 * -----------------------------------------------------------
676 */
677
678 void
679 ospf_apiclient_register_callback (struct ospf_apiclient *oclient,
680 void (*ready_notify) (u_char lsa_type,
681 u_char opaque_type,
682 struct in_addr addr),
683 void (*new_if) (struct in_addr ifaddr,
684 struct in_addr area_id),
685 void (*del_if) (struct in_addr ifaddr),
686 void (*ism_change) (struct in_addr ifaddr,
687 struct in_addr area_id,
688 u_char status),
689 void (*nsm_change) (struct in_addr ifaddr,
690 struct in_addr nbraddr,
691 struct in_addr
692 router_id,
693 u_char status),
694 void (*update_notify) (struct in_addr
695 ifaddr,
696 struct in_addr
697 area_id,
698 u_char self_origin,
699 struct lsa_header *
700 lsa),
701 void (*delete_notify) (struct in_addr
702 ifaddr,
703 struct in_addr
704 area_id,
705 u_char self_origin,
706 struct lsa_header *
707 lsa))
708 {
709 assert (oclient);
710 assert (update_notify);
711
712 /* Register callback function */
713 oclient->ready_notify = ready_notify;
714 oclient->new_if = new_if;
715 oclient->del_if = del_if;
716 oclient->ism_change = ism_change;
717 oclient->nsm_change = nsm_change;
718 oclient->update_notify = update_notify;
719 oclient->delete_notify = delete_notify;
720 }
721
722 /* -----------------------------------------------------------
723 * Asynchronous message handling
724 * -----------------------------------------------------------
725 */
726
727 int
728 ospf_apiclient_handle_async (struct ospf_apiclient *oclient)
729 {
730 struct msg *msg;
731
732 /* Get a message */
733 msg = msg_read (oclient->fd_async);
734
735 if (!msg)
736 {
737 /* Connection broke down */
738 return -1;
739 }
740
741 /* Handle message */
742 ospf_apiclient_msghandle (oclient, msg);
743
744 /* Don't forget to free this message */
745 msg_free (msg);
746
747 return 0;
748 }