/************************************************************************/
/* SISCO SOFTWARE MODULE HEADER *****************************************/
/************************************************************************/
/*	(c) Copyright Systems Integration Specialists Company, Inc.,	*/
/*	1997-2005 All Rights Reserved					*/
/*									*/
/* MODULE NAME : tp0main.c						*/
/* PRODUCT(S)  : Lean-T Stack						*/
/*									*/
/* MODULE DESCRIPTION :							*/
/*	Main processsing functions for TP0.				*/
/*									*/
/* GLOBAL FUNCTIONS DEFINED IN THIS MODULE :				*/
/*	tp0_initialize ()						*/
/*	tp0_terminate ()						*/
/*	tp0_bind ()							*/
/*	tp0_unbind ()							*/
/*	tp0_session_timer ()						*/
/*	tp0_timer_tick ()						*/
/*	tp0_buffers_avail ()						*/
/*	tp0_event ()							*/
/*	tp0_data ()							*/
/*	tp0_disconnect ()						*/
/*	tp0_process_dt ()						*/
/*	tp0_process_dr ()						*/
/*									*/
/* MODIFICATION LOG :							*/
/*  Date     Who   Rev     Comments					*/
/* --------  ---  ------   -------------------------------------------  */
/* 07/30/08  JRB    27     Use DR reason codes valid for TP0.		*/
/* 06/23/06  JRB    26     Fix calc_loc_ref problem (conn_id wrong type)*/
/* 06/06/05  EJV    25     MMSEASE_MOSI: add rfc1006_listener_* funcs.	*/
/* 04/06/05  EJV    24     Added MMSEASE_MOSI support.			*/
/*			   tp0_terminate: check for valid tp0_conn_arr.	*/
/* 03/11/05  JRB    23     Ignore DT.ind or DR.ind if waiting for CR.ind*/
/* 02/22/05  JRB    22     tp0_data: fix return value.			*/
/* 01/24/05  JRB    21     Move tp4_debug_sel & "*_logstr" to lean_var.c*/
/* 09/17/04  JRB    20     tp0_session_timer: chk state only if starting*/
/*			   Use session_cfg.disconnect_timeout.		*/
/* 01/13/04  EJV    19     tp0_initialize: set tp0_cfg.max_spdu_outst.	*/
/*			   Changed tp0_send_... funcs to return value.	*/
/* 			   np_data_req: added eot param.		*/
/* 04/07/03  JRB    18     Add SPDU_RX_NO_PREALLOCATE option.		*/
/* 07/10/02  JRB    17     tp0_buffers_avail return INT_MAX (no limit).	*/
/* 06/20/02  JRB    16     Add tp0_convert_ip function.			*/
/* 05/08/02  JRB    15     Add tp0_connect_outstanding_count function.	*/
/* 01/22/02  JRB    14     Add max_conns arg to tp0_initialize.		*/
/* 05/25/01  JRB    13     Fix thisFile.. warning.			*/
/* 03/15/00  JRB    12     Alloc tp_conn->spdu_rx.spdu_ptr once at	*/
/*			   startup for each conn. Free before exit.	*/
/* 03/15/00  JRB    11     If spdu_rx exceeds max, disconnect.		*/
/* 01/05/01  EJV    10     tp0_initialize: corrected logging.		*/
/* 03/14/00  JRB    09     tp0_terminate: fake DR.ind so stack cleans up*/
/* 09/13/99  MDE    08     Added SD_CONST modifiers			*/
/* 06/08/99  JRB    07     Chg to support 1024 connections.		*/
/* 08/13/98  JRB    06     Lint cleanup.				*/
/* 03/23/98  NAV    05     Free g_tpkt_enc_buf on termination		*/
/* 02/16/98  JRB    04     Cast args.					*/
/* 09/25/97  JRB    03     Don't generate exception for invalid state.	*/
/* 09/23/97  JRB    02     tp0_terminate: disconnect all TP0 conns.	*/
/* 08/01/97  JRB    01     Created (compatible with MMS-EASE 7.0).	*/
/************************************************************************/
#include "glbtypes.h"
#include "sysincs.h"
#include "mem_chk.h"
#include "tp4api.h"		/* User definitions for tp4	*/
#include "tp4.h"		/* Internal definitions for tp4	*/
#include "tp4_encd.h"
#include "tp4_log.h"

#ifdef DEBUG_SISCO
SD_CONST static ST_CHAR *SD_CONST thisFileName = __FILE__;
#endif
/************************************************************************/
/* Local function Prototypes						*/
/************************************************************************/

/************************************************************************/
/* Global Configuration Structure					*/
/* This structure must be filled in by user BEFORE tp0_initialize ().	*/
/* It MUST NOT change after tp0_initialize ().				*/
/* DEBUG: should tp0_initialize make it's own copy of this struct	*/
/*        so that the user CAN NOT screw it up?????			*/
/************************************************************************/
TP0_CFG tp0_cfg;

/************************************************************************/
/* Global Variables initialized by tp0_initialize ().			*/
/* Not accessible to user.						*/
/************************************************************************/
TP0_CONN *tp0_conn_arr;	/* ptr to array of "max_num_conns" structs	*/
ST_UCHAR *g_tpkt_enc_buf;		/* Buffer for encoding TPKTs.	*/
static ST_UINT g_tp0_max_spdu_len;	/* Max SPDU size to send or receive	*/

/************************************************************************/
/* Global Variables initialized by tp0_bind ().				*/
/* Not accessible to user.						*/
/************************************************************************/
#if defined(MMSEASE_MOSI)
/* multiple binds allowed, tp0_bind_id = index into the bind_tsel_tbl  */
typedef struct tagBIND_TSEL
  {
  ST_LONG   user_bind_id;		/* = session bind id		*/
  ST_UCHAR  tsel [1+MAX_TSEL_LEN];	/* Local len & TSEL		*/
  } BIND_TSEL;

ST_UINT    bind_tsel_tbl_max;		/* for now use max_conns	*/
BIND_TSEL *bind_tsel_tbl;		/* entry 0 unused! 		*/
#else  /* !defined(MMSEASE_MOSI) */
/* DEBUG: should we check tp0_bind_called before any calls to user?	*/
static ST_INT tp0_bind_called;	/* Flag to prevent multiple bind calls.	*/
/* CRITICAL: only_tp0_bind_id must be different from only_tp4_bind_id,	*/
/*           because either may be passed up to user in tp4_bind_cnf.	*/
ST_LONG only_tp0_bind_id = 199;		/* Only one tp0_bind_id.	*/
ST_LONG only_tp0_user_bind_id;		/* Only one user_bind_id.	*/
ST_UCHAR only_tp0_loc_tsap [1+MAX_TSEL_LEN];	/* Local len & TSAP addr*/
#endif  /* !defined(MMSEASE_MOSI) */

