/************************************************************************/
/* SISCO SOFTWARE MODULE HEADER *****************************************/
/************************************************************************/
/*   (c) Copyright Systems Integration Specialists Company, Inc., 	*/
/*      	  1986 - 2001, All Rights Reserved.		        */
/*									*/
/*		    PROPRIETARY AND CONFIDENTIAL			*/
/*									*/
/* MODULE NAME : ms_tdef.c						*/
/* PRODUCT(S)  : MMS-EASE						*/
/*									*/
/* MODULE DESCRIPTION :							*/
/*	This module contains some MMS-EASE virtual machine support	*/
/*	functions that help the user with MMS variable access and	*/
/*	type specifications.		 				*/
/*									*/
/* GLOBAL FUNCTIONS DEFINED IN THIS MODULE :				*/
/*									*/
/* MODIFICATION LOG :							*/
/*  Date     Who   Rev			Comments			*/
/* --------  ---  ------   -------------------------------------------	*/
/* 06/22/07  JRB     26    Log max on buffer overrun.			*/
/* 01/17/06  NAV     25    ms_runtime_to_tdl: fix tdl_buf overwrite	*/
/* 07/12/04  JRB     24    For RT_UTF8_STRING, chg "t18" to "t16" in	*/
/*			   "tdlTypes" to match latest 61850-8-1.	*/ 
/* 05/03/04  JRB     23    ms_mk_asn1_type: return err if struct empty,	*/
/*			   ignore extra commas, use ASN1_ENC_* macros,	*/
/*			   & log the ASN.1 produced.			*/
/*			   type_subst: remove whitespace in dest string.*/
/* 10/13/03  JRB     22    ms_runtime_to_tdl: Chg assert to err return.	*/
/* 07/14/03  JRB     21    Use strlen(comp_name), was assuming maxlen=32*/
/* 04/22/03  JRB     20    Allow fixed len UTF8 only if local fmt=UTF16.*/
/* 04/02/03  JRB     19    Add UTF8string support (see RT_UTF8_STRING).	*/
/*			   Use "RT_*" defines in "switch (type)".	*/
/*			   DRASTICALLY simplify ms_runtime_to_tdl.	*/
/*			   add_tdl_separator: add args (& fix spelling):*/
/*			   - allows 3 global vars to be eliminated.	*/
/*			   - allows semaphore to be eliminated.		*/
/* 12/15/02  JRB     18    Add ms_tdl_to_runtime function.		*/
/* 11/11/02  JRB     17    Replace ms_asn1_to.. w/ ms_runtime_create.	*/
/* 03/04/02  JRB     16    Add more SD_CONST. Add str_util.h.		*/
/* 12/20/01  JRB     15    Converted to use ASN1R (re-entrant ASN1)	*/
/* 11/13/01  EJV     14    Added support for new MMS type UtcTime:	*/
/*			   tdlTypes[]: added new type "Utctime" [17]	*/
/*			   ms_mk_asn1_type: added case for type 17.	*/
/*			   anticipated[]: added type 17 and moved other.*/
/*			   ms_runtime_to_tdl: added RT_UTC_TIME case.	*/
/* 03/08/01  JRB     13    Chk return val from ms_comp_name_find.	*/
/* 08/24/00  JRB     12    Use ms_comp_name_find, so code works for	*/
/*			   MMS_LITE (i.e. USE_RT_TYPE_2 or USE_RT_TYPE_3*/
/*			   defined).					*/
/* 07/28/00  JRB     11    Use new defines in #ifdef.			*/
/* 09/13/99  MDE     10    Added SD_CONST modifiers			*/
/* 09/21/98  MDE     09    Minor lint cleanup				*/
/* 08/21/98  MDE     08    Eliminated warning in ms_asn1_to_tdl.	*/
/* 06/03/98  MDE     07    Now check 'ms_asn1_to_runtime' return code 	*/
/* 03/11/98  MDE     06    Removed NEST_RT_TYPES			*/
/* 02/10/98  MDE     05    Runtime type changes for LITE		*/
/* 10/30/97  MDE     04    Calculate size of RT table before alloc	*/
/* 09/16/97  MDE     03    Now don't require @VMD for referenced type	*/
/* 06/19/97  MDE     02    Added 64 bit integer support, cleanup	*/
/* 06/09/97  MDE     01    Changed Runtime Type, and it's use		*/
/* 04/02/97  DTL   7.00    MMSEASE 7.0 release. See MODL70.DOC for	*/
/*			   history.					*/
/************************************************************************/

#include "glbtypes.h"
#include "sysincs.h"
#include "glbsem.h"
#include "mmsdefs.h"
#include "asn1defs.h"
#include "mem_chk.h"
#include "str_util.h"	/* for strnicmp	*/

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

/************************************************************************/
/* 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


/************************************************************************/
/* TDL DEFINES								*/
/* Defined constants giving the primitive syntax for the keywords:	*/
/* Bool, Byte, Short, Long, Ubyte, Ushort, Ulong, Float, Double,	*/
/* Vstring, Fstring, Ostring, Bstring, Gtime, Btime4, Btime8, respec-	*/
/* tively.								*/
/* These are used in 'ms_mk_asn1_type' for substitutions		*/
/************************************************************************/

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

static ST_INT type_subst (ST_CHAR *dest_str, ST_CHAR *src_str, ST_INT src_len);
static ST_RET wr_object_name (ASN1_ENC_CTXT *aCtx, ST_CHAR *objname);
static ST_UCHAR *err_rtn (ST_RET error, ST_CHAR *tspec);

#ifdef MMS_LITE
ST_INT  m_rt_type_limit = 50;	/* max number of runtime blocks per def */
#endif

