/****************************************************************************
*																			*
*						cryptlib TLS Session Write Routines					*
*						Copyright Peter Gutmann 1998-2012					*
*																			*
****************************************************************************/

#if defined( INC_ALL )
  #include "crypt.h"
  #include "misc_rw.h"
  #include "session.h"
  #include "ssl.h"
#else
  #include "crypt.h"
  #include "enc_dec/misc_rw.h"
  #include "session/session.h"
  #include "session/ssl.h"
#endif /* Compiler-specific includes */

#ifdef USE_SSL

/****************************************************************************
*																			*
*							Sub-packet Management Routines					*
*																			*
****************************************************************************/

/* Open and complete an SSL packet:

	 offset										packetEndOfs
		|											|
		v											v
		+---+---+---+----+--------------------------+
		|ID	|Ver|Len|(IV)|							|
		+---+---+---+----+--------------------------+

   An initial openXXX() starts a new packet at the start of a stream and 
   continueXXX() adds another packet after an existing one, or (for the
   xxxHSXXX() variants) adds a handshake sub-packet within an existing 
   packet.  The continueXXX() operations return the start offset of the new 
   packet within the stream, openXXX() always starts at the start of the SSL 
   send buffer so the start offset is an implied 0.  completeXXX() then goes 
   back to the given offset and deposits the appropriate length value in the 
   header that was written earlier.  So typical usage (with state variables 
   and error checking omitted for clarity) would be:

	// Change-cipher-spec packet
	openPacketStreamSSL( CRYPT_USE_DEFAULT, SSL_MSG_CHANGE_CIPHER_SPEC );
	write( stream, ... );
	completePacketStreamSSL( stream, 0 );

	// Finished handshake sub-packet within a handshake packet
	continuePacketStreamSSL( SSL_MSG_HANDSHAKE );
	continueHSPacketStream( SSL_HAND_FINISHED, &offset );
	write( stream, ... );
	completeHSPacketStream( stream, offset );
	// (Packet stream is completed by wrapPacketSSL()) */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
