/************************************************************************/
/* SISCO SOFTWARE MODULE HEADER *****************************************/
/************************************************************************/
/*   (c) Copyright Systems Integration Specialists Company, Inc.,	*/
/*      	       1998 - 2006, All Rights Reserved.		*/
/*									*/
/*		    PROPRIETARY AND CONFIDENTIAL			*/
/*									*/
/* MODULE NAME : mvlu_rpt.c						*/
/* PRODUCT(S)  : MMSEASE						*/
/*									*/
/* MODULE DESCRIPTION : 						*/
/*									*/
/* GLOBAL FUNCTIONS DEFINED IN THIS MODULE :				*/
/*									*/
/* MODIFICATION LOG :							*/
/*  Date     Who   Rev			Comments			*/
/* --------  ---  ------   -------------------------------------------	*/
/* 07/08/08  JRB     62    mvlu_create_rpt_ctrl: clarify args.		*/
/* 04/15/08  JRB     61	   Don't call mvlu_rpt_va_change for Integrity	*/
/*			   (save "reason" later when scan completes).	*/
/*			   mvl61850_brcb_handle_rptena_wr: fix retCode.	*/
/*			   mvlu_rptena_wr..: Fix warning newval not set.*/
/* 03/04/08  JRB     60    Remove AA_SPEC DatSet from RCB on disconnect	*/
/*			   or when 0 written to Resv.			*/
/* 03/16/07  JRB     59    Fix Resv init in ..urcb_handle_rptena_wr.	*/
/* 02/28/07  JRB     58    DO NOT PurgeBuf when OptFlds is changed.	*/
/*			   PurgeBuf when RptID, DatSet, BufTm, TrgOps,	*/
/*			   or IntgPd is changed (tissue 322 final text).*/
/* 02/13/07  JRB     57    mvl61850_get_rcb: del unused net_info arg.	*/
/*			   Chk net_info before trying to find UCA RCB.	*/
/*			   Add _rcb_writable & use it.			*/
/* 12/20/06  JRB     56    PurgeBuf only if val changed (tissue 322).	*/
/*			   PurgeBuf also when BufTm written.		*/
/* 10/30/06  JRB     55    Use new mvl_vmd_* object handling functions.	*/
/*			   u_mvl_get_va_aa: add args.			*/
/*			   Add mvlu_rpt_ctrl_destroy_all		*/
/* 09/13/06  JRB     54    mvlu_rpt_find_typeids: simplify.		*/
/* 08/09/06  JRB     53    If report buffered because of BufTm, send it	*/
/*			   BEFORE any Integrity report.			*/
/* 08/09/06  JRB     52    mvlu_create_rpt_ctrl, mvlu_free_rpt_ctrl,	*/
/*			   mvlu_rpt_service now ONLY for UCA (for 61850,*/
/*			   use new functions in mvl61850_rpt.c).	*/
/*			   Del brcb_enable_allowed (not needed now that	*/
/*			    segmenting done only when sending).		*/
/*			   Reset SqNum=0 when BRCB enabled.		*/
/*			   PurgeBuf when DatSet,OptFlds,TrgOps,IntgPd	*/
/*			    or RptID written.				*/
/*			   Reserve BRCB only when RptEna written (chged	*/
/*			    mvlu_get_wr_rcb, mvlu_rptena_wr_ind_fun).	*/
/*			   mvlu_rpt_find_typeids: create if not found.	*/
/*			   Add mvlu_rpt_ready, mvlu_rpt_rcb_type_find.	*/
/*			   Chg some static functs to extern.		*/
/* 07/27/05  JRB     51    mvlu_optflds_wr..: use rcb_type to figure	*/
/*			   out which bits are writable.			*/
/* 06/27/05  JRB     50    brcb_enable_allowed: reset netInfo on error.	*/
/* 06/27/05  JRB     49    Simplify allocation by deleting all use of	*/
/*			   MVLU_RPT_VA_CTRL & va->mvl_internal & moving	*/
/*			   last_data to MVL_VAR_ASSOC struct.		*/
/*			   Del mvlu_rpt_nvl_add/destroy, use mvl_nvl_add*/
/*			   Del unused mvlu_resolve_uca_nvl.		*/
/* 06/27/05  JRB     48    _mvlu_get_rd_rcb: add (MVLU_RPT_CLIENT **) arg*/
/* 05/24/05  CRM     47	   Added mvlu_rpt_destroy_scan_ctrl		*/
/* 05/16/05  JRB     46    Set failure=ARE_TEMP_UNAVAIL when trying to	*/
/*			   write RCB elements while RptEna=TRUE.	*/
/* 05/09/05  JRB     45    Chk Integrity bit in TrgOps, as we did	*/
/*			   BEFORE change on 03/13/03 (61850 requires it)*/
/* 08/16/04  JRB     44    Reset SqNum when URCB is enabled.		*/
/* 07/20/04  JRB     43    ..chk_reason: make sure OLD data sent in	*/
/*			   first rpt when 2nd change detected.		*/
/* 07/07/04  JRB     42    ..chk_reason: for URCB or BRCB, call		*/
/*			   mvl61850_rcb_build (NOT mvlu_send_report).	*/
/* 07/06/04  JRB     41    Add client for BRCB only when needed,	*/
/*			   allows BRCB initial values to be set first.	*/
/*			   Add mvlu_rpt_create_scan_ctrl2.		*/
/* 06/29/04  JRB     40    mvlu_rpt_nvl_add: alloc new buffer for	*/
/*			   va->data ONLY if use_static_data==FALSE.	*/
/* 12/17/03  JRB     39    61850-8-1 FDIS changes:			*/
/*			   Increase num bits in OptFlds to 10 for ConfRev.*/
/*			   Find type RTYP_INT32U for ConfRev.		*/
/* 05/02/03  JRB     38    switch(rt->el_tag): Use default for most cases*/
/* 04/17/03  JRB     37    Add & use mvlu_setup_scan_read.		*/
/* 04/04/03  JRB     36    mvlu_rpt_va_change: save data at END of funct*/
/* 04/04/03  JRB     35    Fix integrity/GI scan code so multiple	*/
/*			   concurrent scans don't corrupt one another.	*/
/* 03/13/03  JRB     34    Add URCB support including Resv.		*/
/*			   Chg OptFlds back to 8 bits for UCA (9 for IEC).*/
/*			   Trigger Intg Rpt ONLY by setting IntgPd!=0.	*/
/*			   Increment SqNum after each report.		*/
/*			   Chg names in rpt_typeids struct (clearer).	*/
/*			   Fix _mvlu_rpt_find_rpt_va to set scope.	*/
/*			   Use tmp_va_arr for all rptNvl options.	*/
/*			   Simplify mvlu_rpt_va_change.			*/
/*			   Use domain name for NVL, not VA, in RptId string.*/
/*			   Chg TrgOps to Bvstring.			*/
/* 12/16/02  JRB     33    Chg args to mvlu_create_rpt_ctrl.		*/
/*			   Add mvlu_integrity_scan_* functions.		*/
/*			   Move some init/cleanup INTO mvlu_send_report.*/
/*			   Don't allow write of RCB if RptEna=TRUE.	*/
/*			   Use mvl_internal to AVOID conflict w/ user_info.*/
/*			   Write default RptID once in mvlu_create_rpt_ctrl*/
/*			   Start "automatic" scan when IntgPd expires.	*/
/*			   Use BSTR_BIT.. macros to access bitstring bits*/
/*			   Del buffer chain code.			*/
/*			   Del rptTim*, call usr func u_mvlu_rpt_time_get*/
/*			   Del outDat*, use basrcb->DatSetNa (same string)*/
/*			   Init OptFlds.len_1=8. Chg optflds_wr_ind_fun.*/
/*			   Del *typeId globals, use mvlu_rpt_find_typeids*/
/*			   Use BSTR_NUMBITS_TO_..			*/
/* 12/09/02  MDE     32    Removed need for primRef for reports		*/
/* 03/29/02  JRB     31    Fix crash if _mvl_objname_to_va returns NULL.*/
/* 12/12/01  MDE     30    Include mvl_defs.h instead of mvl_acse.h     */
/* 11/26/01  EJV     29    Added support for new MMS type UtcTime.	*/
/* 08/30/01  JRB     28    Fix "..sqnum_rd.." (chg UINT16 to UINT8).	*/
/* 12/21/00  JRB     27    Del return in main loop of mvlu_rpt_va_change*/
/* 10/25/00  JRB     26    Del u_mvl funct ptrs. Call functs directly.	*/
/* 09/08/00  JRB     25    Default to 10 second scan period.		*/
/* 07/13/00  JRB     24    Use new ms_comp_name_find to get comp names.	*/
/* 07/11/00  MDE     23    Changed seqNum to sqNum, made INT8U		*/
/* 05/01/00  JRB     22    Lint cleanup (uninitialized var).		*/
/* 03/10/00  JRB     21    Copy va_scope to appropriate structures.	*/
/* 03/07/00  MDE     20    Fixed integrity report problem  		*/
/* 01/21/00  MDE     19    Now use MEM_SMEM for dynamic memory		*/
/* 09/27/99  NAV     18	   Set OutData data for report			*/
/* 09/13/99  MDE     17    Added SD_CONST modifiers			*/
/* 09/07/99  MDE     16    Revised and enhanced the UCA report system	*/
/* 05/04/99  JRB     15    Fixed several write indication functions.	*/
/* 03/18/99  MDE     14    Changed for runtime connection limits	*/
/* 01/21/99  JRB     13    Del mvlu_create_nvl, mvlu_destroy_nvl. Call	*/
/*			   mvl_nvl_create, mvl_nvl_destroy instead.	*/
/* 12/11/98  MDE     12    Removed scope references from VA		*/
/* 11/16/98  MDE     11    Now use '_mvl_destroy_nvl_entries'		*/
/* 11/16/98  MDE     10    Renamed internal functions (prefix '_')	*/
/* 10/01/98  MDE     09    Changes to MVLU_RPT_CTRL rcb allocation	*/
/* 09/21/98  MDE     08    Minor lint cleanup				*/
/* 09/16/98  MDE     07    Moved RD/WR functions here from rdwrind.c	*/
/* 08/11/98  MDE     06    Changed 'mvlu_create_rpt_ctrl' parameters	*/
/* 07/13/98  MDE     05    We now use our own report control 		*/
/* 06/29/98  MDE     04    Moved report function pointers to mvl_uca.c	*/
/* 06/22/98  MDE     03    SeqNum now ST_UINT16				*/
/* 06/15/98  MDE     02    Changes to allow compile under C++		*/
/* 01/02/98  MDE     01    New						*/
/************************************************************************/

#include "glbtypes.h"
#include "sysincs.h"
#include "glbsem.h"
#include "mmsdefs.h"
#include "mms_pvar.h"
#include "mms_vvar.h"
#include "mvl_uca.h"
#include "mvl_log.h"

/************************************************************************/
/* For debug version, use a static pointer to avoid duplication of 	*/
/* __FILE__ strings.							*/

#ifdef DEBUG_SISCO
SD_CONST static ST_CHAR *SD_CONST thisFileName = __FILE__;
#endif

/************************************************************************/
/************************************************************************/
/* These variables must be set by the user application, and select the 	*/
/* types to be used by the elements listed below.			*/

MVLU_RPT_CTRL *mvlu_rpt_ctrl_list;
MVLU_RPT_CTRL *mvl61850_rpt_ctrl_list;	/* List of only 61850 RCBs.	*/
MVLU_RPT_SCAN_CTRL *mvlu_rpt_scan_list;

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

static MVLU_RPT_CLIENT *_mvlu_add_rpt_client (MVLU_RPT_CTRL *rptCtrl,
				              MVL_NET_INFO *netInfo);
static ST_VOID _mvlu_free_rpt_client (MVLU_RPT_CLIENT *rptClient);

static ST_VOID _mvlu_rpt_client_service (MVLU_RPT_CLIENT *rptClient,
				  ST_DOUBLE timeNow);

static ST_VOID _mvlu_rpt_va_reads_done (MVL_IND_PEND *indCtrl,
					MVL_VAR_ASSOC *va);

static ST_VOID _mvlu_get_rcb (MVL_VAR_ASSOC *baseVa,
			RUNTIME_TYPE *rt,
			MVL_NET_INFO *netInfo,
			MVLU_RPT_CTRL **rptCtrlOut,
			MVLU_RPT_CLIENT **rptClientOut);

static ST_RET _mvlu_count_rpt_var (RUNTIME_TYPE *rt, ST_INT numRt,
		    ST_INT *numRptVarOut, ST_INT *nameSizeOut);

static ST_RET _mvlu_fill_rpt_var (ST_CHAR *baseName, ST_CHAR *domName,
		   RUNTIME_TYPE *rt, ST_INT numRt,
                   ST_INT numObjNames, OBJECT_NAME *objNames,
		   ST_CHAR **nameBufPtr);

static ST_VOID _mvlu_rpt_va_chk_reason (MVLU_RPT_CLIENT *rptClient,
		MVL_VAR_ASSOC *va,
		ST_INT entry_num,
		ST_UCHAR reason);

/************************************************************************/
/************************************************************************/
/************************************************************************/
/* REPORT DATASET HANDLING						*/
/************************************************************************/
/************************************************************************/

/************************************************************************/
/*			mvlu_derive_rpt_ds 				*/
/************************************************************************/

MVL_NVLIST_CTRL *mvlu_derive_rpt_ds (ST_CHAR *domName, ST_CHAR *nvlName,
				     ST_INT numNodes, ST_CHAR **nodeNames)
  {
MVL_NVLIST_CTRL *nvl;
OBJECT_NAME nvlObjName;
OBJECT_NAME *objNames;
ST_RET rc;
ST_INT numDsVar;
ST_CHAR *nameBuf;


  rc = mvlu_derive_ds_va_names (domName, numNodes, nodeNames,
				 &numDsVar, &objNames, &nameBuf);
  if (rc != SD_SUCCESS)
    return (NULL);


/* Create the DataSet NVL */
  nvlObjName.object_tag = DOM_SPEC;
  nvlObjName.domain_id = domName;
  nvlObjName.obj_name.item_id = nvlName;

/* Add the DataSet NVL to the MVL data structures */
  nvl = mvl_vmd_nvl_add (&mvl_vmd,
            &nvlObjName,	/* NVL "object name"	*/
            NULL,		/* MVL_NET_INFO not used	*/
            numDsVar, 		/* num of variables			*/
            objNames,		/* array of var "object names"		*/
            SD_TRUE);		/* copy nvl name to new nvl.		*/

  M_FREE (MSMEM_GEN, nameBuf);
  M_FREE (MSMEM_GEN, objNames);
  return (nvl);
  }

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