/*======================================================================*/
/*======================================================================*/
/* INITIALIZATION AND TERMINATION FUNCTIONS				*/
/*======================================================================*/
/*======================================================================*/

/************************************************************************/
/* tp0_initialize							*/
/* Initialize the TP0 service provider.					*/
/* NOTE:  the configuration parameters in the "tp0_cfg" struct must be	*/
/*        set BEFORE calling this function.				*/
/* WARNING: tp0_cfg MUST NOT be modified after this function called.	*/
/* NOTE:  the value of tp0_cfg.max_num_conns is overwritten with the	*/
/* 	  value passed in the argument max_conns.			*/
/* Return:								*/
/*	SD_SUCCESS or							*/
/*	error code (TP4E_* or error code returned from np_init)		*/
/************************************************************************/
ST_RET tp0_initialize (ST_UINT max_conns, ST_UINT max_spdu_len)
  {
ST_RET errcode;
ST_UINT16 max_tpkt_len;			/* Max len of RFC1006 TPKT.	*/
ST_UINT j;

  if (max_conns > TP_MAX_NUM_CONNS)
    {
    TP_LOG_ERR1 ("TP-ERROR: number of requested TCP connections (%d) exceeds max",
                 max_conns);
    return (TP4E_INVAL_NUM_CONNS);
    }
  tp0_cfg.max_num_conns = max_conns;	/* overwrite configured value	*/

#if !defined (TP4_ENABLED)	/* Otherwise, done in tp4_initialize	*/
#if defined(DEBUG_SISCO)
  tp4_debug_sel |= TP4_LOG_ERR;		/* Always enable error logging.	*/
#endif
  tp4_init_timer ();
#endif	/* !TP4_ENABLED	*/

  /* Save arg in global for later use.		*/
  g_tp0_max_spdu_len = max_spdu_len;

  /* Check that all configured parameter values are legal.		*/

  /* init the max number of SPDUs that can be queued in gensock2	*/
  if (tp0_cfg.max_spdu_outst == 0)
    tp0_cfg.max_spdu_outst = 50;

  /* Check max_tpdu_len. If legal, save binary encoding in global NOW.	*/
  switch (tp0_cfg.max_tpdu_len)
    {					/* code for PDU size	*/
    case   128:	tp0_cfg.max_tpdu_len_enc = TP_PDU_MAX_SIZE_128  ; break;
    case   256:	tp0_cfg.max_tpdu_len_enc = TP_PDU_MAX_SIZE_256  ; break;
    case   512:	tp0_cfg.max_tpdu_len_enc = TP_PDU_MAX_SIZE_512  ; break;
    case  1024:	tp0_cfg.max_tpdu_len_enc = TP_PDU_MAX_SIZE_1024 ; break;
    case  2048:	tp0_cfg.max_tpdu_len_enc = TP_PDU_MAX_SIZE_2048 ; break;
    case  4096:	tp0_cfg.max_tpdu_len_enc = TP_PDU_MAX_SIZE_4096 ; break;
    case  8192:	tp0_cfg.max_tpdu_len_enc = TP_PDU_MAX_SIZE_8192 ; break;
    case 65531:	tp0_cfg.max_tpdu_len_enc = TP_PDU_MAX_SIZE_65531; break;
    default:
      TP_LOG_ERR1 ("TP-ERROR: configured rfc1006_max_tpdu_len (%d) illegal",
                 tp0_cfg.max_tpdu_len);
      return (TP4E_INVAL_TPDU_LEN);
    }

  /* Compute Max TPKT len & allocate TPKT encode buffer.	*/
  max_tpkt_len = tp0_cfg.max_tpdu_len + RFC1006_HEAD_LEN;
  g_tpkt_enc_buf = (ST_UCHAR *) M_MALLOC (MSMEM_STARTUP, max_tpkt_len);

  /* Initialize the Network layer (i.e. RFC1006).			*/




  if ((errcode = np_init (max_conns)) != SD_SUCCESS)
    return (errcode);

  tp0_conn_arr = (TP0_CONN *) M_CALLOC (MSMEM_STARTUP, max_conns, sizeof (TP0_CONN));
  for (j=0; j<max_conns; j++)
    {
#if !defined(SPDU_RX_NO_PREALLOCATE)
    tp0_conn_arr [j].spdu_rx.spdu_ptr = (ST_CHAR *) M_MALLOC (MSMEM_STARTUP, g_tp0_max_spdu_len);
#endif
    }

  return (SD_SUCCESS);
  }

/************************************************************************/
/* tp0_terminate							*/
/************************************************************************/
ST_RET tp0_terminate (ST_VOID)
  {
ST_RET ret = SD_SUCCESS;
ST_UINT j;

  if (tp0_conn_arr)
    {
    for (j = 0;  j < tp0_cfg.max_num_conns;  j++)
      {
      if (tp0_conn_arr [j].state != TP_STATE_CLOSED)
        {
        /* Fake disconnect indication so Upper Layers can clean up.	*/
        tp4_disconnect_ind (tp0_conn_arr[j].user_conn_id, TP_DR_NO_REASON,
                            0, NULL);
        /* Send disconnect request.					*/
        tp0_disconnect (j + MIN_TP0_CONN_ID, 0, NULL);
        }
#if !defined(SPDU_RX_NO_PREALLOCATE)
      M_FREE (MSMEM_STARTUP, tp0_conn_arr [j].spdu_rx.spdu_ptr);
#endif
      }
    M_FREE (MSMEM_STARTUP, tp0_conn_arr);
    tp0_conn_arr = NULL;
    }
  if (g_tpkt_enc_buf)
    M_FREE (MSMEM_STARTUP, g_tpkt_enc_buf);

  ret = np_end ();
  return (ret);
  }
  
