OpenSS7
SS7 for the
Common Man

© Copyright 1997-2004,OpenSS7 Corporation, All Rights Reserved.
Last modified:

Home Overview Status News Documentation Resources About
   
 Overview
 Status
 News
 Documentation
 Resources
 About

   
Home Index Prev Next More Download Info FAQ Mail   Home -> Resources -> Browse Source -> strss7/drivers/m3ua/m3ua_sg.c


File /code/strss7/drivers/m3ua/m3ua_sg.c



static char const ident[] = "$Name:  $($Revision: 0.8.2.3 $) $Date: 2003/04/14 12:13:00 $";

#include <linux/config.h>
#include <linux/version.h>
#ifdef MODVERSIONS
#include <linux/modversions.h>
#endif
#include <linux/module.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/cmn_err.h>

#include "../debug.h"
#include "../bufq.h"

#include <ss7/m3ua.h>
#include <ss7/m3ua_ioctl.h>

#define M3UA_DESCRIP	"M3UA/SCTP STREAMS MULTIPLEXOR."
#define M3UA_COPYRIGHT	"Copyright (c) 2001 OpenSS7 Corp. All Rights Reserved."
#define M3UA_DEVICES	"Supports OpenSS7 drivers."
#define M3UA_CONTACT	"Brian Bidulock <bidulock@openss7.org>"
#define M3UA_LICENSE	"GPL"
#define M3UA_BANNER	M3UA_DESCRIP	"\n" \
			M3UA_COPYRIGHT	"\n" \
			M3UA_DEVICES	"\n" \
			M3UA_CONTACT	"\n"

#ifdef MODULE
MODULE_AUTHOR(M3UA_CONTACT);
MODULE_DESCRIPTION(M3UA_DESCRIP);
MODULE_SUPPORTED_DEVICE(M3UA_DEVICES);
#ifdef MODULE_LICENSE
MODULE_LICENSE(M3UA_LICENSE);
#endif
#define MODULE_STATIC static
#else
#define MOD_INC_USE_COUNT
#define MOD_DEC_USE_COUNT
#define MODULE_STATIC
#endif

#ifdef M3UA_DEBUG
static int m3ua_debug = M3UA_DEBUG;
#else
static int m3ua_debug = 2;
#endif

#define DEBUG_LEVEL m3ua_debug

#ifndef M3UA_CMAJOR
#define M3UA_CMAJOR 248
#endif
#define M3UA_NMINOR 255

/*
 *  =========================================================================
 *
 *  STREAMS Definitions
 *
 *  =========================================================================
 */

static struct module_info m3ua_minfo = {
	0,				/* Module ID number */
	"m3ua",				/* Module name */
	1,				/* Min packet size accepted */
	512,				/* Max packet size accepted */
	8 * 512,			/* Hi water mark */
	1 * 512				/* Lo water mark */
};

static void m3ua_rput(queue_t *, mblk_t *);
static void m3ua_rsrv(queue_t *);
static int m3ua_open(queue_t *, dev_t *, int, int, cred_t *);
static int m3ua_close(queue_t *, int, cred_t *);

static struct qinit m3ua_rinit = {
	m3ua_rput,			/* Read put (msg from below) */
	m3ua_rsrv,			/* Read queue service */
	m3ua_open,			/* Each open */
	m3ua_close,			/* Last close */
	NULL,				/* Admin (not used) */
	&m3ua_minfo,			/* Information */
	NULL				/* Statistics */
};

static void m3ua_wput(queue_t *, mblk_t *);
static void m3ua_wsrv(queue_t *);

static struct qinit m3ua_winit = {
	m3ua_wput,			/* Read put (msg from below) */
	m3ua_wsrv,			/* Read queue service */
	NULL,				/* Each open */
	NULL,				/* Last close */
	NULL,				/* Admin (not used) */
	&m3ua_minfo,			/* Information */
	NULL				/* Statistics */
};

static struct module_info t_minfo = {
	0,				/* Module ID number */
	"m3ua-T",			/* Module name */
	1,				/* Min packet size accepted */
	512,				/* Max packet size accepted */
	8 * 512,			/* Hi water mark */
	1 * 512				/* Lo water mark */
};

static void t_rput(queue_t *, mblk_t *);
static void t_rsrv(queue_t *);