typedef struct
  {
  MVL_VAR_ASSOC *nodeVa;
  ST_INT num_rpt_var;
  RUNTIME_TYPE *rt;
  ST_INT numRt;
  } _MVLU_RPT_DS_NODE_INFO;

ST_RET mvlu_derive_ds_va_names (ST_CHAR *domName,
       			        ST_INT numNodes, ST_CHAR **nodeNames,
				ST_INT *numObjNamesOut,
				OBJECT_NAME **objNameTblOut,
				ST_CHAR **nameBufOut)
  {
OBJECT_NAME nodeObjName;
OBJECT_NAME *objNames;
OBJECT_NAME *objNameDest;
_MVLU_RPT_DS_NODE_INFO *nodeInfoTbl;
MVL_VAR_ASSOC *nodeVa;
RUNTIME_TYPE *rt;
ST_INT numRt;
ST_INT i;
ST_RET rc;
ST_INT numNodeVar;
ST_INT numDsVar;
ST_INT nameSize;
ST_CHAR *nameBuf;
ST_CHAR *nameDest;

  nodeInfoTbl = (_MVLU_RPT_DS_NODE_INFO *)
		     M_CALLOC (MSMEM_GEN, numNodes, sizeof (_MVLU_RPT_DS_NODE_INFO));

  numDsVar = 0;
  nameSize = 0;

/* Find each node VA */
  for (i = 0; i < numNodes; ++i)
    {
    nodeObjName.object_tag = DOM_SPEC;
    nodeObjName.domain_id = domName;
    nodeObjName.obj_name.item_id = nodeNames[i];

    nodeVa = u_mvl_get_va_aa (&mvl_vmd,
                             MMSOP_MVLU_RPT_VA,
			     &nodeObjName, NULL,
  		      	     SD_FALSE, NULL, NULL);
    nodeInfoTbl[i].nodeVa = nodeVa;
    if (nodeVa == NULL)
      {
      MVL_LOG_NERR1 ("Error: Report node %s not found", nodeNames[i]);
      return (SD_FAILURE);
      }

    rc = mvl_get_runtime (nodeVa->type_id, &rt, &numRt);
    if (rc != SD_SUCCESS)
      {
      MVL_LOG_NERR0 ("Error: Runtime type not found");
      return (SD_FAILURE);
      }
    nodeInfoTbl[i].rt = rt;
    nodeInfoTbl[i].numRt = numRt;

    numNodeVar = 0;
    rc = _mvlu_count_rpt_var (rt, numRt, &numNodeVar, &nameSize);
    if (rc != SD_SUCCESS)
      return (SD_FAILURE);

    numDsVar += numNodeVar;
    nodeInfoTbl[i].num_rpt_var = numNodeVar;

  /* the name buffer must add "base$" for each name */
    nameSize += (numDsVar * (strlen (nodeVa->name) + 1));
    }

  objNames = (OBJECT_NAME *) M_CALLOC (MSMEM_GEN, numDsVar, sizeof (OBJECT_NAME));
  nameBuf = M_CALLOC (MSMEM_GEN, 1, nameSize);
  objNameDest = objNames;
  nameDest = nameBuf;
  for (i = 0; i < numNodes; ++i)
    {
    nodeVa = nodeInfoTbl[i].nodeVa;
    rt = nodeInfoTbl[i].rt;
    numRt = nodeInfoTbl[i].numRt;
    rc = _mvlu_fill_rpt_var (nodeVa->name, domName, rt, numRt,
			     nodeInfoTbl[i].num_rpt_var,
		   	     objNameDest, &nameDest);

    objNameDest += nodeInfoTbl[i].num_rpt_var;
    }

  *numObjNamesOut = numDsVar;
  *objNameTblOut = objNames;
  *nameBufOut = nameBuf;

  M_FREE (MSMEM_GEN, nodeInfoTbl);
  return (SD_SUCCESS);
  }

/************************************************************************/
/*			_mvlu_count_rpt_var 				*/
/************************************************************************/

static ST_RET _mvlu_count_rpt_var (RUNTIME_TYPE *rt, ST_INT numRt,
		    	         ST_INT *numRptVarOut, ST_INT *nameSizeOut)
  {
ST_INT numRptVar;
ST_INT nameSize;
RUNTIME_TYPE *lastRt;
ST_CHAR *comp_name;		/* Component name	*/

  if (numRt <= 2 || rt->el_tag != RT_STR_START)
    {
    MVL_LOG_NERR0 ("Error: Not structure, or no structure elements");
    return (SD_FAILURE);
    }

  ++rt;
  numRt -= 2;
  lastRt = rt + numRt;
  numRptVar = 0;
  nameSize = 0;
  while (rt <= lastRt)
    {
    switch (rt->el_tag)
      {
      case RT_STR_END :
      case RT_ARR_END :
      break;

      default:
        ++numRptVar;
        comp_name = ms_comp_name_find (rt);
        if (strlen (comp_name) == 0)
          {
          MVL_LOG_ERR0 ("Error: unnamed structure component");
          return (SD_FAILURE);
          }
        nameSize += (strlen (comp_name) + 1);
      break;
      }

    if (rt->el_tag == RT_STR_START)
      rt += rt->u.str.num_rt_blks + 1;	/* Skip the structure contents	*/

    if (rt->el_tag == RT_ARR_START)
      rt += rt->u.arr.num_rt_blks; /* Skip the array contents	*/

    ++rt;
    }

  *numRptVarOut += numRptVar;
  *nameSizeOut += nameSize;
  return (SD_SUCCESS);
  }

/************************************************************************/
/*			_mvlu_fill_rpt_var 				*/
/************************************************************************/

static ST_RET _mvlu_fill_rpt_var (ST_CHAR *baseName, ST_CHAR *domName,
		   RUNTIME_TYPE *rt, ST_INT numRt,
                   ST_INT numObjNames, OBJECT_NAME *objNames,
		   ST_CHAR **nameBufPtr)
  {
ST_INT dsDvCount;
RUNTIME_TYPE *lastRt;
ST_CHAR *nameBuf;
ST_CHAR *comp_name;		/* Component name	*/

  nameBuf = *nameBufPtr;

  ++rt;
  numRt -= 2;

  dsDvCount = 0;
  lastRt = rt + numRt;
  while (rt <= lastRt)
    {
    switch (rt->el_tag)
      {
      case RT_STR_END :
      case RT_ARR_END :
      break;

      default:
        comp_name = ms_comp_name_find (rt);
        if (strlen (comp_name) == 0)
          {
          MVL_LOG_ERR0 ("Error: unnamed structure component");
          return (SD_FAILURE);
          }
        sprintf (nameBuf, "%s$%s", baseName, comp_name);
        objNames[dsDvCount].object_tag = DOM_SPEC;
        objNames[dsDvCount].domain_id = domName;
        objNames[dsDvCount].obj_name.item_id = nameBuf;
        ++dsDvCount;
        nameBuf += (strlen (nameBuf) + 1);
      break;
      }

    if (rt->el_tag == RT_STR_START)
      rt += rt->u.str.num_rt_blks + 1;	/* Skip the structure contents	*/

    if (rt->el_tag == RT_ARR_START)
      rt += rt->u.arr.num_rt_blks; /* Skip the array contents	*/

    ++rt;
    }

  if (dsDvCount != numObjNames)
    {
    MVL_LOG_ERR0 ("Internal Error: dsDvCount misnatch");
    return (SD_FAILURE);
    }

  *nameBufPtr = nameBuf;
  return (SD_SUCCESS);
  }

/************************************************************************/
/************************************************************************/
/* REPORT CONTROL HANDLING						*/
/************************************************************************/
/************************************************************************/
/*                       mvlu_create_rpt_ctrl				*/
/************************************************************************/
/* Create a UCA Report Control (use mvl61850_create_rpt_ctrl for 61850).*/
/* This function creates a RPT NVL data structure and associated 	*/
/* control data to handle UCA report activity.				*/

/* Note that the 'dsNvl' can be created by 'mvlu_create_rpt_nvl',	*/
/* created by 'u_mvl_get_nvl', or can be configured. The NVL	*/
/* must be completely resolved; that is, all VA must have a valid data	*/
/* pointer, name, and type_id.						*/
/* NOTE: The args brcb_bufsize and ConfRev were added once for IEC 61850*/
/*       support, but now this function is ONLY for UCA, so they are ignored.*/

MVLU_RPT_CTRL *mvlu_create_rpt_ctrl (ST_CHAR *basrcbName,
			MVL_NVLIST_CTRL *dsNvl,
			MVL_VAR_ASSOC *base_va,
			ST_INT rcb_type,
			ST_INT buftim_action,
			ST_INT brcb_bufsize,	/* ignored	*/
			ST_UINT32 ConfRev)	/* ignored	*/

  {
MVLU_RPT_CTRL *rptCtrl;
MVL_NVLIST_CTRL *rptNvl;
RUNTIME_TYPE *incRt;
ST_RET rc;
ST_INT i;
ST_INT numDsVars;
ST_INT rptCtrlSize;
ST_INT inc_size;
RUNTIME_TYPE *rcbHead;
ST_INT numRt;

/* This funct only works for UCA. For IEC 61850, call mvl61850_create_rpt_ctrl*/
  assert (rcb_type == RCB_TYPE_UCA);

/* Find the runtime type of the RCB (saved later in rptCtrl).	*/
/* If successful, this function sets "rcbHead" & "numRt".	*/
  if (mvlu_rpt_rcb_type_find (base_va->type_id, basrcbName, &rcbHead, &numRt))
    return (NULL);	/* error already logged.	*/

/* Alloc report control large enough to contain the inclusion BS data 	*/
/* and reasons VA's (they depend on the number of VA in the report)	*/
  numDsVars = dsNvl->num_of_entries;
  inc_size = BSTR_NUMBITS_TO_NUMBYTES(numDsVars);
  rptCtrlSize = sizeof (MVLU_RPT_CTRL) +
        strlen (basrcbName) + 1;

  rptCtrl = (MVLU_RPT_CTRL *) M_CALLOC (MSMEM_GEN, 1, rptCtrlSize);

/* Set the variable size element pointers */
  rptCtrl->basrcb_name = (ST_CHAR *) (rptCtrl + 1);
  strcpy (rptCtrl->basrcb_name, basrcbName);

/* Initialize the Report Control data elements */
  rptCtrl->dsNvl = dsNvl;
  rptCtrl->base_va = base_va;
  rptCtrl->rcbRtHead = rcbHead;

/* If domname + '/' + basrcb_name will fit in RptID, then write it.	*/
  if (strlen (dsNvl->nvl_scope.dom->name) + strlen (rptCtrl->basrcb_name) + 1
      < sizeof (rptCtrl->common_basrcb.RptID))
    {
    sprintf (rptCtrl->common_basrcb.RptID, "%s/%s",
			dsNvl->nvl_scope.dom->name,
			rptCtrl->basrcb_name);
    if (rcb_type == RCB_TYPE_UCA)
      {    /* replace $ with '.'. Only for UCA.		*/
      for (i = 0; i < (ST_INT) strlen (rptCtrl->common_basrcb.RptID); ++i)
        {
        if (rptCtrl->common_basrcb.RptID[i] == '$')
          rptCtrl->common_basrcb.RptID[i] = '.';
        }
      }
    }
  else
    {
    MVL_LOG_ERR0 ("mvlu_create_rpt_ctrl RptID too long");
    M_FREE (MSMEM_GEN, rptCtrl);
    return (NULL);
    }

  rptCtrl->rcb_type = rcb_type;
  rptCtrl->buftim_action = buftim_action;
  if (mvlu_rpt_find_typeids (&rptCtrl->rpt_typeids))
    {
    MVL_LOG_ERR0 ("mvlu_create_rpt_ctrl error finding base types");
    M_FREE (MSMEM_GEN, rptCtrl);
    return (NULL);
    }

/* Create the RPT NVL control element, used to send the actual reports	*/
  rptNvl = &rptCtrl->rptNvl;
  rptNvl->name = "RPT";

/* Alloc the entries table; allow for RptID, OptFlds, SqNum, RptTim, 	*/
/* OutDat (61850 calls it DatSetName), BufOvfl, EntryID, ConfRev,	*/
/* SubSeqNum, MoreSegmentsFollow, & InclusionBitstring plus ARRAYS of	*/
/* dataRefs, values, and Reasons.					*/
/* "assert" later if "maxNumRptVars" exceeded.				*/
/* Allow (3 * numDsVars) to handle arrays of dataRef, value, and reason.*/
  rptCtrl->maxNumRptVars = MVLU_MAX_RPT_OPTS + (3 * numDsVars);

  rptNvl->entries = (MVL_VAR_ASSOC **) M_CALLOC (MSMEM_GEN, rptCtrl->maxNumRptVars,
					sizeof (MVL_VAR_ASSOC *));

  /* NOTE: The "rptNvl->entries" array is not filled in and
   *       and "rptNvl->num_of_entries" is not set
   *       until a Report is being built.
   */

  /* We need to create a Runtime Type for the inclusion bitstring */
  incRt = &rptCtrl->incRt;
  incRt->el_tag = RT_BIT_STRING;
  incRt->el_size = BSTR_NUMBITS_TO_NUMBYTES(numDsVars);
  incRt->offset_to_last = incRt->el_size;
  incRt->u.p.el_len = (ST_RTINT) numDsVars;
  rc = mvlu_add_rt_type (&rptCtrl->incRt, 1, &rptCtrl->inclusion_typeid);
  if (rc != SD_SUCCESS)
    {
    MVL_LOG_NERR0 ("Error - could not add temp RT type");
    M_FREE (MSMEM_GEN, rptCtrl);
    return (NULL);
    }

/* That's all we can do ahead of time for the RPT variable associations	*/

/* Set a bit of the data for the common BASRCB */
  sprintf (rptCtrl->common_basrcb.DatSetNa, "%s/%s",
			dsNvl->nvl_scope.dom->name, dsNvl->name);

  if (rcb_type == RCB_TYPE_UCA)
    {
    rptCtrl->common_basrcb.OptFlds.len_1 = 8;
    rptCtrl->common_basrcb.TrgOps.len = 8;
    }
  else
    {
    rptCtrl->common_basrcb.OptFlds.len_1 = 10;
    rptCtrl->common_basrcb.TrgOps.len = 6;
    }

  rptCtrl->common_basrcb.TrgOps.data[0] = MVLU_TRGOPS_DATA;

/* Put it on the list */
  list_add_last((ST_VOID **) &mvlu_rpt_ctrl_list, (ST_VOID *) rptCtrl);
  return (rptCtrl);
  }