#if defined(MMSEASE_MOSI)
/************************************************************************/
/*			rfc1006_listener_bind				*/
/* Function to pass TSAP to rfc1006_listener task through UNIX domain	*/
/* socket. Waits for response if TSAP bind successful.			*/
/************************************************************************/
ST_RET rfc1006_listener_bind (ST_UCHAR *tsap)
  {
ST_UINT         tsap_len = (ST_UINT) ((ST_UINT8) tsap [0]+1);
RFC1006_IPC_MSG ipc_msg;
ST_INT          count;
char            resp;

  ipc_msg.magic = RFC1006_IPC_MAGIC_NUM;
  ipc_msg.opcode = RFC1006_IPC_OP_BIND;
  ipc_msg.data_len = tsap_len;

  /* send the BIND msg header */
  count = sendwait (domsock_listener, &ipc_msg, sizeof (RFC1006_IPC_MSG), 0);
  if (count != sizeof (RFC1006_IPC_MSG))
    {
    TP_LOG_ERR0  ("TP-ERROR: rfc1006_listener_bind: send BIND header failed.");
    return (SD_FAILURE);
    }

  /* send the TSAP to bind */
  count = sendwait (domsock_listener, tsap, tsap_len, 0);
  if (count != tsap_len)
    {
    TP_LOG_ERR0  ("TP-ERROR: rfc1006_listener_bind: send BIND TSAP failed.");
    return (SD_FAILURE);
    }

  /* Wait for BIND response, keep trying to read until success.	*/
  count = recvwait (domsock_listener, &ipc_msg, sizeof (RFC1006_IPC_MSG), 0);
  if (count != sizeof (RFC1006_IPC_MSG))
    {
    TP_LOG_ERR0  ("TP-ERROR: rfc1006_listener_bind: failed to receive BIND response")
    return (SD_FAILURE);
    }
  if (ipc_msg.magic != RFC1006_IPC_MAGIC_NUM)
    {
    TP_LOG_ERR1  ("TP-ERROR: rfc1006_listener_bind: received invalid magic='%X'", ipc_msg.magic);
    return (SD_FAILURE);
    }
  if (ipc_msg.opcode != RFC1006_IPC_OP_BIND)
    {
    TP_LOG_ERR1  ("TP-ERROR: rfc1006_listener_bind: received invalid opcode=%u", (ST_UINT) ipc_msg.opcode);
    return (SD_FAILURE);
    }
  if (ipc_msg.data_len != 1)
    {
    TP_LOG_ERR1  ("TP-ERROR: rfc1006_listener_bind: received invalid data_len=%u (expecting 1 byte)",
                  (ST_UINT) ipc_msg.data_len);
    return (SD_FAILURE);
    }

  count = recvwait (domsock_listener, &resp, 1, 0);
  if (count != 1)
    {
    TP_LOG_ERR0  ("TP-ERROR: rfc1006_listener_bind: failed to receive expected 1 byte BIND response");
    return (SD_FAILURE);
    }

  if (resp != SD_SUCCESS)
    {
    /* most probable cause of failure */
    TP_LOG_ERR1 ("TP-ERROR: rfc1006_listener_bind: BIND failed resp=%d,\n TSEL may be already bound to another user.",
                (ST_UINT) resp);
    return (TP4E_TOO_MANY_BINDS);
    }

  return (SD_SUCCESS);
  }

/************************************************************************/
/*			tp0_bind					*/
/* This function substitutes for "tp4_bind" when TP0/RFC1006 is being	*/
/* used.  See "tp4_bind" header for more info.				*/
/************************************************************************/
ST_RET tp0_bind (ST_LONG user_bind_id, 
		  TP4_ADDR *tp4_addr, 
		  ST_INT sharable,
		  ST_INT max_conns)
  {
ST_RET     ret;
ST_UINT    idx;
BIND_TSEL *bind_tsel;
ST_LONG    tp0_bind_id;

  if (tp4_addr->tp_type != TP_TYPE_TCP)
    {						/* Not for TP0	*/
    /* No other TP provider to pass to. This is error.	*/
    TP_LOG_ERR0 ("TP-ERROR: tp0_bind: invalid transport type");
    return (SD_FAILURE);
    }

  if (max_conns > tp0_cfg.max_num_conns)
    {
    TP_LOG_ERR2 ("TP-ERROR: tp0_bind: requested TP conns (%d) greater than number configured (%d)",
                 max_conns, tp0_cfg.max_num_conns);
    return (TP4E_INVAL_NUM_CONNS);
    }

/* DEBUG:: the tbl alloc may need to be moved to tp0_initialize, and free */
/*         in tp0_terminate.						*/
  if (!bind_tsel_tbl)
    {
    bind_tsel_tbl_max = tp0_cfg.max_num_conns + 1;	/* entry 0 unused	*/
    bind_tsel_tbl = (BIND_TSEL *) chk_calloc (bind_tsel_tbl_max, sizeof(BIND_TSEL));
    }

  /* Make sure Local TSAP len is legal before copying.			*/
  if (tp4_addr->tp4_sel_len > 0 && tp4_addr->tp4_sel_len <= MAX_TSEL_LEN)
    {
    /* find a spot in the bind_tsel_tbl (entry 0 unused) */
    for (idx=1; idx<bind_tsel_tbl_max; ++idx)
      {
      bind_tsel = &bind_tsel_tbl[idx]; 
      if (bind_tsel->tsel[0] == 0)	/* if available */
        {
	ST_UCHAR  tmp_tsel[MAX_TSEL_LEN+1];
        tmp_tsel[0] = (ST_UCHAR) tp4_addr->tp4_sel_len;
        memcpy (&tmp_tsel[1], tp4_addr->tp4_sel, tp4_addr->tp4_sel_len);
	if (tp0_check_tsel (tmp_tsel, NULL) == SD_SUCCESS)
	  {
          TP_LOG_ERR0 ("TP-ERROR: tp0_bind: TSEL already bound");
          TP_LOGH_ERR (tp4_addr->tp4_sel_len, tp4_addr->tp4_sel);
          return (TP4E_INVAL_TSEL);
	  }
        ret = rfc1006_listener_bind (tmp_tsel);
	if (ret != SD_SUCCESS)
	  return (ret);
	tp0_bind_id = idx;
        bind_tsel->user_bind_id = user_bind_id;
        memcpy (bind_tsel->tsel, tmp_tsel, tp4_addr->tp4_sel_len+1);
        TP_LOG_REQ1 ("tp0_bind: Bound TSEL to tp0_bind_id=%d", tp0_bind_id);
        TP_LOGH_REQ (tp4_addr->tp4_sel_len, tp4_addr->tp4_sel);
        break;
        }
      }
    if (idx == bind_tsel_tbl_max)
      {
      TP_LOG_ERR0 ("TP-ERROR: too many binds");
      return (TP4E_TOO_MANY_BINDS);
      }
    }
  else
    {
    TP_LOG_ERR2 ("TP-ERROR: tp0_bind: invalid TSEL length=%d octets (len>0 && len<=%d)",
                tp4_addr->tp4_sel_len, MAX_TSEL_LEN);
    return (TP4E_INVAL_TSEL);
    }

  tp4_bind_cnf (user_bind_id, tp0_bind_id, SD_SUCCESS);
  return (SD_SUCCESS);
  }