/* These checks catch if a define change breaks the code using "tdlTypes"*/
/* The way "tdlTypes" is used, does not work if any of the RT_* defines change.*/
/* It would be better to modify the code to always use the RT_* defines,*/
/* but that is not so easy now.						*/
#if (RT_BOOL           != 3)
#error define does't match expected. tdlTypes array won't work.
#endif
#if (RT_BIT_STRING     != 4)
#error define does't match expected. tdlTypes array won't work.
#endif
#if (RT_INTEGER        != 5)
#error define does't match expected. tdlTypes array won't work.
#endif
#if (RT_UNSIGNED       != 6)
#error define does't match expected. tdlTypes array won't work.
#endif
#if (RT_FLOATING_POINT != 7)
#error define does't match expected. tdlTypes array won't work.
#endif
#if (RT_OCTET_STRING   != 9)
#error define does't match expected. tdlTypes array won't work.
#endif
#if (RT_VISIBLE_STRING != 10)
#error define does't match expected. tdlTypes array won't work.
#endif
#if (RT_GENERAL_TIME   != 11)
#error define does't match expected. tdlTypes array won't work.
#endif
#if (RT_BINARY_TIME    != 12)
#error define does't match expected. tdlTypes array won't work.
#endif
#if (RT_BCD            != 13)
#error define does't match expected. tdlTypes array won't work.
#endif
#if (RT_BOOLEANARRAY   != 14)
#error define does't match expected. tdlTypes array won't work.
#endif
#if (RT_UTC_TIME       != 17)
#error define does't match expected. tdlTypes array won't work.
#endif
#if (RT_UTF8_STRING    != 16)
#error define does't match expected. tdlTypes array won't work.
#endif

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

typedef struct 
  {
  ST_CHAR *abbrev;
  ST_CHAR *subType;
  } TDL_TYPES;

/* tdlTypes array
 * CRITICAL: the number after the "t" in the 2nd string of each struct
 * MUST match the ASN.1 tag for that type. The tags are defined in "mms_vvar.h"
 * and they have a prefix of "RT_".
 * For example, the ASN.1 tag for Boolean is 3, so "mms_vvar.h" has the define:
 *   #define RT_BOOL 3
 * and the string in this array for Boolean is:
 *   "t3"
 */

TDL_TYPES tdlTypes[] =
  {
    { "Bool", 		"t3" 		},
    { "Byte", 		"t5+8" 		},
    { "Short", 		"t5+16" 	},
    { "Long", 		"t5+32" 	},
#ifdef INT64_SUPPORT
    { "Int64", 		"t5+64" 	},
#endif
    { "Ubyte", 		"t6+8" 		},
    { "Ushort", 	"t6+16" 	},
    { "Ulong", 		"t6+32" 	},
#ifdef INT64_SUPPORT
    { "Uint64", 	"t6+64" 	},
#endif
    { "Float", 		"t7+2071" 	},
    { "Double", 	"t7+2868" 	},
    { "Vstring",	"t10-" 		},
    { "Fstring",	"t10+" 		},
    { "Ostring",	"t9+" 		},
    { "OVstring",	"t9-" 		},
    { "Bstring", 	"t4+" 		},
    { "BVstring", 	"t4-" 		},
    { "Gtime", 		"t11" 		},
    { "Btime4", 	"t12+0" 	},
    { "Btime6", 	"t12+1" 	},
    { "Bcd", 		"t13+" 		},
    { "Utctime",   	"t17" 		},
    { "UTF8Vstring",   	"t16-" 		},
#if (UNICODE_LOCAL_FORMAT==UNICODE_UTF16)
    { "UTF8string",   	"t16+" 		},
#endif
  }; 
ST_INT numTdlTypes = (sizeof (tdlTypes)/sizeof(TDL_TYPES));