/************************************************************************/
/*                       mvlu_free_rpt_ctrl				*/
/************************************************************************/
/* This function is used to free a MVLU_RPT_CTRL allocated by 		*/
/* 'mvlu_create_rpt_ctrl'						*/

ST_VOID mvlu_free_rpt_ctrl (MVLU_RPT_CTRL *rptCtrl)
  {
MVLU_RPT_CLIENT *rptClient;

/* This funct only works for UCA. For IEC 61850, call mvl61850_free_rpt_ctrl*/
  assert (rptCtrl->rcb_type == RCB_TYPE_UCA);

/* Take it off the report control list */
  list_unlink ((ST_VOID **) &mvlu_rpt_ctrl_list, (ST_VOID *) rptCtrl);

/* Free up the dynamic type used for the inclusion bitstring type */
  mvlu_free_rt_type (rptCtrl->inclusion_typeid);

/* Destroy all clients for this rptCtrl	*/
/* NOTE: list_get_first removes the entry from the linked list.	*/
  while ((rptClient = (MVLU_RPT_CLIENT *) list_get_first (&rptCtrl->rpt_client_list))!=NULL)
    _mvlu_free_rpt_client (rptClient);	/* free client */

/* Now the rest of the report control */
  M_FREE (MSMEM_GEN, rptCtrl->rptNvl.entries);
  M_FREE (MSMEM_GEN, rptCtrl);
  }

/************************************************************************/
/************************************************************************/
/* REPORT CONTROL SERVICING						*/
/************************************************************************/
/************************************************************************/
/*			mvlu_rpt_service 				*/
/************************************************************************/

ST_VOID mvlu_rpt_service ()
  {
MVLU_RPT_CTRL *rptCtrl;
MVLU_RPT_CLIENT *rptClient;
ST_DOUBLE timeNow;

/* Do Report VA Scans */
  mvlu_rpt_va_scan ();

/* See if it is time to send reports	*/
  timeNow = sGetMsTime ();
  for (rptCtrl = mvlu_rpt_ctrl_list;
       rptCtrl != NULL;
       rptCtrl = (MVLU_RPT_CTRL *) list_get_next (mvlu_rpt_ctrl_list, rptCtrl))
    {
    assert (rptCtrl->rcb_type == RCB_TYPE_UCA);	/* must be UCA URCB*/
    /* Loop through linked list of clients for UCA.*/
    for (rptClient = rptCtrl->rpt_client_list;
         rptClient != NULL;
         rptClient = (MVLU_RPT_CLIENT *) list_get_next (rptCtrl->rpt_client_list, rptClient))
      {
      _mvlu_rpt_client_service (rptClient, timeNow);
      }
    }
  }

/************************************************************************/
/*			mvlu_rpt_va_scan 				*/
/************************************************************************/

ST_VOID mvlu_rpt_va_scan ()
  {
MVLU_RPT_SCAN_CTRL *scanCtrl;
ST_DOUBLE timeNow;

  timeNow = sGetMsTime ();
  scanCtrl = mvlu_rpt_scan_list;
  while (scanCtrl != NULL)		/* look through whole list	*/
    {
    if (scanCtrl->enable == SD_TRUE)
      {
      if (timeNow >= scanCtrl->next_scan_start)
        {
	if (scanCtrl->num_va_read_pend == 0)
          {
          scanCtrl->next_scan_start += scanCtrl->scan_period;
          mvlu_rpt_scan_read (scanCtrl);
          }
        else
          scanCtrl->saturated = SD_TRUE;
        }
      }
    scanCtrl = (MVLU_RPT_SCAN_CTRL *) list_get_next (mvlu_rpt_scan_list, scanCtrl);
    }
  }

/************************************************************************/
/*			mvlu_rpt_scan_read 				*/
/************************************************************************/

ST_VOID mvlu_rpt_scan_read (MVLU_RPT_SCAN_CTRL *scanCtrl)
  {
MVLAS_READ_CTRL *rdCtrl;
MVLAS_RD_VA_CTRL *vaCtrl;
MVL_IND_PEND *indCtrl;
ST_INT i;

  if (scanCtrl->num_va_read_pend == 0)
    {
    scanCtrl->num_va_read_pend = scanCtrl->num_scan_va;
    scanCtrl->num_va_changes = 0;

    indCtrl = &scanCtrl->indCtrl;
    rdCtrl = &indCtrl->u.rd;
    rdCtrl->numVar = 0;
    vaCtrl = rdCtrl->vaCtrlTbl;

    for (i = 0; i < scanCtrl->num_scan_va; ++i)
      {
      vaCtrl->va = scanCtrl->scan_va[i];
      memcpy (&vaCtrl->va_scope, &scanCtrl->scan_va_scope[i], sizeof(MVL_SCOPE));
      ++vaCtrl;
      ++rdCtrl->numVar;
      }

  /* OK, now fire off the read indication functions 			*/
  /* (*u_mvlu_report_reads_done_fun) will be invoked when all are ready	*/
    u_mvl_read_ind (indCtrl);
    }
  else
    scanCtrl->saturated = SD_TRUE;
  }

/************************************************************************/
/*			_mvlu_rpt_va_reads_done				*/
/************************************************************************/
/* This function is called by 'mvlu_rd_prim_done' when all primitive	*/
/* read indication functions for a VA have been completed. This means	*/
/* that the data is ready to be sent for this VA.			*/

static ST_VOID _mvlu_rpt_va_reads_done (MVL_IND_PEND *indCtrl,
					MVL_VAR_ASSOC *va)
  {
MVLU_RPT_SCAN_CTRL *scanCtrl;
ST_INT data_size;

/* Do some housekeeping */
  scanCtrl = (MVLU_RPT_SCAN_CTRL *) indCtrl->usr_ind_ctrl;
  --scanCtrl->num_va_read_pend;

/* Let's see if the data has changed */
  data_size = mvl_type_ctrl[va->type_id].data_size;
  if (memcmp (va->last_data, va->data, data_size) != 0)
    {
    MVLU_LOG_FLOW1 ("Report VA Scan detected change for rpt VA '%s'",
			va->name);
    mvlu_rpt_va_change (va, MVLU_TRGOPS_DATA, NULL);
    ++scanCtrl->num_va_changes;
    }
  }

/************************************************************************/
/*			_mvlu_rpt_client_service 			*/
/************************************************************************/

static ST_VOID _mvlu_rpt_client_service (MVLU_RPT_CLIENT *rptClient,
				         ST_DOUBLE timeNow)
  {
MVLU_RPT_CTRL *rptCtrl;
MVLU_BASRCB *basrcb;
MVL_NVLIST_CTRL *dsNvl;
ST_RET rc;
ST_BOOLEAN sendRbe;
ST_INT incSize;

  basrcb = &rptClient->basrcb;

/* Let's see if the report is enabled */
  if (basrcb->RptEna == SD_FALSE)	/* treat any non-zero as TRUE	*/
    return;

/* Make sure we can send one if we want to ... */
  if (a_buffers_avail (rptClient->netInfo->acse_conn_id) <= 0)
    return;

/* Reporting is enabled, let's see if it is time to send the report */
  rptCtrl = rptClient->rpt_ctrl;
  dsNvl = rptCtrl->dsNvl;
  incSize = BSTR_NUMBITS_TO_NUMBYTES(dsNvl->num_of_entries);

/* First, see if Integrity scan is enabled, and if so if time to send	*/
/* This function checks time & if necessary, starts integrity scan.	*/
  mvlu_integrity_timeout (rptClient, timeNow);

/* Check for RBE enable */
/* NOTE: numTrgs ONLY incremented if changes matched the TrgOps	*/
/*       for this client. See mvlu_rpt_va_change.			*/
  if (rptClient->numTrgs > 0)
    {
  /* RBE is enabled, see if a RBE Period is selected ... */
    if (basrcb->RBEPd != 0)
      {
      if (timeNow > rptClient->next_rbe_rpt_time)
        {
      /* OK, send the report! */
        MVLU_LOG_FLOW1 ("UCA Report RBE Period timeout for client %08lx",
							rptClient);
        rc = mvlu_send_report (rptClient, MVLU_RPT_TYPE_RBE);

        rptClient->next_rbe_rpt_time = timeNow + (ST_DOUBLE) basrcb->RBEPd;
        return;
        }
      }
    else	/* NO RBE Period is selected, send right away, subject	*/
      {		/* to BufTime and Trgs parameters 			*/

    /* If neither constraint is in place, send immediately, otherwise 	*/
    /* we must exceed one or both constraints before sending		*/

      if (basrcb->Trgs == 0 && basrcb->BufTim == 0)
        sendRbe = SD_TRUE;
      else
        sendRbe = SD_FALSE;

      if (basrcb->Trgs != 0)
        {
        if (rptClient->numTrgs >= basrcb->Trgs)
          sendRbe = SD_TRUE;
        }
      if (basrcb->BufTim != 0)
        {
        if (timeNow > rptClient->buf_time_done)
          sendRbe = SD_TRUE;
        }

      /* OK, send the report! */
      if (sendRbe == SD_TRUE)
        {
        MVLU_LOG_FLOW1 ("UCA Report RBE for client %08lx", rptClient);
        rc = mvlu_send_report (rptClient, MVLU_RPT_TYPE_RBE);
        return;
        }
      }
    }
  }

/************************************************************************/
/************************************************************************/
/*			mvlu_rpt_va_change 				*/
/* CRITICAL: this function does not save the data in "va->data" until	*/
/* the very end, because depending on RCB state, it may first need to	*/
/* send a report with the previous data (see _mvlu_rpt_va_chk_reason).	*/
/************************************************************************/

ST_VOID mvlu_rpt_va_change (MVL_VAR_ASSOC *va, ST_UCHAR reason,
			    ST_VOID *new_data)
  {
MVLU_RPT_CTRL *rptCtrl;
MVLU_RPT_CLIENT *rptClient;
ST_INT va_index;
ST_INT data_size;

  MVLU_LOG_FLOW2 ("Report VA '%s' change, reason=0x%02X", va->name, reason);

  data_size = mvl_type_ctrl[va->type_id].data_size;

  /* If reason is Integrity or GI, do nothing (handled elsewhere).	*/
  if ((reason & (MVLU_TRGOPS_INTEGRITY | MVLU_TRGOPS_GI)) != 0)
    return;

  /* Check all UCA RCBs.	*/
  for (rptCtrl = mvlu_rpt_ctrl_list;
       rptCtrl != NULL;
       rptCtrl = (MVLU_RPT_CTRL *) list_get_next (mvlu_rpt_ctrl_list, rptCtrl))
    {					/* see if someone is interested	*/
    assert (rptCtrl->rcb_type == RCB_TYPE_UCA);	/* this loop only for UCA*/
    va_index = mvlu_rpt_get_va_index (rptCtrl, va);
    if (va_index >= 0)
      {
      MVLU_LOG_CFLOW1 ("VA is member of report NVL '%s'", rptCtrl->dsNvl->name);
      for (rptClient = rptCtrl->rpt_client_list;
           rptClient != NULL;
           rptClient = (MVLU_RPT_CLIENT *) list_get_next (rptCtrl->rpt_client_list, rptClient))
        {
        /* If enabled, chk "reason" (i.e. compare to TrgOps, etc.)*/
        if (rptClient->basrcb.RptEna)
          {
          _mvlu_rpt_va_chk_reason (rptClient, va, va_index, reason);
          }
        else
          MVLU_LOG_CFLOW0 ("VA change ignored: Report not enabled");
        }
      }
    }

  /* Check all IEC 61850 RCBs.	*/
  for (rptCtrl = mvl61850_rpt_ctrl_list;
       rptCtrl != NULL;
       rptCtrl = (MVLU_RPT_CTRL *) list_get_next (mvl61850_rpt_ctrl_list, rptCtrl))
    {					/* see if someone is interested	*/
    assert (rptCtrl->rcb_type != RCB_TYPE_UCA);	/* this loop only for 61850*/
    va_index = mvlu_rpt_get_va_index (rptCtrl, va);
    if (va_index >= 0)
      {
      MVLU_LOG_CFLOW1 ("VA is member of report NVL '%s'", rptCtrl->dsNvl->name);
      rptClient = &rptCtrl->only_client;	/* only one client	*/
      /* If enabled, or this is IEC BRCB & it was enabled ONCE, chk "reason".*/
      if (rptClient->basrcb.RptEna
          || (rptCtrl->rcb_type == RCB_TYPE_IEC_BRCB
              && rptCtrl->brcbCtrl.enabled_once))
        {
        _mvlu_rpt_va_chk_reason (rptClient, va, va_index, reason);
        }
      else
        MVLU_LOG_CFLOW0 ("VA change ignored: Report not enabled");
      }
    }

/* If the user is passing us the data, need to save it ... */
  if (new_data != NULL)
    {
    MVLU_LOG_CFLOW0 ("Saving report VA data");
    memcpy (va->data, new_data, data_size);
    }

/* Now copy the data into the buffer chain */
/* NOTE: Multiple datasets may reference the same variable, but each	*/
/*   creates its own "va", so each gets its own copy of "last_data".	*/
  memcpy (va->last_data, va->data, data_size);	/* save data for next compare	*/
  }