/************************************************************************/
/*			rfc1006_listener_unbind				*/
/* Function to unbind TSAP in rfc1006_listener task. Passed through	*/
/* UNIX domain socket. Waits for the response, discarding any non	*/
/* RFC1006_IPC_OP_UNBIND messages.					*/
/************************************************************************/
ST_RET rfc1006_listener_unbind (ST_UCHAR *tsap)
  {
ST_UINT         tsap_len = (ST_UINT) ((ST_UINT8) tsap [0]+1);
RFC1006_IPC_MSG ipc_msg;
int             count;
ST_CHAR        *tmp_buf;
char            resp;

  ipc_msg.magic = RFC1006_IPC_MAGIC_NUM;
  ipc_msg.opcode = RFC1006_IPC_OP_UNBIND;
  ipc_msg.data_len = 0;

  /* send the UNBIND msg header */
  count = sendwait (domsock_listener, &ipc_msg, sizeof (RFC1006_IPC_MSG), 0);
  if (count != sizeof (RFC1006_IPC_MSG))
    {
    TP_LOG_ERR0  ("TP-ERROR: rfc1006_listener_unbind: send UNBIND header failed.");
    return (SD_FAILURE);
    }

  /* send the TSAP to unbind */
  count = sendwait (domsock_listener, tsap, tsap_len, 0);
  if (count != tsap_len)
    {
    TP_LOG_ERR0  ("TP-ERROR: rfc1006_listener_unbind: send UNBIND TSAP failed.");
    return (SD_FAILURE);
    }

  /* Keep trying to read until RFC1006_IPC_OP_UNBIND response arrives.	*/
  do
    {	/* read and drop any other IPC_MSG	*/
    count = recvwait (domsock_listener, &ipc_msg, sizeof (RFC1006_IPC_MSG), 0);
    if (count != sizeof (RFC1006_IPC_MSG))
      {
      TP_LOG_ERR0 ("TP-ERROR: rfc1006_listener_unbind: failed to receive UNIBIND response.");
      return (SD_FAILURE);
      }
    if (ipc_msg.magic != RFC1006_IPC_MAGIC_NUM)
      {
      TP_LOG_ERR1  ("TP-ERROR: rfc1006_listener_unbind: received invalid magic='%X'", 
                   ipc_msg.magic);
      return (SD_FAILURE);
      }
    if (ipc_msg.opcode != RFC1006_IPC_OP_UNBIND)
      {
      TP_LOG_IND2 ("rfc1006_listener_unbind: waiting for UNBIND response.\n Ignoring message with opcode=%d, len=%d",
                    ipc_msg.opcode, ipc_msg.data_len);
      if (ipc_msg.data_len > 0)		/* read and discard data if present	*/
        {
        tmp_buf = chk_malloc (ipc_msg.data_len);
        count = read (domsock_listener, tmp_buf, ipc_msg.data_len);
        chk_free (tmp_buf);
        }
      }
    } while (ipc_msg.opcode != RFC1006_IPC_OP_UNBIND);

  if (ipc_msg.data_len != 1)
    {
    TP_LOG_ERR1 ("TP-ERROR: rfc1006_listener_unbind: received invalid data_len=%u (expecting 1 byte)",
                 (ST_UINT) ipc_msg.data_len);
    return (SD_FAILURE);
    }

  count = recvwait (domsock_listener, &resp, 1, 0);
  if (count != 1)
    {
    TP_LOG_ERR0  ("TP-ERROR: rfc1006_listener_unbind: failed to receive expected 1 byte UNBIND response");
    return (SD_FAILURE);
    }

  if (resp != SD_SUCCESS)
    {
    TP_LOG_ERR1 ("TP-ERROR: rfc1006_listener_unbind: UNBIND failed resp=%d.",
                (ST_UINT) resp);
    return (SD_FAILURE);
    }

  return (SD_SUCCESS);
  }

/************************************************************************/
/* tp0_unbind								*/
/************************************************************************/
ST_RET tp0_unbind (ST_LONG tp0_bind_id)
  {
ST_RET     ret = SD_FAILURE;
BIND_TSEL *bind_tsel;

  if (bind_tsel_tbl && tp0_bind_id > 0 && tp0_bind_id <= bind_tsel_tbl_max)
    {
    /* 	tp0_bind_id is the index into the bind table */
    bind_tsel = &bind_tsel_tbl[tp0_bind_id]; 
    if (bind_tsel->tsel[0] != 0)
      {
      ret = rfc1006_listener_unbind (bind_tsel->tsel);
      TP_LOG_REQ1 ("tp0_unbind: tp0_bind_id=%d", tp0_bind_id);
      tp4_unbind_cnf (bind_tsel->user_bind_id);
      bind_tsel->tsel[0] = 0;
      bind_tsel->user_bind_id = 0;
      ret = SD_SUCCESS;
      }
    else
      TP_LOG_ERR0 ("TP-ERROR: TP0 UNBIND failed (not bound).");
    }
  else
    TP_LOG_ERR1 ("TP-ERROR: TP0 UNBIND failed (invalid tp0_bind_id=%ld).", tp0_bind_id);

  return (ret);
  }

/************************************************************************/
/*			tp0_check_tsel					*/
/* Checks if given tsel was bound and sets the user_bind_id if tsel OK.	*/
/* Returns:								*/
/*	SD_SUCCESS	if tsel bound					*/
/*	SD_FAILURE	otherwise					*/
/************************************************************************/
ST_RET tp0_check_tsel (ST_UCHAR *tsel, ST_LONG *user_bind_id)
  {
ST_RET     ret = SD_FAILURE;
ST_UINT    idx;
BIND_TSEL *bind_tsel;

  /* find tsel (entry 0 unused) */
  for (idx=1; idx<bind_tsel_tbl_max; ++idx)
    {
    bind_tsel = &bind_tsel_tbl[idx]; 
    if (bind_tsel->tsel[0] > 0 &&
        memcmp (tsel, bind_tsel->tsel, bind_tsel->tsel[0]+1) == 0)
      {
      if (user_bind_id)
        *user_bind_id = bind_tsel->user_bind_id;
      ret = SD_SUCCESS;			/* tsel is bound		*/
      break;
      }
    }
  return (ret);
  }

/************************************************************************/
/*			tp0_check_bind_id				*/
/* Checks if given bind_id is valid and sets the tsel if bind_id OK.	*/
/* Returns:								*/
/*	SD_SUCCESS	if bind_id valid				*/
/*	SD_FAILURE	otherwise					*/
/************************************************************************/
ST_RET tp0_check_bind_id (ST_LONG bind_id, ST_UCHAR **tsel)
  {
ST_RET     ret = SD_FAILURE;
ST_UINT    idx;
BIND_TSEL *bind_tsel;

  /* check bind_id (entry 0 unused) */
  for (idx=1; idx<bind_tsel_tbl_max; ++idx)
    {
    bind_tsel = &bind_tsel_tbl[idx]; 
    if (bind_tsel->tsel[0] > 0 && (ST_LONG) idx == bind_id)
      {
      *tsel = bind_tsel->tsel;
      ret = SD_SUCCESS;			/* bind_id valid		*/
      break;
      }
    }
  return (ret);
  }