static struct qinit t_rinit = {
	t_rput,				/* Write put (msg from above) */
	t_rsrv,				/* Write queue service */
	NULL,				/* Each open */
	NULL,				/* Last close */
	&t_minfo,			/* Information */
	NULL				/* Statistics */
};

static void t_wput(queue_t *, mblk_t *);
static void t_rput(queue_t *);

static struct qinit t_winit = {
	t_wput,				/* Write put (msg from above) */
	t_wsrv,				/* Write queue service */
	NULL,				/* Each open */
	NULL,				/* Last close */
	&t_minfo,			/* Information */
	NULL				/* Statistics */
};

MODULE_STATIC struct streamtab m3ua_info = {
	&m3ua_rinit,			/* Upper read queue */
	&m3ua_winit,			/* Upper write queue */
	&t_rinit,			/* Lower read queue */
	&t_winit			/* Lower write queue */
};

/*
 *  =========================================================================
 *
 *  PROTOCOL CONFIGURATION IOCTLs
 *
 *  =========================================================================
 */

/*
 *  =========================================================================
 *
 *  M3UA-Provider -> MTP-Provider Primitives (M_CTL, M_PROTO, M_PCPROTO
 *
 *  =========================================================================
 */

static int mtp_transfer_ind(m3ua_t * m3)
{
}

/*
 *  =========================================================================
 *
 *  M3UA-Provider -> T-Provider Primitives (M_CTL, M_PROTO, M_PCPROTO)
 *
 *  =========================================================================
 */

static int t_conn_req(t_t * t)
{
}
static int t_conn_res(t_t * t)
{
}
static int t_discon_req(t_t * t)
{
}
static int t_data_req(t_t * t)
{
}
static int t_exdata_req(t_t * t)
{
}
static int t_info_req(t_t * t)
{
}
static int t_bind_req(t_t * t)
{
}
static int t_unbind_req(t_t * t)
{
}
static int t_unitdata_req(t_t * t)
{
}
static int t_optmgmt_req(t_t * t)
{
}
static int t_ordrel_req(t_t * t)
{
}
static int t_addr_req(t_t * t)
{
}

/*
 *  =========================================================================
 *
 *  PROTOCOL STATE MACHINES
 *
 *  =========================================================================
 */

static int m3ua_error_ind(m3ua_t * m3)
{
}
static int m3ua_notify_ind(m3ua_t * m3)
{
}
static int m3ua_data_ind(m3ua_t * m3)
{
}
static int m3ua_duna_ind(m3ua_t * m3)
{
}
static int m3ua_dava_ind(m3ua_t * m3)
{
}
static int m3ua_duad_ind(m3ua_t * m3)
{
}
static int m3ua_scon_ind(m3ua_t * m3)
{
}
static int m3ua_dupu_ind(m3ua_t * m3)
{
}
static int m3ua_aspup_ind(m3ua_t * m3)
{
}
static int m3ua_aspdn_ind(m3ua_t * m3)
{
}
static int m3ua_asphb_ind(m3ua_t * m3)
{
}
static int m3ua_aspup_ack_ind(m3ua_t * m3)
{
}
static int m3ua_aspdn_ack_ind(m3ua_t * m3)
{
}
static int m3ua_asphb_ack_ind(m3ua_t * m3)
{
}
static int m3ua_aspac_ind(m3ua_t * m3)
{
}
static int m3ua_aspia_ind(m3ua_t * m3)
{
}
static int m3ua_aspac_ack_ind(m3ua_t * m3)
{
}
static int m3ua_aspia_ack_ind(m3ua_t * m3)
{
}

/*
 *  =========================================================================
 *
 *  MTP-Provider -> M3UA-Provider Primitives (M_CTL, M_PROTO, M_PCPROTO)
 *
 *  =========================================================================
 */

#define M3UA_DSTR_FIRST
#define M3UA_DSTR_LAST

