/*****************************************************************************

 @(#) $Id: mtp_route.c,v 0.7.2.9 2000/10/05 10:23:04 brian Exp $

 -----------------------------------------------------------------------------

 Copyright (C) 1997, 1998, 1999, 2000  Brian Bidulock <bidulock@dallas.net>

 All Rights Reserved.

 This program is free software; you can redistribute it and/or modify it under
 the terms of the GNU General Public License as published by the Free Software
 Foundation; either version 2 of the License, or (at your option) any later
 version.

 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 details.

 You should have received a copy of the GNU General Public License along with
 this program; if not, write to the Free Software Foundation, Inc., 675 Mass
 Ave, Cambridge, MA 02139, USA.

 Last Modified $Date: 2000/10/05 10:23:04 $ by $Author: brian $

 -----------------------------------------------------------------------------

 $Log: mtp_route.c,v $
 Revision 0.7.2.9  2000/10/05 10:23:04  brian
 Some work on link management and test.

 Revision 0.7.2.9  2000/10/03 00:53:54  brian
 Some work on link management and test.

 Revision 0.7.2.8  2000/09/21 05:19:07  brian
 Removed some leftover files.  Added changeback to mtp_sm.c.

 Revision 0.7.2.7  2000/09/18 09:36:54  brian
 Corrections to compile.

 Revision 0.7.2.6  2000/09/18 09:33:44  brian
 A bunch of work in state machines.

 Revision 0.7.2.5  2000/09/13 13:47:06  brian
 A lot of work in state machine: haven't check compile yet,
 just wanting to save work for later.

 Revision 0.7.2.4  2000/09/12 18:45:22  brian
 Lots of work within the MTP state machine files.

 Revision 0.7.2.3  2000/09/12 04:57:30  brian
 Still working on state machine: still compiles.

 Revision 0.7.2.2  2000/09/11 23:41:53  brian
 Working on MTP state machine: still compiles.

 Revision 0.7.2.1  2000/09/11 20:20:48  brian
 Cleaned up routing structure.

 Revision 0.7  2000/09/11 14:07:08  brian
 Added files for socket stuff and routing.

 Revision 0.7.2.1  2000/09/11 07:21:34  brian
 Doing a lot of work in socket code files.

 Revision 0.7  2000/09/07 11:12:13  brian
 Initial import of OpenSS7.

 Revision 1.1.1.1  2000/09/05 11:00:20  brian
 Initial import of new OpenSS7 stack for Linux.

 *****************************************************************************/

static char const ident[] = "$Name: Prerelease1 $($Revision: 0.7.2.9 $) $Date: 2000/10/05 10:23:04 $";

#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include "af_ss7.h"
#include "mtp_route.h"
#include "mtp_sm.h"
#include "../../include/linux/ss7link.h"

static struct ss7_routeset * ss7rtab[SS7RTAB_SIZE] = { NULL, };


/*
 *  Some support functions for deteling items in routing
 *  structures.
 */

struct ss7_routeset *ss7_all_routesets = NULL;
struct ss7_route    *ss7_all_routes    = NULL;
struct ss7_linkset  *ss7_all_linksets  = NULL;

/*
 *  Delete a route pair or regular route.  Route pairs are lined up in memory
 *  and are really a two element array of routes.
 */
static __inline void ss7_del_route(struct ss7_route *rt)
{
    static int recur = 0;
    struct ss7_route **r;
    if (!recur && rt->other && rt->other>rt) { recur++; ss7_del_route(rt->other); }
    for ( r = &ss7_all_routes; *r && *r != rt; r = &(*r)-> next ); *r = rt-> next;
    for ( r = &rt->rset      ; *r && *r != rt; r = &(*r)-> rset ); *r = rt-> rset;
    if (rt->routeset && rt->routeset->routes == rt) {
        if (!rt->rset || rt->rset == rt)
             rt->routeset->routes = NULL;
        else rt->routeset->routes = rt->rset;
    }
    r = &rt->lset;       while ( (*r) && (*r)!=rt ) { r = &(*r)->lset; } (*r)=rt->lset;
    if (rt->linkset && rt->linkset->routes == rt) {
        if (!rt->lset || rt->lset == rt)
             rt->linkset->routes = NULL;
        else rt->linkset->routes = rt->lset;
    }
    if (!recur && rt->other && rt->other<rt) { recur++; ss7_del_route(rt->other); }
    if (rt->other && rt->other>rt) kfree(rt);
    recur = 0;
}