/************************************************************************/
/*			_mvlu_rpt_va_chk_reason				*/
/************************************************************************/
static ST_VOID _mvlu_rpt_va_chk_reason (MVLU_RPT_CLIENT *rptClient,
		MVL_VAR_ASSOC *va,
		ST_INT va_index,
		ST_UCHAR reason)
  {
ST_INT data_size;
ST_VOID *new_data;	/* temp copy of new data while sending old data*/

  if ((reason & (MVLU_TRGOPS_DATA | MVLU_TRGOPS_QUALITY | MVLU_TRGOPS_FREEZE)) != 0)
    {
    /* If this "reason" is enabled in TrgOps, process it.	*/
    if ((rptClient->basrcb.TrgOps.data[0] & reason) != 0)
      {
      if (rptClient->basrcb.BufTim != 0)
        {
        /* If this is first change for "ANY" VA, start BufTim timer.	*/
        if (rptClient->numTrgs == 0)
          {	/* First va change. Start BufTim timer.	*/
          rptClient->buf_time_done = sGetMsTime () +
                               (ST_DOUBLE) rptClient->basrcb.BufTim;
          MVLU_LOG_CFLOW1 ("Setting buffer Time Done = %06.3fs",
                                rptClient->buf_time_done/1000);
          }

        /* If change already detected for "THIS" VA & buftim_action = SEND_NOW, send report.*/
        if (BSTR_BIT_GET (rptClient->changed_flags, va_index)
            && rptClient->rpt_ctrl->buftim_action == MVLU_RPT_BUFTIM_SEND_NOW)
          {
          /* Send report now with last data, then process new data.*/
          /* Report is built using "va->data", so must copy old data	*/
          /* to "va->data" until after first rpt built.			*/
          data_size = mvl_type_ctrl[va->type_id].data_size;
          new_data = chk_malloc (data_size);		/* alloc buf to save new data*/
          memcpy (new_data, va->data, data_size);	/* save new data	*/
          memcpy (va->data, va->last_data, data_size);	/* restore old data*/

          MVLU_LOG_CFLOW0 ("Second change for this VA before BufTim. Sending Report with first change. Restarting BufTim.");
          /* send or queue rpt	*/
          mvlu_rpt_ready (rptClient, MVLU_RPT_TYPE_RBE);
          rptClient->buf_time_done = sGetMsTime () +
                       (ST_DOUBLE) rptClient->basrcb.BufTim;

          /* Put back new data in "va->data".	*/
          memcpy (va->data, new_data, data_size);   /* restore new data   */
          chk_free (new_data);
          }
        }	/* end "if (..BufTim != 0)"	*/

      /* Set numTrgs, changed_flags, reasons_data	*/
      ++rptClient->numTrgs;
      MVLU_LOG_CFLOW1 ("Number Triggers is now %d", (int) rptClient->numTrgs);
      BSTR_BIT_SET_ON (rptClient->changed_flags, va_index);
      rptClient->reasons_data[va_index] = reason;
      }	/* end "if client TrgOps matches reason"	*/
    else
      MVLU_LOG_CFLOW0 ("VA change ignored: Reason doesn't match TrgOps");
    }
  else if ((reason & (MVLU_TRGOPS_INTEGRITY | MVLU_TRGOPS_GI)) != 0)
    {
    assert (0);	/* Should never get here (see mvlu_rpt_va_change).	*/
    }
  else
    {
    assert (0);	/* Unrecognized TRGOPS.	*/
    }
  }

/************************************************************************/
/*			mvlu_rpt_get_va_index				*/
/************************************************************************/
/* Given a MVL report control and an associated VA, find the index of	*/
/* the Va. This index is then used for manipulating the inclusion bits,	*/
/* read bits, and reason codes.						*/

ST_INT mvlu_rpt_get_va_index (MVLU_RPT_CTRL *rptCtrl, MVL_VAR_ASSOC *va)
  {
ST_INT i;
MVL_NVLIST_CTRL *dsNvl;

  dsNvl = rptCtrl->dsNvl;

  if (dsNvl!=NULL)	/* NOTE: dsNvl may be NULL	*/
    {
    for (i = 0; i < dsNvl->num_of_entries; ++i)
      {
      if (dsNvl->entries[i] == va)
        {
        return (i);
        }
      }
    }
  return (-1);
  }

/************************************************************************/
/************************************************************************/
/************************************************************************/
/* SENDING REPORTS							*/
/************************************************************************/
/************************************************************************/
/************************************************************************/
/*			mvlu_send_report 				*/
/************************************************************************/

ST_RET mvlu_send_report (MVLU_RPT_CLIENT *rptClient, ST_INT rpt_type)
  {
MVLU_RPT_CTRL *rptCtrl;
MVL_NET_INFO *netInfo;
MVL_NVLIST_CTRL *dsNvl;
MVL_NVLIST_CTRL *rptNvl;
MVL_VAR_ASSOC *va;
ST_INT i;
ST_RET rc;
ST_INT sendIndex = 0;
ST_INT sendIndexSave;	/* save index before data, compare after data	*/
ST_UINT8 optFld;
MVLU_BASRCB *basrcb;
ST_INT incSize;		/* num bytes for inclusion bitstring	*/
MVL_VAR_ASSOC *tmp_va_arr;	/* alloc array of structs	*/
ST_INT tmp_va_arr_size;		/* num of entries in tmp_va_arr	*/
MVL_VAR_ASSOC *tmp_va;		/* current entry in tmp_va_arr	*/
MMS_BTIME6 TimeOfEntry;		/* Time this report generated.		*/

  rptCtrl = rptClient->rpt_ctrl;
  netInfo = rptClient->netInfo;
  basrcb = &rptClient->basrcb;

  optFld = basrcb->OptFlds.data_1[0];
  dsNvl = rptCtrl->dsNvl;
  rptNvl = &rptCtrl->rptNvl;

  incSize = BSTR_NUMBITS_TO_NUMBYTES(dsNvl->num_of_entries);

  MVLU_LOG_FLOW1 ("Sending UCA Report, MVL_NET_INFO %08lx", rptClient->netInfo);

  /* Need tmp va's for options plus array of reasons_data.	*/
  tmp_va_arr_size = MVLU_MAX_RPT_OPTS + dsNvl->num_of_entries;
  tmp_va_arr = M_CALLOC (MSMEM_GEN, sizeof (MVL_VAR_ASSOC), tmp_va_arr_size);
  tmp_va = tmp_va_arr;	/* start out pointing to first entry	*/

/* Prepares a UCA Report NVL to be sent, based on the 			*/
/* options, data, and inclusion bitstring in the rptCtrl. After this	*/
/* function completes, the rptCtrl->rptNvl is ready to be sent.		*/

/* If this is Integrity report, just set all inclusion bits.	*/
  if (rpt_type == MVLU_RPT_TYPE_INTEGRITY_OR_GI)
    memset (rptClient->changed_flags, 0xff, incSize);

/* We will create a NVL to send, using the dsNvl as a model ...		*/

  /* RptID and OptFlds are always the first 2 entries in the Rpt NVL.	*/
  tmp_va->type_id = rptCtrl->rpt_typeids.vstring32;
  tmp_va->data = basrcb->RptID;
  rptNvl->entries[sendIndex++] = tmp_va++;	/* set entry & point to next*/
  MVLU_LOG_CFLOW1 ("  RptID='%s", basrcb->RptID);

  tmp_va->type_id = rptCtrl->rpt_typeids.bvstring8;
  tmp_va->data = &basrcb->OptFlds;
  rptNvl->entries[sendIndex++] = tmp_va++;	/* set entry & point to next*/
  MVLU_LOG_CFLOW1 ("  OptFld = 0x%02x", (int) optFld);

  if (optFld & MVLU_SQNUM_MASK)
    {
    tmp_va->type_id = rptCtrl->rpt_typeids.int8u;
    tmp_va->data = &basrcb->SqNum;
    rptNvl->entries[sendIndex++] = tmp_va++;	/* set entry & point to next*/
    MVLU_LOG_CFLOW1 ("    SqNum : %u", (unsigned) basrcb->SqNum);
    }
  if (optFld & MVLU_RPTTIM_MASK)
    {
    /* Call user function to get report time.	*/
    u_mvlu_rpt_time_get (&TimeOfEntry);

    MVLU_LOG_CFLOW2 ("    RptTim : %lums, %lu days",
              TimeOfEntry.ms, TimeOfEntry.day);

    tmp_va->type_id = rptCtrl->rpt_typeids.btime6;
    tmp_va->data = &TimeOfEntry;
    rptNvl->entries[sendIndex++] = tmp_va++;	/* set entry & point to next*/
    }
  if (optFld & MVLU_OUTDAT_MASK)
    {
    /* OutDat is same as DatSetNa	*/
    MVLU_LOG_CFLOW1 ("    OutDat : %s", basrcb->DatSetNa);
    tmp_va->type_id = rptCtrl->rpt_typeids.vstring65;	/* Obj Ref	*/
    tmp_va->data = basrcb->DatSetNa;
    rptNvl->entries[sendIndex++] = tmp_va++;	/* set entry & point to next*/
    }

  /* Add inclusion bitstring.	*/
  tmp_va->type_id = rptCtrl->inclusion_typeid;
  tmp_va->data = rptClient->changed_flags;
  rptNvl->entries[sendIndex++] = tmp_va++;	/* set entry & point to next*/

/* Now go through the inclusion bitstring and figure out what to send	*/
  sendIndexSave = sendIndex;	/* save index before data included	*/
  for (i = 0; i < dsNvl->num_of_entries; ++i)
    {
    if (BSTR_BIT_GET (rptClient->changed_flags, i))
      {
      va = dsNvl->entries[i];
      MVLU_LOG_CFLOW3 ("  Including variable %d ('%s'), reason=0x%02X",
                      i, va->name, rptClient->reasons_data[i]);
      rptNvl->entries[sendIndex++] = va;

      /* va->data already points to latest data. Done with this va.	*/
      }
    }
  assert (sendIndex>sendIndexSave);	/* make sure SOME data included	*/

  if (optFld & MVLU_REASONS_MASK)
    {
    for (i = 0; i < dsNvl->num_of_entries; ++i)
      {
      if (BSTR_BIT_GET (rptClient->changed_flags, i))
        {
        /* Don't need BVSTR here, because size is fixed.	*/
        tmp_va->type_id = rptCtrl->rpt_typeids.bstr8;
        tmp_va->data = &rptClient->reasons_data[i];
        rptNvl->entries[sendIndex++] = tmp_va++;	/* set entry & point to next*/
	}
      }
    }
  rptNvl->num_of_entries = sendIndex;
  assert (rptNvl->num_of_entries <= rptCtrl->maxNumRptVars);	/* past end of array?*/
  assert ((tmp_va-tmp_va_arr) <= tmp_va_arr_size);	/* past end of array?*/

  rc = mvl_info_variables (rptClient->netInfo, &rptClient->rpt_ctrl->rptNvl, SD_FALSE);

  basrcb->SqNum++;		/* Increment seq number for next rpt.	*/

  /* Reset reasons, etc. to prepare for new report triggers.	*/
  memset (rptClient->reasons_data, 0, dsNvl->num_of_entries);
  memset (rptClient->changed_flags, 0, incSize);
  rptClient->numTrgs = 0;
  M_FREE (MSMEM_GEN, tmp_va_arr);
  return (rc);
  }

/************************************************************************/
/************************************************************************/
/*			_mvlu_rpt_disconnect_rcvd 			*/
/************************************************************************/

ST_VOID _mvlu_rpt_disconnect_rcvd (MVL_NET_INFO *netInfo)
  {
MVLU_RPT_CTRL *rptCtrl;
MVLU_RPT_CLIENT *rptClient;

  /* Loop through list of UCA RCBs.	*/
  for (rptCtrl = mvlu_rpt_ctrl_list;
       rptCtrl != NULL;
       rptCtrl = (MVLU_RPT_CTRL *) list_get_next (mvlu_rpt_ctrl_list, rptCtrl))
    {
    assert (rptCtrl->rcb_type == RCB_TYPE_UCA);
    /* Loop through list of clients.	*/
    for (rptClient = rptCtrl->rpt_client_list;
         rptClient != NULL;
         rptClient = (MVLU_RPT_CLIENT *) list_get_next (rptCtrl->rpt_client_list, rptClient))
      {
      if (rptClient->netInfo == netInfo)
        {
        list_unlink ((ST_VOID **) &rptCtrl->rpt_client_list, (ST_VOID *) rptClient);
        _mvlu_free_rpt_client (rptClient);
        break;	/* one client per connection */
        }
      }
    }

  /* Loop through list of IEC 61850 RCBs (URCB or BRCB).	*/
  for (rptCtrl = mvl61850_rpt_ctrl_list;
       rptCtrl != NULL;
       rptCtrl = (MVLU_RPT_CTRL *) list_get_next (mvl61850_rpt_ctrl_list, rptCtrl))
    {
    assert (rptCtrl->rcb_type != RCB_TYPE_UCA);
    rptClient = &rptCtrl->only_client;	/* only one client	*/
    if (rptClient->netInfo == netInfo)
      {	/* Reset RCB parameters.	*/
      rptClient->netInfo = NULL;
      rptClient->basrcb.RptEna = SD_FALSE;
      rptClient->basrcb.Resv = SD_FALSE;	/* Ignored for BRCB	*/
      /* If RCB DatSet is AA_SPEC, remove it (i.e. cleanup & set DatSet="").	*/
      if (rptCtrl->dsNvl != NULL &&  rptCtrl->dsNvl->nvl_scope.scope == AA_SPEC)
        mvl61850_rpt_dataset_destroy (rptCtrl);
      }
    }
  }

/************************************************************************/
/************************************************************************/
/* MVLU REPORT SCAN CONTROL						*/
/************************************************************************/
/************************************************************************/
/*			mvlu_rpt_create_scan_ctrl2 			*/
/* DEBUG: pass "enable" as additional arg?				*/
/* DEBUG: delete older "mvlu_rpt_create_scan_ctrl" function?		*/
/************************************************************************/

