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

 @(#) $Id: ss7mtp.c,v 0.7.2.1 2000/09/11 07:21:34 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/09/11 07:21:34 $ by $Author: brian $

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

 $Log: ss7mtp.c,v $
 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.

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

/*
 *  This is the file for the MTP module.  This module is responsible for monitoring SS7
 *  link devices and attaching them to an MTP protocol state machine.  The MTP protocol
 *  module is the one interested in receiving device notifications from the links.  Not
 *  much can be done in the SS7 stack without an MTP level.  The MTP is also responsible
 *  for providing three socket interfaces to the user: SOCK_NTWK_ISEQ, SOCK_NTWK_NSEQ, and
 *  SOCK_RAW.  The primary address of the MTP protocol module is the SS7 Point Code (PC).
 *
 */

/*
 * need ss7_mtp_nseq_ops
 * need ss7_mtp_iseq_ops
 * need ss7_mtp_raw_ops
 */


static int ss7mtp_release(struct socket *sock, struct socket *peer);
static int ss7mtp_bind(struct socket *sock, struct sockaddr *umyaddr, int sockaddr_len);
static int ss7mtp_getname(struct socket *sock, struct sockaddr *uaddr, int *usockaddr_len, int peer);
static unsigned int ss7mtp_poll(struct file *file, struct socket *sock, struct poll_table_struct *wait);
static int ss7mtp_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg);
static int ss7mtp_setsockopt(struct socket *sock, int level, int optname, char *optval, int optlen);
static int ss7mtp_getsockopt(struct socket *sock, int level, int optname, char *optval, int *optlen);
static int ss7mtp_sendmsg(struct socket *sock, struct msghdr *m, int total_len, struct scm_cookie *scm);
static int ss7mtp_recvmsg(struct socket *sock, struct msghdr *m, int total_len, int flags, struct scm_cookie *scm);

static struct proto_ops ss7mtp_ops = {
    PF_SS7,
    sock_no_dup,
    ss7mtp_release,
    ss7mtp_bind,
    sock_no_connect,
    sock_no_socketpair,
    sock_no_accept,
    ss7mtp_getname,
    ss7mtp_poll,
    ss7mtp_ioctl,
    sock_no_listen,
    sock_no_shutdown,
    ss7mtp_setsockopt,
    ss7mtp_getsockopt,
    sock_no_fcntl,
    ss7mtp_sendmsg,
    ss7mtp_recvmsg
};

/*
 *  release = close
 *
 *  Results from a close I believe...
 */
static int ss7mtp_release(struct socket *sock, struct socket *peer) {
}
/*
 *  bind = attach the socket to an address/port
 *
 *  When an mtp socket binds, it binds to an mtp address (ss7 point code)
 *  and a user part.  What we need to do is identify the mtp control block
 *  associated with the ss7 point code and then attach this socket to its
 *  user part.  If the user part is already bound, we return an error.  If
 *  the ss7 point code is 0.0.0 then we bind to the user part on the
 *  default ss7 address (SS7ADDR_ANY).
 */
static int ss7mtp_bind(struct socket *sock, struct sockaddr *umyaddr, int sockaddr_len) {
}
/*
 *  getname = Look up the address of the socket.
 */
static int ss7mtp_getname(struct socket *sock, struct sockaddr *uaddr, int *usockaddr_len, int peer) {
}
/*
 *  poll - notify the caller when data is available
 */
static unsigned int ss7mtp_poll(struct file *file, struct socket *sock, struct poll_table_struct *wait) {
}
/*
 *  ioctl - perform ioctl on an ss7 socket
 */
static int ss7mtp_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) {
}
/*
 *  setsockopt = sets options at a number of level
 *
 *  We use this to control some of the options associated with a given mtp
 *  socket.  For example, we can make the mtp part not able to transfer
 *  messages or some other things.
 *
 */
static __inline int ss7mtp_route_setsockopt(struct sock *sk, int optname, char *optval, int optlen)
{
    return (-ENOPROTOPT);
}
static __inline int ss7mtp_gws_setsockopt(struct sock *sk, int optname, char *optval, int optlen)
{
    return (-ENOPROTOPT);
}
static int ss7mtp_setsockopt(struct sock *sk, int level, int optname, char *optval, int optlen)
{
    int val=0,err;

    /* get values from user */
    if (optlen>=sizeof(int)) {
        if (get_user(val, (int *)optval))
            return -EFAULT;
    } else if (optlen>=sizeof(char)) {
        unsigned char ucval;
        if (get_user(ucval, (unsigned char *)opval))
            return -EFAULT;
        val = (int)ucval;
    }

    if (level!=SOL_MTP)
        return -ENOPROTOOPT;

    /* these are mtp routing options */
    if (optname>=SS7RT_OPTS_BASE && optname<=SS7RT_OPTS_BASE+10) {
        return ss7mtp_route_setsockopt(sk,optname,optval,optlen);
    }
    /* these are gateway screening options */
    if (optname>=SS7GWS_OPTS_BASE && optname<=SS7GWS_OPTS_BASE+10) {
        return ss7mtp_gws_setsockopt(sk,optname,optval,optlen);
    }

    switch (optname) {
        /*
         *  This is where we set the options (from val and checking
         *  optlen).  There are not options just yet, so we'll deat with
         *  these later.
         */
        default:
            return (-ENOPROTOPT);
    }
}
/*
 *  getsockopt = gets options at a number of levels
 *
 *  Just the get equivalent of setsockopt.
 *
 */