#else /* !defined(MMSEASE_MOSI) */
/************************************************************************/
/*			tp0_bind					*/
/* This function substitutes for "tp4_bind" when TP0/RFC1006 is being	*/
/* used.  See "tp4_bind" header for more info.				*/
/************************************************************************/
ST_RET tp0_bind (ST_LONG user_bind_id, 
		  TP4_ADDR *tp4_addr, 
		  ST_INT sharable,
		  ST_INT max_conns)
  {




  if (tp4_addr->tp_type != TP_TYPE_TCP)
    {						/* Not for TP0	*/
    /* No other TP provider to pass to. This is error.	*/
    TP_LOG_ERR0 ("TP-ERROR: tp0_bind: invalid transport type");
    return (SD_FAILURE);
    }

  if (tp0_bind_called)
    {
    TP_LOG_ERR0 ("TP-ERROR: too many binds");
    return (TP4E_TOO_MANY_BINDS);
    }

  if (max_conns > tp0_cfg.max_num_conns)
    {
    TP_LOG_ERR2 ("TP-ERROR: requested TP conns (%d) greater than number configured (%d)",
                 max_conns, tp0_cfg.max_num_conns);
    return (TP4E_INVAL_NUM_CONNS);
    }

  /* Save user_bind_id in Global. Use in all calls to user functions.	*/
  only_tp0_user_bind_id = user_bind_id;

  /* Make sure Local TSAP len is legal before copying.			*/
  if (tp4_addr->tp4_sel_len > MAX_TSEL_LEN)
    {
    TP_LOG_ERR0 ("TP-ERROR: TSEL too long");
    return (TP4E_INVAL_TSEL);
    }

  /* Save Local TSAP (only do memcpy if len != 0).			*/
  if ((only_tp0_loc_tsap [0] = (ST_UCHAR) tp4_addr->tp4_sel_len) != 0)
    memcpy (&only_tp0_loc_tsap [1], tp4_addr->tp4_sel, tp4_addr->tp4_sel_len);



  tp4_bind_cnf (user_bind_id, only_tp0_bind_id, SD_SUCCESS);
  tp0_bind_called = SD_TRUE;	/* Only set if successful.		*/



  return (SD_SUCCESS);
  }
  
/************************************************************************/
/* tp0_unbind								*/
/************************************************************************/
ST_RET tp0_unbind (ST_LONG tp0_bind_id)
  {
  if (tp0_bind_id != only_tp0_bind_id)
    return (SD_FAILURE);		/* Invalid bind id.		*/
  if (tp0_bind_called == SD_FALSE)
    return (SD_FAILURE);	/* Bind never made. Cannot unbind.	*/
  tp0_bind_called = SD_FALSE;	/* Clear flag so user can bind again.	*/
  tp4_unbind_cnf (only_tp0_user_bind_id);
  return (SD_SUCCESS);
  }
#endif /* !defined(MMSEASE_MOSI) */

/*======================================================================*/
/*======================================================================*/
/* TIMER FUNCTIONS							*/
/*======================================================================*/
/*======================================================================*/

/************************************************************************/
/*                      tp0_session_timer				*/
/*----------------------------------------------------------------------*/
/* This function starts the session timer (for MOSI implementation).	*/
/* The user function tp4_session_timer_expired will be called if the	*/
/* timer expires.							*/
/*									*/
/* Parameters:								*/
/*  ST_LONG	tp_conn_id	TP0 connection id for this connect.	*/
/*  ST_BOOLEAN	start		SD_TRUE if timer should be started,	*/
/*				SD_FALSE if timer should be stopped.	*/
/*									*/
/* Return:								*/
/*	SD_SUCCESS (0)		if successful				*/
/*	error code		if not successful			*/
/************************************************************************/
ST_RET tp0_session_timer (ST_LONG tp_conn_id, ST_BOOLEAN start)
{
TP0_CONN *tp_conn = &tp0_conn_arr [tp_conn_id-MIN_TP0_CONN_ID];

  if (start)
    {
    if (tp_conn->state != TP_STATE_OPEN)
      {
      TP_LOG_ERR1 ("TP-ERROR: illegal connection state = %d", tp_conn->state);
      return (TP4E_CONN_STATE);
      }

    /* If configured, use configured val. It not, use reasonable val.*/
    if (session_cfg.disconnect_timeout)
      tp_conn->session_timer = session_cfg.disconnect_timeout;
    else
      tp_conn->session_timer = 60;
    }
  else
    tp_conn->session_timer = 0;		/* stopping the session timer	*/

  return (SD_SUCCESS);
}

/************************************************************************/
/*			tp0_timer_tick					*/
/*----------------------------------------------------------------------*/
/* This function is called on 1 second intervals by the TP0-user	*/
/* When this function is called, TP0 decrements all of the timers	*/
/* for all outstanding transport connections.				*/
/*									*/
/* Parameters:								*/
/*	ST_VOID			none				       	*/
/*									*/
/* Return:								*/
/*	ST_VOID			none					*/
/************************************************************************/
ST_VOID tp0_timer_tick ()
  {
ST_UINT conn_id;
TP0_CONN *tp_conn;

  /* Because this function is called often and the entire tp0_conn_arr	*/
  /* must be checked, it must be very efficient.  Therefore, tp_conn	*/
  /* is used to point to each element of array (instead of		*/
  /* tp0_conn_arr[conn_id]).						*/
  for (conn_id = 0, tp_conn = &tp0_conn_arr[0];
       conn_id < tp0_cfg.max_num_conns;
       conn_id++, tp_conn++)
    {
    /* Handle "session timer" (MOSI implementation only)	*/
    /* Only decrement it if it's not 0 and state is legal.	*/
    if (tp_conn->state == TP_STATE_OPEN &&
        tp_conn->session_timer)
      {
      if (--tp_conn->session_timer == 0)
        tp4_session_timer_expired (tp_conn->user_conn_id);
      }		/* End "session timer" handling	*/
    }		/* End "for" loop	*/
  }

