/*
 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
 * 
 * The contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License. Please obtain
 * a copy of the License at http://www.apple.com/publicsource and read it before
 * using this file.
 * 
 * This Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
 * specific language governing rights and limitations under the License.
 */


/*
	File:		sslHandshake.cpp

	Contains:	SSL 3.0 handshake state machine. 

	Written by:	Doug Mitchell

	Copyright: (c) 1999 by Apple Computer, Inc., all rights reserved.

*/

#include "sslContext.h"
#include "sslHandshake.h"
#include "sslMemory.h"
#include "sslAlertMessage.h"
#include "sslSession.h"
#include "sslUtils.h"
#include "sslDebug.h"
#include "appleCdsa.h"
#include "sslDigests.h"

#include <string.h>
#include <assert.h>

#define REQUEST_CERT_CORRECT        0

static OSStatus SSLProcessHandshakeMessage(SSLHandshakeMsg message, SSLContext *ctx);

OSStatus
SSLProcessHandshakeRecord(SSLRecord rec, SSLContext *ctx)
{   OSStatus        err;
    sint32          remaining;
    UInt8           *p;
    SSLHandshakeMsg message;
    SSLBuffer       messageData;
    
    if (ctx->fragmentedMessageCache.data != 0)
    {   if ((err = SSLReallocBuffer(ctx->fragmentedMessageCache,
                    ctx->fragmentedMessageCache.length + rec.contents.length,
                    ctx)) != 0)
        {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
            return err;
        }
        memcpy(ctx->fragmentedMessageCache.data + ctx->fragmentedMessageCache.length,
            rec.contents.data, rec.contents.length);
        remaining = ctx->fragmentedMessageCache.length;
        p = ctx->fragmentedMessageCache.data;
    }
    else
    {   remaining = rec.contents.length;
        p = rec.contents.data;
    }

    while (remaining > 0)
    {   if (remaining < 4)
            break;  /* we must have at least a header */
        
        messageData.data = p;
        message.type = (SSLHandshakeType)*p++;
        message.contents.length = SSLDecodeInt(p, 3);
        if (((int)(message.contents.length + 4)) > remaining)
            break;
        
        p += 3;
        message.contents.data = p;
        p += message.contents.length;
        messageData.length = 4 + message.contents.length;
        assert(p == messageData.data + messageData.length);
        
        /* message fragmentation */
        remaining -= messageData.length;
        if ((err = SSLProcessHandshakeMessage(message, ctx)) != 0)
            return err;
        
        if (message.type != SSL_HdskHelloRequest)
        {   if ((err = SSLHashSHA1.update(ctx->shaState, messageData)) != 0 ||
                (err = SSLHashMD5.update(ctx->md5State, messageData)) != 0)
            {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
                return err;
            }
        }
        
        if ((err = SSLAdvanceHandshake(message.type, ctx)) != 0)
            return err;
    }
    
    if (remaining > 0)      /* Fragmented handshake message */
    {   /* If there isn't a cache, allocate one */
        if (ctx->fragmentedMessageCache.data == 0)
        {   if ((err = SSLAllocBuffer(ctx->fragmentedMessageCache, remaining, ctx)) != 0)
            {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
                return err;
            }
        }
        if (p != ctx->fragmentedMessageCache.data)
        {   memcpy(ctx->fragmentedMessageCache.data, p, remaining);
            ctx->fragmentedMessageCache.length = remaining;
        }
    }
    else if (ctx->fragmentedMessageCache.data != 0)
    {   if ((err = SSLFreeBuffer(ctx->fragmentedMessageCache, ctx)) != 0)
        {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
            return err;
        }
    }
    
    return noErr;
}