static int mtp_transfer_req(m3ua_t * m3, mblk_t * mp)
{
	mtp_transfer_t *m = (mtp_transfer_ind_t *) mp->b_rptr;
	/* 
	 *  Find the appropriate AS using the routing information in the
	 *  request and select an available ASP.  Package up a DATA message
	 *  and send it to the ASP on the appropriate stream.
	 */
}
static int mtp_pause_req(m3ua_t * m3, mblk_t * mp)
{
	mtp_pause_t *m = (mtp_pause_ind_t *) mp->b_rptr;
}
static int mtp_resume_req(m3ua_t * m3, mblk_t * mp)
{
	mtp_resume_t *m = (mtp_resume_ind_t *) mp->b_rptr;
}
static int mtp_status_req(m3ua_t * m3, mblk_t * mp)
{
	mtp_status_t *m = (mtp_status_ind_t *) mp->b_rptr;
}
static int mtp_restart_complete_req(m3ua_t * m3, mblk_t * mp)
{
	mtp_restart_comp_t *m = (mtp_restart_comp_t *) mp->b_rptr;
}

static void

/*
 *  =========================================================================
 *
 *  T-Provider -> M3UA-Provider Primitives (M_CTL, M_PROTO, M_PCPROTO
 *
 *  =========================================================================
 */
#define M3UA_USTR_FIRST	    T_CONN_IND
#define M3UA_USTR_LAST	    T_ADDR_ACK
static void t_conn_ind(t_t * t, mblk_t * mp)
{
}
static void t_conn_con(t_t * t, mblk_t * mp)
{
}
static void t_discon_ind(t_t * t, mblk_t * mp)
{
}
static void t_data_ind(t_t * t, mblk_t * mp)
{
}
static void t_exdata_ind(t_t * t, mblk_t * mp)
{
}
static void t_info_ack(t_t * t, mblk_t * mp)
{
}
static void t_bind_ack(t_t * t, mblk_t * mp)
{
}
static void t_error_ack(t_t * t, mblk_t * mp)
{
}
static void t_ok_ack(t_t * t, mblk_t * mp)
{
}
static void t_unitdata_ind(t_t * t, mblk_t * mp)
{
}
static void t_uderror_ind(t_t * t, mblk_t * mp)
{
}
static void t_optmgmt_ack(t_t * t, mblk_t * mp)
{
}
static void t_nosup(t_t * t, mblk_t * mp)
{
}
static void t_addr_ack(t_t * t, mblk_t * mp)
{
}
static void (*m3ua_t_ops[]) (t_t *, mblk_t *) = {
	t_conn_ind,		/* T_CONN_IND */
	    t_conn_con,		/* T_CONN_CON */
	    t_discon_ind,	/* T_DISCON_IND */
	    t_data_ind,		/* T_DATA_IND */
	    t_exdata_ind,	/* T_EXDATA_IND */
	    t_info_ack,		/* T_INFO_ACK */
	    t_bind_ack,		/* T_BIND_ACK */
	    t_error_ack,	/* T_ERROR_ACK */
	    t_ok_ack,		/* T_OK_ACK */
	    t_unitdata_ind,	/* T_UNITDATA_IND */
	    t_uderror_ind,	/* T_UDERROR_IND */
	    t_optmgmt_ack,	/* T_OPTMGMT_ACK */
	    t_nosup,		/* T_ADDR_REQ */
	    t_addr_ack		/* T_ADDR_ACK */
}

/*
 *  =========================================================================
 *
 *  M_IOCTL handling
 *
 *  =========================================================================
 */

static int m3ua_do_ioctl(m3ua_t * m3, int cmd, void *arg)
{
	int nr = _IOC_NR(cmd);

	trace();
	if (_IOC_TYPE(cmd) == M3UA_IOC_MAGIC)
		if (M3UA_IOC_FIRST <= nr && nr <= M3UA_IOC_LAST)
			if (m3ua_ioc_ops[nr])
				return m3ua_ioc_ops[nr] (m3, cmd, arg);
	return -ENXIO;
}

#ifndef abs
#define abs(x) ((x)<0 ? -(x):(x))
#endif