/************************************************************************/
/* tp0_buffers_avail							*/
/*----------------------------------------------------------------------*/
/* This function returns the number of buffers available for sending	*/
/* SPDUs.  This may be used, for example, when sending Information	*/
/* Reports, to make sure there are always enough buffers for sending	*/
/* responses to incoming requests.					*/
/*									*/
/* Parameters:								*/
/*	ST_LONG tp_conn_id		Transport Connection ID		*/
/*									*/
/* Return:								*/
/*	ST_INT			# of SPDU transmit buffers available	*/
/************************************************************************/
ST_INT tp0_buffers_avail (ST_LONG tp_conn_id)
  {
TP0_CONN *tp_conn = &tp0_conn_arr [tp_conn_id-MIN_TP0_CONN_ID];
ST_UINT spdu_que_cnt;

  spdu_que_cnt = np_get_tx_queue_cnt (tp_conn->sock_info);
  return (tp0_cfg.max_spdu_outst - spdu_que_cnt);
  }

/************************************************************************/
/* tp0_event								*/
/* Receive a n_unitdata NSDU, decode it, process it, and free it.	*/
/************************************************************************/
ST_BOOLEAN tp0_event ()
  {
  tp4_check_timer ();

  return (np_event ());
  }
  
/*======================================================================*/
/*======================================================================*/
/* FUNCTIONS BELOW CALLED BY USER TO ENCODE AND SEND TPDUs.		*/
/*======================================================================*/
/*======================================================================*/

/************************************************************************/
/* tp0_data								*/
/* RETURN CODES:							*/
/*	SD_SUCCESS	(if SPDU successfully queued for sending)	*/
/*      TP4E_CONN_STATE (connection not in proper state for this)	*/
/*	OR error returned from tp0_send_dt.				*/
/************************************************************************/
ST_RET tp0_data (ST_LONG tp_conn_id, ST_INT eot, ST_UINT spdu_len, ST_CHAR *spdu_ptr)
  {
ST_RET ret = SD_SUCCESS;
TP0_CONN *tp_conn = &tp0_conn_arr [tp_conn_id-MIN_TP0_CONN_ID];
ST_UINT spdu_offset;
TPDU_DT tpdu_dt_tx;		/* ptr to TPDU of interest.	*/

  if (tp_conn->state != TP_STATE_OPEN)
    {
    TP_LOG_ERR1 ("TP-ERROR: illegal connection state = %d", tp_conn->state);
    return (TP4E_CONN_STATE);
    }

  tpdu_dt_tx.dst_ref = tp_conn->rem_ref;
  tpdu_dt_tx.sock_info = tp_conn->sock_info;

  /* Set up TPDU user data by pointing into SPDU.	*/
  for (spdu_offset = 0;  spdu_offset < spdu_len;  )
    {
    tpdu_dt_tx.udata_ptr = spdu_ptr + spdu_offset;
    if (spdu_len - spdu_offset <= (ST_UINT)(tp_conn->max_tpdu_len - TP0_HEAD_LEN_DT))
      {
      tpdu_dt_tx.udata_len = spdu_len - spdu_offset;
      tpdu_dt_tx.eot = SD_TRUE;
      }
    else
      {
      TP_LOG_REQ0 ("TP-INFO: SPDU being segmented. Should increase TPDU size.");
      /* Send largest allowed TPDU, taking header size into account.	*/
      tpdu_dt_tx.udata_len = (tp_conn->max_tpdu_len - TP0_HEAD_LEN_DT);
      tpdu_dt_tx.eot = SD_FALSE;
      }
    spdu_offset += tpdu_dt_tx.udata_len;

    ret = tp0_send_dt (&tpdu_dt_tx);
    if (ret != SD_SUCCESS)
      break;
    }	/* end "for"	*/

  return (ret);
  }

/************************************************************************/
/*			tp0_disconnect					*/
/* This function is called by the user to break connection		*/
/************************************************************************/
ST_RET tp0_disconnect (ST_LONG tp0_conn_id,
		        ST_INT conndata_len,
		        char *conndata)
  {
TP0_CONN *tp_conn;	/* ptr to struct in "tp0_conn_arr" array.	*/
TPDU_DR tpdu_dr;
ST_UINT16 conn_id = (ST_UINT16) tp0_conn_id - MIN_TP0_CONN_ID;

  if (conn_id >= tp0_cfg.max_num_conns)
    return (TP4E_BADCONN);

  tp_conn = &tp0_conn_arr [conn_id];

  /* Fill in TPDU_DR struct in case DR needs to be sent.		*/
  tpdu_dr.loc_ref = calc_loc_ref (conn_id, tp_conn->loc_ref_offset);
  tpdu_dr.rem_ref = tp_conn->rem_ref;
  tpdu_dr.sock_info = tp_conn->sock_info;
  tpdu_dr.reason = TP_DR_NO_REASON;

  switch (tp_conn->state)
    {
    case TP_STATE_OPEN:
    case TP_STATE_WFCC:
    case TP_STATE_WFNC:
      np_disconnect_req (tp_conn->sock_info);
      tp_conn->state = TP_STATE_CLOSED;
#if defined(SPDU_RX_NO_PREALLOCATE)
      M_FREE (MSMEM_STARTUP, tp_conn->spdu_rx.spdu_ptr);
#endif
      break;
    case TP_STATE_WFTRESP:
      /* Overwrite assumed "loc_ref". Not valid in this state.	*/
      tpdu_dr.loc_ref = 0;
      tp0_send_dr (&tpdu_dr);
      tp_conn->state = TP_STATE_CLOSED;
#if defined(SPDU_RX_NO_PREALLOCATE)
      M_FREE (MSMEM_STARTUP, tp_conn->spdu_rx.spdu_ptr);
#endif
      break;
    default:
      TP_LOG_ERR1 ("TP-ERROR: invalid state (%d) for sending DR. Not sent.",
        tp_conn->state);
      return (SD_FAILURE);
    }	/* end "switch"	*/
  return (SD_SUCCESS);
  }
    
/*======================================================================*/
/*======================================================================*/
/* FUNCTIONS BELOW CALLED BY TP0 TO PROCESS TPDUs RECEIVED		*/
/* (TPDUs ALREADY DECODED BY TP0).					*/
/*======================================================================*/
/*======================================================================*/