static int startPacketStream( INOUT STREAM *stream, 
							  const SESSION_INFO *sessionInfoPtr, 
							  IN_RANGE( SSL_MSG_FIRST, \
										SSL_MSG_LAST ) const int packetType )
	{
	SSL_INFO *sslInfo = sessionInfoPtr->sessionSSL;
	int status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isReadPtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );

	REQUIRES( packetType >= SSL_MSG_FIRST && packetType <= SSL_MSG_LAST );

	/* Write the packet header:

		byte		ID = packetType
		byte[2]		version = { 0x03, 0x0n }
		uint16		len = 0 (placeholder) 
	  [ byte[]		iv	- TLS 1.1+ only ] */
	sputc( stream, packetType );
	sputc( stream, SSL_MAJOR_VERSION );
	sputc( stream, sessionInfoPtr->version );
	status = writeUint16( stream, 0 );		/* Placeholder */
	if( cryptStatusError( status ) )
		return( status );
	if( TEST_FLAG( sessionInfoPtr->flags, 
				   SESSION_FLAG_ISSECURE_WRITE ) && \
		sslInfo->ivSize > 0 )
		{
		MESSAGE_DATA msgData;
		BYTE iv[ CRYPT_MAX_IVSIZE + 8 ];

		setMessageData( &msgData, iv, sslInfo->ivSize );
		krnlSendMessage( SYSTEM_OBJECT_HANDLE, IMESSAGE_GETATTRIBUTE_S, 
						 &msgData, CRYPT_IATTRIBUTE_RANDOM_NONCE );
		status = swrite( stream, iv, sslInfo->ivSize );
		}
	return( status );
	}

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
int openPacketStreamSSL( OUT STREAM *stream, 
						 const SESSION_INFO *sessionInfoPtr, 
						 IN_DATALENGTH_OPT const int bufferSize, 
						 IN_RANGE( SSL_HAND_FIRST, \
								   SSL_HAND_LAST ) const int packetType )
	{
	const int streamSize = ( bufferSize == CRYPT_USE_DEFAULT ) ? \
						   sessionInfoPtr->sendBufSize - EXTRA_PACKET_SIZE : \
						   bufferSize + sessionInfoPtr->sendBufStartOfs;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isReadPtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) && \
			isWritePtrDynamic( sessionInfoPtr->sendBuffer, streamSize ) );

	REQUIRES( bufferSize == CRYPT_USE_DEFAULT || \
			  ( packetType == SSL_MSG_APPLICATION_DATA && \
			    bufferSize == 0 ) || \
			  ( bufferSize > 0 && bufferSize < MAX_BUFFER_SIZE ) );
			  /* When wrapping up data packets we only write the implicit-
				 length header so the buffer size is zero */
	REQUIRES( packetType >= SSL_MSG_FIRST && packetType <= SSL_MSG_LAST );
	REQUIRES( streamSize >= sessionInfoPtr->sendBufStartOfs && \
			  streamSize <= sessionInfoPtr->sendBufSize - EXTRA_PACKET_SIZE );

	/* Create the stream */
	sMemOpen( stream, sessionInfoPtr->sendBuffer, streamSize );
	return( startPacketStream( stream, sessionInfoPtr, packetType ) );
	}

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2, 4 ) ) \
int continuePacketStreamSSL( INOUT STREAM *stream, 
							 const SESSION_INFO *sessionInfoPtr, 
							 IN_RANGE( SSL_HAND_FIRST, \
									   SSL_HAND_LAST ) const int packetType,
							 OUT_LENGTH_SHORT_Z int *packetOffset )
	{
	const int offset = stell( stream );
	int status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isReadPtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
	assert( isWritePtr( packetOffset, sizeof( int ) ) );
	
	REQUIRES( packetType >= SSL_MSG_FIRST && packetType <= SSL_MSG_LAST );
	REQUIRES( offset >= SSL_HEADER_SIZE && \
			  offset <= sessionInfoPtr->sendBufSize );

	/* Clear return value */
	*packetOffset = 0;

	/* Continue the stream */
	status = startPacketStream( stream, sessionInfoPtr, packetType );
	if( cryptStatusError( status ) )
		return( status );
	*packetOffset = offset;

	return( CRYPT_OK );
	}

CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int completePacketStreamSSL( INOUT STREAM *stream, 
							 IN_LENGTH_Z const int offset )
	{
	const int packetEndOffset = stell( stream );
	int status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	
	REQUIRES( ( offset == 0 || offset >= SSL_HEADER_SIZE ) && \
			  offset <= packetEndOffset - ( ID_SIZE + VERSIONINFO_SIZE ) );
	REQUIRES( packetEndOffset >= SSL_HEADER_SIZE && \
			  packetEndOffset < MAX_INTLENGTH_SHORT );

	/* Update the length field at the start of the packet */
	sseek( stream, offset + ID_SIZE + VERSIONINFO_SIZE );
	status = writeUint16( stream, ( packetEndOffset - offset ) - \
								  SSL_HEADER_SIZE );
	sseek( stream, packetEndOffset );

	return( status );
	}

/* Start and complete a handshake packet within an SSL packet.  Since this
   continues an existing packet stream that's been opened using 
   openPacketStreamSSL(), it's denoted as continueXXX() rather than 
   openXXX() */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 3 ) ) \
int continueHSPacketStream( INOUT STREAM *stream, 
							IN_RANGE( SSL_HAND_FIRST, \
									  SSL_HAND_LAST ) const int packetType,
							OUT_LENGTH_SHORT_Z int *packetOffset )
	{
	const int offset = stell( stream );
	int status;

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

	REQUIRES( packetType >= SSL_HAND_FIRST && packetType <= SSL_HAND_LAST );
	REQUIRES( offset >= SSL_HEADER_SIZE && \
			  offset < MAX_INTLENGTH_SHORT );

	/* Clear return value */
	*packetOffset = 0;

	/* Write the handshake packet header:

		byte		ID = packetType
		uint24		len = 0 (placeholder) */
	sputc( stream, packetType );
	status = writeUint24( stream, 0 );
	if( cryptStatusError( status ) )
		return( status );
	*packetOffset = offset;

	return( CRYPT_OK );
	}

CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int completeHSPacketStream( INOUT STREAM *stream, 
							IN_LENGTH const int offset )
	{
	const int packetEndOffset = stell( stream );
	int status;

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

	REQUIRES( offset >= SSL_HEADER_SIZE && \
			  offset <= packetEndOffset - ( ID_SIZE + LENGTH_SIZE ) );
			  /* HELLO_DONE has size zero so 
			     offset == packetEndOffset - HDR_SIZE */
	REQUIRES( packetEndOffset >= SSL_HEADER_SIZE && \
			  packetEndOffset < MAX_INTLENGTH_SHORT );

	/* Update the length field at the start of the packet */
	sseek( stream, offset + ID_SIZE );
	status = writeUint24( stream, packetEndOffset - \
								  ( offset + ID_SIZE + LENGTH_SIZE ) );
	sseek( stream, packetEndOffset );
	DEBUG_PRINT(( "Wrote %s (%d) handshake packet, length %ld.\n", \
				  getSSLHSPacketName( DEBUG_GET_STREAMBYTE( stream, offset ) ), 
				  DEBUG_GET_STREAMBYTE( stream, offset ),
				  ( packetEndOffset - offset ) - ( ID_SIZE + LENGTH_SIZE ) ));
	DEBUG_DUMP_STREAM( stream, offset + ( ID_SIZE + LENGTH_SIZE ), 
					   ( packetEndOffset - offset ) - ( ID_SIZE + LENGTH_SIZE ) );

	return( status );
	}

/****************************************************************************
*																			*
*							Write/Wrap a Packet								*
*																			*
****************************************************************************/

/* Wrap an SSL data packet.  There are three forms of this, the first for
   standard MAC-then-encrypt:

	sendBuffer hdrPtr	dataPtr
		|		|-----		|-------------------			  MAC'd
		v		v			v================================ Encrypted
		+-------+-----+-----+-------------------+-----+-----+
		|///////| hdr | IV	|		data		| MAC | pad |
		+-------+-----+-----+-------------------+-----+-----+
				^<----+---->|<-- dataLength --->^			|
				|	  |		 <-------- bMaxLen -|---------->
			 offset sBufStartOfs		  stell( stream )

   The second for encrypt-then-MAC:

	sendBuffer hdrPtr	dataPtr
		|		|			|=========================		  Encrypted
		v		v-----------v-------------------------        MAC'd
		+-------+-----+-----+-------------------+-----+-----+
		|///////| hdr | IV	|		data		| pad | MAC |
		+-------+-----+-----+-------------------+-----+-----+
				^<----+---->|<-- dataLength --->^			|
				|	  |		 <-------- bMaxLen -|---------->
			 offset sBufStartOfs		  stell( stream )

   And the third for GCM:

	sendBuffer hdrPtr	dataPtr
		|		|			|
		v		v-----		v============================== AuthEnc'd
		+-------+-----+-----+-----------------------+-----+
		|///////| hdr | IV	|		data			| ICV |
		+-------+-----+-----+-----------------------+-----+
				^<----+---->|<-- dataLength --->^	  |
				|	  |		 <-------- bMaxLen -----|---->
			 offset sBufStartOfs			  stell( stream )

   These are sufficiently different that we use three distinct functions to
   do the job.  These MAC/ICV the data, add the IV if necessary, pad and 
   encrypt, and update the header */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 3, 5 ) ) \