MVLU_RPT_SCAN_CTRL *mvlu_rpt_create_scan_ctrl2 (
	MVL_NVLIST_CTRL *nvl,
	ST_RET (*scan_done_fun) (struct mvl_ind_pend *ind_pend),
	ST_UINT report_scan_rate)	/* report scan rate (millisec)	*/
  {
MVLU_RPT_SCAN_CTRL *scanCtrl;
ST_INT scanCtrlSize;
ST_INT numScanVa = nvl->num_of_entries;
ST_INT j;

  scanCtrlSize = sizeof (MVLU_RPT_SCAN_CTRL) +
  		 (numScanVa * sizeof (MVLAS_RD_VA_CTRL)) +
  		 (numScanVa * sizeof (MVL_SCOPE)) +
  		 (numScanVa * sizeof (MVL_VAR_ASSOC *));

  scanCtrl = (MVLU_RPT_SCAN_CTRL *) M_CALLOC (MSMEM_GEN, 1, scanCtrlSize);
  scanCtrl->scan_va = (MVL_VAR_ASSOC **) (scanCtrl + 1);
  scanCtrl->num_scan_va = numScanVa;
  /* Point after array of (MVL_VAR_ASSOC *)	*/
  scanCtrl->scan_va_scope = (MVL_SCOPE *) (scanCtrl->scan_va + numScanVa);

  scanCtrl->indCtrl.event = &scanCtrl->commEvent;
  scanCtrl->commEvent.event_type = MVLU_RPT_COMM_EVENT;
  scanCtrl->indCtrl.op = MMSOP_MVLU_RPT_VA;
  /* Point after array of (MVL_SCOPE)	*/
  scanCtrl->indCtrl.u.rd.vaCtrlTbl =
		(MVLAS_RD_VA_CTRL *) (scanCtrl->scan_va_scope + numScanVa);

  scanCtrl->indCtrl.scan_va_done_fun = _mvlu_rpt_va_reads_done;

  scanCtrl->indCtrl.usr_ind_ctrl = scanCtrl;

  /* Fill in other structure elements from args passed.			*/
  for (j = 0; j < nvl->num_of_entries; ++j)
    scanCtrl->scan_va[j]= nvl->entries[j];
  memcpy (scanCtrl->scan_va_scope, nvl->va_scope,
          nvl->num_of_entries * sizeof (MVL_SCOPE));
  scanCtrl->enable = SD_TRUE;		/* ALWAYS enable		*/
  /* Optional user function to be called when each scan completes.	*/
  scanCtrl->indCtrl.usr_resp_fun = scan_done_fun;
  scanCtrl->scan_period = (ST_DOUBLE) report_scan_rate;

  list_add_last((ST_VOID **) &mvlu_rpt_scan_list, (ST_VOID *) scanCtrl);
  return (scanCtrl);
  }

/************************************************************************/
/*			mvlu_rpt_create_scan_ctrl 			*/
/************************************************************************/

MVLU_RPT_SCAN_CTRL *mvlu_rpt_create_scan_ctrl (ST_INT numScanVa)
  {
MVLU_RPT_SCAN_CTRL *scanCtrl;
ST_INT scanCtrlSize;

  scanCtrlSize = sizeof (MVLU_RPT_SCAN_CTRL) +
  		 (numScanVa * sizeof (MVLAS_RD_VA_CTRL)) +
  		 (numScanVa * sizeof (MVL_SCOPE)) +
  		 (numScanVa * sizeof (MVL_VAR_ASSOC *));

  scanCtrl = (MVLU_RPT_SCAN_CTRL *) M_CALLOC (MSMEM_GEN, 1, scanCtrlSize);
  scanCtrl->scan_va = (MVL_VAR_ASSOC **) (scanCtrl + 1);
  scanCtrl->num_scan_va = numScanVa;
  /* Point after array of (MVL_VAR_ASSOC *)	*/
  scanCtrl->scan_va_scope = (MVL_SCOPE *) (scanCtrl->scan_va + numScanVa);

  scanCtrl->indCtrl.event = &scanCtrl->commEvent;
  scanCtrl->commEvent.event_type = MVLU_RPT_COMM_EVENT;
  scanCtrl->indCtrl.op = MMSOP_MVLU_RPT_VA;
  /* Point after array of (MVL_SCOPE)	*/
  scanCtrl->indCtrl.u.rd.vaCtrlTbl =
		(MVLAS_RD_VA_CTRL *) (scanCtrl->scan_va_scope + numScanVa);

  scanCtrl->indCtrl.scan_va_done_fun = _mvlu_rpt_va_reads_done;

  scanCtrl->indCtrl.usr_ind_ctrl = scanCtrl;
  scanCtrl->scan_period = 10000;	/* Default to 10 second scan period*/

  list_add_last((ST_VOID **) &mvlu_rpt_scan_list, (ST_VOID *) scanCtrl);
  return (scanCtrl);
  }
/************************************************************************/
/*                       mvlu_rpt_destroy_scan_ctrl			*/
/* This function destroys a MVLU_RPT_SCAN_CTRL created by		*/
/* 'mvlu_rpt_create_scan_ctrl'						*/
/************************************************************************/
ST_VOID mvlu_rpt_destroy_scan_ctrl (MVLU_RPT_SCAN_CTRL *scanCtrl)
  {
  /* Take it off the scan control list */
  list_unlink ((ST_VOID **) &mvlu_rpt_scan_list, (ST_VOID *) scanCtrl);
  M_FREE (MSMEM_GEN, scanCtrl);
  }


/************************************************************************/
/************************************************************************/
/* 			BASRCB READ/WRITE HANDLERS			*/
/************************************************************************/

/************************************************************************/
/*			_mvlu_get_rcb 					*/
/************************************************************************/

static ST_VOID _mvlu_get_rcb (MVL_VAR_ASSOC *baseVa,
			RUNTIME_TYPE *rt,
			MVL_NET_INFO *netInfo,
			MVLU_RPT_CTRL **rptCtrlOut,
			MVLU_RPT_CLIENT **rptClientOut)
  {
MVLU_RPT_CTRL *rptCtrl;
MVLU_RPT_CLIENT *rptClient;

  *rptCtrlOut = NULL;
  *rptClientOut = NULL;

   /* Find the associated Report Control, using this RT as a key */
  rptCtrl = mvlu_rpt_ctrl_list;
  while (rptCtrl != NULL)		/* look through whole list	*/
    {
    if (rptCtrl->base_va == baseVa &&
        (rt > rptCtrl->rcbRtHead) &&
        (rt <= rptCtrl->rcbRtHead + rptCtrl->rcbRtHead->u.str.num_rt_blks))
      {
      *rptCtrlOut = rptCtrl;
      break;
      }
    rptCtrl = (MVLU_RPT_CTRL *) list_get_next (mvlu_rpt_ctrl_list, rptCtrl);
    }
  if (*rptCtrlOut == NULL)
    {
    MVL_LOG_NERR0 ("Warning: could not find report control");
    return;
    }

  /* Report Control found.	*/
  assert (rptCtrl->rcb_type == RCB_TYPE_UCA);	/* must be UCA URCB*/

   /* Now see if this connection has an active client */
  rptClient = rptCtrl->rpt_client_list;
  while (rptClient != NULL)		/* look through whole list	*/
    {
    if (rptClient->netInfo == netInfo)
      break;				/* return a pointer to it	*/

    rptClient = (MVLU_RPT_CLIENT *) list_get_next (rptCtrl->rpt_client_list, rptClient);
    }
  *rptClientOut = rptClient;
  }


/************************************************************************/
/*			_mvlu_get_rd_rcb 				*/
/* NOTE: Caller can access MVLU_RPT_CTRL, if needed, by using "rpt_ctrl"*/
/* member of MVLU_RPT_CLIENT.						*/
/************************************************************************/
MVLU_BASRCB *_mvlu_get_rd_rcb (MVLU_RD_VA_CTRL *mvluRdVaCtrl,
				MVLU_RPT_CLIENT **rptClientOut)
  {
MVLU_RPT_CTRL *rptCtrl;
MVLU_RPT_CLIENT *rptClient;
MVLU_BASRCB *rcb;

  /* Try to find 61850 RCB first. If found, return it.	*/
  if ((rcb = mvl61850_get_rcb (mvluRdVaCtrl->rdVaCtrl->va->base_va,
	    mvluRdVaCtrl->rt,
            rptClientOut)) != NULL)
    return (rcb);

  /* Could not find 61850 RCB, so now try to find UCA RCB.	*/

  /* NOTE: Need net_info to find correct UCA RCB, so if it's not */
  /* known, give up now. This problem can occur if user includes*/
  /* RCB element in a Report DataSet (not supported for UCA).	*/
  if (mvluRdVaCtrl->indCtrl->event == NULL  ||
      mvluRdVaCtrl->indCtrl->event->net_info == NULL)
    return (NULL);	/* don't know the connection, so can't find UCA RCB.*/

  _mvlu_get_rcb (mvluRdVaCtrl->rdVaCtrl->va->base_va,
	    mvluRdVaCtrl->rt,
  	    mvluRdVaCtrl->indCtrl->event->net_info,
	    &rptCtrl,
	    &rptClient);

  if (rptClientOut != NULL)
    *rptClientOut = rptClient;

/* If we have a report client, use it's BASRCB */
  if (rptClient != NULL)
    return (&rptClient->basrcb);

/* If not, and the report control was found, use the commom data */
  if (rptCtrl != NULL)
    return (&rptCtrl->common_basrcb);

/* Whoops, could not find the report control at all */
  return (NULL);
  }

/************************************************************************/
/*                       _mvlu_get_wr_rcb					*/
/* NOTE: Caller can access MVLU_RPT_CTRL, if needed, by using "rpt_ctrl"*/
/* member of MVLU_RPT_CLIENT.						*/
/************************************************************************/
MVLU_BASRCB *_mvlu_get_wr_rcb (MVLU_WR_VA_CTRL *mvluWrVaCtrl,
				      MVLU_RPT_CLIENT **rptClientOut)
  {
MVLU_RPT_CTRL *rptCtrl;
MVLU_RPT_CLIENT *rptClient;
MVL_NET_INFO *netInfo;
MVLU_RPT_CLIENT *foundClient=NULL;	/* If correct Client found,	*/
					/* this is set to point to it.	*/
MVLU_BASRCB *rcb;

  /* Try to find 61850 RCB first. If found, return it.	*/
  if ((rcb = mvl61850_get_rcb (mvluWrVaCtrl->wrVaCtrl->va->base_va,
	    mvluWrVaCtrl->rt,
            rptClientOut)) != NULL)
    return (rcb);

/* Find the associated Report Control */
  netInfo = mvluWrVaCtrl->indCtrl->event->net_info;
  _mvlu_get_rcb (mvluWrVaCtrl->wrVaCtrl->va->base_va,
	    mvluWrVaCtrl->rt,
  	    netInfo,
	    &rptCtrl,
	    &rptClient);

  if (rptClient)   			/* if Client was found,	*/
    foundClient = rptClient;		/* use it	*/
  else if (rptCtrl)			/* else if rptCtrl was found,	*/
    foundClient = _mvlu_add_rpt_client (rptCtrl, netInfo);	/* add client, use it*/

  /* Set caller's ptr.	*/
  if (rptClientOut != NULL)
    *rptClientOut = foundClient;

  /* If client found, return its basrcb, else return NULL.	*/
  return (foundClient ? (&foundClient->basrcb) : NULL);
  }
/************************************************************************/
/*			_rcb_writable					*/
/* Check if an RCB element is writable at this moment.			*/
/* NOTE: Special RCB elements may require additional checking.		*/
/* RETURNS: SD_TRUE if writable, SD_FALSE if NOT writable.		*/
/************************************************************************/
ST_RET _rcb_writable (MVLU_BASRCB *rcb,	MVLU_RPT_CLIENT *rptClient,
	MVLU_WR_VA_CTRL *mvluWrVaCtrl)
  {
ST_RET writable;

  writable = SD_FALSE;	/* assume NOT writable. Change if conditions correct.*/

  /* First, the "rcb" must be valid (!=NULL), and NOT enabled.	*/
  if (rcb != NULL  &&  rcb->RptEna == 0)
    {	/* if rcb is valid, rptClient must also be valid	*/
    assert (rptClient != NULL);
    if (rptClient->rpt_ctrl->rcb_type == RCB_TYPE_IEC_URCB)
      {	/* if not reserved OR reserved by us, allow write.	*/
      if (rcb->Resv == 0  ||  rptClient->netInfo == mvluWrVaCtrl->indCtrl->event->net_info)
        writable = SD_TRUE;
      }
    else	/* rcb_type == RCB_TYPE_IEC_BRCB or RCB_TYPE_UCA	*/
      {	/* nothing else to check, allow write.	*/
      writable = SD_TRUE;
      }
    }
  return (writable);
  }

/************************************************************************/
/*			_mvlu_add_rpt_client 				*/
/************************************************************************/

static MVLU_RPT_CLIENT *_mvlu_add_rpt_client (MVLU_RPT_CTRL *rptCtrl,
				              MVL_NET_INFO *netInfo)
  {
MVLU_RPT_CLIENT *rptClient;
ST_INT rptClientSize;
ST_INT numDsVars;
ST_INT changed_flags_size;

  assert (rptCtrl->rcb_type == RCB_TYPE_UCA);	/* this funct only for UCA*/
/* Alloc report control client enough to contain the reasons and 	*/
/* changed_flags data							*/
  numDsVars = rptCtrl->dsNvl->num_of_entries;
  changed_flags_size = BSTR_NUMBITS_TO_NUMBYTES(numDsVars);
  rptClientSize = sizeof (MVLU_RPT_CLIENT) +
	(numDsVars * sizeof (ST_UINT8)) +	  /* reasons_data     	*/
	2 * changed_flags_size;	/* changed_flags & segmented_inclusion	*/

  rptClient = M_CALLOC (MSMEM_GEN, 1, rptClientSize);
  rptClient->rpt_ctrl = rptCtrl;
  rptClient->netInfo = netInfo;
  memcpy (&rptClient->basrcb, &rptCtrl->common_basrcb, sizeof (MVLU_BASRCB));
  rptClient->reasons_data = (ST_UINT8 *) (rptClient + 1);
  rptClient->changed_flags = (ST_UINT8 *) (rptClient->reasons_data + numDsVars);
  rptClient->segmented_inclusion = rptClient->changed_flags + changed_flags_size;

  list_add_last((ST_VOID **) &rptCtrl->rpt_client_list, (ST_VOID *) rptClient);
  ++rptCtrl->num_rpt_clients;

  _mvlu_rpt_disconnect_rcvd_fun = _mvlu_rpt_disconnect_rcvd;
  return (rptClient);
  }

/************************************************************************/
/*			_mvlu_free_rpt_client 				*/
/************************************************************************/

static ST_VOID _mvlu_free_rpt_client (MVLU_RPT_CLIENT *rptClient)
  {
  M_FREE (MSMEM_GEN, rptClient);
  }

/************************************************************************/
/************************************************************************/
/*			mvlu_rptid_rd_ind_fun				*/
/************************************************************************/