static OSStatus
SSLProcessHandshakeMessage(SSLHandshakeMsg message, SSLContext *ctx)
{   OSStatus      err;
    
    err = noErr;
    SSLLogHdskMsg(message.type, 0);
    switch (message.type)
    {   case SSL_HdskHelloRequest:
            if (ctx->protocolSide != SSL_ClientSide)
                goto wrongMessage;
            if (message.contents.length > 0)
                err = errSSLProtocol;
            break;
        case SSL_HdskClientHello:
            if (ctx->state != SSL_HdskStateServerUninit)
                goto wrongMessage;
            err = SSLProcessClientHello(message.contents, ctx);
            break;
        case SSL_HdskServerHello:
            if (ctx->state != SSL_HdskStateServerHello &&
                ctx->state != SSL_HdskStateServerHelloUnknownVersion)
                goto wrongMessage;
            err = SSLProcessServerHello(message.contents, ctx);
            break;
        case SSL_HdskCert:
            if (ctx->state != SSL_HdskStateCert &&
                ctx->state != SSL_HdskStateClientCert)
                goto wrongMessage;
            err = SSLProcessCertificate(message.contents, ctx);
			if(ctx->protocolSide == SSL_ServerSide) {
				if(err) {
					ctx->clientCertState = kSSLClientCertRejected;
				}
				else if(ctx->peerCert != NULL) {
					/* 
					 * This still might change if cert verify msg
					 * fails. Note we avoid going to state
					 * if we get en empty cert message which is
					 * otherwise valid.
					 */
					ctx->clientCertState = kSSLClientCertSent;
				}
			}
            break;
        case SSL_HdskCertRequest:
            if (((ctx->state != SSL_HdskStateHelloDone) && 
			     (ctx->state != SSL_HdskStateKeyExchange))
                 || ctx->certRequested)
                goto wrongMessage;
            err = SSLProcessCertificateRequest(message.contents, ctx);
            break;
        case SSL_HdskServerKeyExchange:
       		/* 
        	 * Since this message is optional, and completely at the
        	 * server's discretion, we need to be able to handle this
        	 * in one of two states...
        	 */
        	switch(ctx->state) {
        		case SSL_HdskStateKeyExchange:	/* explicitly waiting for this */
        		case SSL_HdskStateHelloDone:
        			break;
        		default:
                	goto wrongMessage;
        	}
            err = SSLProcessServerKeyExchange(message.contents, ctx);
            break;
        case SSL_HdskServerHelloDone:
            if (ctx->state != SSL_HdskStateHelloDone)
                goto wrongMessage;
            err = SSLProcessServerHelloDone(message.contents, ctx);
            break;
        case SSL_HdskCertVerify:
            if (ctx->state != SSL_HdskStateClientCertVerify)
                goto wrongMessage;
            err = SSLProcessCertificateVerify(message.contents, ctx);
			assert(ctx->protocolSide == SSL_ServerSide);
			if(err) {
				ctx->clientCertState = kSSLClientCertRejected;
			}
            break;
        case SSL_HdskClientKeyExchange:
            if (ctx->state != SSL_HdskStateClientKeyExchange)
                goto wrongMessage;
            err = SSLProcessKeyExchange(message.contents, ctx);
            break;
        case SSL_HdskFinished:
            if (ctx->state != SSL_HdskStateFinished)
                goto wrongMessage;
            err = SSLProcessFinished(message.contents, ctx);
            break;
        default:
            goto wrongMessage;
            break;
    }
    
    if (err)
    {   if (err == errSSLProtocol)
            SSLFatalSessionAlert(SSL_AlertIllegalParam, ctx);
        else if (err == errSSLNegotiation)
            SSLFatalSessionAlert(SSL_AlertHandshakeFail, ctx);
        else
            SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
    }
    return err;
    
wrongMessage:
    SSLFatalSessionAlert(SSL_AlertUnexpectedMsg, ctx);
    return errSSLProtocol;
}