static __inline int ss7mtp_route_getsockopt(struct sock *sk, int optname, char *optval, int *optlen)
{
    return (-ENOPROTOPT);
}
static __inline int ss7mtp_gws_getsockopt(struct sock *sk, int optname, char *optval, int *optlen)
{
    return (-ENOPROTOPT);
}
static int ss7mtp_getsockopt(struct sock *sk, int level, int optname, char *optval, int *optlen)
{
    int val,len;

    if (level!=SOL_MTP)
        return -ENOPROTOPT;

    /* these are mtp routing options */
    if (optname>=SS7RT_OPTS_BASE && optname<=SS7RT_OPTS_BASE+10) {
        return ss7mtp_route_getsockopt(sk,optname,optval,optlen);
    }
    /* these are gateway screening options */
    if (optname>=SS7GWS_OPTS_BASE && optname<=SS7GWS_OPTS_BASE+10) {
        return ss7mtp_gws_getsockopt(sk,optname,optval,optlen);
    }

    if (get_user(len,optlen))
        return -EFAULT;

    switch (optname) {
        /*
         *  This is where we get the options (into val and set len). There
         *  are not options just yet, so we'll deal with these later.
         */
        default:
            return (-ENOPROTOPT);
    }

    /* return values to user */
    if (len<sizeof(int) && len>0 && val>=0 && val<255) {
        unsigned char ucval = (unsigned char)val;
        len = 1;
        if (put_user(len,optlen))
            return -EFAULT;
        if (copy_to_user(optval,&ucval,1))
            return -EFAULT;
    } else {
        len = min(sizeof(int),len);
        if (put_user(len,optlen))
            return -EFAULT;
        if (copy_to_user(optval,&val,len))
            return -EFAULT;
    }
    return 0;
}
/*
 *  sendmsg = sends a message
 */
static int ss7mtp_sendmsg(struct socket *sock, struct msghdr *m, int total_len, struct scm_cookie *scm) {
}
/*
 *  recvmsg = receives a message
 */
static int ss7mtp_recvmsg(struct socket *sock, struct msghdr *m, int total_len, int flags, struct scm_cookie *scm) {
}

static struct proto_ops ss7_ops = {
    PF_SS7,
    ss7_dup,
    ss7_release,
    ss7_bind,
    ss7_connect,
    ss7_socketpair,
    ss7_accept,
    ss7_getname,
    ss7_poll,
    ss7_ioctl,
    ss7_listen,
    ss7_shutdown,
    ss7_setsockopt,
    ss7_getsockopt,
    ss7_fcntl,
    ss7_sendmsg,
    ss7_recvmsg
};

/*
 *  Someone brought the link into service.  We should automatically tell it to align and
 *  prepare to accept traffic.  Can't veto this action.
 */
int ss7_iface_event_up(struct device* device) {
    (void)device;
    return NETDEV_DONE; /* for now */
}

/*
 *  Someone brought the link down and out of servier.  We should automatically mark the
 *  link out of service and not send it any more traffic.
 */
int ss7_iface_event_down(struct device* device) {
    (void)device;
    return NETDEV_DONE; /* for now */
}

/*
 *  The device has rebooted. We should got through the out-of-service actions (pull
 *  transmission buffers from the interface associated with the device.)  And then attempt
 *  to place the link back into service by trying to bring the device up.
 */
int ss7_iface_event_reboot(struct device* device) {
    (void)device;
    return NETDEV_DONE; /* for now */
}

/*
 *  Something about the state of the device changed.
 */
int ss7_iface_event_change(struct device* device) {
    (void)device;
    return NETDEV_DONE; /* for now */
}

/*
 *  The device has registered.  That means that a driver has just been loaded and
 *  initialized agains the device.  We should go and configure the device and try to bring
 *  it up.
 */
int ss7_iface_event_register(sturct device* device) {
    (void)device;
    return NETDEV_DONE; /* for now */
}

/*
 *  The device has unregistered.  It's gone now.  Go through the out-of-service cycle for
 *  this device.
 */
int ss7_iface_event_unregister(sturct device* device) {
    (void)device;
    return NETDEV_DONE; /* for now */
}

/*
 *  Devices should not change their name or their MTUs.
 */
int ss7_iface_event_changemtu(sturct device* device) {
    (void)device;
    return NETDEV_BAD; /* for now */
}

/*
 *  The device is having its address changed.  We must take the device out of service in
 *  its present context and then bring it back into service in the new context.  We could
 *  return NOTIFY_BAD if the address is invalid.
 */