ST_VOID mvlu_rptid_rd_ind_fun (MVLU_RD_VA_CTRL *mvluRdVaCtrl)
  {
ST_CHAR *dest;
MVLU_BASRCB *rcb;
ST_RET rc;

  rc = SD_FAILURE;

  rcb = _mvlu_get_rd_rcb (mvluRdVaCtrl, NULL);
  if (rcb != NULL)
    {
    dest = (ST_CHAR *) mvluRdVaCtrl->primData;
    strcpy (dest, rcb->RptID);
    rc = SD_SUCCESS;
    }
  mvlu_rd_prim_done (mvluRdVaCtrl, rc);
  }

/************************************************************************/
/*			mvlu_rptena_rd_ind_fun				*/
/************************************************************************/

ST_VOID mvlu_rptena_rd_ind_fun (MVLU_RD_VA_CTRL *mvluRdVaCtrl)
  {
ST_BOOLEAN *dest;
MVLU_BASRCB *rcb;
ST_RET rc;

  rc = SD_FAILURE;
  rcb = _mvlu_get_rd_rcb (mvluRdVaCtrl, NULL);
  if (rcb != NULL)
    {
    dest = (ST_BOOLEAN *) mvluRdVaCtrl->primData;
    *dest = rcb->RptEna;
    rc = SD_SUCCESS;
    }
  mvlu_rd_prim_done (mvluRdVaCtrl, rc);
  }

/************************************************************************/
/*			mvlu_resv_rd_ind_fun				*/
/************************************************************************/

ST_VOID mvlu_resv_rd_ind_fun (MVLU_RD_VA_CTRL *mvluRdVaCtrl)
  {
ST_BOOLEAN *dest;
MVLU_BASRCB *rcb;
ST_RET rc;

  rc = SD_FAILURE;
  rcb = _mvlu_get_rd_rcb (mvluRdVaCtrl, NULL);
  if (rcb != NULL)
    {
    dest = (ST_BOOLEAN *) mvluRdVaCtrl->primData;
    *dest = rcb->Resv;
    rc = SD_SUCCESS;
    }
  mvlu_rd_prim_done (mvluRdVaCtrl, rc);
  }

/************************************************************************/
/*			mvlu_datsetna_rd_ind_fun				*/
/************************************************************************/

ST_VOID mvlu_datsetna_rd_ind_fun (MVLU_RD_VA_CTRL *mvluRdVaCtrl)
  {
ST_CHAR *dest;
MVLU_BASRCB *rcb;
ST_RET rc;

  rc = SD_FAILURE;
  rcb = _mvlu_get_rd_rcb (mvluRdVaCtrl, NULL);
  if (rcb != NULL)
    {
    assert (strlen(rcb->DatSetNa) <= (size_t)abs(mvluRdVaCtrl->rt->u.p.el_len));
    dest = mvluRdVaCtrl->primData;
    strcpy (dest, rcb->DatSetNa);
    rc = SD_SUCCESS;
    }
  mvlu_rd_prim_done (mvluRdVaCtrl, rc);
  }

/************************************************************************/
/*			mvlu_optflds_rd_ind_fun				*/
/************************************************************************/

ST_VOID mvlu_optflds_rd_ind_fun (MVLU_RD_VA_CTRL *mvluRdVaCtrl)
  {
MVLU_BASRCB *rcb;
ST_RET rc;

  rc = SD_FAILURE;
  rcb = _mvlu_get_rd_rcb (mvluRdVaCtrl, NULL);
  if (rcb != NULL)
    {
    assert (rcb->OptFlds.len_1 <= sizeof(rcb->OptFlds.data_1)*8);
    bvstrcpy ((MMS_BVSTRING *) mvluRdVaCtrl->primData,
              (MMS_BVSTRING *) &rcb->OptFlds);
    rc = SD_SUCCESS;
    }
  mvlu_rd_prim_done (mvluRdVaCtrl, rc);
  }

/************************************************************************/
/*			mvlu_buftim_rd_ind_fun				*/
/************************************************************************/

ST_VOID mvlu_buftim_rd_ind_fun (MVLU_RD_VA_CTRL *mvluRdVaCtrl)
  {
ST_UINT32 *dest;
MVLU_BASRCB *rcb;
ST_RET rc;

  rc = SD_FAILURE;
  rcb = _mvlu_get_rd_rcb (mvluRdVaCtrl, NULL);
  if (rcb != NULL)
    {
    dest = (ST_UINT32 *) mvluRdVaCtrl->primData;
    *dest = rcb->BufTim;
    rc = SD_SUCCESS;
    }
  mvlu_rd_prim_done (mvluRdVaCtrl, rc);
  }

/************************************************************************/
/*	       		mvlu_trgs_rd_ind_fun				*/
/************************************************************************/

ST_VOID mvlu_trgs_rd_ind_fun (MVLU_RD_VA_CTRL *mvluRdVaCtrl)
  {
ST_UINT16 *dest;
MVLU_BASRCB *rcb;
ST_RET rc;

  rc = SD_FAILURE;
  rcb = _mvlu_get_rd_rcb (mvluRdVaCtrl, NULL);
  if (rcb != NULL)
    {
    dest = (ST_UINT16 *) mvluRdVaCtrl->primData;
    *dest = rcb->Trgs;
    rc = SD_SUCCESS;
    }
  mvlu_rd_prim_done (mvluRdVaCtrl, rc);
  }

/************************************************************************/
/*			mvlu_sqnum_rd_ind_fun				*/
/************************************************************************/

ST_VOID mvlu_sqnum_rd_ind_fun (MVLU_RD_VA_CTRL *mvluRdVaCtrl)
  {
ST_UINT8 *dest;
MVLU_BASRCB *rcb;
ST_RET rc;

  rc = SD_FAILURE;
  rcb = _mvlu_get_rd_rcb (mvluRdVaCtrl, NULL);
  if (rcb != NULL)
    {
    dest = (ST_UINT8 *) mvluRdVaCtrl->primData;
    *dest = rcb->SqNum;
    rc = SD_SUCCESS;
    }
  mvlu_rd_prim_done (mvluRdVaCtrl, rc);
  }

/************************************************************************/
/*			mvlu_trgops_rd_ind_fun				*/
/************************************************************************/

ST_VOID mvlu_trgops_rd_ind_fun (MVLU_RD_VA_CTRL *mvluRdVaCtrl)
  {
MVLU_BASRCB *rcb;
ST_RET rc;

  rc = SD_FAILURE;
  rcb = _mvlu_get_rd_rcb (mvluRdVaCtrl, NULL);
  if (rcb != NULL)
    {
    assert (rcb->TrgOps.len <= sizeof(rcb->TrgOps.data)*8);
    bvstrcpy ((MMS_BVSTRING *) mvluRdVaCtrl->primData,
              (MMS_BVSTRING *) &rcb->TrgOps);
    rc = SD_SUCCESS;
    }
  mvlu_rd_prim_done (mvluRdVaCtrl, rc);
  }

/************************************************************************/
/*			mvlu_rbepd_rd_ind_fun				*/
/************************************************************************/

ST_VOID mvlu_rbepd_rd_ind_fun (MVLU_RD_VA_CTRL *mvluRdVaCtrl)
  {
ST_UINT32 *dest;
MVLU_BASRCB *rcb;
ST_RET rc;

  rc = SD_FAILURE;
  rcb = _mvlu_get_rd_rcb (mvluRdVaCtrl, NULL);
  if (rcb != NULL)
    {
    dest = (ST_UINT32 *) mvluRdVaCtrl->primData;
    *dest = rcb->RBEPd;
    rc = SD_SUCCESS;
    }
  mvlu_rd_prim_done (mvluRdVaCtrl, rc);
  }

/************************************************************************/
/*			mvlu_intgpd_rd_ind_fun				*/
/************************************************************************/

ST_VOID mvlu_intgpd_rd_ind_fun (MVLU_RD_VA_CTRL *mvluRdVaCtrl)
  {
ST_UINT32 *dest;
MVLU_BASRCB *rcb;
ST_RET rc;

  rc = SD_FAILURE;
  rcb = _mvlu_get_rd_rcb (mvluRdVaCtrl, NULL);
  if (rcb != NULL)
    {
    dest = (ST_UINT32 *) mvluRdVaCtrl->primData;
    *dest = rcb->IntgPd;
    rc = SD_SUCCESS;
    }
  mvlu_rd_prim_done (mvluRdVaCtrl, rc);
  }

/************************************************************************/
/*			mvlu_rptid_wr_ind_fun				*/
/************************************************************************/

ST_VOID mvlu_rptid_wr_ind_fun (MVLU_WR_VA_CTRL *mvluWrVaCtrl)
  {
ST_CHAR *src;
MVLU_BASRCB *rcb;
ST_RET rc;
MVLU_RPT_CLIENT *rptClient;
ST_BOOLEAN do_purge = SD_FALSE;	/* Chg to SD_TRUE if Purge Buffer required*/

  rc = SD_FAILURE;
  rcb = _mvlu_get_wr_rcb (mvluWrVaCtrl, &rptClient);
  if (_rcb_writable (rcb, rptClient, mvluWrVaCtrl))
    {
    src = mvluWrVaCtrl->primData;
    if (rptClient->rpt_ctrl->rcb_type == RCB_TYPE_IEC_BRCB
        && strcmp (rcb->RptID, src))
      do_purge = SD_TRUE;	/* BRCB & val changed, must purge buffer*/
    strcpy (rcb->RptID, src);
    rc = SD_SUCCESS;
    }
  else
    mvluWrVaCtrl->wrVaCtrl->failure = ARE_TEMP_UNAVAIL;

  if (do_purge)
    mvl61850_brcb_rpt_lists_clean (&rptClient->rpt_ctrl->brcbCtrl);

  mvlu_wr_prim_done (mvluWrVaCtrl, rc);
  }

/************************************************************************/
/*			mvl61850_brcb_handle_rptena_wr			*/
/* Process "RptEna" write for 61850 BRCB.				*/
/************************************************************************/
ST_RET mvl61850_brcb_handle_rptena_wr (MVLU_RPT_CLIENT *rptClient, MVLU_BASRCB *rcb,
	MVL_NET_INFO *net_info, ST_BOOLEAN newval)
  {
ST_RET retCode;
  if (rptClient->netInfo == NULL  ||
      rptClient->netInfo == net_info)
    {	/* BRCB not enabled, or already enabled by us, so allow write.*/
    retCode = SD_SUCCESS;	/* allow write		*/
    if (newval != 0)
      {	/* trying to enable	*/
      rptClient->rpt_ctrl->brcbCtrl.enabled_once = SD_TRUE;
      rptClient->netInfo = net_info;	/* reserve BRCB	*/
      rcb->SqNumInt16u = 0;			/* reset SqNum	*/
      /*renxiaobao 61850 第二版修改*/
      /*使能后的第一个报文置溢出位*/
      rptClient->rpt_ctrl->brcbCtrl.BufOvfl = SD_TRUE;
      }
    else
      {	/* trying to disable	*/
      rptClient->netInfo = NULL;	/* release BRCB reservation	*/
      }
    }
  else
    {	/* RCB reserved by someone else		*/
    retCode = SD_FAILURE;	/* DO NOT allow write	*/
    }
  return (retCode);
  }
/************************************************************************/
/*			mvl61850_urcb_handle_rptena_wr			*/
/* Process "RptEna" write for 61850 URCB.				*/
/************************************************************************/
ST_RET mvl61850_urcb_handle_rptena_wr (MVLU_RPT_CLIENT *rptClient, MVLU_BASRCB *rcb,
	MVL_NET_INFO *net_info, ST_BOOLEAN newval)
  {
ST_RET retCode;
  if (rptClient->netInfo == NULL  ||
      rptClient->netInfo == net_info)
    {	/* RCB not reserved, or reserved by us	*/
    retCode = SD_SUCCESS;	/* allow write	*/
    if (newval != 0)
      {	/* enabling RCB	*/
      if (rcb->Resv == 0)	/* If URCB not reserved yet,	*/
        rcb->Resv = 1;		/* reserve it now.		*/
      rcb->SqNum = 0;	/* reset SqNum for URCB only	*/
      rptClient->netInfo = net_info;	/* reserve RCB	*/
      }
    /* If newval==0, do nothing (just allow write). RCB not released until Resv set to 0.*/
    }
  else
    {	/* RCB reserved by someone else		*/
    retCode = SD_FAILURE;	/* DO NOT allow write	*/
    }
  return (retCode);
  }

/************************************************************************/
/*			mvlu_rptena_wr_ind_fun				*/
/************************************************************************/

ST_VOID mvlu_rptena_wr_ind_fun (MVLU_WR_VA_CTRL *mvluWrVaCtrl)
  {
ST_BOOLEAN newval;	/* val to write to RptEna	*/
MVLU_BASRCB *rcb;
MVLU_RPT_CLIENT *rptClient;
ST_RET rc;
MVL_NET_INFO *net_info;	/* current connection info	*/

  newval = *(ST_BOOLEAN *) mvluWrVaCtrl->primData;
  net_info = mvluWrVaCtrl->indCtrl->event->net_info;

  rc = SD_FAILURE;
  rcb = _mvlu_get_wr_rcb (mvluWrVaCtrl, &rptClient);
  if (rcb != NULL)
    {
    /* CRITICAL: don't allow enable if dataSet not set (dsNvl==NULL).	*/
    if (newval != 0 && rptClient->rpt_ctrl->dsNvl == NULL)
      {		/* Just log it. rc already set.*/
      MVL_LOG_NERR0 ("Cannot enable RCB with NULL DataSet.");
      }
    else
      {
      switch (rptClient->rpt_ctrl->rcb_type)
        {
        case RCB_TYPE_IEC_BRCB:
          rc = mvl61850_brcb_handle_rptena_wr (rptClient, rcb, net_info, newval);
          break;
        case RCB_TYPE_IEC_URCB:
          rc = mvl61850_urcb_handle_rptena_wr (rptClient, rcb, net_info, newval);
          break;
        default:	/* must be UCA URCB	*/
          rc = SD_SUCCESS;	/* ALWAYS allow write for URCB	*/
          if (newval != 0)	/* enabling RCB	*/
            rcb->SqNum = 0;	/* reset SqNum for URCB only	*/
          break;
        }
      }
    }

  /* If allowing write, store new value.	*/
  if (rc == SD_SUCCESS)
    rcb->RptEna = newval;
  else	/* for any error, return TEMP_UNAVAIL	*/
    mvluWrVaCtrl->wrVaCtrl->failure = ARE_TEMP_UNAVAIL;

  mvlu_wr_prim_done (mvluWrVaCtrl, rc);
  }