/************************************************************************/
/*			   ms_mk_asn1_type				*/
/*  Function to convert a character string type specification to an	*/
/*  ASN.1 string, as required by the ms_add_named_type function.  It	*/
/*  is assumed that the function strt_asn1 has been called elsewhere	*/
/*  in the user's program.  The input 'tspec' character string must 	*/
/*  conform to the following syntax for specifying a type:		*/
/*									*/
/*	Let N be a number, i.e. a sequence of digits.  Let 		*/
/*	IDENTIFIER be a character string which is a valid MMS 		*/
/*	identifier.  And let OBJECT_NAME be a character string 		*/
/*	which represents a valid MMS object name of the form:  		*/
/*	string1:IDENTIFIER, where string1 is "+" for a VMD-		*/
/*	specific object, "-" for an AA-specific object, or a		*/
/*	valid IDENTIFIER naming a domain for a domain-specific 		*/
/*	object.  Then we have the following Bacchus-Nauer rules		*/
/*	for specifying the syntax:					*/
/*									*/
/*	Type ::= <OBJECT_NAME> | tN+N | tN-N | tN | [N:Type] 		*/
/*		| [pN:Type] | {Component_list} | {p:Component_list}	*/
/*	Component_list ::= Component | Component_list,Component		*/
/*	Component ::= Type | (IDENTIFIER)Type 	 			*/
/*									*/
/*  The above syntax may contain white space (blanks, tabs and new-	*/
/*  lines) to make it more readable.  (Hence, a character string	*/
/*  constant input for 'tspec' may extend over several lines.)  	*/
/*  However, the symbols appearing in the definition above (called	*/
/*  terminal symbols), such as '<', '>', 't', '+', ,'-', '[', ':', ']',	*/
/*  '{', '}', '(', ')', 'p' and ',', must be used exactly as specified.	*/
/*  The meaning of the above syntax is given below:			*/
/*									*/
/*	<OBJECT_NAME>   - Name of a type object that has already been	*/
/*			  declared to MMS-EASE using ms_add_named_type	*/
/*			  or ms_add_std_types.				*/
/*	tN+N, tN-N, tN  - Primitive type, where the first N indicates	*/
/*			  the basic type, and the second N is the	*/
/*			  length of the type (normally in bits, but	*/
/*			  for octet-strings and visible-strings, it	*/
/*			  represents the number of bytes, and floats	*/
/*			  are a special case - see note below).  The 	*/
/*			  basic types are explained below.  If the type */
/*			  length (+N or -N) is left off, it defaults 	*/
/*			  to zero.  Note that char strings may have 	*/
/*			  negative type lengths, indicating variable-	*/
/*			  length (i.e., can be <= N), but all  other	*/
/*			  types must have nonnegative type lengths.	*/
/*	[N:Type]	- Array of N elements of the specified type.	*/
/*     {Component_list} - Structure consisting of one or more compo-	*/
/*			  nents, separated by commas, with or without	*/
/*			  component names.				*/
/*     (IDENTIFIER)Type - A component of a structure identified by a	*/
/*			  component name, in order that it may be 	*/
/*			  referenced in a subsequent alternate access.	*/
/*	p		- Used in arrays or structures to indicate 	*/
/*			  that it is 'packed', i.e. compressed in some 	*/
/*			  implementation-specific way.			*/
/*									*/
/*  The basic types are numbers (first N following the 't' of a primi-	*/
/*  tive type) with the following meanings:				*/
/*									*/
/*	3  - Boolean, type length = 0 (or absent). Ex: "t3".		*/
/*	4  - Bit-string, type length >= 0.  Ex: "t4+27" for a 27-bit	*/
/*		bit-string.						*/
/*	5  - Integer, type length = 16 or 32 for most computers.  Ex:	*/
/*		"t5+16" for a 2-byte integer, "t5+32" for a 4-byte int.	*/
/*	6  - Unsigned, type length = 16 or 32 for most computers.  Ex:	*/
/*		"t6+16" for a 2-byte unsigned integer.			*/
/*	7  - Floating point, type length = 2071 for single precision	*/
/*		or 2868 for double precision on most computers.  See	*/
/*		the note below.						*/
/*	9  - Octet-string, type length >= 0.  Ex: "t9+19" for a 19-	*/
/*		byte octet-string.					*/
/*	10 - Visible-string, any type length.  Ex: "t10+30" specifies 	*/
/*		a fixed-length string of exactly 30 characters, whereas */
/*		"t10-12" specifies a variable-length string of at most 	*/
/*		12 characters.						*/
/*	11 - Generalized-time, type length = 0 (or absent).  Ex: "t11".	*/
/*	12 - Binary-time, type length = 0 or 1 (SD_FALSE or SD_TRUE).  Ex:	*/
/*		"t12+1" for a 6-byte binary-time time of day, including */
/*		date.							*/
/*	13 - BCD, type length >= 0  AND <= 8				*/
/*		computers).  Ex: "t13+8" for a 8-nibble BCD integer.	*/
/*      17 - UTC Time is struct of 3 unsigned integers encoded as	*/
/*		8 bytes octet string (ssssqmmm), where 			*/	
/*              ssss seconds since Jan 1, 1970, 4 bytes padded with	*/
/*		     leading 0's					*/
/*		q    quality flags, 1 byte				*/
/*              mmm  microseconds in a second, 3 bytes in range		*/
/*		     [0,999999], padded with 0's.			*/
/*	     Note: this is new type added to the spec in 2001/2002.	*/
/*	16 - UTF8string, any type length.  Ex: "t16+30" specifies 	*/
/*		a fixed-length string of exactly 30 characters, whereas */
/*		"t16-12" specifies a variable-length string of at most 	*/
/*		12 characters.						*/
/*									*/
/*	NOTE: Floating point is a special case in that its length is	*/
/*	a combination of two numbers: fractional width (say 52 bits) 	*/
/*	and exponent width (say 11 bits).  The length of the floating	*/
/*	point type is computed according to the following algorithm:	*/
/*			N = e*256 + f, 					*/
/*	where f = fractional width and e = exponent width.  For stan-	*/
/*	dard IEEE floating point format, N = 2071 for single-precision 	*/
/*	and N = 2868 for double-precision.  Hence, most users will use 	*/
/*	the following notation for floating point types:		*/
/*	  "t7+2071" for single prec. and "t7+2868" for double prec.	*/
/*	WARNING: This function will not accept type specifications for	*/
/*	floating point exponents with a width greater than 127 bits or	*/
/*	mantissas with a width greater than 255 bits.			*/
/*									*/
/*  The other input arguments of this function are the length of a 	*/
/*  buffer to hold the ASN.1 string, and a pointer to that buffer.  	*/
/*  The 'asn1_len' argument is modified to equal the actual length of	*/
/*  the ASN.1 string produced.  					*/
/*									*/
/*  If there is no error in this function, the return value is a 	*/
/*  pointer to where the ASN.1 string starts in	the buffer.  (The 	*/
/*  ASN.1 string always ends at the end of the buffer, but it begins 	*/
/*  anywhere within the buffer.)  If an error does occur in this 	*/
/*  function, a NULL pointer is returned, and the global variable	*/
/*  'mms_op_err' gives further detail as to the nature of the problem 	*/
/*  that has caused this function to fail.				*/
/************************************************************************/

#define TYPE_NAME 	0
#define BEG_STRUCT 	2
#define PACKED 		0
#define COMPONENT_NAME 	0 
#define BEG_COMP_TYPE 	1 