static __inline void ss7_del_linkset(struct ss7_linkset *ls)
{
    struct ss7_linkset **s;
    s = &ss7_all_linksets; while ( (*s) && (*s)!=ls ) { s=&(*s)->next; } (*s)=ls->next;
    while (ls->routes) ss7_del_route(ls->routes);
    kfree(ls);
}

/*
 *  Returns a pointer to the pointer to the entry which has and address which
 *  matches `addr' and an address mask which matches `mask' and which has all
 *  the bits set in flags and type which are set in `flags' and `type'.  If it
 *  fails it returns a pointer to NULL at a location where it is possible to
 *  insert the a new item matching the criteria.
 */
static __inline struct ss7_routeset **ss7_rtab_get(__u32 addr, __u32 mask, __u8 flags, __u8 type)
{
    struct ss7_routeset **rs;
    for (   rs = &ss7rtab[(addr+(addr>>8)+(addr>>16))&0xff];
                (*rs) && (
                (*rs)->addr!=addr ||
                (*rs)->mask!=mask ||
                ( flags && ((*rs)->flags ^ flags) & flags ) ||
                ( flags && ((*rs)->type  ^ type ) & type  )  );
            rs=&(*rs)->collide
        );
    return rs;
}

/*
 *  Returns a pointer to the entry which has an address which matches `addr'
 *  and an address mask which matches `mask' and which has all the bits set in
 *  flags and type which are set in `flags' and `type'.  If no such entry
 *  exists, space is allocated for such an entry and the routeset entry is
 *  intialized and the pointer returned.  If no entry exists and memory
 *  allocation fails, it returns NULL, otherwise it always succeeds.
 */
static __inline struct ss7_routeset *ss7_rtab_add(struct ss7_addr saddr, __u32 mask, __u8 flags, __u8 type)
{
    __u32 addr = saddr.s_addr & mask;
    struct ss7_routeset **rs;
    if ( (rs = ss7_rtab_get(addr,mask,0,0)) != NULL ||
            (*rs = kmalloc(sizeof(**rs),GFP_ATOMIC)) )
    {
        struct ss7_routeset *r = *rs;
        memset(r,0x00,sizeof(*r));
        r->addr = addr;
        r->mask = mask;
        r->type = type;
        r->flags = flags|SS7_RS_INHIBITED; /* FIXME: not required */
        r->next = ss7_all_routesets;
        ss7_all_routesets = r;
    }
    return (*rs); /* NULL on failure */
}

/*
 *  Lookup a linkset by its local and adjacent routeset pointers and return a
 *  newly formed route attached to the remote routeset and the linkset.
 *  Normally called when receiving TFA for a new destination during MTP
 *  restart.  Returns NULL on failure (no linkset for loc/adj pair).
 */
extern __inline struct ss7_route *ss7_add_route(
    struct ss7_routeset *rem, struct ss7_routeset *loc, struct ss7_routeset *adj)
{
    struct ss7_route *r = NULL;
    struct ss7_linkset *ls;
    for (ls = ss7_all_linksets; ls; ls = ls->next)
        if ( ( ls->local == loc ) && ( ls->adjacent == adj ) )
            break;
    if (!ls) return r;
    if (ls->other) {  /* we really want a route pair */
        if (!(r = kmalloc(2*sizeof(*r),GFP_ATOMIC))) return r;
        memset(r,0x00,2*sizeof(*r));
        r->flags = SS7_RT_INHIBITED;
        r->priority = ls->other->type;
        r->linkset = ls->other;
        r->routeset = rem;
        r->rset = rem->routes; rem->routes = r;
        r->lset = ls->other->routes; ls->other->routes = r;
        r->other = r+1;
        r->other->other = r;
        r->next = ss7_all_routes;
        ss7_all_routes = r;
        r = r+1;
    } else {
        if (!(r = kmalloc(sizeof(*r),GFP_ATOMIC))) return r;
        memset(r,0x00,sizeof(*r));
    }
    {
        r->flags = SS7_RT_INHIBITED;
        r->priority = ls->type;
        r->linkset = ls;
        r->routeset = rem;
        r->rset = rem->routes; rem->routes = r;
        r->lset = ls->routes; ls->routes = r;
        r->next = ss7_all_routes;
        ss7_all_routes = r;
    }
    return r;
}