static inline int m3ua_m_ioctl(queue_t * q, mblk_t * mp)
{
	t_t *t;
	m3ua_t *m3ua = (m3ua_t *) q->q_ptr;
	struct iocblk *iocp = (struct iocblk *) mp->b_rptr;
	void *arg = mp->b_cont ? mp->b_cont->b_rptr : NULL;
	int cmd = iocp->ioc_cmd, count = iocp->ioc_count;
	int ret = -EINVAL;
	struct linkblk *lp = (struct linkblk *) arg;

	trace();
	switch (cmd) {
	case I_LINK:
	case I_PLINK:
		/* 
		 *  NOTE: PLINK can be a bit different and could be rather useful.
		 *  A PLINKED SCTP stream will never be closed, even if the
		 *  configuration daemon crashes, even if the m3ua stream is
		 *  closed.  This permits more permanent M3UA configuration
		 *  structures.  The m3uad upon restart reopens the control
		 *  channel with root priviledges and can access the exisitng
		 *  configuration.
		 *
		 *  NOTE: do a MOD_INC_USE_COUNT when PLINKs are added and a
		 *  MOD_DEC_USE_COUNT when they are removed.  Otherwise the module
		 *  may autoclean.
		 */
		if (getminor(m3ua->devnum)) {
			ret = -EPERM;
			break;
		}
		if (!(t = kmalloc(sizeof(*t), GFP_KERNEL))) {
			ret = -ENOMEM;
			break;
		}
		bzero(t, sizeof(*t));

		t->next = m3ua->links;
		m3ua->links = t;
		t->linked = m3ua;
		t->muxid = lp->l_index;
		t->rq = RD(lp->l_qbot);
		t->rq->q_ptr = t;
		t->wq = WR(lp->l_qbot);
		t->wq->q_ptr = t;

		ret = 0;
		break;
	case I_UNLINK:
	case I_PUNLINK:
		if (getminor(m3ua->devnum)) {
			ret = -EPERM;
			break;
		}
		if (t->next)
			t->next->prev = t->prev;
		if (t->prev)
			t->prev->next = t->next;
		kfree(t);
		ret = 0;
		break;
	default:
		if (count >= _IOC_SIZE(cmd)) {
			ret = m3ua_do_ioctl(m3ua, cmd, arg);
		}
		if (abs(ret) == ENXIO) {
			if (q->q_next) {
				putnext(q, mp);
				return (0);
			}
		}
		break;
	}
	if (ret == 0) {
		mp->b_datap->db_type = M_IOCACK;
		iocp->ioc_error = 0;
		iocp->ioc_rval = 0;
	} else {
		mp->b_datap->db_type = M_IOCNAK;
		iocp->ioc_error = abs(ret);
		iocp->ioc_rval = -1;
	}
	qreply(q, mp);
	return (0);
}

/*
 *  =========================================================================
 *
 *  M_PROTO, M_PCPROTO handling
 *
 *  =========================================================================
 */

static inline int m3ua_m_proto(queue_t * q, mblk_t * mp)
{
	int err = EOPNOTSUPP;
	m3ua_t *m3ua = (m3ua_t *) q->q_ptr;
	int prim = ntohs(((struct m3ua_hdr *) (mp->b_rptr))->type);

	trace();
	if (M3UA_DSTR_FIRST <= prim && prim <= M3UA_DSTR_LAST) {
		(*m3ua_dstr_ops[M3UA_DSTR_FIRST - prim]) (m3ua, mp);
		return (0);
	}
	if (q->q_next) {
		putnext(q, mp);
		return (0);
	}
	return err;
}

static inline int t_m_proto(queue_t * q, mblk_t * mp)
{
	int err = EOPNOTSUPP;
	t_t *t = (t_t *) q->q_ptr;
	int prim = ((union T_primitives *) (mp->b_rptr))->PRIM_type;

	trace();
	if (M3UA_USTR_FIRST <= prim && prim <= M3UA_USTR_LAST) {
		(*m3ua_ustr_ops[M3UA_USTR_FIRST - prim]) (t, mp);
		return (0);
	}
	if (q->q_next) {
		putnext(q, mp);
		return (0);
	}
	return err;
}

/*
 *  =========================================================================
 *
 *  M_DATA handling
 *
 *  =========================================================================
 */

static inline int m3ua_m_data(queue_t * q, mblk_t * mp)
{
	m3ua_t *m3 = (m3ua_t *) q->q_ptr;
	trace();
	m3ua_data(m3, mp);
	return (0);
}

static inline int t_m_data(queue_t * q, mblk_t * mp)
{
	t_t *t = (t_t *) q->q_ptr;
	trace();
	t_data(t, mp);
	return (0);
}

/*
 *  =======================================================================
 *
 *  STREAMS QUEUE PUT and QUEUE SERVICE routines
 *
 *  =======================================================================
 */

/*
 *  ------------------------------------------
 *
 *  UPPER QUEUE PUT and QUEUE SERVICE routines
 *
 *  ------------------------------------------
 */