OSStatus
SSLAdvanceHandshake(SSLHandshakeType processed, SSLContext *ctx)
{   OSStatus        err;
    SSLBuffer       sessionIdentifier;
    
    switch (processed)
    {   case SSL_HdskHelloRequest:
			/* 
			 * Reset the client auth state machine in case this is 
			 * a renegotiation.
			 */
			ctx->certRequested = 0;
			ctx->certSent = 0;
			ctx->certReceived = 0;
			ctx->x509Requested = 0;
			ctx->clientCertState = kSSLClientCertNone;
            if ((err = SSLPrepareAndQueueMessage(SSLEncodeClientHello, ctx)) != 0)
                return err;
            SSLChangeHdskState(ctx, SSL_HdskStateServerHello);
            break;
        case SSL_HdskClientHello:
            assert(ctx->protocolSide == SSL_ServerSide);
            if (ctx->sessionID.data != 0)   
			/* If session ID != 0, client is trying to resume */
            {   if (ctx->resumableSession.data != 0)
                {   if ((err = SSLRetrieveSessionID(ctx->resumableSession, 
								&sessionIdentifier, ctx)) != 0)
                        return err;
                    if (sessionIdentifier.length == ctx->sessionID.length &&
                        memcmp(sessionIdentifier.data, ctx->sessionID.data, 
										ctx->sessionID.length) == 0)
                    {   /* Everything matches; resume the session */
						sslLogResumSessDebug("===RESUMING SSL3 server-side session");
                        if ((err = SSLInstallSessionFromData(ctx->resumableSession,
								ctx)) != 0)
                        {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
                            return err;
                        }
                        if ((err = SSLPrepareAndQueueMessage(SSLEncodeServerHello, 
									ctx)) != 0)
                            return err;
                        if ((err = SSLInitPendingCiphers(ctx)) != 0 ||
                            (err = SSLFreeBuffer(sessionIdentifier, ctx)) != 0)
                        {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
                            return err;
                        }
                        if ((err = 
								SSLPrepareAndQueueMessage(SSLEncodeChangeCipherSpec,
									ctx)) != 0)
                            return err;
                        /* Install new cipher spec on write side */
                        if ((err = SSLDisposeCipherSuite(&ctx->writeCipher, 
								ctx)) != 0)
                        {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
                            return err;
                        }
                        ctx->writeCipher = ctx->writePending;
                        ctx->writeCipher.ready = 0;     
								/* Can't send data until Finished is sent */
                        memset(&ctx->writePending, 0, sizeof(CipherContext));       
								/* Zero out old data */
                        if ((err = SSLPrepareAndQueueMessage(SSLEncodeFinishedMessage, 
								ctx)) != 0)
                            return err;
                        /* Finished has been sent; enable data t6ransfer on 
						 * write channel */
                        ctx->writeCipher.ready = 1;
                        SSLChangeHdskState(ctx, SSL_HdskStateChangeCipherSpec);
                        break;
                    }
					else {
						sslLogResumSessDebug(
							"===FAILED TO RESUME SSL3 server-side session");
					}
                    if ((err = SSLFreeBuffer(sessionIdentifier, ctx)) != 0 ||
                        (err = SSLDeleteSessionData(ctx)) != 0)
                    {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
                        return err;
                    }
                }
                if ((err = SSLFreeBuffer(ctx->sessionID, ctx)) != 0)
                {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
                    return err;
                }
            }
            
            /* 
			 * If we get here, we're not resuming; generate a new session ID 
			 * if we know our peer 
			 */
            if (ctx->peerID.data != 0)
            {   /* Ignore errors; just treat as uncached session */
                assert(ctx->sessionID.data == 0);
                err = SSLAllocBuffer(ctx->sessionID, SSL_SESSION_ID_LEN, ctx);
                if (err == 0)
                {   
                	if((err = sslRand(ctx, &ctx->sessionID)) != 0)
                    {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
                        return err;
                    }
                }
            }
            
            if ((err = SSLPrepareAndQueueMessage(SSLEncodeServerHello, ctx)) != 0)
                return err;
            switch (ctx->selectedCipherSpec->keyExchangeMethod)
            {   case SSL_NULL_auth:
            	#if		APPLE_DH
                case SSL_DH_anon:
                case SSL_DH_anon_EXPORT:
                	if(ctx->clientAuth == kAlwaysAuthenticate) {
                		/* app requires this; abort */
                		SSLFatalSessionAlert(SSL_AlertHandshakeFail, ctx);
                		return errSSLNegotiation;
                	}
                	ctx->tryClientAuth = false;
					/* DH server side needs work */
                    break;
                #endif	/* APPLE_DH */
                default:        /* everything else */
                    if ((err = SSLPrepareAndQueueMessage(SSLEncodeCertificate, 
							ctx)) != 0)
                        return err;
                    break;
            }
			/*
			 * At this point we decide whether to send a server key exchange
			 * method. For Apple servers, I think we'll ALWAYS do this, because
			 * of key usage restrictions (can't decrypt and sign with the same
			 * private key), but conceptually in this code, we do it if 
			 * enabled by the presence of encryptPrivKey. 
			 */
			#if		SSL_SERVER_KEYEXCH_HACK	
				/*
					* This is currently how we work with Netscape. It requires
					* a CSP which can handle private keys which can both
					* sign and decrypt. 
					*/
				if((ctx->selectedCipherSpec->keyExchangeMethod != SSL_RSA) &&
					(ctx->encryptPrivKey != NULL)) {
					err = SSLPrepareAndQueueMessage(SSLEncodeServerKeyExchange, ctx);
					if(err) {
						return err;
					}
				}
			#else	/* !SSL_SERVER_KEYEXCH_HACK */
				/*
					* This is, I believe the "right" way, but Netscape doesn't
					* work this way.
					*/
				if (ctx->encryptPrivKey != NULL) {
					err = SSLPrepareAndQueueMessage(SSLEncodeServerKeyExchange, ctx);
					if(err) {
						return err;
					}
				}
			#endif	/* SSL_SERVER_KEYEXCH_HACK */

            if (ctx->tryClientAuth)
            {   if ((err = SSLPrepareAndQueueMessage(SSLEncodeCertificateRequest, 
						ctx)) != 0)
                    return err;
                ctx->certRequested = 1;
				ctx->clientCertState = kSSLClientCertRequested;
            }
            if ((err = SSLPrepareAndQueueMessage(SSLEncodeServerHelloDone, ctx)) != 0)
                return err;
            if (ctx->certRequested) {
                SSLChangeHdskState(ctx, SSL_HdskStateClientCert);
            }
            else {
                SSLChangeHdskState(ctx, SSL_HdskStateClientKeyExchange);
            }
            break;
        case SSL_HdskServerHello:
            if (ctx->resumableSession.data != 0 && ctx->sessionID.data != 0)
            {   if ((err = SSLRetrieveSessionID(ctx->resumableSession, 
						&sessionIdentifier, ctx)) != 0)
                {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
                    return err;
                }
                if (sessionIdentifier.length == ctx->sessionID.length &&
                    memcmp(sessionIdentifier.data, ctx->sessionID.data, 
							ctx->sessionID.length) == 0)
                {   /* Everything matches; resume the session */
					sslLogResumSessDebug("===RESUMING SSL3 client-side session");
                    if ((err = SSLInstallSessionFromData(ctx->resumableSession,
							ctx)) != 0 ||
                        (err = SSLInitPendingCiphers(ctx)) != 0 ||
                        (err = SSLFreeBuffer(sessionIdentifier, ctx)) != 0)
                    {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
                        return err;
                    }
                    SSLChangeHdskState(ctx, SSL_HdskStateChangeCipherSpec);
                    break;
                }
				else {
					sslLogResumSessDebug("===FAILED TO RESUME SSL3 client-side "
							"session");
				}
                if ((err = SSLFreeBuffer(sessionIdentifier, ctx)) != 0)
                {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
                    return err;
                }
            }
            switch (ctx->selectedCipherSpec->keyExchangeMethod)
            {   
            	/* these require a key exchange message */
            	case SSL_NULL_auth:
                case SSL_DH_anon:
                case SSL_DH_anon_EXPORT:
                    SSLChangeHdskState(ctx, SSL_HdskStateKeyExchange);
                    break;
                case SSL_RSA:
                case SSL_DH_DSS:
                case SSL_DH_DSS_EXPORT:
                case SSL_DH_RSA:
                case SSL_DH_RSA_EXPORT:
                case SSL_RSA_EXPORT:
                case SSL_DHE_DSS:
                case SSL_DHE_DSS_EXPORT:
                case SSL_DHE_RSA:
                case SSL_DHE_RSA_EXPORT:
                case SSL_Fortezza:
                    SSLChangeHdskState(ctx, SSL_HdskStateCert);
                    break;
                default:
                    assert("Unknown key exchange method");
                    break;
            }
            break;
        case SSL_HdskCert:
            if (ctx->state == SSL_HdskStateCert)
                switch (ctx->selectedCipherSpec->keyExchangeMethod)
                {   case SSL_RSA:
                 	/*
                	 * I really think the two RSA cases should be
                	 * handled the same here - the server key exchange is
                	 * optional, and is up to the server.
                	 * Note this isn't the same as SSL_SERVER_KEYEXCH_HACK;
                	 * we're a client here.
                	 */                   
                	case SSL_RSA_EXPORT:
                    case SSL_DH_DSS:
                    case SSL_DH_DSS_EXPORT:
                    case SSL_DH_RSA:
                    case SSL_DH_RSA_EXPORT:
                        SSLChangeHdskState(ctx, SSL_HdskStateHelloDone);
                        break;
                    case SSL_DHE_DSS:
                    case SSL_DHE_DSS_EXPORT:
                    case SSL_DHE_RSA:
                    case SSL_DHE_RSA_EXPORT:
                    case SSL_Fortezza:
                        SSLChangeHdskState(ctx, SSL_HdskStateKeyExchange);
                        break;
                    default:
                        assert("Unknown or unexpected key exchange method");
                        break;
                }
            else if (ctx->state == SSL_HdskStateClientCert)
            {   SSLChangeHdskState(ctx, SSL_HdskStateClientKeyExchange);
                if (ctx->peerCert != 0)
                    ctx->certReceived = 1;
            }
            break;
        case SSL_HdskCertRequest:   
			/* state stays in SSL_HdskStateHelloDone; distinction is in
			 *  ctx->certRequested */
            if (ctx->peerCert == 0)
            {   SSLFatalSessionAlert(SSL_AlertHandshakeFail, ctx);
                return errSSLProtocol;
            }
            ctx->certRequested = 1;
			ctx->clientCertState = kSSLClientCertRequested;
            break;
        case SSL_HdskServerKeyExchange:
            SSLChangeHdskState(ctx, SSL_HdskStateHelloDone);
            break;
        case SSL_HdskServerHelloDone:
            if (ctx->certRequested) {
				/* 
				 * Server wants a client authentication cert - do 
				 * we have one? 
				 */
                if (ctx->localCert != 0 && ctx->x509Requested) {
					if ((err = SSLPrepareAndQueueMessage(SSLEncodeCertificate,
							ctx)) != 0) {
						return err;
					}
                }
                else {
					/* response for no cert depends on protocol version */
					if(ctx->negProtocolVersion == TLS_Version_1_0) {
						/* TLS: send empty cert msg */
						if ((err = SSLPrepareAndQueueMessage(SSLEncodeCertificate,
								ctx)) != 0) {
							return err;
						}
					}
					else {
						/* SSL3: "no cert" alert */
						if ((err = SSLSendAlert(SSL_AlertLevelWarning, SSL_AlertNoCert,
								ctx)) != 0) {
							return err;
						}
					}
                }	/* no cert to send */
            }	/* server requested a cert */
            if ((err = SSLPrepareAndQueueMessage(SSLEncodeKeyExchange, ctx)) != 0)
                return err;
			assert(ctx->sslTslCalls != NULL);
            if ((err = ctx->sslTslCalls->generateMasterSecret(ctx)) != 0 ||
                (err = SSLInitPendingCiphers(ctx)) != 0)
            {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
                return err;
            }
			memset(ctx->preMasterSecret.data, 0, ctx->preMasterSecret.length);
            if ((err = SSLFreeBuffer(ctx->preMasterSecret, ctx)) != 0) {
                return err;
			}
            if (ctx->certSent) {
                if ((err = SSLPrepareAndQueueMessage(SSLEncodeCertificateVerify, 
						ctx)) != 0) {
                    return err;
				}
			}
            if ((err = SSLPrepareAndQueueMessage(SSLEncodeChangeCipherSpec, 
					ctx)) != 0) {
                return err;
			}
            /* Install new cipher spec on write side */
            if ((err = SSLDisposeCipherSuite(&ctx->writeCipher, ctx)) != 0)
            {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
                return err;
            }
            ctx->writeCipher = ctx->writePending;
			/* Can't send data until Finished is sent */
            ctx->writeCipher.ready = 0;     
			
			/* Zero out old data */
            memset(&ctx->writePending, 0, sizeof(CipherContext));    
			ctx->writePending.encrypting = 1;   
            if ((err = SSLPrepareAndQueueMessage(SSLEncodeFinishedMessage, ctx)) != 0)
                return err;
            /* Finished has been sent; enable data transfer on write channel */
            ctx->writeCipher.ready = 1;
            SSLChangeHdskState(ctx, SSL_HdskStateChangeCipherSpec);
            break;
        case SSL_HdskCertVerify:
            SSLChangeHdskState(ctx, SSL_HdskStateChangeCipherSpec);
            break;
        case SSL_HdskClientKeyExchange:
 			assert(ctx->sslTslCalls != NULL);
			if ((err = ctx->sslTslCalls->generateMasterSecret(ctx)) != 0 ||
                (err = SSLInitPendingCiphers(ctx)) != 0)
            {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
                return err;
            }
			memset(ctx->preMasterSecret.data, 0, ctx->preMasterSecret.length);
            if ((err = SSLFreeBuffer(ctx->preMasterSecret, ctx)) != 0)
                return err;
            if (ctx->certReceived) {
                SSLChangeHdskState(ctx, SSL_HdskStateClientCertVerify);
            }
            else {
                SSLChangeHdskState(ctx, SSL_HdskStateChangeCipherSpec);
            }
            break;
        case SSL_HdskFinished:
            /* Handshake is over; enable data transfer on read channel */
            ctx->readCipher.ready = 1;
            /* If writePending is set, we haven't yet sent a finished message; 
			 * send it */
            if (ctx->writePending.ready != 0)
            {   if ((err = SSLPrepareAndQueueMessage(SSLEncodeChangeCipherSpec, 
						ctx)) != 0)
                    return err;
                
                /* Install new cipher spec on write side */
                if ((err = SSLDisposeCipherSuite(&ctx->writeCipher, ctx)) != 0)
                {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
                    return err;
                }
                ctx->writeCipher = ctx->writePending;
                ctx->writeCipher.ready = 0;     
						/* Can't send data until Finished is sent */
                memset(&ctx->writePending, 0, sizeof(CipherContext));       
						/* Zero out old data */
                if ((err = SSLPrepareAndQueueMessage(SSLEncodeFinishedMessage, 
							ctx)) != 0)
                    return err;
                ctx->writeCipher.ready = 1;
            }
            if (ctx->protocolSide == SSL_ServerSide) {
                SSLChangeHdskState(ctx, SSL2_HdskStateServerReady);
            }
            else {
                SSLChangeHdskState(ctx, SSL2_HdskStateClientReady);
            }
            if (ctx->peerID.data != 0)
                SSLAddSessionData(ctx);
            break;
        default:
            assert("Unknown State");
            break;
    }
    
    return noErr;
}