/************************************************************************/
/*			mvlu_resv_wr_ind_fun				*/
/************************************************************************/

ST_VOID mvlu_resv_wr_ind_fun (MVLU_WR_VA_CTRL *mvluWrVaCtrl)
  {
MVLU_BASRCB *rcb;
MVLU_RPT_CLIENT *rptClient;
ST_RET rc = SD_FAILURE;

  /* Only for 61850, so use mvl61850_get_rcb, not mvlu_get_wr_rcb.	*/
  rcb = mvl61850_get_rcb (mvluWrVaCtrl->wrVaCtrl->va->base_va,
	    mvluWrVaCtrl->rt,
            &rptClient);
  if (rcb != NULL)
    {
    assert (rptClient->rpt_ctrl->rcb_type == RCB_TYPE_IEC_URCB);
    if (rptClient->netInfo == NULL  ||
        rptClient->netInfo == mvluWrVaCtrl->indCtrl->event->net_info)
      {	/* RCB not reserved, or reserved by us, so allow write.*/
      rc = SD_SUCCESS;	/* allow write	*/
      rcb->Resv = *(ST_BOOLEAN *) mvluWrVaCtrl->primData;
      if (rcb->Resv != 0)
        rptClient->netInfo = mvluWrVaCtrl->indCtrl->event->net_info;	/* reserve RCB	*/
      else
        {
        /* If RCB DatSet is AA_SPEC, remove it (i.e. cleanup & set DatSet="").	*/
        if (rptClient->rpt_ctrl->dsNvl != NULL &&
            rptClient->rpt_ctrl->dsNvl->nvl_scope.scope == AA_SPEC)
          mvl61850_rpt_dataset_destroy (rptClient->rpt_ctrl);
        rptClient->netInfo = NULL;					/* unreserve RCB*/
        }
      }
    /* else rc=SD_FAILURE already set	*/
    }

  if (rc != SD_SUCCESS)
    mvluWrVaCtrl->wrVaCtrl->failure = ARE_TEMP_UNAVAIL;

  mvlu_wr_prim_done (mvluWrVaCtrl, rc);
  }

/************************************************************************/
/*			mvlu_optflds_wr_ind_fun				*/
/************************************************************************/

ST_VOID mvlu_optflds_wr_ind_fun (MVLU_WR_VA_CTRL *mvluWrVaCtrl)
  {
MVLU_BASRCB *rcb;
ST_RET rc;
MMS_BVSTRING *bvstr;
ST_INT numbits;
MVLU_RPT_CLIENT *rptClient;

  rc = SD_FAILURE;
  rcb = _mvlu_get_wr_rcb (mvluWrVaCtrl, &rptClient);
  if (_rcb_writable (rcb, rptClient, mvluWrVaCtrl))
    {
    /* Use 'rcb_type' to figure out how many bits are writable.	*/
    ST_INT rcb_type = rptClient->rpt_ctrl->rcb_type;

    bvstr = (MMS_BVSTRING *)  mvluWrVaCtrl->primData;
    /* NOTE: don't change OptFlds.len_1. May only be writing a few	*/
    /* bits, but want to send ALL bits in read response or Info Rpt.	*/

    if (rcb_type == RCB_TYPE_UCA)
      {
      numbits = min (bvstr->len, 5);	/* 5 bits writable. Ignore higher bits.*/
      bstrcpy (rcb->OptFlds.data_1, bvstr->data, numbits);
      }
    else
      {		/* must be either IEC_URCB or IEC_BRCB	*/
      numbits = min (bvstr->len, 9);	/* 9 bits writable. Ignore higher bits.*/
      /*renxiaobao 61850 第二版修改*/
      if(bstrcmp (rcb->OptFlds.data_1, bvstr->data, numbits))
    	  memset(rcb->EntryID,0,8);
      /*end*/
      bstrcpy (rcb->OptFlds.data_1, bvstr->data, numbits);
      if (rcb_type == RCB_TYPE_IEC_URCB)
        {	/* 61850-8-1 says to ignore buffer-overflow, entryID bits, so clear them*/
        BSTR_BIT_SET_OFF (rcb->OptFlds.data_1, OPTFLD_BITNUM_BUFOVFL);
        BSTR_BIT_SET_OFF (rcb->OptFlds.data_1, OPTFLD_BITNUM_ENTRYID);
        }
      	  
      }
    rc = SD_SUCCESS;
    }
  else
    mvluWrVaCtrl->wrVaCtrl->failure = ARE_TEMP_UNAVAIL;

  mvlu_wr_prim_done (mvluWrVaCtrl, rc);
  }

/************************************************************************/
/*			mvlu_buftim_wr_ind_fun				*/
/************************************************************************/

ST_VOID mvlu_buftim_wr_ind_fun (MVLU_WR_VA_CTRL *mvluWrVaCtrl)
  {
ST_UINT32 *src;
MVLU_BASRCB *rcb;
ST_RET rc;
MVLU_RPT_CLIENT *rptClient;
ST_BOOLEAN do_purge = SD_FALSE;	/* Chg to SD_TRUE if Purge Buffer required*/

  rc = SD_FAILURE;
  rcb = _mvlu_get_wr_rcb (mvluWrVaCtrl, &rptClient);
  if (_rcb_writable (rcb, rptClient, mvluWrVaCtrl))
    {
    src = (ST_UINT32 *) mvluWrVaCtrl->primData;
    if (rptClient->rpt_ctrl->rcb_type == RCB_TYPE_IEC_BRCB
        && rcb->BufTim != *src)
    {
      do_purge = SD_TRUE;	/* BRCB & val changed, must purge buffer*/
      /*renxiaobao 61850 第二版修改*/
      memset(rcb->EntryID,0,8);
    }
    rcb->BufTim = *src;
    rc = SD_SUCCESS;
    }
  else
    mvluWrVaCtrl->wrVaCtrl->failure = ARE_TEMP_UNAVAIL;

  if (do_purge)
    mvl61850_brcb_rpt_lists_clean (&rptClient->rpt_ctrl->brcbCtrl);

  mvlu_wr_prim_done (mvluWrVaCtrl, rc);
  }

/************************************************************************/
/*			mvlu_trgs_wr_ind_fun				*/
/* NOTE: for UCA RCB only.						*/
/************************************************************************/

ST_VOID mvlu_trgs_wr_ind_fun (MVLU_WR_VA_CTRL *mvluWrVaCtrl)
  {
ST_UINT16 *src;
MVLU_BASRCB *rcb;
ST_RET rc;

  rc = SD_FAILURE;
  rcb = _mvlu_get_wr_rcb (mvluWrVaCtrl, NULL);
  if (rcb != NULL && rcb->RptEna == SD_FALSE)	/* if enabled, can't write*/
    {
    src = (ST_UINT16 *) mvluWrVaCtrl->primData;
    rcb->Trgs = *src;
    rc = SD_SUCCESS;
    }
  else
    mvluWrVaCtrl->wrVaCtrl->failure = ARE_TEMP_UNAVAIL;

  mvlu_wr_prim_done (mvluWrVaCtrl, rc);
  }

/************************************************************************/
/*                       mvlu_sqnum_wr_ind_fun				*/
/* NOTE: for UCA RCB only. In IEC 61850 RCB, SqNum may not be written.	*/
/************************************************************************/

ST_VOID mvlu_sqnum_wr_ind_fun (MVLU_WR_VA_CTRL *mvluWrVaCtrl)
  {
ST_UINT8 *src;
MVLU_BASRCB *rcb;
ST_RET rc;

  rc = SD_FAILURE;
  rcb = _mvlu_get_wr_rcb (mvluWrVaCtrl, NULL);
  if (rcb != NULL && rcb->RptEna == SD_FALSE)	/* if enabled, can't write*/
    {
    src =(ST_UINT8 *)  mvluWrVaCtrl->primData;
    rcb->SqNum = *src;
    rc = SD_SUCCESS;
    }
  else
    mvluWrVaCtrl->wrVaCtrl->failure = ARE_TEMP_UNAVAIL;

  mvlu_wr_prim_done (mvluWrVaCtrl, rc);
  }

/************************************************************************/
/*			mvlu_trgops_wr_ind_fun				*/
/************************************************************************/
extern int  rcb_TrgOps_wr_check(char *rpt_id,char wr_data);
ST_VOID mvlu_trgops_wr_ind_fun (MVLU_WR_VA_CTRL *mvluWrVaCtrl)
  {
MVLU_BASRCB *rcb;
ST_RET rc;
MMS_BVSTRING *bvstr;
MVLU_RPT_CLIENT *rptClient;
ST_BOOLEAN do_purge = SD_FALSE;	/* Chg to SD_TRUE if Purge Buffer required*/

  rc = SD_FAILURE;
  rcb = _mvlu_get_wr_rcb (mvluWrVaCtrl, &rptClient);
  if (_rcb_writable (rcb, rptClient, mvluWrVaCtrl))
    {
    bvstr = (MMS_BVSTRING *)  mvluWrVaCtrl->primData;
    /* NOTE: don't change TrgOps.len. May only be writing a few	*/
    /* bits, but want to send ALL bits in read response or Info Rpt.	*/

   /* assert (bvstr->len <= (sizeof (rcb->TrgOps.data)*8));*/
    if (rptClient->rpt_ctrl->rcb_type == RCB_TYPE_IEC_BRCB
        && bstrcmp (rcb->TrgOps.data, bvstr->data, bvstr->len))
    {
      do_purge = SD_TRUE;	/* BRCB & val changed, must purge buffer*/
      /*renxiaobao 61850 第二版修改*/
      memset(rcb->EntryID,0,8);
    }
    if(rcb_TrgOps_wr_check(rptClient->rpt_ctrl->common_basrcb.RptID,bvstr->data[0]))
		bstrcpy (rcb->TrgOps.data, bvstr->data, bvstr->len);



    rc = SD_SUCCESS;
    }
  else
    mvluWrVaCtrl->wrVaCtrl->failure = ARE_TEMP_UNAVAIL;

  if (do_purge)
    mvl61850_brcb_rpt_lists_clean (&rptClient->rpt_ctrl->brcbCtrl);

  mvlu_wr_prim_done (mvluWrVaCtrl, rc);
  }

/************************************************************************/
/*			mvlu_rbepd_wr_ind_fun				*/
/* NOTE: for UCA RCB only.						*/
/************************************************************************/

ST_VOID mvlu_rbepd_wr_ind_fun (MVLU_WR_VA_CTRL *mvluWrVaCtrl)
  {
ST_UINT32 *src;
MVLU_BASRCB *rcb;
ST_RET rc;

  rc = SD_FAILURE;
  rcb = _mvlu_get_wr_rcb (mvluWrVaCtrl, NULL);
  if (rcb != NULL && rcb->RptEna == SD_FALSE)	/* if enabled, can't write*/
    {
    src = (ST_UINT32 *) mvluWrVaCtrl->primData;
    rcb->RBEPd = *src;
    rc = SD_SUCCESS;
    }
  else
    mvluWrVaCtrl->wrVaCtrl->failure = ARE_TEMP_UNAVAIL;

  mvlu_wr_prim_done (mvluWrVaCtrl, rc);
  }

/************************************************************************/
/*			mvlu_intgpd_wr_ind_fun				*/
/************************************************************************/

ST_VOID mvlu_intgpd_wr_ind_fun (MVLU_WR_VA_CTRL *mvluWrVaCtrl)
  {
ST_UINT32 *src;
MVLU_BASRCB *rcb;
ST_RET rc;
MVLU_RPT_CLIENT *rptClient;
ST_BOOLEAN do_purge = SD_FALSE;	/* Chg to SD_TRUE if Purge Buffer required*/

  rc = SD_FAILURE;
  rcb = _mvlu_get_wr_rcb (mvluWrVaCtrl, &rptClient);
  if (_rcb_writable (rcb, rptClient, mvluWrVaCtrl))
    {
    src = (ST_UINT32 *) mvluWrVaCtrl->primData;
    if (rptClient->rpt_ctrl->rcb_type == RCB_TYPE_IEC_BRCB
        && rcb->IntgPd != *src)
    {
      do_purge = SD_TRUE;	/* BRCB & val changed, must purge buffer*/
      /*renxiaobao 61850 第二版修改*/
      rptClient->next_integ_rpt_time = 0;	/* reset timer		*/
      memset(rcb->EntryID,0,8);
    }
    rcb->IntgPd = *src;
    rc = SD_SUCCESS;
    }
  else
    mvluWrVaCtrl->wrVaCtrl->failure = ARE_TEMP_UNAVAIL;

  if (do_purge)
    mvl61850_brcb_rpt_lists_clean (&rptClient->rpt_ctrl->brcbCtrl);

  mvlu_wr_prim_done (mvluWrVaCtrl, rc);
  }

/************************************************************************/
/************************************************************************/
/* For backward compatibility ... do not use */
ST_VOID mvlu_seqnum_rd_ind_fun (MVLU_RD_VA_CTRL *mvluRdVaCtrl)
  {
  mvlu_sqnum_rd_ind_fun (mvluRdVaCtrl);
  }
ST_VOID mvlu_seqnum_wr_ind_fun (MVLU_WR_VA_CTRL *mvluWrVaCtrl)
  {
  mvlu_sqnum_wr_ind_fun (mvluWrVaCtrl);
  }

/*
 *			mvlu_integrity_scan_destroy
 * Free data allocated by mvlu_integrity_scan_read.
 */
ST_VOID mvlu_integrity_scan_destroy (MVL_IND_PEND *indCtrl)
  {
  M_FREE (MSMEM_GEN, indCtrl);
  }

/*
 *			mvlu_integrity_scan_read
 * This function "BEGINS" the scan of ALL data for one Report DataSet (NVL).
 * It is called automatically when an Integrity Report or a
 * General Interrogation Report needs to be sent. This is much more
 * efficient than constantly scanning.
 * - It allocates & initializes a temporary MVL_IND_PEND structure.
 * - It calls "u_mvl_read_ind" to begin the scan.
 * - The scan may complete synchronously or asynchronously.
 * - When the scan completes, the funct pointed to by "scan_done_fun" arg
 *   is called to build the report and cleanup.
 * CRITICAL: Function pointed to by "scan_done_fun" must call
 * "mvlu_integrity_scan_destroy" to free temporary MVL_IND_PEND struct.
 */