int ss7_iface_event_changeaddr(sturct device* device) {
    (void)device;
    return NETDEV_DONE; /* for now */
}

/*
 *  The device is going down, its loosing it.  Perform the out-of-service cycle on this
 *  device and its data structures.
 */
int ss7_iface_event_goingdown(sturct device* device) {
    (void)device;
    return NETDEV_DONE; /* for now */
}

/*
 *  Devices should not change their name or their MTUs.
 */
int ss7_iface_event_changename(sturct device* device) {
    (void)device;
    return NETDEV_BAD; /* for now */
}

static int ss7_iface_event_done(struct device* device)
{
    (void)msg;
    (void)device;
    return NETDEV_DONE;
}

static int ss7_iface_event_bad(struct device* device)
{
    (void)msg;
    (void)device;
    return NETDEV_DONE;
}

static int (*)(struct device*) ss7_iface_event_funcs[16] = {
    &ss7_iface_event_done, &ss7_iface_event_done, &ss7_iface_event_done, &ss7_iface_event_done,
    &ss7_iface_event_done, &ss7_iface_event_done, &ss7_iface_event_done, &ss7_iface_event_done,
    &ss7_iface_event_done, &ss7_iface_event_done, &ss7_iface_event_done, &ss7_iface_event_done,
    &ss7_iface_event_done, &ss7_iface_event_done, &ss7_iface_event_done, &ss7_iface_event_done
};

static void ss7_iface_event_init(void) {
    ss7_iface_event_funcs[NETDEV_UP        &15] = &ss7_iface_event_up;
    ss7_iface_event_funcs[NETDEV_DOWN      &15] = &ss7_iface_event_down;
    ss7_iface_event_funcs[NETDEV_REBOOT    &15] = &ss7_iface_event_reboot;
    ss7_iface_event_funcs[NETDEV_CHANGE    &15] = &ss7_iface_event_change;
    ss7_iface_event_funcs[NETDEV_REGISTER  &15] = &ss7_iface_event_register;
    ss7_iface_event_funcs[NETDEV_UNREGISTER&15] = &ss7_iface_event_unregister;
    ss7_iface_event_funcs[NETDEV_CHANGEMTU &15] = &ss7_iface_event_changemtu;
    ss7_iface_event_funcs[NETDEV_CHANGEADDR&15] = &ss7_iface_event_changeaddr;
    ss7_iface_event_funcs[NETDEV_GOING_DOWN&15] = &ss7_iface_event_goingdown;
    ss7_iface_event_funcs[NETDEV_CHANGENAME&15] = &ss7_iface_event_changename;
};

static int ss7_device_event(struct notifier_block *this, unsigned long msg, void *data)
{
    struct device *dev = (struct device *)data;
    /*
     *  FIrst determine whether we actually have an SS7 device, so we have defined the
     *  dummy ARP Hardware type APRHRD_SS7 in <linux/if_arp.h>.  This brings up an
     *  interesting concept: it could be possible to define an ARP procedures for SS7 over
     *  Ethernet whereby we find the MAC address by broadcasting and resolving on the SS7
     *  point code.
     */
    if (dev->type != ARPHRD_SS7)
        return NOTIFY_DONE;
    if (msg&~15)
        return NETDEV_DONE;
    /*
     *  Invoke the appropriate handler.
     */
    return (*ss7_iface_event_funcs[msg&15])(msg,dev);
};

static struct notifier_block ss7_netdevice_notifier =
{
    ss7_device_event,
    NULL,
    0
};

#ifdef MODULE
void cleanup_module(void)
{
#ifdef CONFIG_PROC_FS
        proc_net_unregister(PROC_NET_SS7);
#endif

        /* do some groovy cleanup functions, free up stuff */

#ifdef CONFIG_SYSCTL
        ss7_unregister_sysctl();
#endif

        unregister_netdevice_notifier(&ss7_netdevice_notifier);

        ss7_packet_type.type = htons(ETH_P_SS7);
        dev_remove_pack(&ss7_packet_type);

        sock_unregister(ss7_family_ops.family);

}

int init_module(void)
#else
__initfunc(void ss7_proto_init(struct net_proto* pro))
#endif
{
        spin_lock_init(&ss7_ifacelist_lock);
        (void)ss7_ifacelist_lock;

        sock_register(&ss7_family_ops);

        ss7_iface_event_init();
        ss7_packet_type.type = htons(ETH_P_SS7);
        dev_add_pack(&ss7_packet_type);

        register_netdevice_notifier(&ss7_netdev_notifier);

#ifdef CONFIG_SYSCTL
        ss7_register_sysctl();
#endif

#ifdef CONFIG_PROC_FS
        proc_net_register(&proc_ss7);   /* register others on their own time! */
#endif

        printk(KERN_INFO "NET4: SS7 for Linux. $Revision: 0.7.2.1 $ for Linux NET4.0\n");
        /* do some groovy initializations here */
#ifdef MODULE
        return 0;
#endif
}

#endif /* defined CONFIG_SS7 */