static void m3ua_wput(queue_t * q, mblk_t * mp)
{
	int err = EOPNOTSUPP;

	trace();
	if (q->q_count && mp->b_datap->db_type < QPCTL) {
		putq(q, mp);
		return;
	}
	switch (mp->b_datap->db_type) {
	case M_DATA:
		if ((err = m3ua_m_proto(q, mp)))
			break;
		return;
	case M_CTL:
	case M_PROTO:
	case M_PCPROTO:
		if ((err = m3ua_m_proto(q, mp)))
			break;
		return;
	case M_FLUSH:
		if (*mp->b_wptr & FLUSHW) {
			flushq(q, FLUSHALL);
			if (q->q_next) {
				putnext(q, mp);
				return;
			}
			*mp->b_rptr &= ~FLUSHW;
		}
		if (*mp->b_rptr & FLUSHR) {
			flushq(RD(q), FLUSHALL);
			qreply(q, mp);
		} else
			break;
		return;
	case M_IOCTL:
		if ((err = ls_m_ioctl(q, mp)))
			break;
		return;
	}
	switch (err) {
	case EAGAIN:
		if (mp->b_datap->db_type < QPCTL) {
			putq(q, mp);
			return;
		}
		break;
	case EOPNTOSUPP:
		if (q->q_next) {
			putnext(q, mp);
			return;
		}
	}
	trace();
	freemsg(mp);
	return;
}

static void m3ua_rsrv(queue_t * q)
{
	mblk_t *mp;
	int err = EOPNOTSUPP;

	trace();
	while ((mp = getq(q))) {
		if (mp->b_datap->db_type < QPCTL && !canputnext(q)) {
			putbq(q, mp);
			return;
		}
		switch (mp->b_datap->db_type) {
		case M_DATA:
			if ((err = m3ua_m_data(q, mp)))
				break;
			continue;
		case M_CTL:
		case M_PROTO:
		case M_PCPROTO:
			if ((err = m3ua_m_proto(q, mp)))
				break;
			continue;
		}
		switch (err) {
		case EAGAIN:
			if (mp->b_datap->db_type < QPCTL) {
				putbq(q, mp);
				return;
			}
			break;
		case EOPNOTSUPP:
			if (q->q_next) {
				putnext(q, mp);
				return;
			}
		}
		freemsg(mp);
	}
}

/*
 *  ------------------------------------------
 *
 *  LOWER QUEUE PUT and QUEUE SERVICE routines
 *
 *  ------------------------------------------
 */

static void t_wput(queue_t * q, mblk_t * mp)
{
	trace();
	if (mp->b_datap->db_type < QPCTL && (q->q_count || !canputnext(q)))
		putq(q, mp);
	else
		putnext(q, mp);
}

static void t_wsrv(queue_t * q)
{
	mblk_t *mp;

	trace();
	while ((mp = getq(q))) {
		if (mp->b_datap->db_type < QPCTL && !canputnext(q)) {
			putbq(q, mp);
			return;
		}
		putnext(q, mp);
	}
}

static void t_rput(queue_t * q, mblk_t * mp)
{
	int err = EOPNOTSUPP;

	trace();
	if (q->q_count && mp->b_datap->db_type < QPCTL) {
		putq(q, mp);
		return;
	}
	switch (mp->b_datap->db_type) {
	case M_DATA:
		if ((err = t_m_data(q, mp)))
			break;
		return;
	case M_CTL:
	case M_PROTO:
	case M_PCPROTO:
		if ((err = t_m_proto(q, mp)))
			break;
		return;
	}
	switch (err) {
	case EAGAIN:
		putq(q, mp);
		return;
	case EOPNOTSUPP:
		if (q->q_next) {
			putnext(q, mp);
			return;
		}
	}
	freemsg(mp);
}

static void t_rsrv(queue_t * q)
{
	mblk_t *mp;
	int err = EOPNOTSUPP;

	trace();
	while ((mp = getq(q))) {
		if (mp->b_datap->db_type < QPCTL && !canputnext(q)) {
			putbq(q, mp);
			return;
		}
		switch (mp->b_datap->db_type) {
		case M_DATA:
			if ((err = t_m_data(q, mp)))
				break;
			continue;
		case M_CTL:
		case M_PROTO:
		case M_PCPROTO:
			if ((err = t_m_proto(q, mp)))
				break;
			continue;
		}
		switch (err) {
		case EAGAIN:
			if (mp->b_datap->db_type < QPCTL) {
				putbq(q, mp);
				return;
			}
			break;
		case EOPNOTSUPP:
			putnext(q, mp);
			return;
		}
		freemsg(mp);
	}
}