ST_VOID mvlu_integrity_scan_read (MVLU_RPT_CLIENT *rptClient,
			ST_VOID (*scan_va_done_fun)(MVL_IND_PEND *indCtrl,
					MVL_VAR_ASSOC *va),
			ST_RET (*scan_done_fun)(MVL_IND_PEND *indCtrl)
			)
  {
MVL_IND_PEND *indCtrl;

  /* Create a simulated read indication for the DataSet NVL.	*/
  indCtrl = mvlu_setup_scan_read (rptClient->rpt_ctrl->dsNvl,
                          scan_va_done_fun, scan_done_fun);

  indCtrl->usr_ind_ctrl = rptClient;	/* Save rptClient		*/

  /* OK, now fire off the read indication functions.	*/
  u_mvl_read_ind (indCtrl);
  }

/**
 *			mvlu_setup_scan_read
 * This function sets up the scan of ALL data for one NVL.
 * It allocates & initializes a temporary MVL_IND_PEND structure for a
 * simulated read indication.
 * RETURNS: (MVL_IND_PEND *)
 */

MVL_IND_PEND *mvlu_setup_scan_read (MVL_NVLIST_CTRL *nvl,
		ST_VOID (*scan_va_done_fun)(MVL_IND_PEND *indCtrl,
					MVL_VAR_ASSOC *va),
		ST_RET (*scan_done_fun)(MVL_IND_PEND *indCtrl)
		)
  {
MVLAS_RD_VA_CTRL *vaCtrl;
MVL_IND_PEND *indCtrl;
ST_INT j;

/* Allocate space for MVL_IND_PEND & array of MVLAS_RD_VA_CTRL.	*/
  indCtrl = M_CALLOC (MSMEM_GEN, 1,
              sizeof (MVL_IND_PEND) +
              nvl->num_of_entries * sizeof (MVLAS_RD_VA_CTRL));

  indCtrl->u.rd.numVar = nvl->num_of_entries;
  indCtrl->op = MMSOP_MVLU_RPT_VA;	/* indicates this is not normal read*/

  indCtrl->u.rd.vaCtrlTbl = (MVLAS_RD_VA_CTRL *)(indCtrl+1);/*point after indCtrl*/

  for (j = 0, vaCtrl = indCtrl->u.rd.vaCtrlTbl;
       j < nvl->num_of_entries;
       j++, vaCtrl++)
    {
    vaCtrl->va = nvl->entries[j];
    memcpy (&vaCtrl->va_scope, &nvl->va_scope[j], sizeof(MVL_SCOPE));
    }

  /* Save funct ptr. Called from "mvlu_rd_prim_done" when scan of ONE va complete.*/
  indCtrl->scan_va_done_fun = scan_va_done_fun;

  /* Save funct ptr. Called from "mvlu_rd_prim_done" when scan complete.*/
  indCtrl->usr_resp_fun = scan_done_fun;	/* CRITICAL:	*/

  return (indCtrl);
  }

/************************************************************************/
/*			mvlu_integrity_scan_va_done			*/
/* This function is called by 'mvlu_rd_prim_done' when all "leaf"	*/
/* functions for a VA have been completed (i.e. data ready for this VA).*/
/************************************************************************/

ST_VOID mvlu_integrity_scan_va_done (MVL_IND_PEND *indCtrl,
					MVL_VAR_ASSOC *va)
  {
  ST_INT data_size;
  /* CRITICAL: DO NOT call mvlu_rpt_va_change. rptClient->reasons_data	*/
  /*     will be set in mvlu_integrity_scan_done when scan completes.	*/
  data_size = mvl_type_ctrl[va->type_id].data_size;
  memcpy (va->last_data, va->data, data_size);
  }

/************************************************************************/
/*			mvlu_integrity_scan_done			*/
/* Same as "_mvlu_rpt_scan_done" except arg is (MVL_IND_PEND *)		*/
/* instead of (MVLU_RPT_SCAN_CTRL *).					*/
/*									*/
/* This function is called when the integrity scan is complete.		*/
/* (i.e. 'mvlu_rd_prim_done' has been called for all "leafs" of all	*/
/* variables in the report). Everything is ready to build a report,	*/
/* so build it now.							*/
/************************************************************************/

ST_RET mvlu_integrity_scan_done (MVL_IND_PEND *indCtrl)
  {
ST_RET retCode;
MVLU_RPT_CLIENT *rptClient;
ST_INT j;

  /* Get "rptClient", saved in "indCtrl" when scan initialized.	*/
  rptClient = (MVLU_RPT_CLIENT *) indCtrl->usr_ind_ctrl;

  /* Set "reason" for each variable of Dataset now.	*/
  for (j = 0; j < rptClient->rpt_ctrl->dsNvl->num_of_entries; j++)
    rptClient->reasons_data[j] = MVLU_TRGOPS_INTEGRITY;

  /* send or queue rpt	*/
  retCode = mvlu_rpt_ready (rptClient, MVLU_RPT_TYPE_INTEGRITY_OR_GI);

  if (retCode != SD_SUCCESS)
    MVLU_LOG_FLOW1 ("Integrity report send failed: err=0x%X", retCode);

  /* set time for next integrity report	*/
  rptClient->next_integ_rpt_time = sGetMsTime ()
                     + (ST_DOUBLE) rptClient->basrcb.IntgPd;
  rptClient->integ_scan_in_progress = SD_FALSE;
  mvlu_integrity_scan_destroy (indCtrl);	/* destroy temporary struct*/
  return (retCode);
  }

/************************************************************************/
/*			find_or_create_typeid				*/
/* Try to find the type_id. If that fails, try to create it.		*/
/************************************************************************/
ST_INT find_or_create_typeid (ST_CHAR *type_name, ST_CHAR *tdl)
  {
ST_INT type_id;
  if ((type_id = mvl_typename_to_typeid (type_name)) < 0)	/* find	*/
    type_id = mvl_type_id_create_from_tdl (type_name, tdl);	/* create*/
  return (type_id);
  }

/************************************************************************/
/*			mvlu_rpt_find_typeids				*/
/* Find or create ALL types needed to encode reports.			*/
/* These types may have been defined in the ODF file and created by	*/
/* Foundry or may have been created by an earlier call to this function.*/
/* RETURNS:	SD_SUCCESS if ALL types found or created		*/
/*		SD_FAILURE if ANY type could not be found or created.	*/
/************************************************************************/
ST_RET mvlu_rpt_find_typeids (MVLU_RPT_TYPEIDS *rpt_typeids)
  {
ST_RET retCode = SD_FAILURE;	/* assume FAILURE for now	*/
ST_CHAR *type_name;		/* name of type to be found	*/

  /* stop on any error	*/
  do
    {	/* "one-time" loop: just to have something to break out of	*/
    if ((rpt_typeids->mmsbool = find_or_create_typeid (type_name = "RTYP_BOOL", "Bool")) < 0)
      break;
    if ((rpt_typeids->bstr6 = find_or_create_typeid (type_name = "RTYP_BSTR6", "Bstring6")) < 0)
      break;
    if ((rpt_typeids->bstr8 = find_or_create_typeid (type_name = "RTYP_BSTR8", "Bstring8")) < 0)
      break;
    if ((rpt_typeids->bvstring6 = find_or_create_typeid (type_name = "RTYP_BVSTR6", "Bvstring6")) < 0)
      break;
    if ((rpt_typeids->bvstring8 = find_or_create_typeid (type_name = "RTYP_BVSTR8", "Bvstring8")) < 0)
      break;
    if ((rpt_typeids->bvstring10 = find_or_create_typeid (type_name = "RTYP_BVSTR10", "Bvstring10")) < 0)
      break;
    if ((rpt_typeids->btime6 = find_or_create_typeid (type_name = "RTYP_BTIME6", "Btime6")) < 0)
      break;
    if ((rpt_typeids->int8u = find_or_create_typeid (type_name = "RTYP_INT8U", "Ubyte")) < 0)
      break;
    if ((rpt_typeids->int16u = find_or_create_typeid (type_name = "RTYP_INT16U", "Ushort")) < 0)
      break;
    if ((rpt_typeids->int32u = find_or_create_typeid (type_name = "RTYP_INT32U", "Ulong")) < 0)
      break;
    if ((rpt_typeids->ostring8 = find_or_create_typeid (type_name = "RTYP_OSTR8", "Ostring8")) < 0)
      break;
    if ((rpt_typeids->vstring32 = find_or_create_typeid (type_name = "RTYP_VSTR32", "Vstring32")) < 0)
      break;
    if ((rpt_typeids->vstring65 = find_or_create_typeid (type_name = "RTYP_VSTR65", "Vstring65")) < 0)
      break;
    if ((rpt_typeids->vstring129 = find_or_create_typeid (type_name = "RTYP_VSTR129", "Vstring129")) < 0)
      break;

    retCode = SD_SUCCESS;  /* If we get here, all were successful	*/
    } while (0);	/* end of "one-time" loop	*/

  if (retCode)
    MVL_LOG_ERR1 ("Can't find or create type '%s'", type_name);
  return (retCode);	/* If ANY find failed, SD_FAILURE is returned	*/
  }

/************************************************************************/
/*			mvlu_integrity_timeout				*/
/* Check for Integrity period timeout. If timeout occurred, start	*/
/* integrity scan.							*/
/* RETURNS: SD_TRUE if IntgPd is set and timeout occurred		*/
/*          SD_FALSE otherwise						*/
/************************************************************************/
ST_RET mvlu_integrity_timeout (MVLU_RPT_CLIENT *rptClient, ST_DOUBLE timeNow)
  {
ST_RET ret;

  if (rptClient->basrcb.IntgPd != 0
      && BSTR_BIT_GET (rptClient->basrcb.TrgOps.data, TRGOPS_BITNUM_INTEGRITY)	/* enabled*/
      && (!rptClient->integ_scan_in_progress)
      && timeNow > rptClient->next_integ_rpt_time)
    {
    MVLU_LOG_FLOW1 ("UCA Report Integrity Period timeout for client %08lx",
							rptClient);

    /* Send last buffered entry (due to BufTm buffering) BEFORE this integrity report.*/
    if (rptClient->numTrgs > 0)
      {		/* send or queue report	*/
      mvlu_rpt_ready (rptClient, MVLU_RPT_TYPE_RBE);
      }

    /* Set scan_in_progress flag so new scan can't start before last
     * scan finished. Clear flag in "mvlu_integrity_scan_done".
     */
    rptClient->integ_scan_in_progress = SD_TRUE;

    /* This function "begins" the integrity scan. When the scan is
     * complete, "mvlu_integrity_scan_done" is called to build the
     * the report. If ALL "leaf" functions are synchronous,
     * "mvlu_integrity_scan_done" is called BEFORE this function returns.
     */
    mvlu_integrity_scan_read (rptClient,
                mvlu_integrity_scan_va_done,
                mvlu_integrity_scan_done);
    ret = SD_TRUE;
    }
  else
    ret = SD_FALSE;
  return (ret);
  }

/************************************************************************/
/*			mvlu_rpt_rcb_type_find				*/
/* Find the runtime type of the RCB and save it in *rcbHeadOut & *numRtOut.*/
/************************************************************************/
ST_RET mvlu_rpt_rcb_type_find (ST_INT type_id, ST_CHAR *basrcbName,
	RUNTIME_TYPE **rcbHeadOut, ST_INT *numRtOut)
  {
ST_RET ret = SD_FAILURE;
ST_CHAR *ptr;

  ptr = strstr (basrcbName, "$");
  if (ptr)
    {
    ++ptr;	/* Skip the '$' */
    if (mvlu_find_comp_type (type_id, ptr, rcbHeadOut, numRtOut) == SD_SUCCESS)
      {
      if ((*rcbHeadOut)->el_tag == RT_STR_START)
        ret = SD_SUCCESS;	/* only path to success	*/
      else
        MVL_LOG_NERR1 ("'%s' is not a structure", basrcbName);
      }
    else
      MVL_LOG_NERR1 ("Could not find type for component '%s'", basrcbName);
    }
  else
    MVL_LOG_NERR1 ("Invalid BASRCB name: %s", basrcbName);

  return (ret);
  }

/************************************************************************/
/*			mvlu_rpt_ready					*/
/* Report is ready to send or save in "buffer".				*/
/* Check the RCB type and call appropriate function.			*/
/************************************************************************/
ST_RET mvlu_rpt_ready (MVLU_RPT_CLIENT *rptClient, ST_INT rpt_type)
  {
ST_RET retCode;
  switch (rptClient->rpt_ctrl->rcb_type)
    {
    case RCB_TYPE_UCA:
      retCode = mvlu_send_report (rptClient, rpt_type);
      break;
    case RCB_TYPE_IEC_BRCB:
      retCode = mvl61850_brcb_rpt_save (rptClient);	/* save rpt in buffer*/
      break;
    case RCB_TYPE_IEC_URCB:
      retCode = mvl61850_urcb_rpt_send (rptClient->rpt_ctrl, rptClient, rpt_type);
      break;
    default:
      retCode = SD_FAILURE;	/* should never happen	*/
      break;
    }
  return (retCode);
  }

/************************************************************************/
/*			mvlu_rpt_ctrl_destroy_all			*/
/* Destroy all UCA report controls.					*/
/************************************************************************/
ST_VOID mvlu_rpt_ctrl_destroy_all ()
  {
MVLU_RPT_CTRL *rptCtrl;
MVLU_RPT_SCAN_CTRL *scanCtrl;
  /* Use list_find_last to avoid calling list_unlink twice.	*/
  while ((scanCtrl = (MVLU_RPT_SCAN_CTRL *) list_find_last ((DBL_LNK *)mvlu_rpt_scan_list)) != NULL)
    {
    mvlu_rpt_destroy_scan_ctrl (scanCtrl);	/* this also unlinks it from list*/
    }

  /* Use list_find_last to avoid calling list_unlink twice.	*/
  while ((rptCtrl = (MVLU_RPT_CTRL *) list_find_last ((DBL_LNK *)mvlu_rpt_ctrl_list)) != NULL)
    {
    mvlu_free_rpt_ctrl (rptCtrl);	/* this also unlinks it from list*/
    }
  }

