mirror of
https://gitlab.nic.cz/labs/bird.git
synced 2024-12-23 02:01:55 +00:00
528932368a
loss of my only neighbor.
601 lines
14 KiB
C
601 lines
14 KiB
C
/*
|
|
* BIRD -- OSPF Topological Database
|
|
*
|
|
* (c) 1999 Martin Mares <mj@ucw.cz>
|
|
* (c) 1999 - 2000 Ondrej Filip <feela@network.cz>
|
|
*
|
|
* Can be freely distributed and used under the terms of the GNU GPL.
|
|
*/
|
|
|
|
#include "nest/bird.h"
|
|
#include "lib/string.h"
|
|
|
|
#include "ospf.h"
|
|
|
|
#define HASH_DEF_ORDER 6 /* FIXME: Increase */
|
|
#define HASH_HI_MARK *4
|
|
#define HASH_HI_STEP 2
|
|
#define HASH_HI_MAX 16
|
|
#define HASH_LO_MARK /5
|
|
#define HASH_LO_STEP 2
|
|
#define HASH_LO_MIN 8
|
|
|
|
void *
|
|
originate_rt_lsa_body(struct ospf_area *oa, u16 *length, struct proto_ospf *p)
|
|
{
|
|
struct ospf_iface *ifa;
|
|
int j=0,k=0,v=0,e=0,b=0;
|
|
u16 i=0;
|
|
struct ospf_lsa_rt *rt;
|
|
struct ospf_lsa_rt_link *ln;
|
|
struct ospf_neighbor *neigh;
|
|
struct top_hash_entry *old;
|
|
struct proto_ospf *po=(struct proto_ospf *)p;
|
|
|
|
DBG("%s: Originating RT_lsa body for area \"%I\".\n", po->proto.name,
|
|
oa->areaid);
|
|
|
|
WALK_LIST (ifa, p->iface_list)
|
|
{
|
|
if((ifa->an==oa->areaid) && (ifa->state!=OSPF_IS_DOWN))
|
|
{
|
|
i++;
|
|
if(ifa->type==OSPF_IT_VLINK) v=1;
|
|
}
|
|
}
|
|
rt=mb_allocz(p->proto.pool, sizeof(struct ospf_lsa_rt)+
|
|
i*sizeof(struct ospf_lsa_rt_link));
|
|
if((p->areano>1) && (!oa->stub)) e=1;
|
|
rt->VEB=(v>>LSA_RT_V)+(e>>LSA_RT_E)+(b>>LSA_RT_B);
|
|
ln=(struct ospf_lsa_rt_link *)(rt+1);
|
|
|
|
WALK_LIST (ifa, p->iface_list)
|
|
{
|
|
if((ifa->an==oa->areaid) && (ifa->state!=OSPF_IS_DOWN))
|
|
{
|
|
if(ifa->state==OSPF_IS_LOOP)
|
|
{
|
|
ln->type=3;
|
|
ln->id=ipa_to_u32(ifa->iface->addr->ip);
|
|
ln->data=0xffffffff;
|
|
ln->metric=0;
|
|
ln->notos=0;
|
|
}
|
|
else
|
|
{
|
|
switch(ifa->type)
|
|
{
|
|
case OSPF_IT_PTP: /* rfc2328 - pg126 */
|
|
neigh=(struct ospf_neighbor *)HEAD(ifa->neigh_list);
|
|
if((neigh!=NULL) || (neigh->state==NEIGHBOR_FULL))
|
|
{
|
|
ln->type=LSART_PTP;
|
|
ln->id=neigh->rid;
|
|
ln->metric=ifa->cost;
|
|
ln->notos=0;
|
|
if(ifa->iface->flags && IA_UNNUMBERED)
|
|
{
|
|
ln->data=ifa->iface->index;
|
|
}
|
|
else
|
|
{
|
|
ln->id=ipa_to_u32(ifa->iface->addr->ip);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(ifa->state==OSPF_IS_PTP)
|
|
{
|
|
ln->type=LSART_STUB;
|
|
ln->id=ln->id=ipa_to_u32(ifa->iface->addr->opposite);
|
|
ln->metric=ifa->cost;
|
|
ln->notos=0;
|
|
ln->data=0xffffffff;
|
|
}
|
|
else
|
|
{
|
|
i--; /* No link added */
|
|
}
|
|
}
|
|
break;
|
|
case OSPF_IT_BCAST: /*FIXME Go on */
|
|
case OSPF_IT_NBMA:
|
|
if(ifa->state==OSPF_IS_WAITING)
|
|
{
|
|
ln->type=LSART_STUB;
|
|
ln->id=ipa_to_u32(ifa->iface->addr->prefix);
|
|
ln->data=ipa_to_u32(ipa_mkmask(ifa->iface->addr->pxlen));
|
|
ln->metric=ifa->cost;
|
|
ln->notos=0;
|
|
}
|
|
else
|
|
{
|
|
j=0,k=0;
|
|
WALK_LIST(neigh, ifa->neigh_list)
|
|
{
|
|
if((neigh->rid==ifa->drid) &&
|
|
(neigh->state==NEIGHBOR_FULL)) k=1;
|
|
if(neigh->state==NEIGHBOR_FULL) j=1;
|
|
}
|
|
if(((ifa->state==OSPF_IS_DR) && (j==1)) || (k==1))
|
|
{
|
|
ln->type=LSART_NET;
|
|
ln->id=ipa_to_u32(ifa->drip);
|
|
ln->data=ipa_to_u32(ifa->iface->addr->ip);
|
|
ln->metric=ifa->cost;
|
|
ln->notos=0;
|
|
}
|
|
else
|
|
{
|
|
ln->type=LSART_STUB;
|
|
ln->id=ipa_to_u32(ifa->iface->addr->prefix);
|
|
ln->data=ipa_to_u32(ipa_mkmask(ifa->iface->addr->pxlen));
|
|
ln->metric=ifa->cost;
|
|
ln->notos=0;
|
|
}
|
|
}
|
|
break;
|
|
case OSPF_IT_VLINK: /* FIXME Add virtual links! */
|
|
i--;
|
|
break;
|
|
}
|
|
}
|
|
if(ifa->type==OSPF_IT_VLINK) v=1;
|
|
}
|
|
ln=(ln+1);
|
|
}
|
|
rt->links=i;
|
|
*length=i*sizeof(struct ospf_lsa_rt_link)+sizeof(struct ospf_lsa_rt)+
|
|
sizeof(struct ospf_lsa_header);
|
|
return rt;
|
|
}
|
|
|
|
void
|
|
addifa_rtlsa(struct ospf_iface *ifa)
|
|
{
|
|
struct ospf_area *oa;
|
|
struct proto_ospf *po=ifa->proto;
|
|
u32 rtid;
|
|
struct top_graph_rtlsa_link *li, *lih;
|
|
|
|
rtid=po->proto.cf->global->router_id;
|
|
DBG("%s: New OSPF area \"%d\" adding.\n", po->proto.name, ifa->an);
|
|
oa=NULL;
|
|
|
|
|
|
WALK_LIST(NODE oa,po->area_list)
|
|
{
|
|
if(oa->areaid==ifa->an) break;
|
|
}
|
|
|
|
if(EMPTY_LIST(po->area_list) || (oa->areaid!=ifa->an)) /* New area */
|
|
{
|
|
struct ospf_lsa_header *lsa;
|
|
|
|
oa=mb_allocz(po->proto.pool, sizeof(struct ospf_area));
|
|
add_tail(&po->area_list,NODE oa);
|
|
oa->areaid=ifa->an;
|
|
oa->gr=ospf_top_new(po);
|
|
s_init_list(&(oa->lsal));
|
|
oa->rt=NULL;
|
|
oa->po=po;
|
|
oa->disp_timer=tm_new(po->proto.pool);
|
|
oa->disp_timer->data=oa;
|
|
oa->disp_timer->randomize=0;
|
|
oa->disp_timer->hook=area_disp;
|
|
oa->disp_timer->recurrent=DISPTICK;
|
|
oa->lage=now;
|
|
tm_start(oa->disp_timer,DISPTICK);
|
|
oa->calcrt=1;
|
|
oa->origrt=0;
|
|
fib_init(&oa->infib,po->proto.pool,sizeof(struct infib),16,init_infib);
|
|
/* FIXME 16?? (Oh, sweet 16.... :-) */
|
|
po->areano++;
|
|
DBG("%s: New OSPF area \"%d\" added.\n", po->proto.name, ifa->an);
|
|
}
|
|
|
|
ifa->oa=oa;
|
|
|
|
originate_rt_lsa(oa);
|
|
DBG("RT LSA: rt: %I, id: %I, type: %u\n",oa->rt->lsa.rt,oa->rt->lsa.id,oa->rt->lsa.type);
|
|
flood_lsa(NULL,NULL,&oa->rt->lsa,po,NULL,oa,1);
|
|
}
|
|
|
|
void
|
|
originate_rt_lsa(struct ospf_area *oa)
|
|
{
|
|
struct ospf_lsa_header lsa;
|
|
struct proto_ospf *po=oa->po;
|
|
u32 rtid=po->proto.cf->global->router_id;
|
|
struct top_hash_entry *en;
|
|
void *body;
|
|
|
|
debug("%s: Originating RT_lsa for area \"%I\".\n",po->proto.name,oa->areaid);
|
|
|
|
lsa.age=0;
|
|
lsa.id=rtid;
|
|
lsa.type=LSA_T_RT;
|
|
lsa.rt=rtid;
|
|
lsa.options=0;
|
|
if(oa->rt==NULL)
|
|
{
|
|
lsa.sn=LSA_INITSEQNO;
|
|
}
|
|
else
|
|
{
|
|
lsa.sn=oa->rt->lsa.sn+1;
|
|
}
|
|
body=originate_rt_lsa_body(oa, &lsa.length, po);
|
|
lsasum_calculate(&lsa,body,po);
|
|
en=lsa_install_new(&lsa, body, oa, &po->proto);
|
|
oa->rt=en;
|
|
flood_lsa(NULL,NULL,&oa->rt->lsa,po,NULL,oa,1);
|
|
}
|
|
|
|
void *
|
|
originate_net_lsa_body(struct ospf_iface *ifa, u16 *length,
|
|
struct proto_ospf *po)
|
|
{
|
|
u16 i=1;
|
|
struct ospf_neighbor *n;
|
|
u32 *body;
|
|
struct ospf_lsa_net *net;
|
|
|
|
net=mb_alloc(po->proto.pool,sizeof(u32)*(ifa->fadj+1)+
|
|
sizeof(struct ospf_lsa_net));
|
|
net->netmask=ipa_mkmask(ifa->iface->addr->pxlen);
|
|
|
|
body=(u32 *)(net+1);
|
|
i=1;
|
|
*body=po->proto.cf->global->router_id;
|
|
WALK_LIST(n,ifa->neigh_list)
|
|
{
|
|
if(n->state==NEIGHBOR_FULL)
|
|
{
|
|
*(body+i)=n->rid;
|
|
i++;
|
|
}
|
|
}
|
|
*length=i*sizeof(u32)+sizeof(struct ospf_lsa_header)+
|
|
sizeof(struct ospf_lsa_net);
|
|
return net;
|
|
}
|
|
|
|
void
|
|
originate_net_lsa(struct ospf_iface *ifa, struct proto_ospf *po)
|
|
{
|
|
struct ospf_lsa_header lsa;
|
|
u32 rtid=po->proto.cf->global->router_id;
|
|
struct top_hash_entry *en;
|
|
void *body;
|
|
|
|
debug("%s: Originating Net lsa for iface \"%s\".\n", po->proto.name,
|
|
ifa->iface->name);
|
|
|
|
if((ifa->state!=OSPF_IS_DR)||(ifa->fadj==0))
|
|
{
|
|
if(ifa->nlsa==NULL) return;
|
|
|
|
ifa->nlsa->lsa.sn+=1;
|
|
ifa->nlsa->lsa.age=LSA_MAXAGE;
|
|
flood_lsa(NULL,NULL,&ifa->nlsa->lsa,po,NULL,ifa->oa,0);
|
|
s_rem_node(SNODE ifa->nlsa);
|
|
ospf_hash_delete(ifa->oa->gr, ifa->nlsa);
|
|
schedule_rtcalc(ifa->oa);
|
|
ifa->nlsa=NULL;
|
|
return ;
|
|
}
|
|
|
|
lsa.age=0;
|
|
lsa.id=ipa_to_u32(ifa->iface->addr->ip);
|
|
lsa.type=LSA_T_NET;
|
|
lsa.rt=rtid;
|
|
lsa.options=0;
|
|
if(ifa->nlsa==NULL)
|
|
{
|
|
lsa.sn=LSA_INITSEQNO;
|
|
}
|
|
else
|
|
{
|
|
lsa.sn=ifa->nlsa->lsa.sn+1;
|
|
}
|
|
|
|
body=originate_net_lsa_body(ifa, &lsa.length, po);
|
|
lsasum_calculate(&lsa,body,po);
|
|
ifa->nlsa=lsa_install_new(&lsa, body, ifa->oa, &po->proto);
|
|
flood_lsa(NULL,NULL,&ifa->nlsa->lsa,po,NULL,ifa->oa,1);
|
|
}
|
|
|
|
static void *
|
|
originate_ext_lsa_body(net *n, rte *e, struct proto_ospf *po, struct ea_list *attrs)
|
|
{
|
|
struct proto *p=&po->proto;
|
|
struct ospf_lsa_ext *ext;
|
|
struct ospf_lsa_ext_tos *et;
|
|
neighbor *nn;
|
|
u32 m1 = ea_get_int(attrs, EA_OSPF_METRIC1, 0);
|
|
u32 m2 = ea_get_int(attrs, EA_OSPF_METRIC2, 0);
|
|
u32 tag = ea_get_int(attrs, EA_OSPF_TAG, 0);
|
|
int inas=0;
|
|
|
|
ext=mb_alloc(p->pool,sizeof(struct ospf_lsa_ext)+
|
|
sizeof(struct ospf_lsa_ext_tos));
|
|
ext->netmask=ipa_mkmask(n->n.pxlen);
|
|
|
|
et=(struct ospf_lsa_ext_tos *)(ext+1);
|
|
|
|
if(!m2)
|
|
{
|
|
et->etos=0;
|
|
et->metric=m1;
|
|
}
|
|
else
|
|
{
|
|
et->etos=0x80;
|
|
et->metric=m2;
|
|
}
|
|
et->padding=0;
|
|
et->tag=tag;
|
|
if(ipa_compare(e->attrs->gw,ipa_from_u32(0))!=0)
|
|
{
|
|
if(find_iface((struct proto_ospf *)p, e->attrs->iface)!=NULL) inas=1;
|
|
}
|
|
|
|
if(!inas) et->fwaddr= ipa_from_u32(0);
|
|
else et->fwaddr=e->attrs->gw;
|
|
return ext;
|
|
}
|
|
|
|
void
|
|
originate_ext_lsa(net *n, rte *e, struct proto_ospf *po, struct ea_list *attrs)
|
|
{
|
|
struct ospf_lsa_header lsa;
|
|
u32 rtid=po->proto.cf->global->router_id;
|
|
struct top_hash_entry *en=NULL;
|
|
void *body=NULL;
|
|
struct ospf_iface *ifa;
|
|
|
|
debug("%s: Originating Ext lsa for %I/%d.\n", po->proto.name, n->n.prefix,
|
|
n->n.pxlen);
|
|
|
|
lsa.age=0;
|
|
lsa.id=ipa_to_u32(n->n.prefix);
|
|
lsa.type=LSA_T_EXT;
|
|
lsa.rt=rtid;
|
|
lsa.sn=LSA_INITSEQNO;
|
|
body=originate_ext_lsa_body(n, e, po, attrs);
|
|
lsa.length=sizeof(struct ospf_lsa_ext)+sizeof(struct ospf_lsa_ext_tos)+
|
|
sizeof(struct ospf_lsa_header);
|
|
lsasum_calculate(&lsa,body,po);
|
|
WALK_LIST(ifa, po->iface_list)
|
|
{
|
|
en=lsa_install_new(&lsa, body, ifa->oa, &po->proto);
|
|
}
|
|
if(en==NULL) die("Some bug in Ext lsa generating\n");
|
|
flood_lsa(NULL,NULL,&en->lsa,po,NULL,ifa->oa,1);
|
|
}
|
|
|
|
|
|
static void
|
|
ospf_top_ht_alloc(struct top_graph *f)
|
|
{
|
|
f->hash_size = 1 << f->hash_order;
|
|
f->hash_mask = f->hash_size - 1;
|
|
if (f->hash_order > HASH_HI_MAX - HASH_HI_STEP)
|
|
f->hash_entries_max = ~0;
|
|
else
|
|
f->hash_entries_max = f->hash_size HASH_HI_MARK;
|
|
if (f->hash_order < HASH_LO_MIN + HASH_LO_STEP)
|
|
f->hash_entries_min = 0;
|
|
else
|
|
f->hash_entries_min = f->hash_size HASH_LO_MARK;
|
|
DBG("Allocating OSPF hash of order %d: %d hash_entries, %d low, %d high\n",
|
|
f->hash_order, f->hash_size, f->hash_entries_min, f->hash_entries_max);
|
|
f->hash_table = mb_alloc(f->pool, f->hash_size * sizeof(struct top_hash_entry *));
|
|
bzero(f->hash_table, f->hash_size * sizeof(struct top_hash_entry *));
|
|
}
|
|
|
|
static inline void
|
|
ospf_top_ht_free(struct top_hash_entry **h)
|
|
{
|
|
mb_free(h);
|
|
}
|
|
|
|
static inline u32
|
|
ospf_top_hash_u32(u32 a)
|
|
{
|
|
/* Shamelessly stolen from IP address hashing in ipv4.h */
|
|
a ^= a >> 16;
|
|
a ^= a << 10;
|
|
return a;
|
|
}
|
|
|
|
static inline unsigned
|
|
ospf_top_hash(struct top_graph *f, u32 lsaid, u32 rtrid, u32 type)
|
|
{
|
|
#if 1 /* Dirty patch to make rt table calculation work. */
|
|
return (ospf_top_hash_u32(lsaid) + ospf_top_hash_u32((type==2) ? lsaid : rtrid) + type) & f->hash_mask;
|
|
#else
|
|
return (ospf_top_hash_u32(lsaid) + ospf_top_hash_u32(rtrid) + type) & f->hash_mask;
|
|
#endif
|
|
}
|
|
|
|
struct top_graph *
|
|
ospf_top_new(struct proto_ospf *p)
|
|
{
|
|
struct top_graph *f;
|
|
|
|
f = mb_allocz(p->proto.pool, sizeof(struct top_graph));
|
|
f->pool = p->proto.pool;
|
|
f->hash_slab = sl_new(f->pool, sizeof(struct top_hash_entry));
|
|
f->hash_order = HASH_DEF_ORDER;
|
|
ospf_top_ht_alloc(f);
|
|
f->hash_entries = 0;
|
|
f->hash_entries_min = 0;
|
|
return f;
|
|
}
|
|
|
|
void
|
|
ospf_top_free(struct top_graph *f)
|
|
{
|
|
rfree(f->hash_slab);
|
|
ospf_top_ht_free(f->hash_table);
|
|
mb_free(f);
|
|
}
|
|
|
|
static void
|
|
ospf_top_rehash(struct top_graph *f, int step)
|
|
{
|
|
unsigned int oldn, oldh;
|
|
struct top_hash_entry **n, **oldt, **newt, *e, *x;
|
|
|
|
oldn = f->hash_size;
|
|
oldt = f->hash_table;
|
|
DBG("Re-hashing topology hash from order %d to %d\n", f->hash_order, f->hash_order+step);
|
|
f->hash_order += step;
|
|
ospf_top_ht_alloc(f);
|
|
newt = f->hash_table;
|
|
|
|
for(oldh=0; oldh < oldn; oldh++)
|
|
{
|
|
e = oldt[oldh];
|
|
while (e)
|
|
{
|
|
x = e->next;
|
|
n = newt + ospf_top_hash(f, e->lsa.id, e->lsa.rt, e->lsa.type);
|
|
e->next = *n;
|
|
*n = e;
|
|
e = x;
|
|
}
|
|
}
|
|
ospf_top_ht_free(oldt);
|
|
}
|
|
|
|
struct top_hash_entry *
|
|
ospf_hash_find_header(struct top_graph *f, struct ospf_lsa_header *h)
|
|
{
|
|
return ospf_hash_find(f,h->id,h->rt,h->type);
|
|
}
|
|
|
|
struct top_hash_entry *
|
|
ospf_hash_get_header(struct top_graph *f, struct ospf_lsa_header *h)
|
|
{
|
|
return ospf_hash_get(f,h->id,h->rt,h->type);
|
|
}
|
|
|
|
struct top_hash_entry *
|
|
ospf_hash_find(struct top_graph *f, u32 lsa, u32 rtr, u32 type)
|
|
{
|
|
struct top_hash_entry *e = f->hash_table[ospf_top_hash(f, lsa, rtr, type)];
|
|
|
|
#if 1
|
|
if(type==2 && lsa==rtr)
|
|
{
|
|
while (e && (e->lsa.id != lsa || e->lsa.type != 2 ))
|
|
e = e->next;
|
|
}
|
|
else
|
|
{
|
|
while (e && (e->lsa.id != lsa || e->lsa.type != type || e->lsa.rt != rtr))
|
|
e = e->next;
|
|
}
|
|
#else
|
|
while (e && (e->lsa.id != lsa || e->lsa.rt != rtr || e->lsa.type != type))
|
|
e = e->next;
|
|
#endif
|
|
return e;
|
|
}
|
|
|
|
struct top_hash_entry *
|
|
ospf_hash_get(struct top_graph *f, u32 lsa, u32 rtr, u32 type)
|
|
{
|
|
struct top_hash_entry **ee = f->hash_table + ospf_top_hash(f, lsa, rtr, type);
|
|
struct top_hash_entry *e = *ee;
|
|
|
|
while (e && (e->lsa.id != lsa || e->lsa.rt != rtr || e->lsa.type != type))
|
|
e = e->next;
|
|
if (e)
|
|
return e;
|
|
e = sl_alloc(f->hash_slab);
|
|
e->lsa.id = lsa;
|
|
e->lsa.rt = rtr;
|
|
e->lsa.type = type;
|
|
e->lsa_body = NULL;
|
|
e->nhi=NULL;
|
|
e->next=*ee; /* MJ you forgot this :-) */
|
|
*ee=e;
|
|
if (f->hash_entries++ > f->hash_entries_max)
|
|
ospf_top_rehash(f, HASH_HI_STEP);
|
|
return e;
|
|
}
|
|
|
|
void
|
|
ospf_hash_delete(struct top_graph *f, struct top_hash_entry *e)
|
|
{
|
|
unsigned int h = ospf_top_hash(f, e->lsa.id, e->lsa.rt, e->lsa.type);
|
|
struct top_hash_entry **ee = f->hash_table + h;
|
|
|
|
while (*ee)
|
|
{
|
|
if (*ee == e)
|
|
{
|
|
*ee = e->next;
|
|
sl_free(f->hash_slab, e);
|
|
if (f->hash_entries-- < f->hash_entries_min)
|
|
ospf_top_rehash(f, -HASH_LO_STEP);
|
|
return;
|
|
}
|
|
ee = &((*ee)->next);
|
|
}
|
|
bug("ospf_hash_delete() called for invalid node");
|
|
}
|
|
|
|
void
|
|
ospf_top_dump(struct top_graph *f)
|
|
{
|
|
unsigned int i;
|
|
debug("Hash entries: %d\n", f->hash_entries);
|
|
|
|
for(i=0; i<f->hash_size; i++)
|
|
{
|
|
struct top_hash_entry *e = f->hash_table[i];
|
|
while (e)
|
|
{
|
|
debug("\t%1x %8I %8I %4u 0x%08x\n", e->lsa.type, e->lsa.id,
|
|
e->lsa.rt, e->lsa.age, e->lsa.sn);
|
|
e = e->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* This is very uneficient, please don't call it often */
|
|
|
|
/* I should also test for every LSA if it's in some link state
|
|
* retransmision list for every neighbor. I will not test it.
|
|
* It can happen that I'll receive some strange ls ack's.
|
|
*/
|
|
|
|
int
|
|
can_flush_lsa(struct ospf_area *oa)
|
|
{
|
|
struct ospf_iface *ifa;
|
|
struct ospf_neighbor *n;
|
|
struct proto_ospf *po=oa->po;
|
|
|
|
WALK_LIST(ifa, iface_list)
|
|
{
|
|
if(ifa->oa==oa)
|
|
{
|
|
WALK_LIST(n, ifa->neigh_list)
|
|
{
|
|
if((n->state==NEIGHBOR_EXCHANGE)||(n->state==NEIGHBOR_LOADING))
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|