static int wrapPacketSSLStd( INOUT SESSION_INFO *sessionInfoPtr, 
							 IN_RANGE( SSL_MSG_FIRST, \
									   SSL_MSG_LAST ) const int packetType,
							 INOUT_BUFFER( bufMaxLen, *length ) \
								BYTE *dataPtr,
							 IN_LENGTH_SHORT const int bufMaxLen,
							 OUT_LENGTH_BOUNDED_Z( bufMaxLen ) \
								int *length,
							 IN_LENGTH_SHORT const int dataLength )
	{
	SSL_INFO *sslInfo = sessionInfoPtr->sessionSSL;
	int effectiveBufMaxLen, payloadLength, status;

	assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
	assert( isReadPtrDynamic( dataPtr, bufMaxLen ) );
	assert( isWritePtr( length, sizeof( int ) ) );

	REQUIRES( packetType >= SSL_MSG_FIRST && packetType <= SSL_MSG_LAST );
	REQUIRES( bufMaxLen > 0 && bufMaxLen <= sessionInfoPtr->sendBufSize );
	REQUIRES( dataLength >= 0 && dataLength <= MAX_PACKET_SIZE );

	/* Clear return values */
	*length = 0;

	/* MAC the payload */
#ifdef USE_SSL3
	if( sessionInfoPtr->version == SSL_MINOR_VERSION_SSL )
		{
		status = createMacSSL( sessionInfoPtr, dataPtr, bufMaxLen, 
							   &payloadLength, dataLength, packetType );
		}
	else
#endif /* USE_SSL3 */
		{
		status = createMacTLS( sessionInfoPtr, dataPtr, bufMaxLen, 
							   &payloadLength, dataLength, packetType );
		}
	if( cryptStatusError( status ) )
		return( status );

	/* If it's TLS 1.1+ and we're using a block cipher, adjust for the 
	   explicit IV that precedes the data.  This is because the IV load is
	   handled implicitly by encrypting it as part of the data.  We know 
	   that the resulting values are within bounds because 
	   dataPtr = headerPtr + hdr + IV */
	if( sslInfo->ivSize > 0 )
		{
		REQUIRES( sessionInfoPtr->sendBufStartOfs >= SSL_HEADER_SIZE + \
													 sslInfo->ivSize && \
				  sessionInfoPtr->sendBufStartOfs <= sessionInfoPtr->sendBufSize );
		dataPtr -= sslInfo->ivSize;
		payloadLength += sslInfo->ivSize;
		effectiveBufMaxLen = bufMaxLen + sslInfo->ivSize;
		ENSURES( payloadLength > 0 && payloadLength <= effectiveBufMaxLen )
		}
	else
		effectiveBufMaxLen = bufMaxLen;
	DEBUG_PRINT(( "Wrote %s (%d) packet, length %ld.\n", 
				  getSSLPacketName( packetType ), packetType, 
				  payloadLength ));
	DEBUG_DUMP_DATA( dataPtr, payloadLength );

	/* Pad and encrypt the payload */
	status = encryptData( sessionInfoPtr, dataPtr, effectiveBufMaxLen, 
						  &payloadLength, payloadLength );
	if( cryptStatusError( status ) )
		return( status );

	/* Tell the caller what the final packet size is */
	*length = payloadLength;

	return( CRYPT_OK );
	}

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 3, 5 ) ) \
static int wrapPacketTLSMAC( INOUT SESSION_INFO *sessionInfoPtr, 
							 IN_RANGE( SSL_MSG_FIRST, \
									   SSL_MSG_LAST ) const int packetType,
							 INOUT_BUFFER( bufMaxLen, *length ) \
								BYTE *dataPtr,
							 IN_LENGTH_SHORT const int bufMaxLen,
							 OUT_LENGTH_BOUNDED_Z( bufMaxLen ) int *length,
							 IN_LENGTH_SHORT const int dataLength )
	{
	SSL_INFO *sslInfo = sessionInfoPtr->sessionSSL;
	int effectiveBufMaxLen, payloadLength = dataLength, status;

	assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
	assert( isReadPtrDynamic( dataPtr, bufMaxLen ) );
	assert( isWritePtr( length, sizeof( int ) ) );

	REQUIRES( packetType >= SSL_MSG_FIRST && packetType <= SSL_MSG_LAST );
	REQUIRES( bufMaxLen > 0 && bufMaxLen <= sessionInfoPtr->sendBufSize );
	REQUIRES( dataLength >= 0 && dataLength <= MAX_PACKET_SIZE );

	/* Clear return values */
	*length = 0;

	/* If it's TLS 1.1+ and we're using a block cipher, adjust for the 
	   explicit IV that precedes the data.  This is because the IV load is 
	   handled implicitly by encrypting it as part of the data.  We know 
	   that the resulting values are within bounds because 
	   dataPtr = headerPtr + hdr + IV */
	if( sslInfo->ivSize > 0 )
		{
		REQUIRES( sessionInfoPtr->sendBufStartOfs >= SSL_HEADER_SIZE + \
													 sslInfo->ivSize && \
				  sessionInfoPtr->sendBufStartOfs <= sessionInfoPtr->sendBufSize );
		dataPtr -= sslInfo->ivSize;
		payloadLength += sslInfo->ivSize;
		effectiveBufMaxLen = bufMaxLen + sslInfo->ivSize;
		ENSURES( payloadLength > 0 && payloadLength <= effectiveBufMaxLen )
		}
	else
		effectiveBufMaxLen = bufMaxLen;
	DEBUG_PRINT(( "Wrote %s (%d) packet, length %ld.\n", 
				  getSSLPacketName( packetType ), packetType, 
				  payloadLength ));
	DEBUG_DUMP_DATA( dataPtr, payloadLength );

	/* Pad and encrypt the payload */
	status = encryptData( sessionInfoPtr, dataPtr, effectiveBufMaxLen, 
						  &payloadLength, payloadLength );
	if( cryptStatusError( status ) )
		return( status );

	/* MAC the payload */
	status = createMacTLS( sessionInfoPtr, dataPtr, bufMaxLen, 
						   &payloadLength, payloadLength, packetType );
	if( cryptStatusError( status ) )
		return( status );

	/* Tell the caller what the final packet size is */
	*length = payloadLength;

	return( CRYPT_OK );
	}

#ifdef USE_GCM

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 3, 5 ) ) \
static int wrapPacketTLSGCM( INOUT SESSION_INFO *sessionInfoPtr, 
							 IN_RANGE( SSL_MSG_FIRST, \
									   SSL_MSG_LAST ) const int packetType,
							 INOUT_BUFFER( bufMaxLen, *length ) \
									BYTE *dataPtr,
							 IN_LENGTH_SHORT const int bufMaxLen,
							 OUT_LENGTH_BOUNDED_Z( bufMaxLen ) int *length,
							 IN_LENGTH_SHORT const int dataLength )
	{
	SSL_INFO *sslInfo = sessionInfoPtr->sessionSSL;
	MESSAGE_DATA msgData;
	BYTE iv[ CRYPT_MAX_IVSIZE + 8 ];
	int payloadLength = dataLength, status;

	assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
	assert( isReadPtrDynamic( dataPtr, bufMaxLen ) );
	assert( isWritePtr( length, sizeof( int ) ) );

	REQUIRES( packetType >= SSL_MSG_FIRST && packetType <= SSL_MSG_LAST );
	REQUIRES( bufMaxLen > 0 && bufMaxLen <= sessionInfoPtr->sendBufSize );
	REQUIRES( dataLength >= 0 && dataLength <= MAX_PACKET_SIZE );

	/* Clear return values */
	*length = 0;

	/* For GCM the IV has to be assembled from implicit and explicit 
	   components and set explicitly.  The implicit portion is fixed for 
	   each session and equivalent to the TLS client/server_write_IV derived
	   from the master secret, the explicit portion is randomly chosen for 
	   each message.
	   
	   There are a variety of options for this one, all of which are 
	   designed to meet the general requirement that the nonce not repeat, 
	   which leads to a catastrophic failure of security: use a counter 
	   starting at zero, use the sequence number, use a counter starting at 
	   a random position, and use a completely random number.  The last 
	   option runs into a potential collision problem after 2^32 TLS 
	   messages on average, however due to the incredible brittleness of GCM 
	   the use of completely unrelated, random nonces may be the best way of 
	   protecting against issues (apart from not using GCM at all, which is 
	   the default config setting).  Given cryptlib's field of application, 
	   the chances of it being used to send several billion TLS messages in 
	   a single session is extremely remote, so we use the (possible) extra
	   protection offered by the use of unrelated nonces.

	   The reason why it's twelve bytes is because AES-GCM preferentially 
	   uses 96 bits of IV followed by 32 bits of 000...1, with other lengths 
	   being possible but then the data has to be cryptographically reduced 
	   to 96 bits before processing, so TLS specifies a fixed length of 96 
	   bits:

		|<--- 12 bytes ---->|
		+-------+-----------+
		| Salt	|	Nonce	|
		+-------+-----------+
		|<- 4 ->|<--- 8 --->| 

	   In addition we have to process the packet metadata that's normally
	   MACed as GCM AAD */
	REQUIRES( boundsCheck( sslInfo->gcmSaltSize, sslInfo->ivSize, 
						   CRYPT_MAX_IVSIZE ) );
	memcpy( iv, sslInfo->gcmWriteSalt, sslInfo->gcmSaltSize );
	memcpy( iv + sslInfo->gcmSaltSize, dataPtr - sslInfo->ivSize, 
			sslInfo->ivSize );
	setMessageData( &msgData, iv, GCM_IV_SIZE );
	status = krnlSendMessage( sessionInfoPtr->iCryptOutContext,
							  IMESSAGE_SETATTRIBUTE_S,
							  &msgData, CRYPT_CTXINFO_IV );
	if( cryptStatusError( status ) )
		return( status );

	/* Process the packet metadata as GCM AAD */
	status = macDataTLSGCM( sessionInfoPtr->iCryptOutContext, 
							sslInfo->writeSeqNo, sessionInfoPtr->version, 
							payloadLength, packetType );
	if( cryptStatusError( status ) )
		return( status );
	sslInfo->writeSeqNo++;

	/* Pad and encrypt the payload */
	status = encryptData( sessionInfoPtr, dataPtr, bufMaxLen, 
						  &payloadLength, payloadLength );
	if( cryptStatusError( status ) )
		return( status );

	/* Adjust the length to account for the IV data at the start (for non-
	   GCM modes this is handled implicitly by making the IV part of the 
	   data to encrypt) */
	payloadLength += sslInfo->ivSize;

	/* Tell the caller what the final packet size is */
	*length = payloadLength;

	return( CRYPT_OK );
	}
#endif /* USE_GCM */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
int wrapPacketSSL( INOUT SESSION_INFO *sessionInfoPtr, 
				   INOUT STREAM *stream, 
				   IN_LENGTH_Z const int offset )
	{
	SSL_INFO *sslInfo = sessionInfoPtr->sessionSSL;
	STREAM lengthStream;
	BYTE lengthBuffer[ UINT16_SIZE + 8 ];
	BYTE *dataPtr, *headerPtr;
	const int payloadLength = stell( stream ) - \
							  ( offset + sessionInfoPtr->sendBufStartOfs );
	const int bufMaxLen = payloadLength + sMemDataLeft( stream );
	int packetType, length, status;

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

	REQUIRES( sanityCheckSessionSSL( sessionInfoPtr ) );
	REQUIRES( TEST_FLAG( sessionInfoPtr->flags, 
						 SESSION_FLAG_ISSECURE_WRITE ) );
	REQUIRES( sStatusOK( stream ) );
	REQUIRES( offset >= 0 && \
			  offset <= stell( stream ) - \
							( payloadLength + \
							  sessionInfoPtr->sendBufStartOfs ) );
	REQUIRES( payloadLength >= 0 && payloadLength <= MAX_PACKET_SIZE && \
			  payloadLength < sessionInfoPtr->sendBufSize - \
							  ( sessionInfoPtr->sendBufStartOfs + \
								sslInfo->ivSize ) );

	/* Get pointers into the data stream for the crypto processing */
	status = sMemGetDataBlockAbs( stream, offset, ( void ** ) &headerPtr, 
								  SSL_HEADER_SIZE + sslInfo->ivSize + \
													bufMaxLen );
	if( cryptStatusError( status ) )
		return( status );
	dataPtr = headerPtr + SSL_HEADER_SIZE + sslInfo->ivSize;
	packetType = byteToInt( *headerPtr );
	ENSURES( packetType >= SSL_MSG_FIRST && packetType <= SSL_MSG_LAST );

	/* Wrap the data based on the type of processing that we're using */
	if( TEST_FLAG( sessionInfoPtr->protocolFlags, SSL_PFLAG_ENCTHENMAC ) )
		{
		status = wrapPacketTLSMAC( sessionInfoPtr, packetType, dataPtr, 
								   bufMaxLen, &length, payloadLength );
		}
	else
		{
#ifdef USE_GCM
		if( TEST_FLAG( sessionInfoPtr->protocolFlags, SSL_PFLAG_GCM ) )
			{
			status = wrapPacketTLSGCM( sessionInfoPtr, packetType, dataPtr, 
									   bufMaxLen, &length, payloadLength );
			}
		else
#endif /* USE_GCM */
			{
			status = wrapPacketSSLStd( sessionInfoPtr, packetType, dataPtr, 
									   bufMaxLen, &length, payloadLength );
			}
		}
	if( cryptStatusError( status ) )
		return( status );

	/* Insert the final packet payload length into the packet header.  We 
	   directly copy the data in because the stream may have been opened in 
	   read-only mode if we're using it to write pre-assembled packet data 
	   that's been passed in by the caller */
	sMemOpen( &lengthStream, lengthBuffer, UINT16_SIZE );
	status = writeUint16( &lengthStream, length );
	sMemDisconnect( &lengthStream );
	if( cryptStatusError( status ) )
		return( status );
	memcpy( headerPtr + ID_SIZE + VERSIONINFO_SIZE, lengthBuffer, 
			UINT16_SIZE );

	/* Sync the stream information to match the new payload size */
	return( sSkip( stream, length - ( sslInfo->ivSize + payloadLength ),
				   SSKIP_MAX ) );
	}

/* Wrap up and send an SSL packet */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
int sendPacketSSL( INOUT SESSION_INFO *sessionInfoPtr, 
				   INOUT STREAM *stream, 
				   const BOOLEAN sendOnly )
	{
	const int length = stell( stream );
	void *dataPtr;
	int status;

	assert( isReadPtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
	assert( isWritePtr( stream, sizeof( STREAM ) ) );

	REQUIRES( sanityCheckSessionSSL( sessionInfoPtr ) );
	REQUIRES( sStatusOK( stream ) );
	REQUIRES( sendOnly == TRUE || sendOnly == FALSE );
	REQUIRES( length >= SSL_HEADER_SIZE && \
			  length <= sessionInfoPtr->sendBufSize );

	/* Update the length field at the start of the packet if necessary */
	if( !sendOnly )
		{
		status = completePacketStreamSSL( stream, 0 );
		if( cryptStatusError( status ) )
			return( status );
		}

	/* Send the packet to the peer */
	status = sMemGetDataBlockAbs( stream, 0, &dataPtr, length );
	if( cryptStatusError( status ) )
		return( status );
	ANALYSER_HINT( dataPtr != NULL );
	status = swrite( &sessionInfoPtr->stream, dataPtr, length );
	if( cryptStatusError( status ) )
		{
		sNetGetErrorInfo( &sessionInfoPtr->stream,
						  &sessionInfoPtr->errorInfo );
		return( status );
		}
	DEBUG_DUMP_SSL( dataPtr, length, NULL, 0 );

	return( CRYPT_OK );	/* swrite() returns a byte count */
	}

/****************************************************************************
*																			*
*								Send SSL Alerts								*
*																			*
****************************************************************************/

/* Send a close alert, with appropriate protection if necessary */

STDC_NONNULL_ARG( ( 1 ) ) \
static void sendAlert( INOUT SESSION_INFO *sessionInfoPtr, 
					   IN_RANGE( SSL_ALERTLEVEL_WARNING, \
								 SSL_ALERTLEVEL_FATAL ) const int alertLevel, 
					   IN_RANGE( SSL_ALERT_FIRST, \
								 SSL_ALERT_LAST ) const int alertType,
					   const BOOLEAN alertReceived )
	{
	STREAM stream;
	int length DUMMY_INIT, status;

	assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
	
	REQUIRES_V( alertLevel == SSL_ALERTLEVEL_WARNING || \
				alertLevel == SSL_ALERTLEVEL_FATAL );
	REQUIRES_V( alertType >= SSL_ALERT_FIRST && \
				alertType <= SSL_ALERT_LAST );
	REQUIRES_V( alertReceived == TRUE || alertReceived == FALSE );

	/* Make sure that we only send a single alert.  Normally we do this 
	   automatically on shutdown, but we may have already sent it earlier 
	   as part of an error-handler */
	if( TEST_FLAG( sessionInfoPtr->protocolFlags, SSL_PFLAG_ALERTSENT ) )
		return;
	SET_FLAG( sessionInfoPtr->protocolFlags, SSL_PFLAG_ALERTSENT );

	/* Create the alert.  We can't really do much with errors at this point, 
	   although we can throw an exception in the debug version to draw 
	   attention to the fact that there's a problem.  The one error type 
	   that we don't complain about is an access permission problem, which 
	   can occur when cryptlib is shutting down, for example when the 
	   current thread is blocked waiting for network traffic and another 
	   thread shuts things down.
	   
	   If we encounter an error during this processing, we continue anyway 
	   and drop through and perform a clean shutdown even if the creation of 
	   the close alert fails */
	status = openPacketStreamSSL( &stream, sessionInfoPtr, 
								  CRYPT_USE_DEFAULT, SSL_MSG_ALERT );
	if( cryptStatusOK( status ) )
		{
		sputc( &stream, alertLevel );
		status = sputc( &stream, alertType );
		}
	if( cryptStatusOK( status ) )
		{
		if( TEST_FLAG( sessionInfoPtr->flags, 
					   SESSION_FLAG_ISSECURE_WRITE ) )
			{
			status = wrapPacketSSL( sessionInfoPtr, &stream, 0 );
			assert( cryptStatusOK( status ) || \
					status == CRYPT_ERROR_PERMISSION );
			}
		else
			status = completePacketStreamSSL( &stream, 0 );
		if( cryptStatusOK( status ) )
			length = stell( &stream );
		sMemDisconnect( &stream );
		}
	/* Fall through with status passed on to the following code */

	/* Send the alert, or if there was an error at least perform a clean 
	   shutdown */
	if( cryptStatusOK( status ) )
		{
		status = sendCloseNotification( sessionInfoPtr, 
										sessionInfoPtr->sendBuffer, 
										length );
		}
	else
		status = sendCloseNotification( sessionInfoPtr, NULL, 0 );
	if( cryptStatusError( status ) || alertReceived )
		return;

	/* Read back the other side's close alert acknowledgement.  Again, since 
	   we're closing down the session anyway there's not much that we can do 
	   in response to an error */
	( void ) readHSPacketSSL( sessionInfoPtr, NULL, &length, 
							  SSL_MSG_ALERT );
	}

STDC_NONNULL_ARG( ( 1 ) ) \
void sendCloseAlert( INOUT SESSION_INFO *sessionInfoPtr, 
					 const BOOLEAN alertReceived )
	{
	assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );

	REQUIRES_V( sanityCheckSessionSSL( sessionInfoPtr ) );
	REQUIRES_V( alertReceived == TRUE || alertReceived == FALSE );

	sendAlert( sessionInfoPtr, SSL_ALERTLEVEL_WARNING, 
			   SSL_ALERT_CLOSE_NOTIFY, alertReceived );
	}

STDC_NONNULL_ARG( ( 1 ) ) \
void sendHandshakeFailAlert( INOUT SESSION_INFO *sessionInfoPtr,
							 IN_RANGE( SSL_ALERT_FIRST, \
									   SSL_ALERT_LAST ) const int alertType )
	{
	assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );

	REQUIRES_V( sanityCheckSessionSSL( sessionInfoPtr ) );
	REQUIRES_V( alertType >= SSL_ALERT_FIRST && \
				alertType <= SSL_ALERT_LAST );

	/* We set the alertReceived flag to true when sending a handshake
	   failure alert to avoid waiting to get back an ack, since this 
	   alert type isn't acknowledged by the other side */
	sendAlert( sessionInfoPtr, SSL_ALERTLEVEL_FATAL, alertType, TRUE );
	}
#endif /* USE_SSL */