static __inline void ss7_del_routeset(struct ss7_routeset *rs)
{
    struct ss7_routeset **r;
    r = &ss7_all_routesets; while ( (*r) && (*r)!=rs ) { r = &(*r)->next; } (*r)=rs->next;
    while (rs->routes) ss7_del_route(rs->routes);
    /* remove from hash tables */
    r = ss7_rtab_get(rs->addr,rs->mask,0,0);
    if (r && (*r)) (*r)=rs->collide;
    kfree(rs);
}

extern __inline struct ss7_routeset *ss7_rt(struct ss7_addr saddr)
{
    struct ss7_routeset **rsp, *rt=NULL;
    
    if ( !(rsp = ss7_rtab_get(saddr.s_addr,SS7_RS_MASK_MEMBER ,0,0)) && !(rt = *rsp) )
    if ( !(rsp = ss7_rtab_get(saddr.s_addr,SS7_RS_MASK_CLUSTER,0,0)) && !(rt = *rsp) )
    if ( !(rsp = ss7_rtab_get(saddr.s_addr,SS7_RS_MASK_NETWORK,0,0)) && !(rt = *rsp) );
    
    return rt;
}

extern __inline struct ss7_routeset *ss7_rs_add(struct ss7_addr saddr, __u8 flags, __u8 type)
{
    __u32 mask;
    
    if ( (type & SS7_RST_SCOPE) == SS7_RST_MEMBER  ) { mask = SS7_RS_MASK_MEMBER ; } else
    if ( (type & SS7_RST_SCOPE) == SS7_RST_CLUSTER ) { mask = SS7_RS_MASK_CLUSTER; } else
    if ( (type & SS7_RST_SCOPE) == SS7_RST_NETWORK ) { mask = SS7_RS_MASK_NETWORK; }
    else {
        mask = SS7_RS_MASK_MEMBER;
        type = (type & ~SS7_RST_SCOPE) | SS7_RST_MEMBER;
    }
    return ss7_rtab_add(saddr,mask,flags,type);
}

extern __inline ss7_routeset *ss7_rt_type(struct ss7_addr saddr, __u8 flags, __u8 type)
{
    __u32 mask;
    struct ss7_routeset **rsp;
    
    if ( (type & SS7_RST_SCOPE) == SS7_RST_MEMBER  ) { mask = SS7_RS_MASK_MEMBER ; } else
    if ( (type & SS7_RST_SCOPE) == SS7_RST_CLUSTER ) { mask = SS7_RS_MASK_CLUSTER; } else
    if ( (type & SS7_RST_SCOPE) == SS7_RST_NETWORK ) { mask = SS7_RS_MASK_NETWORK; }
    else {
        mask = SS7_RS_MASK_MEMBER;
        type = (type & ~SS7_RST_SCOPE) | SS7_RST_MEMBER;
    }
    if ( !(rsp = ss7_rtab_get(saddr.s_addr,mask,flags,type)) )
        return NULL;
    return (*rsp);
}

/* used to bind sockets in af_ss7.c */
extern __inline unsigned  ss7_addr_type(struct ss7_addr saddr)
{
    struct ss7_routeset *rt = ss7_rt(saddr);

    if ( !rt ) return 0;
    return ( rt->type );
}

/*
 *  Limit ourselves from a routeset down to a route.  Route
 *  lists associated with routesets are sorted by priority,
 *  however, we will always shift from a congested route to
 *  a non-congested route regardless of priority.
 *
 *  This will find the highest priority usable route with
 *  the least congestion.  The selected route will not be
 *  via a restarting SP.
 *
 *  Caller must check for controlled reroute on RESTRICTED
 *  routes.
 */
extern __inline struct ss7_route *ss7_select_rt(struct ss7_routeset *rs)
{
    struct ss7_route *i;
    struct ss7_route *rl = rs->routes;
    for (i=rl;i;i=i->rset)
        if (!(i->flags&SS7_RT_DONTUSE))
            rl = i;
    if (rl && !(rl->flags&SS7_RT_DONTUSE)) return rl;
    return NULL;
}

/*
 *  Links selection method for selecting a link within a
 *  combined linkset on the basis of SLS as provided.  Does
 *  not update last in sequence to preserve loadsharing.
 */