ST_UCHAR *ms_mk_asn1_type (ST_INT *asn1_len, ST_UCHAR *asn1, ST_CHAR *type_spec)
  {
ST_CHAR *tspec;
ST_INT type;  
ST_BOOLEAN literal;  
ST_BOOLEAN number;
ST_INT type_len;
ST_BOOLEAN name;
ST_BOOLEAN m_packed;
ST_INT i;
ST_LONG num_elts;
ST_UCHAR acc_float_size;
ASN1_ENC_CTXT localEncCtx;	/* For readability, use "aCtx" to access this.*/
ASN1_ENC_CTXT *aCtx = &localEncCtx;


/* Initialize everything first.						*/

  S_LOCK_COMMON_RESOURCES ();
  num_elts = 0L;
  number = SD_FALSE;
  name = SD_FALSE;
  literal = SD_FALSE;
  m_packed = SD_FALSE;
  type_len = 0;
  i = strlen (type_spec);
  tspec = (ST_CHAR *) chk_malloc (2*i);
  asn1r_strt_asn1_bld (aCtx, asn1,*asn1_len);

/*  Replace abbreviations in the type definition string with the	*/
/*  appropriate syntax and reset new length of type spec string.	*/
  if ((i = type_subst (tspec,type_spec,i)) < 1)
    {
    S_UNLOCK_COMMON_RESOURCES ();
    return (err_rtn (MVE_TYPEDEF_LEN0,tspec));
    }
/*  Loop through the 'tspec' string, from the back to the front.	*/

  while (i > 0)
    {
    switch (tspec[--i])
      {
      case '>' :
	if (number || m_packed)
          {
          S_UNLOCK_COMMON_RESOURCES ();
	  return (err_rtn (MVE_TYPEDEF_SYM_GT,tspec));
          }
	literal = SD_TRUE;
	tspec[i] = '\0';
	asn1r_strt_constr (aCtx);
	break;

      case '<' :
	if (number || m_packed)
          {
          S_UNLOCK_COMMON_RESOURCES ();
	  return (err_rtn (MVE_TYPEDEF_SYM_LT,tspec));
          }
	literal = SD_FALSE;
	if (wr_object_name (aCtx, &tspec[i+1]))
          {
          S_UNLOCK_COMMON_RESOURCES ();
	  return (err_rtn (MVE_TYPEDEF_OBJNAME,tspec));
          }
	asn1r_fin_constr (aCtx, TYPE_NAME, CTX, 0);
	break;

      case '}' :
	if (number || m_packed)
          {
          S_UNLOCK_COMMON_RESOURCES ();
	  return (err_rtn (MVE_TYPEDEF_SYM_RBRACE,tspec));
          }
	asn1r_strt_constr (aCtx);				/* Start structure type.*/
	asn1r_strt_constr (aCtx);				/* Start component part.*/
	asn1r_strt_constr (aCtx);				/* Start last component.*/
	asn1r_strt_constr (aCtx);				/* Start component's 	*/
	break;					/* explicit type spec.	*/

      case '{' :
	if (number)
          {
          S_UNLOCK_COMMON_RESOURCES ();
	  return (err_rtn (MVE_TYPEDEF_SYM_LBRACE,tspec));
          }
        if (tspec[i+1]=='}')	/* Empty struct	*/
          {
          MLOG_ERR2 ("ms_mk_asn1_type: empty struct defined starting at offset %d TDL=%s", i, &tspec[i]);
          S_UNLOCK_COMMON_RESOURCES ();
	  return (err_rtn (MVE_TYPEDEF_SYM_LBRACE,tspec));
          }
	if (!name)
	  asn1r_fin_constr (aCtx, BEG_COMP_TYPE, CTX, 0);
	asn1r_fin_constr (aCtx, SEQ_CODE, UNI, 0);
	name = SD_FALSE;
	asn1r_fin_constr (aCtx, 1, CTX, 0);
	if (m_packed)
	  {
	  asn1r_wr_i16 (aCtx, 1);
	  asn1r_fin_prim (aCtx, PACKED,CTX);
	  }
	asn1r_fin_constr (aCtx, BEG_STRUCT, CTX, 0);
	m_packed = SD_FALSE;
	break;

      case ',' :
	if (number)
          {
          S_UNLOCK_COMMON_RESOURCES ();
	  return (err_rtn (MVE_TYPEDEF_SYM_COMMA,tspec));
          }
        if (tspec[i+1]=='}' || tspec[i+1]==',')	/* Ignore extra commas	*/
          {
          MLOG_ENC0 ("ms_mk_asn1_type: ',' followed by ',' or '}' in TDL string ignored.");
          }
        else
          {
          if (!name)
            asn1r_fin_constr (aCtx, BEG_COMP_TYPE, CTX, 0);
          asn1r_fin_constr (aCtx, SEQ_CODE, UNI, 0);
          name = SD_FALSE;
          asn1r_strt_constr (aCtx);
          asn1r_strt_constr (aCtx);
          }
	break;

      case ')' :
	if (number || m_packed)
          {
          S_UNLOCK_COMMON_RESOURCES ();
	  return (err_rtn (MVE_TYPEDEF_SYM_RPAREN,tspec));
          }
	asn1r_fin_constr (aCtx, BEG_COMP_TYPE, CTX, 0);
	name = SD_TRUE;
	literal = SD_TRUE;
	tspec[i] = '\0';
	break;	

      case '(' :
	if (number || m_packed)
          {
          S_UNLOCK_COMMON_RESOURCES ();
	  return (err_rtn (MVE_TYPEDEF_SYM_LPAREN,tspec));
          }
	literal = SD_FALSE;
	asn1r_wr_vstr (aCtx, &tspec[i+1]);
	asn1r_fin_prim (aCtx, COMPONENT_NAME, CTX);
	break;

      case 'p' :
        if (literal)
          break;
	if (m_packed)
          {
          S_UNLOCK_COMMON_RESOURCES ();
	  return (err_rtn (MVE_TYPEDEF_SYM_P,tspec));
          }
	m_packed = SD_TRUE;
	break;	

      case '+' :
        if (literal)
          break;
	if (!number || m_packed)
          {
          S_UNLOCK_COMMON_RESOURCES ();
	  return (err_rtn (MVE_TYPEDEF_SYM_PLUS,tspec));
          }
	type_len = atoi (&tspec[i+1]);
	tspec[i] = '\0';
	number = SD_FALSE;
	break;

      case '-' :
        if (literal)
          break;
	if (!number || m_packed)
          {
          S_UNLOCK_COMMON_RESOURCES ();
	  return (err_rtn (MVE_TYPEDEF_SYM_MINUS,tspec));
          }
	type_len = -atoi (&tspec[i+1]);
	tspec[i] = '\0';
	number = SD_FALSE;
	break;

      case 't' :
	if (literal)
	  break;
	if (!number || m_packed)
          {
          S_UNLOCK_COMMON_RESOURCES ();
	  return (err_rtn (MVE_TYPEDEF_SYM_T,tspec));
          }
	type = atoi (&tspec[i+1]);
	switch (type)
	  {
	  case RT_BOOL :
	  case RT_GENERAL_TIME :
	  case RT_UTC_TIME :
	    if (type_len)
              {
              S_UNLOCK_COMMON_RESOURCES ();
	      return (err_rtn (MVE_TYPEDEF_BADLEN3,tspec));
              }
	    asn1r_fin_prim (aCtx, (ST_UINT16) type,CTX);
	    break;

	  case RT_BIT_STRING :
	  case RT_OCTET_STRING :
	  case RT_VISIBLE_STRING :
          case RT_UTF8_STRING:
	    asn1r_wr_i32 (aCtx, (ST_INT32) type_len);
	    asn1r_fin_prim (aCtx, (ST_UINT16) type,CTX);
	    break;

	  case RT_INTEGER :
	  case RT_UNSIGNED :
	  case RT_BINARY_TIME :
	    if (type_len > 127)
              {
              S_UNLOCK_COMMON_RESOURCES ();
	      return (err_rtn (MVE_TYPEDEF_BADLEN5,tspec));
              }
	    asn1r_wr_u8 (aCtx, (ST_UCHAR) type_len);
	    asn1r_fin_prim (aCtx, (ST_UINT16) type, CTX);
	    break;
			
	  case RT_BCD :
	    if (type_len > 8 || type_len <= 0)
              {
              S_UNLOCK_COMMON_RESOURCES ();
	      return (err_rtn (MVE_TYPEDEF_BADLEN5,tspec));
              }
	    asn1r_wr_u8 (aCtx, (ST_UCHAR) type_len);
	    asn1r_fin_prim (aCtx, (ST_UINT16) type, CTX);
	    break;

	  case RT_FLOATING_POINT :
	    if (type_len < 256)
              {
              S_UNLOCK_COMMON_RESOURCES ();
	      return (err_rtn (MVE_TYPEDEF_BADLEN7,tspec));
              }
	    asn1r_strt_constr (aCtx);
	    asn1r_wr_u8 (aCtx, (ST_UCHAR) ((type_len>>8) & 0xFF));	  /* write exp	*/
	    asn1r_fin_prim (aCtx, INT_CODE,UNI);

/* For IS MMS, first octet is exponent + sign + mantissa 		*/
/* For DIS MMS, first octet is mantissa 				*/

            if (!m_use_dis_float)
              {
	      acc_float_size =(ST_UCHAR) ((type_len>>8) & 0xFF);	/* exp 	*/
	      acc_float_size += (ST_UCHAR)(type_len & 0xFF);	/* fract*/
              ++acc_float_size;					/* sign */
  	      asn1r_wr_u8 (aCtx, acc_float_size);
              }
            else
              asn1r_wr_u8 (aCtx, (ST_UCHAR)(type_len & 0xFF));		/* write fract	*/

	    asn1r_fin_prim (aCtx, INT_CODE,UNI);
	    asn1r_fin_constr (aCtx, (ST_UINT16) type, CTX, 0);
	    break;

	  default :
            S_UNLOCK_COMMON_RESOURCES ();
     	    return (err_rtn (MVE_TYPEDEF_BADTYPE,tspec));
	  }
	type_len = 0;
	number = SD_FALSE;
	break;

      case ':' :
	if (literal)
	  break;
	if (number || m_packed)
          {
          S_UNLOCK_COMMON_RESOURCES ();
	  return (err_rtn (MVE_TYPEDEF_SYM_COLON,tspec));
          }
	tspec [i] = '\0';
	num_elts = -1L;
	break;

      case ']' :
	if (number || m_packed)
          {
          S_UNLOCK_COMMON_RESOURCES ();
	  return (err_rtn (MVE_TYPEDEF_SYM_RBRACKET,tspec));
          }
	asn1r_strt_constr (aCtx);
	asn1r_strt_constr (aCtx);
	tspec [i] = '\0';
	break;

      case '[' :
	if (!number || !num_elts)
          {
          S_UNLOCK_COMMON_RESOURCES ();
	  return (err_rtn (MVE_TYPEDEF_SYM_LBRACKET,tspec));
          }
	asn1r_fin_constr (aCtx, 2,CTX,0);
	if (m_packed)
	  num_elts = atol (strchr(&tspec[i+1],'p') + 1);
	else
	  num_elts = atol (&tspec[i+1]);
	if (num_elts < 1L)
          {
          S_UNLOCK_COMMON_RESOURCES ();
	  return (err_rtn (MVE_TYPEDEF_NUM_ELTS,tspec));
          }
	asn1r_wr_i32 (aCtx, num_elts);
	asn1r_fin_prim (aCtx, 1, CTX);
	if (m_packed)
	  {
	  asn1r_wr_i16 (aCtx, 1);
	  asn1r_fin_prim (aCtx, PACKED,CTX);
	  }
	asn1r_fin_constr (aCtx, 1, CTX, 0);
	num_elts = 0L;
	number = SD_FALSE;
	m_packed = SD_FALSE;
	break;

      case ' ' :
      case '\t' :
      case '\n' :
	break;

      default :
	if (literal)
	  break;
	if (tspec[i] >= '0' && tspec[i] <= '9')
	  {
	  if (m_packed)
            {
            S_UNLOCK_COMMON_RESOURCES ();
	    return (err_rtn (MVE_TYPEDEF_SYM_DIGIT,tspec));
            }
	  number = SD_TRUE;
	  break;
	  }
	else
          {
          S_UNLOCK_COMMON_RESOURCES ();
	  return (err_rtn (MVE_TYPEDEF_SYM_OTHER,tspec));
          }
      }
    }				/* end of while loop		*/

  
  chk_free (tspec);
  if (aCtx->asn1r_encode_overrun)
    {
    mms_op_err = ME_ASN1_ENCODE_OVERRUN;
    S_UNLOCK_COMMON_RESOURCES ();
    return (NULL);
    }
  S_UNLOCK_COMMON_RESOURCES ();
  MLOG_ENC1 ("ASN.1 type def encoded from TDL=%s", type_spec);
  MLOG_ENCH (ASN1_ENC_LEN(aCtx), ASN1_ENC_PTR(aCtx));
  *asn1_len = ASN1_ENC_LEN(aCtx);
  return (ASN1_ENC_PTR(aCtx));
  }

/************************************************************************/
/*			      type_subst				*/
/*  Function to pre-processes type definition strings.  It substitutes 	*/
/*  primitive syntax for certain abbreviations defined in the 'abbrev' 	*/
/*  array.  The syntax of the resulting string is able to be parsed 	*/
/*  simply by ms_mk_asn1_type.  This function returns the length of the */
/*  destination string.							*/
/************************************************************************/

static ST_INT type_subst (ST_CHAR *dest_str, ST_CHAR *src_str, ST_INT src_len)
  {
ST_BOOLEAN literal1, literal2;	/* booleans indicating we're in a type	*/
      			/* name or component name literal string*/
ST_INT i, j, k, n;		/* indexes and counters			*/
ST_BOOLEAN found;			/* boolean indicating a string starting	*/
				/* with a capital letter matches an	*/
				/* abbreviation and is to be replaced	*/

  literal1 = literal2 = SD_FALSE;
  i = j = 0;

  while (i < src_len)		/* step through the source string	*/
    {
    if (literal1)
      {
      if (src_str[i] == '>')	/* Keep track of when a type name	*/
	literal1 = SD_FALSE;	/* (delimited by '<' and '>') is	*/
      }				/* beginning and ending.  Abbreviations	*/
    else if (literal2)		/* are not substituted inside type	*/
      {				/* names.  Ditto for component names	*/
      if (src_str[i] == ')')	/* (delimited by '(' and ')').		*/
	literal2 = SD_FALSE;
      }
    else if (src_str[i] == '<')
      literal1 = SD_TRUE;
    else if (src_str[i] == '(')
      literal2 = SD_TRUE;
    else if (src_str[i] >= 'A' && src_str[i] <= 'z')
      {
      found = SD_FALSE;
      for (n = 0; n < numTdlTypes; n++)
	{
	if (strnicmp (tdlTypes[n].abbrev, &src_str[i], strlen (tdlTypes[n].abbrev)))
	  continue;
	else
	  {
	  found = SD_TRUE;
	  break;
	  }
	}
      if (found)
	{
	k = strlen (tdlTypes[n].subType);
	memcpy (&dest_str[j],tdlTypes[n].subType,k);
	j += k;
	i += strlen (tdlTypes[n].abbrev);
	continue;
	}
      }
    if (!isspace(src_str[i]))	/* do not copy whitespace chars	*/
      dest_str[j++] = src_str[i];
    i++;			/* ALWAYS increment src index	*/
    }
  dest_str[j] = '\0';
  return (j);
  }



/************************************************************************/
/*			   wr_object_name				*/
/*  Function to write an object name string to the ASN.1 stream 	*/
/*  according to the ObjectName production in the MMS protocol 		*/
/*  specification.  The input string takes the form:			*/
/*  string1:IDENTIFIER, where string1 is "@VMD" for a VMD-specific 	*/
/*  object,  "@AA" for an AA-specific object, or a valid IDENTIFIER 	*/
/*  naming a domain for a domain-specific object.  IDENTIFIER is the 	*/
/*  actual name of the object within the domain, VMD, or association-	*/
/*  specific environment.  Returns 0 if successful, nonzero otherwise.	*/
/************************************************************************/

static ST_RET wr_object_name (ASN1_ENC_CTXT *aCtx, ST_CHAR *objname)
  {
ST_CHAR *ptr;

  if ((ptr = strchr (objname,':')) == NULL)	/* check for colon	*/
    {
    asn1r_wr_vstr (aCtx, objname);		/* Default is VMD specific		*/
    asn1r_fin_prim (aCtx, 0,CTX);
    return (SD_SUCCESS);
    }

  *ptr = '\0';				/* replace colon with null char	*/
  ptr++;				/* "ptr" pts to string after ':'*/
  if (!strcmp (objname,"@VMD"))
    {
    asn1r_wr_vstr (aCtx, ptr);
    asn1r_fin_prim (aCtx, 0,CTX);
    }
  else if (!strcmp (objname,"@AA"))
    {
    asn1r_wr_vstr (aCtx, ptr);
    asn1r_fin_prim (aCtx, 2,CTX);
    }
  else
    {
    asn1r_strt_constr (aCtx);			/* Domain-specific when string	*/
    asn1r_wr_vstr (aCtx, ptr);			/* before ':' is anything else	*/
    asn1r_fin_prim (aCtx, VISTR_CODE,UNI);
    asn1r_wr_vstr (aCtx, objname);
    asn1r_fin_prim (aCtx, VISTR_CODE,UNI);
    asn1r_fin_constr (aCtx, 1,CTX,0);
    }    
  return (SD_SUCCESS);
  }


/************************************************************************/
/*			      err_rtn					*/
/*  Function to be called when there is an error in ms_mk_asn1_type.	*/
/************************************************************************/

static ST_UCHAR *err_rtn (ST_RET error, ST_CHAR *tspec)
  {
  mms_op_err = error;
  chk_free (tspec);
  return (NULL);
  }


/************************************************************************/
/*			    ms_asn1_to_tdl				*/
/************************************************************************/

ST_CHAR *ms_asn1_to_tdl (ST_UCHAR *asn1_ptr, ST_INT asn1_len, ST_INT max_tdl_len)
  {
ST_CHAR	*tdl_buf;
ST_INT	tdl_len;
RUNTIME_CTRL *rt_ctrl;

  S_LOCK_COMMON_RESOURCES ();

  /* Create temporary runtime type table.			*/
  /* Last arg allows biggest array "theoretically" possible to allocate.*/
  rt_ctrl = ms_runtime_create (asn1_ptr, asn1_len,
                               (UINT_MAX/sizeof(RUNTIME_TYPE))-100);
  if (!rt_ctrl)			/* if ASN.1 decode failed		*/
    {
    mms_op_err = MVE_ASN1_TO_RT;
    S_UNLOCK_COMMON_RESOURCES ();
    return (NULL);		/* quit 				*/
    }

  tdl_buf = (ST_CHAR *) chk_calloc (1, max_tdl_len);
  tdl_len = ms_runtime_to_tdl (rt_ctrl->rt_first, rt_ctrl->rt_num, tdl_buf, max_tdl_len);

  if (tdl_len == 0)
    {
    mms_op_err = MVE_ASN1_TO_RT;
    chk_free (tdl_buf);
    S_UNLOCK_COMMON_RESOURCES ();
    return (NULL);
    }

  tdl_buf = (ST_CHAR *) chk_realloc (tdl_buf, tdl_len);
  ms_runtime_destroy (rt_ctrl);	/* destroy temp rt type	*/

  S_UNLOCK_COMMON_RESOURCES ();
  return (tdl_buf);
  }

/************************************************************************/
/*			     ms_runtime_to_tdl				*/
/************************************************************************/
static ST_INT add_tdl_separator (ST_CHAR *tdl_buf, ST_INT index,
				ST_BOOLEAN member_found,
				ST_BOOLEAN named_component_found);

ST_INT ms_runtime_to_tdl (SD_CONST RUNTIME_TYPE *rt, ST_INT rt_num,
			  ST_CHAR *tdl_buf, ST_INT max_tdl_len)
  {
ST_INT			i;
ST_INT			total_size;
SD_CONST RUNTIME_TYPE *	rt_ptr;
#define	SIZE_OF_TEMP_BUF	30
ST_CHAR			temp_buf[SIZE_OF_TEMP_BUF];
ST_INT			temp_size;	/* len of string in temp_buf	*/
ST_BOOLEAN		overrun_detected;
SD_CONST ST_CHAR *	comp_name;	/* component name	*/
ST_INT			comp_name_len;	/* len of component name*/

struct	struct_tracking {
  ST_BOOLEAN	member_found;
  };
struct struct_tracking	nest_level[ASN1_MAX_LEVEL];
ST_BOOLEAN named_component_found;

ST_INT 	cur_nest_level;

  for (i = 0; i < ASN1_MAX_LEVEL; i++)
    {
    nest_level[i].member_found = SD_FALSE;
    }

  i=0;
  overrun_detected = SD_FALSE;
  rt_ptr = rt;
  total_size = 0;
  cur_nest_level = 0;
  while ((i < rt_num) && (overrun_detected == SD_FALSE))
    {
    named_component_found = SD_FALSE;	/* assume comp name not found	*/
    comp_name = ms_comp_name_find (rt_ptr);
    if (comp_name != NULL  &&  comp_name[0] != 0)
      {
      comp_name_len = strlen (comp_name);

      /* Make sure there's room for comp_name + separator + '(' + ')'.	*/
      if ((total_size + comp_name_len + 3) < max_tdl_len)
        {
        /* CRITICAL: named_component_found is not yet set for this call.*/
        total_size += add_tdl_separator (tdl_buf, total_size,
                      nest_level[cur_nest_level].member_found,
                      named_component_found);
        strcpy (&tdl_buf[total_size], "(");
        total_size += 1;
        strcpy (&tdl_buf[total_size],comp_name);
        total_size += comp_name_len;
        strcpy (&tdl_buf[total_size], ")");
        total_size += 1;
        }
      else
        {
        overrun_detected = SD_TRUE;
        }
      named_component_found = SD_TRUE;
      }

    if (rt_ptr->el_tag == RT_ARR_END || rt_ptr->el_tag == RT_STR_END)
      nest_level[cur_nest_level].member_found = SD_FALSE;
    else
      {
      if (total_size + 1 < max_tdl_len)
	{
	total_size += add_tdl_separator (tdl_buf, total_size,
		      nest_level[cur_nest_level].member_found,
		      named_component_found);
	}
      else
	overrun_detected = SD_TRUE;
      nest_level[cur_nest_level].member_found = SD_TRUE;
      }
      
    temp_buf[0] = '\0';		/* start with NULL string	*/
    switch (rt_ptr->el_tag)
      {
      case RT_ARR_START	  :
        sprintf (temp_buf, "[%d:", (ST_INT)rt_ptr->u.arr.num_elmnts);
        cur_nest_level++;
        break;

      case RT_STR_START	  :
        strcpy (temp_buf, "{");
        cur_nest_level++;
        break;

      case RT_BOOL	  :	  
        strcpy (temp_buf, "Bool");
        break;

      case RT_BIT_STRING  :	  
        if (rt_ptr->u.p.el_len < 0)
          sprintf (temp_buf, "BVstring%d", abs (rt_ptr->u.p.el_len));
        else
          sprintf (temp_buf, "Bstring%d", rt_ptr->u.p.el_len);
        break;

      case RT_INTEGER	  :
        switch (rt_ptr->u.p.el_len)
          {
          case 1:
            strcpy (temp_buf, "Byte");
            break;
          case 2:
            strcpy (temp_buf, "Short");
            break;
          case 4:
            strcpy (temp_buf, "Long");
            break;
#ifdef INT64_SUPPORT
          case 8:
            strcpy (temp_buf, "Int64");
            break;
#endif
          }
        break;

      case RT_UNSIGNED	  :
        switch (rt_ptr->u.p.el_len)
          {
          case 1:
            strcpy (temp_buf, "Ubyte");
            break;
          case 2:
            strcpy (temp_buf, "Ushort");
            break;
          case 4:
            strcpy (temp_buf, "Ulong");
            break;
#ifdef INT64_SUPPORT
          case 8:
            strcpy (temp_buf, "Uint64");
            break;
#endif
          }
        break;

      case RT_FLOATING_POINT :
        switch (rt_ptr->u.p.el_len)
          {
          case 4:
            strcpy (temp_buf, "Float");
            break;
          case 8:
            strcpy (temp_buf, "Double");
            break;
          }
        break;

      case RT_OCTET_STRING   :	  
        if (rt_ptr->u.p.el_len < 0)
          sprintf (temp_buf, "OVstring%d", abs (rt_ptr->u.p.el_len));
        else
          sprintf (temp_buf, "Ostring%d", rt_ptr->u.p.el_len);
        break;

      case RT_VISIBLE_STRING :
        if (rt_ptr->u.p.el_len < 0)
          sprintf (temp_buf, "Vstring%d", abs (rt_ptr->u.p.el_len));
        else
          sprintf (temp_buf, "Fstring%d", rt_ptr->u.p.el_len);
        break;

      case RT_GENERAL_TIME   :	  
        strcpy (temp_buf, "Gtime");
        break;

      case RT_BINARY_TIME    :	  
        sprintf (temp_buf, "Btime%d", rt_ptr->u.p.el_len);
        break;

      case RT_BCD	     :	  
        sprintf (temp_buf, "Bcd%d", (ST_INT)rt_ptr->u.p.el_len);
        break;

      case RT_BOOLEANARRAY   :	  
        break;

      case RT_UTC_TIME:	  
        strcpy (temp_buf, "Utctime");
        break;

      case RT_UTF8_STRING:	  
        if (rt_ptr->u.p.el_len < 0)
          sprintf (temp_buf, "UTF8Vstring%d", (ST_INT) abs (rt_ptr->u.p.el_len));
        else
          sprintf (temp_buf, "UTF8string%d", (ST_INT) (rt_ptr->u.p.el_len));
        break;

      case RT_STR_END 	     :
        strcpy (temp_buf, "}");
        cur_nest_level--;
        break;

      case RT_ARR_END 	     :
        strcpy (temp_buf, "]");
        cur_nest_level--;
        break;
      }


    temp_size = strlen (temp_buf);
    if (temp_size == 0)
      {			/* If buffer empty, rt_ptr contains unrecognized type*/
      /* DEBUG: this should NEVER happen. Chk for memory corruption.	*/
      MLOG_ERR1 ("RUNTIME_TYPE contains illegal el_tag = %d", rt_ptr->el_tag);
      return (0);	/* error	*/
      }
    if (total_size + temp_size < max_tdl_len)
      {
      strcpy (&tdl_buf[total_size], temp_buf);
      total_size += temp_size;
      }
    else
      overrun_detected = SD_TRUE;

    rt_ptr++;				/* point to next runtime element*/
    i++;
    }		/* end main loop*/

  if (overrun_detected == SD_TRUE)
    {
    MLOG_NERR1 ("ms_runtime_to_tdl: tdl buffer overrun (max=%d)", max_tdl_len);
    total_size = 0;
    }
  else
    {
    tdl_buf[total_size] = 0;		/* ensure a Null terminator 	*/
    total_size++;			/* account for a Null terminator*/
    }

  return (total_size);
  }

/************************************************************************/
/*                          add_tdl_separator				*/
/************************************************************************/

static ST_INT add_tdl_separator (ST_CHAR *tdl_buf, ST_INT index,
				ST_BOOLEAN member_found,
				ST_BOOLEAN named_component_found)
  {
  if ((member_found == SD_TRUE) &&
       (named_component_found == SD_FALSE))
    {
    strcpy (&tdl_buf[index], ",");
    return (1);
    }
  else
    {
    return (0);
    }
  }

/************************************************************************/
/*			ms_tdl_to_runtime				*/
/* This function converts TDL to ASN.1 and then ASN.1 to RUNTIME_TYPE.	*/
/* NOTE: this function allocates array of RUNTIME_TYPE structs by	*/
/*       calling "ms_runtime_create". The array must be freed by	*/
/*       calling "ms_runtime_destroy" (DO NOT use M_FREE or chk_free).	*/
/************************************************************************/
RUNTIME_CTRL *ms_tdl_to_runtime (ST_CHAR *tdl,	/* TDL string	*/
			ST_UCHAR *asn1_buf,	/* ASN.1 buffer	*/
			ST_INT asn1_buf_len)	/* buffer len	*/
  {
ST_UCHAR *asn1_start;		/* ptr to ASN.1 encoded type	*/
RUNTIME_CTRL *rt_ctrl = NULL;	/* assume failure	*/

  /* NOTE: ms_mk_.. overwrites asn1_buf_len with len of ASN.1 encoding	*/
  asn1_start = ms_mk_asn1_type (&asn1_buf_len, asn1_buf, tdl);
  if (asn1_start)				/* successful	*/
    rt_ctrl = ms_runtime_create (asn1_start, asn1_buf_len,
                     (UINT_MAX/sizeof(RUNTIME_TYPE))-100);
  return (rt_ctrl);
  }