/************************************************************************/
/* tp0_process_dt							*/
/* Only one SPDU per connection is ever being processed.  As soon as	*/
/* the EOT is received, the SPDU is passed up to the Transport-user.	*/
/************************************************************************/
ST_VOID tp0_process_dt (TPDU_DT *tpdu_dt_rx)
  {
TP0_CONN *tp_conn;

  if (tpdu_dt_rx->sock_info->user_conn_id == INVALID_CONN_ID)
    {
    TP_LOG_ERR0 ("TP-ERROR: unexpected TPDU-DT.ind ignored. Waiting for TPDU-CR.ind.");
    return;
    }

  tp_conn = (TP0_CONN *) tpdu_dt_rx->sock_info->user_conn_id;
  if (tp_conn - tp0_conn_arr > tp0_cfg.max_num_conns)	/* check "conn_id"*/
    {
    TP_LOG_ERR0 ("TP-ERROR: illegal connection id");
    return;	/* conn_id is invalid.  Ignore this TPDU.		*/
    }
  
  switch (tp_conn->state)
    {
    case TP_STATE_CLOSED:
      return;			/* Ignore this DT.			*/
    case TP_STATE_OPEN:
      break;			/* Continue				*/
    default:
      TP_LOG_ERR1 ("TP-ERROR: invalid state (%d) for received DT. Ignored.",
        tp_conn->state);
      return;
    }
    
  /* If this is beginning of SPDU, initialize buffer for it.		*/
  if (tp_conn->spdu_rx_start)	/* init when CR rcvd or CR sent 	*/
    {		/* Last TPDU contained EOT. This TPDU starts new SPDU.	*/
    /* We can freely overwrite "spdu_rx.spdu_ptr" because the user	*/
    /* is now responsible for the last one.				*/
    tp_conn->spdu_rx.spdu_len = 0;
    }

  if ((ST_UINT) (tp_conn->spdu_rx.spdu_len + tpdu_dt_rx->udata_len)
      > g_tp0_max_spdu_len)
    {
    /* Would exceed max SPDU size. CAN'T DO memcpy. WON'T FIT IN BUFFER.*/
    /* No good recovery, so just disconnect.				*/
    /* NOTE: This is really a Session problem, but we handle it here.	*/

    TP_LOG_ERR1 ("Received msg exceeds max_spdu_len (%d)", g_tp0_max_spdu_len);

    /* Pass up disconnect indication.	*/
    tp4_disconnect_ind (tp_conn->user_conn_id, TP_DR_NO_REASON,
                        0, NULL);
    /* Send disconnect request (compute conn_id from tp_conn.	*/
    tp0_disconnect (tp_conn - tp0_conn_arr + MIN_TP0_CONN_ID, 0, NULL);
    return;					/* DO NOT CONTINUE	*/
    }

  memcpy (tp_conn->spdu_rx.spdu_ptr + tp_conn->spdu_rx.spdu_len, tpdu_dt_rx->udata_ptr,
          tpdu_dt_rx->udata_len);
  tp_conn->spdu_rx.spdu_len += tpdu_dt_rx->udata_len;

  if (tpdu_dt_rx->eot)
    {		/* This TPDU contains EOT. This TPDU ends SPDU.		*/
    tp_conn->spdu_rx_start = SD_TRUE;		/* Next TPDU will begin a SPDU.	*/

    /* Pass up SPDU to user. Note that we always pass up complete SPDU	*/
    /* so eot is always SD_TRUE.					*/
    /* "tp4_data_ind" must completely process the data at spdu_ptr.	*/
    /* When it returns, the buffer may be reused.			*/

    tp4_data_ind (tp_conn->user_conn_id, tpdu_dt_rx->eot, tp_conn->spdu_rx.spdu_len,
                  tp_conn->spdu_rx.spdu_ptr);
    }
  else
    tp_conn->spdu_rx_start = SD_FALSE;	/* Next TPDU is part of this SPDU*/
  }

/************************************************************************/
/* tp0_process_dr							*/
/* Process a DR (Disconnect Request) TPDU.				*/
/************************************************************************/
ST_VOID tp0_process_dr (TPDU_DR *tpdu_dr)
  {
TP0_CONN *tp_conn;
ST_INT reason;

  if (tpdu_dr->sock_info->user_conn_id == INVALID_CONN_ID)
    {
    TP_LOG_ERR0 ("TP-ERROR: unexpected TPDU-DR.ind ignored. Waiting for TPDU-CR.ind.");
    return;
    }

  /* tp_conn was saved in "sock_info" (as user_conn_id) when CR sent/rcvd*/
  tp_conn = (TP0_CONN *) tpdu_dr->sock_info->user_conn_id;

  switch (tp_conn->state)
    {
    case TP_STATE_CLOSED:
      break;
    case TP_STATE_WFCC:
      reason = 0;			/* Normal disconnection	*/
      /* NOTE: To save memory, user data is never passed up to user.	*/
      tp4_disconnect_ind (tp_conn->user_conn_id, reason,
                          0, NULL);	/* No user data passed up.	*/
      np_disconnect_req (tp_conn->sock_info);	/* Disconnect network conn.*/
      tp_conn->state = TP_STATE_CLOSED;	/* Skip REFWAIT state.		*/
#if defined(SPDU_RX_NO_PREALLOCATE)
      M_FREE (MSMEM_STARTUP, tp_conn->spdu_rx.spdu_ptr);
#endif
      break;
    case TP_STATE_OPEN:
      TP_LOG_ERR0 ("TP-ERROR: Received TP0 DR in OPEN state. Ignoring.");
      break;
    default:
      TP_LOG_ERR1 ("TP-ERROR: invalid state (%d) for received DR. Ignored.",
        tp_conn->state);
      break;
    }
  return;
  }

/*======================================================================*/
/*======================================================================*/
/* INTERNAL TP0 FUNCTIONS						*/
/*======================================================================*/
/*======================================================================*/

/************************************************************************/
/* tp0_conn_clean							*/
/* Initialize connection struct for a new connection.			*/
/************************************************************************/
ST_VOID tp0_conn_clean (TP0_CONN *tp_conn)
  {
  tp_conn->spdu_rx_start = SD_TRUE;	/* CRITICAL: Initialize flag.	*/
#if defined(SPDU_RX_NO_PREALLOCATE)
  tp_conn->spdu_rx.spdu_ptr = (ST_CHAR *) M_MALLOC (MSMEM_STARTUP, g_tp0_max_spdu_len);
#endif
  return;
  }

/************************************************************************/
/* tp0_send_dt								*/
/* Send a DT (Data) TPDU.						*/
/************************************************************************/
ST_RET tp0_send_dt (TPDU_DT *tpdu_dt)
  {
ST_RET ret = SD_FAILURE;
ST_UINT16 data_len;

  tp_log_tsdu ((ST_VOID *) tpdu_dt, (ST_UCHAR) TP_PDU_TYPE_DT, TP_REQ);

  /* Fill in g_tpkt_enc_buf with TPDU					*/
  data_len = tp0_encode_dt (g_tpkt_enc_buf + RFC1006_HEAD_LEN, tpdu_dt);
  if (data_len > 0)
    {
    /* Fill in g_tpkt_enc_buf HEADER					*/
    data_len += RFC1006_HEAD_LEN;	/* include header in len	*/
    g_tpkt_enc_buf [0] = 3;				/* vrsn		*/
    g_tpkt_enc_buf [1] = 0;				/* reserved	*/
    g_tpkt_enc_buf [2] = (ST_UCHAR) (data_len >> 8);	/* high byte	*/
    g_tpkt_enc_buf [3] = (ST_UCHAR) (data_len & 0xff);	/* low byte	*/
    ret = np_data_req (tpdu_dt->sock_info, data_len, g_tpkt_enc_buf, tpdu_dt->eot);
    }
  return (ret);
  }