extern __inline struct ss7_link *ss7_select_link(struct ss7_route *rt, unsigned char sls)
{
    unsigned set, lnk, i;
    struct ss7_linkset *ls1 = NULL, *ls0 = rt->linkset;
    struct ss7_link *l = NULL;

    if (rt->linkset->type==SS7_LS_TYPE_A) {
        set=(sls>>1)&0x1;
        lnk = ((sls>>1)&0x6)|(sls&0x1);
    }
    else {
        set=sls&0x1;
        lnk = (sls>>1);
    }
    if (ls0->other) { if (ls0->index^set) { ls0=ls0->other; ls1=ls0->other; } }

    if  (!l && ls0) { l = ls0->links[lnk];
        if (!l) for (i=0;i<8;i++) if ((l=ls0->links[lnk^i])) break; }
    if  (!l && ls1) { l = ls1->links[lnk];
        if (!l) for (i=0;i<8;i++) if ((l=ls1->links[lnk^i])) break; }

    return l;
}

/*
 *  Link selection method for loadsharing within a route
 *  (between links and linksets of a combined linkset).
 *  Selects the next linkset in a loadsharing pattern.
 */
extern __inline struct ss7_link *ss7_select_link_ls(struct ss7_route *rt)
{
    rt->last = (rt->last+1)&0xf;
    return ss7_select_link(rt,rt->last);
}

/*
 *  This function is responsible for adding or moving an
 *  existing link to a linkset based on its local point
 *  code, remote point code and SLS value.  This function is
 *  called at ioctl so it returns error codes.
 */
extern int ss7_move_link(struct ss7_link *link, struct ss7_routeset *local,
        struct ss7_routeset *adjacent, unsigned char sls)
{
    /*
     *  FIXME: make this function do something.  It must go
     *  searching for the adjacent point code in the local
     *  routeset's links looking for a match, and then
     *  checking sls position. If the link already has an
     *  SLS position it must move it, otherwise add it.
     */
    return -EINVAL;
}

/*
 *  Discovers what the address of the signalling point
 *  adjacent to the link that the device which generated
 *  this sk_buff is.  That is the adjacent signaling point
 *  code for the message.  Returns 0 or error.
 */
extern __inline __u32 ss7_get_adjacent(struct sk_buff *skb)
{
    struct ss7_iface *iface;
    struct ss7_link *link;

    if ( ( !skb ) || ( !skb->dev ) || ( !skb->dev->ss7_ptr ) )
        return (0); 
    iface = (struct ss7_iface *)skb->dev->ss7_ptr; 
    if ( iface->dev != skb->dev ) return (0); 
    link = &iface->link; 
    if ( !link->linkset || !link->linkset->adjacent ) return (0); 
    return (link->linkset->adjacent->addr);
}

void ss7_rtab_init(void)
{
    memset(&ss7rtab,0x0,sizeof(ss7rtab));
}


/*
 *
 *  SS7 MTP Level 3 Routing Tables and routines.
 *
 *  */

#if 0
static int ss7_add_route(ss7_address *address, struct device *dev);
static void ss7_remove_route(struct ss7_route *ss7_route);
static int ss7_del_route(ss7_address *address, struct device *dev);
static int ss7_route_device_down(struct device *dev);
static device *ss7_iface_get(char *devname);

/*
 *  Find a device given an SS7 address.
 */
struct device* ss7_get_route(ss7_address *addr);
#endif

/*
 *  Handle the ioctls that control the routing functions.
 */
int ss7_route_ioctl(unsigned int cmd, void *arg)
{
#if 0
    switch (cmd) {
        case MTPGROUTESET:
        case MTPGROUTE:
        case MTPGLINKSET:
        case MTPGRSF:
        case MTPGRTF:
        case MTPGLSF:
        case MTPSROUTESET:
        case MTPSROUTE:
        case MTPSLINKSET:
        case MTPSRSF:
        case MTPSRTF:
        case MTPSLSF:
        default:
#endif
            return -ENOIOCTLCMD;
#if 0
    }
#endif
};

#if 0
int ss7_routes_get_info(char *buffer, char **start, off_t offset, int length, int dummy);
#endif

/*
 *  Release all memory associated with SS7 routing
 *  structures
 */
void ss7_rtab_free(void)
{
    volatile struct ss7_routeset *rs;
    volatile struct ss7_linkset  *ls;

    while ((rs = ss7_all_routesets)) ss7_del_routeset((struct ss7_routeset *)rs);
    while ((ls = ss7_all_linksets )) ss7_del_linkset ((struct ss7_linkset  *)ls);
};