OSStatus
SSLPrepareAndQueueMessage(EncodeMessageFunc msgFunc, SSLContext *ctx)
{   OSStatus        err;
    SSLRecord       rec;
    
    if ((err = msgFunc(rec, ctx)) != 0)
    {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
        goto fail;
    }
    
    if (rec.contentType == SSL_RecordTypeHandshake)
    {   if ((err = SSLHashSHA1.update(ctx->shaState, rec.contents)) != 0 ||
            (err = SSLHashMD5.update(ctx->md5State, rec.contents)) != 0)
        {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
            goto fail;
        }
        SSLLogHdskMsg((SSLHandshakeType)rec.contents.data[0], 1);
    }
    
	assert(ctx->sslTslCalls != NULL);
    if ((err = ctx->sslTslCalls->writeRecord(rec, ctx)) != 0)
        goto fail;
    
    err = noErr;
fail:
    SSLFreeBuffer(rec.contents, ctx);
    
    return err;
}

OSStatus
SSL3ReceiveSSL2ClientHello(SSLRecord rec, SSLContext *ctx)
{   OSStatus      err;
    
    if ((err = SSLInitMessageHashes(ctx)) != 0)
        return err;
    
    if ((err = SSLHashSHA1.update(ctx->shaState, rec.contents)) != 0 ||
        (err = SSLHashMD5.update(ctx->md5State, rec.contents)) != 0)
    {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
        return err;
    }
    
    if ((err = SSLAdvanceHandshake(SSL_HdskClientHello, ctx)) != 0)
        return err;
    
    return noErr;
}

/* log changes in handshake state */
#ifndef	NDEBUG
#include <stdio.h>

char *hdskStateToStr(SSLHandshakeState state)
{
	static char badStr[100];
	
	switch(state) {
		case SSL_HdskStateUninit:
			return "Uninit";	
		case SSL_HdskStateServerUninit:
			return "ServerUninit";	
		case SSL_HdskStateClientUninit:
			return "ClientUninit";	
		case SSL_HdskStateGracefulClose:
			return "GracefulClose";	
		case SSL_HdskStateErrorClose:
			return "ErrorClose";		
		case SSL_HdskStateNoNotifyClose:
			return "NoNotifyClose";
		case SSL_HdskStateServerHello:
			return "ServerHello";	
		case SSL_HdskStateServerHelloUnknownVersion:
			return "ServerHelloUnknownVersion";	
		case SSL_HdskStateKeyExchange:
			return "KeyExchange";	
		case SSL_HdskStateCert:
			return "Cert";	
		case SSL_HdskStateHelloDone:
			return "HelloDone";	
		case SSL_HdskStateClientCert:
			return "ClientCert";	
		case SSL_HdskStateClientKeyExchange:
			return "ClientKeyExchange";	
		case SSL_HdskStateClientCertVerify:
			return "ClientCertVerify";	
		case SSL_HdskStateChangeCipherSpec:
			return "ChangeCipherSpec";	
		case SSL_HdskStateFinished:
			return "Finished";	
		case SSL2_HdskStateClientMasterKey:
			return "SSL2_ClientMasterKey";
		case SSL2_HdskStateClientFinished:
			return "SSL2_ClientFinished";	
		case SSL2_HdskStateServerHello:
			return "SSL2_ServerHello";	
		case SSL2_HdskStateServerVerify:
			return "SSL2_ServerVerify";	
		case SSL2_HdskStateServerFinished:
			return "SSL2_ServerFinished";	
		case SSL2_HdskStateServerReady:
			return "SSL2_ServerReady";	
		case SSL2_HdskStateClientReady:
			return "SSL2_ClientReady";
		default:
			sprintf(badStr, "Unknown state (%d(d)", state);
			return badStr;
	}
}

void SSLChangeHdskState(SSLContext *ctx, SSLHandshakeState newState)
{
	/* FIXME - this ifndef should not be necessary */
	#ifndef	NDEBUG
	sslHdskStateDebug("...hdskState = %s", hdskStateToStr(newState));
	#endif
	ctx->state = newState;
}


/* log handshake messages */

static char *hdskMsgToStr(SSLHandshakeType msg)
{
	static char badStr[100];
	
	switch(msg) {
		case SSL_HdskHelloRequest:
			return "SSL_HdskHelloRequest";	
		case SSL_HdskClientHello:
			return "SSL_HdskClientHello";	
		case SSL_HdskServerHello:
			return "SSL_HdskServerHello";	
		case SSL_HdskCert:
			return "SSL_HdskCert";	
		case SSL_HdskServerKeyExchange:
			return "SSL_HdskServerKeyExchange";	
		case SSL_HdskCertRequest:
			return "SSL_HdskCertRequest";	
		case SSL_HdskServerHelloDone:
			return "SSL_HdskServerHelloDone";	
		case SSL_HdskCertVerify:
			return "SSL_HdskCertVerify";	
		case SSL_HdskClientKeyExchange:
			return "SSL_HdskClientKeyExchange";	
		case SSL_HdskFinished:
			return "SSL_HdskFinished";	
		case SSL_HdskNoCertAlert:
			return "SSL_HdskNoCertAlert";	
		default:
			sprintf(badStr, "Unknown state (%d(d)", msg);
			return badStr;
	}
}

void SSLLogHdskMsg(SSLHandshakeType msg, char sent)
{
	sslHdskMsgDebug("---%s handshake msg %s", 
		hdskMsgToStr(msg), (sent ? "sent" : "recv"));
}

#endif	/* NDEBUG */