/************************************************************************/
/* tp0_send_cr								*/
/* Send a CR (Connect Request) TPDU.					*/
/************************************************************************/
ST_RET tp0_send_cr (TPDU_CX *tpdu_cr)
  {
ST_RET ret = SD_FAILURE;
ST_UINT16 data_len;

  tp_log_tsdu ((ST_VOID *) tpdu_cr, (ST_UCHAR) TP_PDU_TYPE_CR, TP_REQ);

  /* Fill in g_tpkt_enc_buf with TPDU					*/
  data_len = tp_encode_cx (g_tpkt_enc_buf + RFC1006_HEAD_LEN, tpdu_cr,
  				       TP_PDU_TYPE_CR);
  if (data_len > 0)
    {
    /* Fill in g_tpkt_enc_buf HEADER					*/
    data_len += RFC1006_HEAD_LEN;	/* include header in len	*/
    g_tpkt_enc_buf [0] = 3;				/* vrsn		*/
    g_tpkt_enc_buf [1] = 0;				/* reserved	*/
    g_tpkt_enc_buf [2] = (ST_UCHAR) (data_len >> 8);	/* high byte	*/
    g_tpkt_enc_buf [3] = (ST_UCHAR) (data_len & 0xff);	/* low byte	*/
    ret = np_data_req (tpdu_cr->sock_info, data_len, g_tpkt_enc_buf, SD_TRUE);
    }
  return (ret);
  }

/************************************************************************/
/* tp0_send_cc								*/
/* Send a CC (Connect Confirm) TPDU.					*/
/************************************************************************/
ST_RET tp0_send_cc (TPDU_CX *tpdu_cc)
  {
ST_RET ret = SD_FAILURE;
ST_UINT16 data_len;

  tp_log_tsdu ((ST_VOID *) tpdu_cc, (ST_UCHAR) TP_PDU_TYPE_CC, TP_REQ);

  /* Fill in g_tpkt_enc_buf with TPDU					*/
  data_len = tp_encode_cx (g_tpkt_enc_buf + RFC1006_HEAD_LEN, tpdu_cc,
  				       TP_PDU_TYPE_CC);
  if (data_len > 0)
    {
    /* Fill in g_tpkt_enc_buf HEADER					*/
    data_len += RFC1006_HEAD_LEN;	/* include header in len	*/
    g_tpkt_enc_buf [0] = 3;				/* vrsn		*/
    g_tpkt_enc_buf [1] = 0;				/* reserved	*/
    g_tpkt_enc_buf [2] = (ST_UCHAR) (data_len >> 8);	/* high byte	*/
    g_tpkt_enc_buf [3] = (ST_UCHAR) (data_len & 0xff);	/* low byte	*/
    ret = np_data_req (tpdu_cc->sock_info, data_len, g_tpkt_enc_buf, SD_TRUE);
    }
  return (ret);
  }

/************************************************************************/
/* tp0_send_dr								*/
/* Send a DR (Disconnect Request) TPDU.					*/
/************************************************************************/
int cotp_test_mode=0;
ST_RET tp0_send_dr (TPDU_DR *tpdu_dr)
  {
ST_RET ret = SD_FAILURE;
ST_UINT16 data_len;


  if(cotp_test_mode)	return SD_FAILURE;  /*renxiaobao °²È«¼ì²â*/

  tp_log_tsdu ((ST_VOID *) tpdu_dr, TP_PDU_TYPE_DR, TP_REQ);

  /* Fill in g_tpkt_enc_buf with TPDU					*/
  data_len = tp_encode_dr (g_tpkt_enc_buf + RFC1006_HEAD_LEN, tpdu_dr);
  if (data_len > 0)
    {
    /* Fill in g_tpkt_enc_buf HEADER					*/
    data_len += RFC1006_HEAD_LEN;	/* include header in len	*/
    g_tpkt_enc_buf [0] = 3;				/* vrsn		*/
    g_tpkt_enc_buf [1] = 0;				/* reserved	*/
    g_tpkt_enc_buf [2] = (ST_UCHAR) (data_len >> 8);	/* high byte	*/
    g_tpkt_enc_buf [3] = (ST_UCHAR) (data_len & 0xff);	/* low byte	*/
    ret = np_data_req (tpdu_dr->sock_info, data_len, g_tpkt_enc_buf, SD_TRUE);
    }
  return (ret);
  }

/************************************************************************/
/*		tp0_connect_outstanding_count				*/
/* NOTE: can't presently use tp4Addr for anything, but in the future	*/
/* we may use it to check only for outstanding connections to the same	*/
/* IP Address.								*/
/************************************************************************/
ST_INT tp0_connect_outstanding_count (TP4_ADDR *tp4Addr)
  {
ST_INT connectOutstandingCount = 0;
ST_INT j;

  for (j=0; j<tp0_cfg.max_num_conns; j++)
    {
    /* Waiting for Network connect (i.e., socket connect) to complete?	*/
    if (tp0_conn_arr [j].state == TP_STATE_WFNC)
      connectOutstandingCount++;		/* YES, increment count	*/
    }
  return (connectOutstandingCount);
  }

/************************************************************************/
/*			tp0_convert_ip					*/
/* RETURNS: IP Address in network byte order OR				*/
/*          "htonl(INADDR_NONE)" if conversion fails.			*/
/************************************************************************/
ST_ULONG tp0_convert_ip (ST_CHAR *pHostName)
  {
ST_ULONG ipAddr;

  ipAddr = inet_addr(pHostName);
  if (ipAddr == htonl(INADDR_NONE))
    {
#if defined (VXWORKS)		/* gethostbyname not supported	*/
    TP_LOG_ERR1 ("Invalid IP address %s", pHostName);
#else	/* !VXWORKS	*/
HOSTENT	*pHostEnt;			/* host database entry for remote host	*/
    pHostEnt = gethostbyname(pHostName);
    if (pHostEnt == NULL)
      {
      TP_LOG_ERR1 ("Can't get IP address for host %s", pHostName);
      }
    else
      ipAddr = *(ST_UINT32 *)(pHostEnt->h_addr);
#endif	/* !VXWORKS	*/
    }
  return (ipAddr);
  }