/*
 *  =======================================================================
 *
 *  OPEN and CLOSE
 *
 *  =======================================================================
 */

static m3ua_t *m3ua_devices = NULL;
static queue_t *m3ua_ctrlq = NULL;

static int m3ua_open(queue_t * q, dev_t * devp, int flag, int sflag, cred_t * crp)
{
	m3ua_t *m3ua, **m3p = &m3ua_devices;
	int cmajor = getmajor(*devp);
	int cminor = getmajor(*devp);

	trace();
	if (q->q_ptr != NULL)
		return (0);

	if (sflag == MODOPEN || WR(q)->q_next)
		return EIO;

	if (!cminor) {
		if (!m3ua_ctrlq)
			m3ua_ctrlq = q;
		else
			sflag = CLONEOPEN;
	}

	if (sflag == CLONEOPEN)
		cminor = 1;

	for (; cminor <= M3UA_NMINOR && *m3p; m3p = &(*m3p)->next) {
		int dminor = getminor((*m3p)->devnum);
		if (cminor < dminor)
			break;
		if (cminor == dminor) {
			if (sflag == CLONEOPEN) {
				cminor++;
				continue;
			}
			return EIO;
		}
	}
	if (cminor > M3UA_MINOR)
		return ENXIO;

	*devp = makedevice(cmajor, cminor);

	if (!(m3ua = kmalloc(sizeof(*m3ua), GFP_KERNEL)))
		return ENOMEM;

	bzero(m3ua, sizeof(*m3ua));

	q->q_ptr = WR(q)->q_ptr;
	m3ua->rq = RD(q);
	m3ua->wq = WR(q);

	m3ua->state = FIXME;
	m3ua->devnum = *devp;

	return (0);
}

static int m3ua_close(queue_t * q, int flag, cred_t * crp)
{
	m3ua_t *m3 = (m3ua_t *) q->q_ptr;
	kfree(m3);
	return (0);
}

/*
 *  =======================================================================
 *
 *  LiS Module Initialization
 *
 *  =======================================================================
 */

static int m3ua_initialized = 0;

#ifndef LIS_REGISTERED
static inline void m3ua_init(void)
#else
__initfunc(void m3ua_init(void))
#endif
{
	if (m3ua_initialized)
		return;
	m3ua_initialized = 1;
	printk(KERN_INFO LS_BANNER);	/* console splash */
#ifndef LIS_REGISTERED
	if (lis_register_strdev(LS_CMAJOR, &m3ua_info, LS_NMINOR, m3ua_minfo.mi_idname) < 0) {
		cmn_err(CE_NOTE, "m3ua: couldn't register module\n");
		m3ua_minfo.mi_idnum = 0;
	}
	m3ua_minfo.mi_idnum = 1;
#endif
};

#ifndef LIS_REGISTERED
static inline void m3ua_terminate(void)
#else
__initfunc(void m3ua_terminate(void))
#endif
{
	if (!m3ua_initialized)
		return;
	m3ua_initialized = 0;
#ifndef LIS_REGSITERED
	if (m3ua_minfo.mi_idnum)
		if (lis_unregister_strdev(m3ua_minfo.mi_idnum) < 0) {
			cmn_err(CE_WARN, "m3ua: couldn't unregister module!\n");
		}
#endif
};

/*
 *  =======================================================================
 *
 *  Kernel Module Initialization
 *
 *  =======================================================================
 */

#ifdef MODULE
int init_module(void)
{
	trace();
	m3ua_init();
	return (0);
}

void cleanup_module(void)
{
	trace();
	m3ua_terminate();
	return;
}
#endif


Home Index Prev Next More Download Info FAQ Mail   Home -> Resources -> Browse Source -> strss7/drivers/m3ua/m3ua_sg.c

OpenSS7
SS7 for the
Common Man
Home Overview Status News Documentation Resources About

© Copyright 1997-2004,OpenSS7 Corporation, All Rights Reserved.
Last modified: