/****************************************************************************
*																			*
*								ASN.1 Write Routines						*
*						Copyright Peter Gutmann 1992-2025					*
*																			*
****************************************************************************/

#if defined( INC_ALL )
  #include "crypt.h"
  #include "bn.h"
  #include "asn1.h"
  #include "asn1_ext.h"
#else
  #include "crypt.h"
  #include "bn/bn.h"
  #include "enc_dec/asn1.h"
  #include "enc_dec/asn1_ext.h"
#endif /* Compiler-specific includes */

#ifdef USE_INT_ASN1

/****************************************************************************
*																			*
*								Utility Routines							*
*																			*
****************************************************************************/

/* Calculate the size of the encoded length octets */

CHECK_RETVAL_RANGE( 1, 5 ) \
static int calculateLengthSize( IN_LENGTH_Z const int length )
	{
	REQUIRES( isIntegerRange( length ) );

	/* Use the short form of the length octets if possible */
	if( length <= 0x7F )
		return( 1 );

	/* Use the long form of the length octets, a length-of-length followed 
	   by an 8, 16, 24, or 32-bit length.  We order the comparisons by 
	   likelihood of occurrence, shorter lengths are far more common than 
	   longer ones */
	if( length <= 0xFF )
		return( 1 + 1 );
	if( length <= 0xFFFFL )
		return( 1 + 2 );
	return( 1 + ( ( length > 0xFFFFFFL ) ? 4 : 3 ) );
	}

/* Write the length octets for an ASN.1 item */

RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
static int writeLength( INOUT_PTR STREAM *stream, IN_LENGTH_Z const int length )
	{
	BYTE buffer[ 8 + 8 ];
	const int noLengthOctets = ( length <= 0xFF ) ? 1 : \
							   ( length <= 0xFFFFL ) ? 2 : \
							   ( length <= 0xFFFFFFL ) ? 3 : 4;
	int bufPos = 1;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );

	REQUIRES_S( isIntegerRange( length ) );

	/* Use the short form of the length octets if possible */
	if( length <= 0x7F )
		return( sputc( stream, length & 0xFF ) );

	/* Encode the number of length octets followed by the octets themselves */
	buffer[ 0 ] = intToByte( 0x80 | noLengthOctets );
	if( noLengthOctets > 3 )
		buffer[ bufPos++ ] = intToByte( length >> 24 );
	if( noLengthOctets > 2 )
		buffer[ bufPos++ ] = intToByte( length >> 16 );
	if( noLengthOctets > 1 )
		buffer[ bufPos++ ] = intToByte( length >> 8 );
	buffer[ bufPos++ ] = intToByte( length );
	return( swrite( stream, buffer, bufPos ) );
	}

/* Write a (non-bignum) numeric value, used by several routines.  The 
   easiest way to do this is to encode the bytes starting from the LSB
   and then output them in reverse order to get a big-endian encoding */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
static int writeNumeric( INOUT_PTR STREAM *stream, 
						 IN_INT const long integer )
	{
	BYTE buffer[ 16 + 8 ], outBuffer[ 16 + 8 ];
	long intValue = integer;
	LOOP_INDEX i;
	int length = 0;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );

	REQUIRES_S( isIntegerRange( integer ) );

	/* The value 0 is handled specially */
	if( intValue == 0 )
		return( swrite( stream, "\x01\x00", 2 ) );

	/* Assemble the encoded value in little-endian order */
	if( intValue > 0 )
		{
		LOOP_SMALL( length = 0, length < 8 && intValue > 0, length++ )
			{
			ENSURES_S( LOOP_INVARIANT_SMALL( length, 0, 7 ) );

			buffer[ length ] = intToByte( intValue );
			intValue >>= 8;
			}
		ENSURES_S( LOOP_BOUND_OK );

		/* Make sure that we don't inadvertently set the sign bit if the 
		   high bit of the value is set */
		ENSURES_S( rangeCheck( length, 1, 8 ) );
		if( buffer[ length - 1 ] & 0x80 )
			buffer[ length++ ] = 0x00;
		}
	else
		{
		/* Write a negative integer value.  This code is never executed (and
		   is actually checked for by the precondition at the start of this 
		   function), it's present only in case it's ever needed in the 
		   future */
		LOOP_SMALL( length = 0, length < 8 && intValue != -1, length++ )
			{
			ENSURES_S( LOOP_INVARIANT_SMALL( length, 0, 7 ) );

			buffer[ length ] = intToByte( intValue );
			intValue >>= 8;
			}
		ENSURES_S( LOOP_BOUND_OK );

		/* Make sure that we don't inadvertently clear the sign bit if the 
		   high bit of the value is clear */
		ENSURES_S( rangeCheck( length, 1, 8 ) );
		if( !( buffer[ length - 1 ] & 0x80 ) )
			buffer[ length++ ] = 0xFF;
		}
	ENSURES_S( rangeCheck( length, 1, 8 ) );

	/* Output the value in reverse (big-endian) order.  The loop index looks
	   a bit odd, it's effectively i + 1 because for the destination we're
	   writing one past the length byte and for the source the length is 
	   1-based while the index is 0-based */
	outBuffer[ 0 ] = intToByte( length );
	LOOP_SMALL( i = 1, i <= length, i++ )
		{
		ENSURES_S( LOOP_INVARIANT_SMALL( i, 1, length ) );

		outBuffer[ i ] = buffer[ length - i ];
		}
	ENSURES_S( LOOP_BOUND_OK );
	return( swrite( stream, outBuffer, 1 + length ) );
	}

/****************************************************************************
*																			*
*								Sizeof Routines								*
*																			*
****************************************************************************/

/* Determine the encoded size of an object given only a length.  This
   function is a bit problematic because it's frequently called as part 
   of a complex expression where in theory it should never be passed a 
   negative value but due to some sort of exceptional circumstances may
   end up being passed one.  Since this is a can't-occur condition we 
   don't want to go overboard with checking for it (it would require having 
   to check the return value of every single use of sizeofObject() within a
   complex expression), but we also need some means of being able to cope 
   with it.  To deal with this we always return a safe length of zero on 
   error.
   
   There's also checkEncodeOverflow() that we use where possible to check
   for problems in nested sizeofObject() computations.
   
   In addition to the general sizeofObject(), we also provide a 
   sizeofShortObject() that avoids the need to cast values to ints all over 
   the code */

RETVAL_LENGTH_NOERROR \
int sizeofObject( IN_LENGTH_Z const int length )
	{
	/* If we've been passed an error code as input or we're about to exceed 
	   the maximum safe length range, don't try and go any further */
	if( length < 0 || checkOverflowAdd( length, 16 ) )
		{
		DEBUG_DIAG( ( "Invalid value passed to sizeofObject()" ) );
		assert( DEBUG_WARN );
		return( 0 );
		}

	return( 1 + calculateLengthSize( length ) + length );
	}

RETVAL_LENGTH_SHORT_NOERROR \
int sizeofShortObject( IN_LENGTH_SHORT_Z const int length )
	{
	/* If we've been passed an error code as input or we're about to exceed 
	   the maximum safe length range, don't try and go any further.  We
	   can't use checkOverflowAdd() here because the maximum allowed length
	   is a short integer, not an integer */
	if( length < 0 || length > MAX_INTLENGTH_SHORT - 16 )
		{
		DEBUG_DIAG( ( "Invalid value passed to sizeofShortObject()" ) );
		assert( DEBUG_WARN );
		return( 0 );
		}

	return( 1 + calculateLengthSize( length ) + length );
	}

/* Check whether an ASN.1 encoding operation, expressed as:

	sizeofObject*( sizeofObject*( length ) + extraLen );
	
   would overflow.  This is mostly redundant because we always keep lengths 
   below MAX_INTLENGTH which means that we can safely apply a series of 
   sizeofObject() operations and add small amounts of extra data like 
   parameters without getting close to INT_MAX, but we provide the check 
   anyway to document that it's been done.
   
   Sample usages:
   
		sizeofObject( sizeofObject( length ) )
			-> ( length, 2, 0, 0 )
		sizeofObject( sizeofObject( length ) + extraLength )
			-> ( length, 1, extraLength, 1 )
		sizeofObject( length + extraLength ) )
			-> ( length + extraLength, 1, 0, 0 ) */

CHECK_RETVAL_LENGTH \
static int sizeofObjectChecked( IN_LENGTH_Z const int length )
	{
	const int lengthSize = calculateLengthSize( length );
	
	if( checkOverflowAdd( 1 + lengthSize, length ) )
		return( -1 );
	return( 1 + lengthSize + length );
	}

CHECK_RETVAL_BOOL \
BOOLEAN checkEncodeOverflow( IN_LENGTH const int length,
							 IN_RANGE( 0, 5 ) const int lengthNestingLevel,
							 IN_LENGTH_SHORT_Z const int extraLen,
							 IN_RANGE( 0, 5 ) const int extraLenNestingLevel )
	{
	int calculatedLength = length;
	LOOP_INDEX i;
	
	REQUIRES_EXT( isIntegerRange( length ), TRUE );
	REQUIRES_EXT( rangeCheck( lengthNestingLevel, 0, 5 ), TRUE );
	REQUIRES_EXT( isShortIntegerRange( extraLen ),  TRUE );
	REQUIRES_EXT( rangeCheck( extraLenNestingLevel, 0, 5 ), TRUE );
	
	/* Check whether the sizeofObject() for length would overflow */
	LOOP_SMALL( i = 0, i < lengthNestingLevel, i++ )
		{
		ENSURES_EXT( LOOP_INVARIANT_SMALL( i, 0, lengthNestingLevel - 1 ),
					 TRUE );

		calculatedLength = sizeofObjectChecked( calculatedLength );
		if( calculatedLength <= 0 )
			return( TRUE );
		}
	ENSURES_EXT( LOOP_BOUND_OK, TRUE );
	
	/* Check whether the extra length would overflow */
	if( checkOverflowAdd( calculatedLength, extraLen ) )
		return( TRUE );
	calculatedLength += extraLen;
	
	/* Check whether the overall sizeofObject() would overflow */
	LOOP_SMALL( i = 0, i < extraLenNestingLevel, i++ )
		{
		ENSURES_EXT( LOOP_INVARIANT_SMALL( i, 0, extraLenNestingLevel - 1 ),
					 TRUE );

		calculatedLength = sizeofObjectChecked( calculatedLength );
		if( calculatedLength <= 0 )
			return( TRUE );
		}
	ENSURES_EXT( LOOP_BOUND_OK, TRUE );
	
	/* No overflow detected */
	return( FALSE );
	}

/* Determine the size of a time value following the RFC 3280 rules for 
   dealing with the non-Y2K-safe times used in ASN.1 */

#if MAX_TIME_VALUE > MAX_TIME_VALUE_Y2038

RETVAL_LENGTH_NOERROR \
int sizeofTime( const time_t timeVal )
	{
	/* We can handle times beyond Y2038, if this is a time past 2050 write 
	   it as a GeneralizedTime */
	if( sizeof( time_t ) > 4 && \
		timeVal >= YEARS_TO_SECONDS( 2050 - 1970 ) )
		return( sizeofGeneralizedTime() );

	return( sizeofUTCTime() );
	}
#else

RETVAL_LENGTH_NOERROR \
int sizeofTime( STDC_UNUSED const time_t timeVal )
	{
	return( sizeofUTCTime() );
	}
#endif /* Time beyond Y2038 */

#ifdef USE_PKC

/* Determine the size of a bignum.  When we're writing these we can't use 
   sizeofObject() directly because the internal representation is unsigned 
   whereas the encoded form is signed */

RETVAL_RANGE_NOERROR( 0, MAX_INTLENGTH_SHORT ) STDC_NONNULL_ARG( ( 1 ) ) \
int signedBignumSize( IN_PTR TYPECAST( BIGNUM * ) const struct BN *bignum )
	{
	const int length = BN_num_bytes( bignum );

	assert( isReadPtr( bignum, sizeof( BIGNUM ) ) );

	/* The output from this function is typically used in calculations
	   involving multiple bignums, for which it doesn't make much sense to
	   individually check the return value of each function call for a
	   condition that can only be caused by an internal error, so we throw
	   an exception in debug mode but otherwise convert the condition to
	   a no-op length value */
	if( cryptStatusError( length ) )
		retIntError_Ext( 0 );

	/* Return the bignum length plus a leading zero byte if the high bit is 
	   set */
	return( length + ( ( BN_high_bit( ( BIGNUM * ) bignum ) ) ? 1 : 0 ) );
	}
#endif /* USE_PKC */

/****************************************************************************
*																			*
*					Write Routines for Primitive Objects					*
*																			*
****************************************************************************/

/* Write a short/large/bignum integer value */

RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int writeShortInteger( INOUT_PTR STREAM *stream, 
					   IN_INT_Z const long integer, 
					   IN_TAG const int tag )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );

	REQUIRES_S( isIntegerRange( integer ) );
	REQUIRES_S( tag == DEFAULT_TAG || ( tag >= 0 && tag < MAX_TAG_VALUE ) );

	writeTag( stream, ( tag == DEFAULT_TAG ) ? \
			  BER_INTEGER : MAKE_CTAG_PRIMITIVE( tag ) );
	return( writeNumeric( stream, integer ) );
	}

RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
int writeInteger( INOUT_PTR STREAM *stream, 
				  IN_BUFFER( integerLength ) const BYTE *integer, 
				  IN_LENGTH_SHORT const int integerLength, 
				  IN_TAG const int tag )
	{
	const int leadingZero = ( integerLength > 0 && ( *integer & 0x80 ) ) ? \
							1 : 0;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isReadPtrDynamic( integer, integerLength ) );

	REQUIRES_S( isShortIntegerRangeNZ( integerLength ) );
	REQUIRES_S( tag == DEFAULT_TAG || ( tag >= 0 && tag < MAX_TAG_VALUE ) );

	writeTag( stream, ( tag == DEFAULT_TAG ) ? \
			  BER_INTEGER : MAKE_CTAG_PRIMITIVE( tag ) );
	writeLength( stream, integerLength + leadingZero );
	if( leadingZero )
		sputc( stream, 0 );
	return( swrite( stream, integer, integerLength ) );
	}

#ifdef USE_PKC

RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
int writeBignumTag( INOUT_PTR STREAM *stream, 
					IN_PTR TYPECAST( BIGNUM * ) const struct BN *bignum, 
					IN_TAG const int tag )
	{
	BYTE buffer[ CRYPT_MAX_PKCSIZE + 16 + 8 ];
	int length, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isReadPtr( bignum, sizeof( BIGNUM ) ) );

	REQUIRES_S( !BN_is_zero( ( BIGNUM * ) bignum ) );
	REQUIRES_S( tag == DEFAULT_TAG || ( tag >= 0 && tag < MAX_TAG_VALUE ) );

	/* If it's a dummy write, don't go through the full encoding process.
	   This optimisation both speeds things up and reduces unnecessary
	   writing of key data to memory */
	if( sIsNullStream( stream ) )
		{
		length = sizeofBignum( bignum );
		ENSURES_S( rangeCheck( length, 1, CRYPT_MAX_PKCSIZE + 16 ) );
		memset( buffer, 0, length );
		return( swrite( stream, buffer, length ) );
		}

	status = exportBignum( buffer, CRYPT_MAX_PKCSIZE, &length, bignum );
	if( cryptStatusError( status ) )
		retIntError_Stream( stream );
	status = writeInteger( stream, buffer, length, tag );
	zeroise( buffer, CRYPT_MAX_PKCSIZE );
	return( status );
	}
#endif /* USE_PKC */

/* Write an enumerated value */

RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int writeEnumerated( INOUT_PTR STREAM *stream, 
					 IN_RANGE( 0, 999 ) const int enumerated, 
					 IN_TAG const int tag )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );

	REQUIRES_S( enumerated >= 0 && enumerated < 1000 );
	REQUIRES_S( tag == DEFAULT_TAG || ( tag >= 0 && tag < MAX_TAG_VALUE ) );

	writeTag( stream, ( tag == DEFAULT_TAG ) ? \
			  BER_ENUMERATED : MAKE_CTAG_PRIMITIVE( tag ) );
	return( writeNumeric( stream, ( long ) enumerated ) );
	}

/* Write a null value */

RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int writeNull( INOUT_PTR STREAM *stream, IN_TAG const int tag )
	{
	BYTE buffer[ 8 + 8 ];

	assert( isWritePtr( stream, sizeof( STREAM ) ) );

	REQUIRES_S( tag == DEFAULT_TAG || ( tag >= 0 && tag < MAX_TAG_VALUE ) );

	buffer[ 0 ] = ( tag == DEFAULT_TAG ) ? \
				  BER_NULL : intToByte( MAKE_CTAG_PRIMITIVE( tag ) );
	buffer[ 1 ] = 0;
	return( swrite( stream, buffer, 2 ) );
	}

/* Write a boolean value */

RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int writeBoolean( INOUT_PTR STREAM *stream, 
				  IN_BOOL const BOOLEAN boolean, 
				  IN_TAG const int tag )
	{
	BYTE buffer[ 8 + 8 ];

	assert( isWritePtr( stream, sizeof( STREAM ) ) );

	REQUIRES_S( isBooleanValue( boolean ) );
	REQUIRES_S( tag == DEFAULT_TAG || ( tag >= 0 && tag < MAX_TAG_VALUE ) );

	buffer[ 0 ] = ( tag == DEFAULT_TAG ) ? \
				  BER_BOOLEAN : intToByte( MAKE_CTAG_PRIMITIVE( tag ) );
	buffer[ 1 ] = 1;
	buffer[ 2 ] = boolean ? 0xFF : 0;
	return( swrite( stream, buffer, 3 ) );
	}

/* Write an octet string */

RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
int writeOctetString( INOUT_PTR STREAM *stream, 
					  IN_BUFFER( length ) const BYTE *string, 
					  IN_LENGTH_SHORT const int length, 
					  IN_TAG const int tag )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isReadPtrDynamic( string, length ) );

	REQUIRES_S( isShortIntegerRangeNZ( length ) );
	REQUIRES_S( tag == DEFAULT_TAG || ( tag >= 0 && tag < MAX_TAG_VALUE ) );

	writeTag( stream, ( tag == DEFAULT_TAG ) ? \
			  BER_OCTETSTRING : MAKE_CTAG_PRIMITIVE( tag ) );
	writeLength( stream, length );
	return( swrite( stream, string, length ) );
	}

/* Write a character string.  This handles any of the myriad ASN.1 character
   string types.  The handling of the tag works somewhat differently here to
   the usual manner in that since the function is polymorphic, the tag
   defines the character string type and is always used (there's no
   DEFAULT_TAG like the other functions use) */

RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
int writeCharacterString( INOUT_PTR STREAM *stream, 
						  IN_BUFFER( length ) const void *string, 
						  IN_LENGTH_SHORT const int length, 
						  IN_TAG_ENCODED const int tag )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isReadPtrDynamic( string, length ) );

	REQUIRES_S( isShortIntegerRangeNZ( length ) );
	REQUIRES_S( ( tag >= BER_STRING_UTF8 && tag <= BER_STRING_BMP ) || \
				( tag >= MAKE_CTAG_PRIMITIVE( 0 ) && \
				  tag <= MAKE_CTAG_PRIMITIVE( MAX_CTAG_VALUE ) ) );

	writeTag( stream, tag );
	writeLength( stream, length );
	return( swrite( stream, string, length ) );
	}

/* Write a bit string */

RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int writeBitString( INOUT_PTR STREAM *stream, IN_INT_Z const int bitString, 
					IN_TAG const int tag )
	{
	BYTE buffer[ 16 + 8 ];
	unsigned int value = 0;
	LOOP_INDEX i;
	int data = bitString, noBits = 0;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );

	REQUIRES_S( bitString >= 0 && bitString < INT_MAX );
	REQUIRES_S( tag == DEFAULT_TAG || ( tag >= 0 && tag < MAX_TAG_VALUE ) );

	/* ASN.1 bitstrings start at bit 0 so we need to reverse the order of
	   the bits before we write them out.  This doesn't assume 32-bit ints
	   but merely extracts the first 32 bits from whatever size int we're 
	   passed, most bitstrings are only a few bits with the one exception 
	   being CMP's braindamaged error codes which for no known reason are 
	   encoded as a bitstring rather than an enum or int */
	LOOP_MED( i = 0, i < 32, i++ )
		{
		ENSURES_S( LOOP_INVARIANT_MED( i, 0, 31 ) );

		/* Update the number of significant bits */
		if( data > 0 )
			noBits++;

		/* Reverse the bits */
		value <<= 1;
		if( data & 1 )
			value |= 1;
		data >>= 1;
		}
	ENSURES_S( LOOP_BOUND_OK );

	/* Write the data as an ASN.1 BITSTRING */
	buffer[ 0 ] = ( tag == DEFAULT_TAG ) ? \
				  BER_BITSTRING : intToByte( MAKE_CTAG_PRIMITIVE( tag ) );
	buffer[ 1 ] = 1 + intToByte( ( ( noBits + 7 ) >> 3 ) );
	buffer[ 2 ] = intToByte( ~( ( noBits - 1 ) & 7 ) & 7 );
	buffer[ 3 ] = intToByte( value >> 24 );
	buffer[ 4 ] = intToByte( value >> 16 );
	buffer[ 5 ] = intToByte( value >> 8 );
	buffer[ 6 ] = intToByte( value );
	return( swrite( stream, buffer, 3 + ( ( noBits + 7 ) >> 3 ) ) );
	}

/* Write a canonical UTCTime and GeneralizedTime value */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
static int writeTimeData( INOUT_PTR STREAM *stream, 
						  const time_t timeVal, 
						  IN_TAG const int tag, 
						  IN_BOOL const BOOLEAN isUTCTime )
	{
	struct tm timeInfo, *timeInfoPtr = &timeInfo;
	BYTE buffer[ 32 + 8 ];
	const int length = isUTCTime ? 13 : 15;
	int result;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );

	REQUIRES_S( timeVal >= MIN_STORED_TIME_VALUE );
	REQUIRES_S( tag == DEFAULT_TAG || ( tag >= 0 && tag < MAX_TAG_VALUE ) );
	REQUIRES_S( isBooleanValue( isUTCTime ) );

	timeInfoPtr = gmTime_s( &timeVal, timeInfoPtr );
	ENSURES_S( timeInfoPtr != NULL && timeInfoPtr->tm_year > 90 );
	buffer[ 0 ] = ( tag != DEFAULT_TAG ) ? \
					intToByte( MAKE_CTAG_PRIMITIVE( tag ) ) : \
				  isUTCTime ? BER_TIME_UTC : BER_TIME_GENERALIZED;
	buffer[ 1 ] = intToByte( length );
	if( isUTCTime )
		{
		/* This and the following sprintf_s() give a bogus buffer-overflow 
		   warning from gcc because it doesn't understand the difference 
		   between %02d and %d */
		result = sprintf_s( buffer + 2, 16, "%02d%02d%02d%02d%02d%02dZ", 
							timeInfoPtr->tm_year % 100, 
							timeInfoPtr->tm_mon + 1, 
							timeInfoPtr->tm_mday, timeInfoPtr->tm_hour, 
							timeInfoPtr->tm_min, timeInfoPtr->tm_sec );
		}
	else
		{
		/* We leave the Y10K bug for the year field in here so that 
		   whatever's using this code in 8,000 years time has something to 
		   do */
		result = sprintf_s( buffer + 2, 16, "%04d%02d%02d%02d%02d%02dZ", 
							timeInfoPtr->tm_year + 1900, 
							timeInfoPtr->tm_mon + 1, 
							timeInfoPtr->tm_mday, timeInfoPtr->tm_hour, 
							timeInfoPtr->tm_min, timeInfoPtr->tm_sec );
		}
	ENSURES_S( rangeCheck( result, length, 15 ) );
	return( swrite( stream, buffer, length + 2 ) );
	}

RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int writeUTCTime( INOUT_PTR STREAM *stream, const time_t timeVal, 
				  IN_TAG const int tag )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );

	REQUIRES_S( timeVal >= MIN_STORED_TIME_VALUE );
	REQUIRES_S( tag == DEFAULT_TAG || ( tag >= 0 && tag < MAX_TAG_VALUE ) );

	return( writeTimeData( stream, timeVal, tag, TRUE ) );
	}

RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int writeGeneralizedTime( INOUT_PTR STREAM *stream, const time_t timeVal, 
						  IN_TAG const int tag )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );

	REQUIRES_S( timeVal >= MIN_STORED_TIME_VALUE );
	REQUIRES_S( tag == DEFAULT_TAG || ( tag >= 0 && tag < MAX_TAG_VALUE ) );

	return( writeTimeData( stream, timeVal, tag, FALSE) );
	}

/* Write a time value following the RFC 3280 rules for dealing with the 
   non-Y2K-safe times used in ASN.1 */

RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int writeTime( INOUT_PTR STREAM *stream, const time_t timeVal )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );

	REQUIRES_S( timeVal >= MIN_STORED_TIME_VALUE );

#if MAX_TIME_VALUE > MAX_TIME_VALUE_Y2038
	if( sizeof( time_t ) > 4 && \
		( timeVal >= YEARS_TO_SECONDS( 2050 - 1970 ) ) )
		{
		/* We can handle times beyond Y2038 and this is a time past 2050, 
		   write it as a GeneralizedTime */
		return( writeGeneralizedTime( stream, timeVal, DEFAULT_TAG ) );
		}
#endif /* Time beyond Y2038 */

	return( writeUTCTime( stream, timeVal, DEFAULT_TAG ) );
	}

/****************************************************************************
*																			*
*					Write Routines for Constructed Objects					*
*																			*
****************************************************************************/

/* Write the start of an encapsulating SEQUENCE, SET, or generic tagged
   constructed object.  The difference between writeOctet/BitStringHole() and
   writeGenericHole() is that the octet/bit-string versions create a normal
   or context-specific-tagged primitive string while the generic version 
   creates a pure hole with no processing of tags */

RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int writeSequence( INOUT_PTR STREAM *stream, 
				   IN_LENGTH_Z const int length )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	
	REQUIRES_S( isIntegerRange( length ) );

	writeTag( stream, BER_SEQUENCE );
	return( writeLength( stream, length ) );
	}

RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int writeSet( INOUT_PTR STREAM *stream, 
			  IN_LENGTH_SHORT_Z const int length )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );

	REQUIRES_S( isShortIntegerRange( length ) );

	writeTag( stream, BER_SET );
	return( writeLength( stream, length ) );
	}

RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int writeConstructed( INOUT_PTR STREAM *stream, 
					  IN_LENGTH_Z const int length,
					  IN_TAG const int tag )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );

	REQUIRES_S( isIntegerRange( length ) );
	REQUIRES_S( tag == DEFAULT_TAG || ( tag >= 0 && tag < MAX_TAG_VALUE ) );

	writeTag( stream, ( tag == DEFAULT_TAG ) ? \
			  BER_SEQUENCE : MAKE_CTAG( tag ) );
	return( writeLength( stream, length ) );
	}

RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int writeOctetStringHole( INOUT_PTR STREAM *stream, 
						  IN_LENGTH_Z const int length,
						  IN_TAG const int tag )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );

	REQUIRES_S( isIntegerRange( length ) );
	REQUIRES_S( tag == DEFAULT_TAG || ( tag >= 0 && tag < MAX_TAG_VALUE ) );

	writeTag( stream, ( tag == DEFAULT_TAG ) ? \
			  BER_OCTETSTRING : MAKE_CTAG_PRIMITIVE( tag ) );
	return( writeLength( stream, length ) );
	}

RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int writeBitStringHole( INOUT_PTR STREAM *stream, 
						IN_LENGTH_SHORT_Z const int length,
						IN_TAG const int tag )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );

	REQUIRES_S( isShortIntegerRange( length ) );
	REQUIRES_S( tag == DEFAULT_TAG || ( tag >= 0 && tag < MAX_TAG_VALUE ) );

	writeTag( stream, ( tag == DEFAULT_TAG ) ? \
			  BER_BITSTRING : MAKE_CTAG_PRIMITIVE( tag ) );
	writeLength( stream, length + 1 );	/* +1 for bit count */
	return( sputc( stream, 0 ) );
	}

RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int writeGenericHole( INOUT_PTR STREAM *stream, 
					  IN_LENGTH_Z const int length,
					  IN_TAG const int tag )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );

	REQUIRES_S( isIntegerRange( length ) );
	REQUIRES_S( tag >= 0 && tag < MAX_TAG_VALUE );

	writeTag( stream, tag );
	return( writeLength( stream, length ) );
	}
#endif /* USE_INT_ASN1 */
