/*
 * Copyright (C) 2019-2020 Teluu Inc. (http://www.teluu.com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <pj/ssl_sock.h>
#include <pj/os.h>
#include <pj/pool.h>
#include <pj/rand.h>

/* Only build when PJ_HAS_SSL_SOCK and the implementation is Apple SSL. */
#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 && \
    (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_APPLE)

#define THIS_FILE               "ssl_sock_apple.m"

#define SSL_SOCK_IMP_USE_CIRC_BUF
#define SSL_SOCK_IMP_USE_OWN_NETWORK

#include "ssl_sock_imp_common.h"
#include "ssl_sock_imp_common.c"

#include "TargetConditionals.h"

#include <err.h>
#include <CoreFoundation/CoreFoundation.h>
#include <CommonCrypto/CommonDigest.h> 
#include <Foundation/NSLock.h>
#include <Network/Network.h>
#include <Security/Security.h>

/* IMPORTANT note from Apple's Concurrency Programming Guide doc:
 * "Because Grand Central Dispatch manages the relationship between the tasks
 * you provide and the threads on which those tasks run, you should generally
 * avoid calling POSIX thread routines from your task code"
 *
 * Since network events happen in a dispatch function block, we need to make
 * sure not to call any PJLIB functions there (not even pj_pool_alloc() nor
 * pj_log()). Instead, we will post those events to a singleton event manager
 * to be polled by ioqueue polling thread(s).
 */

/* Secure socket structure definition. */
typedef struct applessl_sock_t {
    pj_ssl_sock_t  	base;

    nw_listener_t 	listener;
    nw_connection_t     connection;
    dispatch_queue_t	queue;
    dispatch_semaphore_t ev_semaphore;

    SecTrustRef		trust;
    tls_ciphersuite_t	cipher;
    sec_identity_t	identity;
} applessl_sock_t;


/*
 *******************************************************************
 * Event manager
 *******************************************************************
 */
 
 typedef enum event_id
{
    EVENT_ACCEPT,
    EVENT_CONNECT,
    EVENT_VERIFY_CERT,
    EVENT_HANDSHAKE_COMPLETE,
    EVENT_DATA_READ,
    EVENT_DATA_SENT
} event_id;

typedef struct event_t
{
    PJ_DECL_LIST_MEMBER(struct event_t);

    event_id 		 type;
    pj_ssl_sock_t 	*ssock;
    pj_bool_t		 async;

    union
    {
        struct
        {
            nw_connection_t	 newconn;
            pj_sockaddr		 src_addr;
            int			 src_addr_len;
            pj_status_t 	 status;
        } accept_ev;

        struct
        {
            pj_status_t 	 status;
        } connect_ev;

	struct
        {
            pj_status_t 	 status;
        } handshake_ev;

        struct
        {
            pj_ioqueue_op_key_t *send_key;
	    pj_ssize_t 		 sent;
        } data_sent_ev;

        struct
        {
	    void 		*data;
	    pj_size_t 		 size;
	    pj_status_t 	 status;
	    pj_size_t 		 remainder;
        } data_read_ev;

    } body;
} event_t;

typedef struct event_manager
{
    NSLock	*lock;
    event_t	 event_list;
    event_t	 free_event_list;
} event_manager;

static event_manager *event_mgr = NULL;

/*
 *******************************************************************
 * Event manager's functions
 *******************************************************************
 */

static pj_status_t verify_cert(applessl_sock_t *assock, pj_ssl_cert_t *cert);

static void event_manager_destroy()
{
    event_manager *mgr = event_mgr;
    
    event_mgr = NULL;

    while (!pj_list_empty(&mgr->free_event_list)) {
    	event_t *event = mgr->free_event_list.next;
        pj_list_erase(event);
        free(event);
    }

    while (!pj_list_empty(&mgr->event_list)) {
    	event_t *event = mgr->event_list.next;
        pj_list_erase(event);
        free(event);
    }
    
    [mgr->lock release];

    free(mgr);
}

static pj_status_t event_manager_create()
{
    event_manager *mgr;
    
    if (event_mgr)
    	return PJ_SUCCESS;
    
    mgr = malloc(sizeof(event_manager));
    if (!mgr) return PJ_ENOMEM;

    mgr->lock = [[NSLock alloc]init];
    pj_list_init(&mgr->event_list);
    pj_list_init(&mgr->free_event_list);

    event_mgr = mgr;
    pj_atexit(&event_manager_destroy);

    return PJ_SUCCESS;
}

/* Post event to the event manager. If the event is posted
 * synchronously, the function will wait until the event is processed.
 */
static pj_status_t event_manager_post_event(pj_ssl_sock_t *ssock,
					    event_t *event_item,
					    pj_bool_t async)
{
    event_manager *mgr = event_mgr;
    event_t *event;
    
    [mgr->lock lock];

    if (pj_list_empty(&mgr->free_event_list)) {
        event = malloc(sizeof(event_t));
    } else {
        event = mgr->free_event_list.next;
        pj_list_erase(event);
    }
    
    pj_memcpy(event, event_item, sizeof(event_t));
    event->ssock = ssock;
    event->async = async;
    pj_list_push_back(&mgr->event_list, event);
    
    [mgr->lock unlock];
    
    if (!async) {
	dispatch_semaphore_wait(((applessl_sock_t *)ssock)->ev_semaphore,
			        DISPATCH_TIME_FOREVER);
    }

    return PJ_SUCCESS;
}

/* Remove all events associated with the socket. */
static void event_manager_remove_events(pj_ssl_sock_t *ssock)
{
    event_t *event;
    	
    [event_mgr->lock lock];
    event = event_mgr->event_list.next;
    while (event != &event_mgr->event_list) {
    	event_t *event_ = event;

    	event = event->next;
    	if (event_->ssock == ssock) {
    	    pj_list_erase(event_);
	    /* If not async, signal the waiting socket */
	    if (!event_->async) {
	        applessl_sock_t * assock;
	        assock = (applessl_sock_t *)event_->ssock;
	    	dispatch_semaphore_signal(assock->ev_semaphore);
	    }
    	}
    }
    [event_mgr->lock unlock];
}

pj_status_t ssl_network_event_poll()
{
    if (!event_mgr)
    	return PJ_SUCCESS;

    while (!pj_list_empty(&event_mgr->event_list)) {
    	applessl_sock_t * assock;
    	event_t *event;
    	pj_bool_t ret = PJ_TRUE;
    	
    	[event_mgr->lock lock];
    	/* Check again, this time by holding the lock */
    	if (pj_list_empty(&event_mgr->event_list)) {
    	    [event_mgr->lock unlock];
    	    break;
    	}
    	event = event_mgr->event_list.next;
    	pj_list_erase(event);
    	[event_mgr->lock unlock];

	assock = (applessl_sock_t *)event->ssock;
	switch (event->type) {
	    case EVENT_ACCEPT:
		ret = ssock_on_accept_complete(event->ssock,
		    PJ_INVALID_SOCKET,
		    event->body.accept_ev.newconn,
	    	    &event->body.accept_ev.src_addr,
	    	    event->body.accept_ev.src_addr_len,
	    	    event->body.accept_ev.status);
	    	break;
	    case EVENT_CONNECT:
	    	ret = ssock_on_connect_complete(event->ssock,
	    	    event->body.connect_ev.status);
	    	break;
	    case EVENT_VERIFY_CERT:
	    	verify_cert(assock, event->ssock->cert);
	    	break;
	    case EVENT_HANDSHAKE_COMPLETE:
	        event->ssock->ssl_state = SSL_STATE_ESTABLISHED;
	    	ret = on_handshake_complete(event->ssock,
	    	    event->body.handshake_ev.status);
	    	break;
	    case EVENT_DATA_SENT:
	    	ret = ssock_on_data_sent(event->ssock,
	    	    event->body.data_sent_ev.send_key,
	    	    event->body.data_sent_ev.sent);
	    	break;
	    case EVENT_DATA_READ:
	    	ret = ssock_on_data_read(event->ssock,
	    	    event->body.data_read_ev.data,
	    	    event->body.data_read_ev.size,
	    	    event->body.data_read_ev.status,
	    	    &event->body.data_read_ev.remainder);
	    	break;
	    default:
	    	break;
	}

	/* If not async and not destroyed, signal the waiting socket */
	if (ret && !event->async && ret) {
	    dispatch_semaphore_signal(assock->ev_semaphore);
	}
	
    	/* Put the event into the free list to be reused */
    	[event_mgr->lock lock];
    	pj_list_push_back(&event_mgr->free_event_list, event);
    	[event_mgr->lock unlock];	
    }

    return 0;
}

/*
 *******************************************************************
 * Static/internal functions.
 *******************************************************************
 */
 
#define PJ_SSL_ERRNO_START		(PJ_ERRNO_START_USER + \
					 PJ_ERRNO_SPACE_SIZE*6)

#define PJ_SSL_ERRNO_SPACE_SIZE		PJ_ERRNO_SPACE_SIZE

/* Convert from Apple SSL error to pj_status_t. */
static pj_status_t pj_status_from_err(applessl_sock_t *assock,
				      const char *msg,
				      OSStatus err)
{
    pj_status_t status = (pj_status_t)-err;
    CFStringRef errmsg;
    
    errmsg = SecCopyErrorMessageString(err, NULL);
    PJ_LOG(3, (THIS_FILE, "Apple SSL error %s [%d]: %s",
    	       (msg? msg: ""), err,
    	       CFStringGetCStringPtr(errmsg, kCFStringEncodingUTF8)));
    CFRelease(errmsg);

    if (status > PJ_SSL_ERRNO_SPACE_SIZE)
	status = PJ_SSL_ERRNO_SPACE_SIZE;
    status += PJ_SSL_ERRNO_START;

    if (assock)
        assock->base.last_err = err;

    return status;
}

/* Read cert or key file */
static pj_status_t create_data_from_file(CFDataRef *data,
					 pj_str_t *fname, pj_str_t *path)
{
    CFURLRef file;
    CFReadStreamRef read_stream;
    UInt8 data_buf[8192];
    CFIndex nbytes = 0;
    
    if (path) {
        CFURLRef filepath;
        CFStringRef path_str;
        
        path_str = CFStringCreateWithBytes(NULL, (const UInt8 *)path->ptr,
        				   path->slen,
        				   kCFStringEncodingUTF8, false);
        if (!path_str) return PJ_ENOMEM;
    
        filepath = CFURLCreateWithFileSystemPath(NULL, path_str,
        					 kCFURLPOSIXPathStyle, true);
        CFRelease(path_str);
        if (!filepath) return PJ_ENOMEM;
    
        path_str = CFStringCreateWithBytes(NULL, (const UInt8 *)fname->ptr,
        				   fname->slen,
    		   			   kCFStringEncodingUTF8, false);
        if (!path_str) {
    	    CFRelease(filepath);
    	    return PJ_ENOMEM;
        }
    
        file = CFURLCreateCopyAppendingPathComponent(NULL, filepath,
        					     path_str, false);
        CFRelease(path_str);
        CFRelease(filepath);
    } else {
        file = CFURLCreateFromFileSystemRepresentation(NULL,
    	       (const UInt8 *)fname->ptr, fname->slen, false);
    }
    
    if (!file)
        return PJ_ENOMEM;
    
    read_stream = CFReadStreamCreateWithFile(NULL, file);
    CFRelease(file);
    
    if (!read_stream)
        return PJ_ENOTFOUND;
    
    if (!CFReadStreamOpen(read_stream)) {
        PJ_LOG(2, (THIS_FILE, "Failed opening file"));
        CFRelease(read_stream);
        return PJ_EINVAL;
    }
    
    nbytes = CFReadStreamRead(read_stream, data_buf,
    			      sizeof(data_buf));
    if (nbytes > 0)
        *data = CFDataCreate(NULL, data_buf, nbytes);
    else
    	*data = NULL;
    
    CFReadStreamClose(read_stream);
    CFRelease(read_stream);
    
    return (*data? PJ_SUCCESS: PJ_EINVAL);
}

static pj_status_t create_identity_from_cert(applessl_sock_t *assock,
			    		     pj_ssl_cert_t *cert,
			    		     sec_identity_t *p_identity)
{
    CFStringRef password = NULL;
    CFDataRef cert_data = NULL;
    void *keys[1] = {NULL};
    void *values[1] = {NULL};
    CFDictionaryRef options;
    CFArrayRef items;
    CFIndex i, count;
    SecIdentityRef identity = NULL;
    OSStatus err;
    pj_status_t status;

    /* Init */
    *p_identity = NULL;

    if (cert->privkey_file.slen || cert->privkey_buf.slen ||
    	cert->privkey_pass.slen)
    {
    	PJ_LOG(5, (THIS_FILE, "Ignoring supplied private key. Private key "
    			      "must be placed in the keychain instead."));
    }

    if (cert->cert_file.slen) {
    	status = create_data_from_file(&cert_data, &cert->cert_file, NULL);
    	if (status != PJ_SUCCESS) {
    	    PJ_PERROR(2, (THIS_FILE, status, "Failed reading cert file"));
    	    return status;
    	}
    } else if (cert->cert_buf.slen) {
    	cert_data = CFDataCreate(NULL, (const UInt8 *)cert->cert_buf.ptr,
    				 cert->cert_buf.slen);
    	if (!cert_data)
    	    return PJ_ENOMEM;
    }

    if (cert_data) {
        if (cert->privkey_pass.slen) {
	    password = CFStringCreateWithBytes(NULL,
		           (const UInt8 *)cert->privkey_pass.ptr,
		           cert->privkey_pass.slen,
		           kCFStringEncodingUTF8,
		           false);
	    keys[0] = (void *)kSecImportExportPassphrase;
	    values[0] = (void *)password;
        }
    
        options = CFDictionaryCreate(NULL, (const void **)keys,
    				     (const void **)values,
    				     (password? 1: 0), NULL, NULL);
        if (!options)
    	    return PJ_ENOMEM;
        
#if TARGET_OS_IPHONE
        err = SecPKCS12Import(cert_data, options, &items);
#else
        {
    	    SecExternalFormat ext_format[3] = {kSecFormatPKCS12,
    	    				       kSecFormatPEMSequence,
    	    				       kSecFormatX509Cert/* DER */};
    	    SecExternalItemType ext_type = kSecItemTypeCertificate;
    	    SecItemImportExportKeyParameters key_params;
    
    	    pj_bzero(&key_params, sizeof(key_params));
    	    key_params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
    	    key_params.passphrase = password;
    
    	    for (i = 0; i < PJ_ARRAY_SIZE(ext_format); i++) {
    	    	items = NULL;
    		err = SecItemImport(cert_data, NULL, &ext_format[i],
    				    &ext_type, 0, &key_params, NULL, &items);
    		if (err == noErr && items) {
    		    break;
    		}
    	    }
        }
#endif
    
        CFRelease(options);
        if (password)
    	    CFRelease(password);
        CFRelease(cert_data);
        if (err != noErr || !items) {
    	    return pj_status_from_err(assock, "SecItemImport", err);
        }
    
        count = CFArrayGetCount(items);
    
        for (i = 0; i < count; i++) {
    	    CFTypeRef item;
    	    CFTypeID item_id;
    	
    	    item = (CFTypeRef) CFArrayGetValueAtIndex(items, i);
    	    item_id = CFGetTypeID(item);
    
    	    if (item_id == CFDictionaryGetTypeID()) {
    	    	identity = (SecIdentityRef)
    		           CFDictionaryGetValue((CFDictionaryRef) item,
    					        kSecImportItemIdentity);
    	        break;
    	    }
#if !TARGET_OS_IPHONE
    	    else if (item_id == SecCertificateGetTypeID()) {
    	    	err = SecIdentityCreateWithCertificate(NULL,
    		          (SecCertificateRef) item, &identity);
    	    	if (err != noErr) {
    		    pj_status_from_err(assock, "SecIdentityCreate", err);
    	    	    if (err == errSecItemNotFound) {
    	    	    	PJ_LOG(2, (THIS_FILE, "Private key must be placed in "
    	    	    			      "the keychain"));
    	    	    }
    	    	} else {
    	    	    break;
    	    	}
    	    }
#endif
        }
    
        CFRelease(items);
    
        if (!identity) {
    	    PJ_LOG(2, (THIS_FILE, "Failed extracting identity from "
    			      	  "the cert file"));
    	    return PJ_EINVAL;
        }

	*p_identity = sec_identity_create(identity);
    
        CFRelease(identity);
    }

    return PJ_SUCCESS;
}

static pj_status_t verify_cert(applessl_sock_t *assock, pj_ssl_cert_t *cert)
{
    CFDataRef ca_data = NULL;
    SecTrustRef trust = assock->trust;
    bool result;
    CFErrorRef error;
    pj_status_t status = PJ_SUCCESS;
    OSStatus err = noErr;
    
    if (trust && cert && cert->CA_file.slen) {
    	status = create_data_from_file(&ca_data, &cert->CA_file,
    				       (cert->CA_path.slen? &cert->CA_path:
    				        NULL));
    	if (status != PJ_SUCCESS)
    	    PJ_LOG(2, (THIS_FILE, "Failed reading CA file"));
    } else if (trust && cert && cert->CA_buf.slen) {
    	ca_data = CFDataCreate(NULL, (const UInt8 *)cert->CA_buf.ptr,
    			       cert->CA_buf.slen);
    	if (!ca_data)
    	    PJ_LOG(2, (THIS_FILE, "Not enough memory for CA buffer"));
    }
    
    if (ca_data) {
        SecCertificateRef ca_cert;
        CFMutableArrayRef ca_array;

    	ca_cert = SecCertificateCreateWithData(NULL, ca_data);
    	CFRelease(ca_data);	
    	if (!ca_cert) {
    	    PJ_LOG(2, (THIS_FILE, "Failed creating certificate from "
    	    		      	  "CA file/buffer. It has to be "
    	    		      	  "in DER format."));
    	    status = PJ_EINVAL;
    	    goto on_return;
    	}
    
    	ca_array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
   	if (!ca_array) {
    	    PJ_LOG(2, (THIS_FILE, "Not enough memory for CA array"));
    	    CFRelease(ca_cert);
    	    status = PJ_ENOMEM;
    	    goto on_return;
    	}

    	CFArrayAppendValue(ca_array, ca_cert);
    	CFRelease(ca_cert);
    	
    	err = SecTrustSetAnchorCertificates(trust, ca_array);
    	CFRelease(ca_array);
    	if (err != noErr)
  	    pj_status_from_err(assock, "SetAnchorCerts", err);

    	err = SecTrustSetAnchorCertificatesOnly(trust, true);
    	if (err != noErr)
  	    pj_status_from_err(assock, "SetAnchorCertsOnly", err);
    }

    result = SecTrustEvaluateWithError(trust, &error);
    if (!result) {
        pj_ssl_sock_t *ssock = &assock->base;
	SecTrustResultType trust_result;
        
        err = SecTrustGetTrustResult(trust, &trust_result);
        if (err == noErr) {
    	    switch (trust_result) {
    		case kSecTrustResultInvalid:
		    ssock->verify_status |= PJ_SSL_CERT_EINVALID_FORMAT;
		    break;

    		case kSecTrustResultDeny:
    		case kSecTrustResultFatalTrustFailure:
    		    ssock->verify_status |= PJ_SSL_CERT_EUNTRUSTED;
    		    break;

    		case kSecTrustResultRecoverableTrustFailure:
    		    /* Doc: "If you receive this result, you can retry
    		     * after changing settings. For example, if trust is
    		     * denied because the certificate has expired, ..."
    		     * But this error can also mean another (recoverable)
    		     * failure, though.
    		     */
    		    ssock->verify_status |= PJ_SSL_CERT_EVALIDITY_PERIOD;
    		    break;
    	
    		case kSecTrustResultOtherError:
		    ssock->verify_status |= PJ_SSL_CERT_EUNKNOWN;
		    break;

		default:
		    break;
	    }
	}

        if (error) 
            CFRelease(error);

        /* Evaluation failed */
        status = PJ_EEOF;
    }

on_return:
    if (status != PJ_SUCCESS && assock->base.verify_status == 0)
    	assock->base.verify_status |= PJ_SSL_CERT_EUNKNOWN;

    return status;
}


/*
 *******************************************************************
 * Network functions.
 *******************************************************************
 */

/* Send data. */
static pj_status_t network_send(pj_ssl_sock_t *ssock,
				pj_ioqueue_op_key_t *send_key,
				const void *data,
				pj_ssize_t *size,
				unsigned flags)
{
    applessl_sock_t *assock = (applessl_sock_t *)ssock;
    dispatch_data_t content;
    
    content = dispatch_data_create(data, *size, assock->queue,
    				   DISPATCH_DATA_DESTRUCTOR_DEFAULT);
    if (!content)
    	return PJ_ENOMEM;

    nw_connection_send(assock->connection, content,
    		       NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, true,
    		       ^(nw_error_t error)
    {
    	event_t event;

	if (error != NULL) {
    	    errno = nw_error_get_error_code(error);
    	    if (errno == 89) {
    	    	/* Error 89 is network cancelled, not a send error. */
    	    	return;
    	    } else {
    	    	warn("Send error");
    	    }
        }

	event.type = EVENT_DATA_SENT;
	event.body.data_sent_ev.send_key = send_key;
	if (error != NULL) {
	    event.body.data_sent_ev.sent = (errno > 0)? -errno: errno;
	} else {
	    event.body.data_sent_ev.sent = dispatch_data_get_size(content);
	}

	event_manager_post_event(ssock, &event, PJ_TRUE);
    });
    dispatch_release(content);
    
    return PJ_EPENDING;
}

static pj_status_t network_start_read(pj_ssl_sock_t *ssock,
				      unsigned async_count,
				      unsigned buff_size,
				      void *readbuf[],
				      pj_uint32_t flags)
{
    applessl_sock_t *assock = (applessl_sock_t *)ssock;
    unsigned i;
    
    for (i = 0; i < async_count; i++) {
	nw_connection_receive(assock->connection, 1, buff_size,
	    ^(dispatch_data_t content, nw_content_context_t context,
	      bool is_complete, nw_error_t error)
	{
	    pj_status_t status = PJ_SUCCESS;
	    
	    if (error != NULL) {
    	    	errno = nw_error_get_error_code(error);
    	    	if (errno == 89) {
    	    	    /* Since error 89 is network intentionally cancelled by
    	    	     * us, we immediately return.
    	    	     */
    	    	    return;
    	    	} else {
    	    	    warn("Read error, stopping further receives");
    	    	    status = PJ_EEOF;
    	    	}
            }

	    /* is_complete will be marked when the entire stream has been
	     * closed in the reading direction.
	     */
	    if (is_complete)
	    	status = PJ_EEOF;

	    dispatch_block_t schedule_next_receive = 
	    ^{
	    	/* If there was no error in receiving, request more data. */
	    	if (!error && !is_complete && assock->connection) {
		    network_start_read(ssock, async_count, buff_size,
				       readbuf, flags);
	    	}
	    };

            if (content) {
            	dispatch_data_apply(content, 
                    ^(dispatch_data_t region, size_t offset,
                      const void *buffer, size_t inSize)
            	{
            	    /* This block can be invoked multiple times,
            	     * each for every contiguous memory region in the content.
            	     */
            	    event_t event;

		    memcpy(ssock->asock_rbuf[i], buffer, inSize);

		    event.type = EVENT_DATA_READ;
		    event.body.data_read_ev.data = ssock->asock_rbuf[i];
		    event.body.data_read_ev.size = inSize;
		    event.body.data_read_ev.status = status;
		    event.body.data_read_ev.remainder = 0;

		    event_manager_post_event(ssock, &event, PJ_FALSE);

                    return (bool)true;
                });
                
		schedule_next_receive();

            } else {
            	if (status != PJ_SUCCESS) {
            	    event_t event;

            	    /* Report read error to application */
		    event.type = EVENT_DATA_READ;
		    event.body.data_read_ev.data = NULL;
		    event.body.data_read_ev.size = 0;
		    event.body.data_read_ev.status = status;
		    event.body.data_read_ev.remainder = 0;

		    event_manager_post_event(ssock, &event, PJ_TRUE);
            	}
            
	    	schedule_next_receive();
	    }
        });
    }
	
    return PJ_SUCCESS;
}

/* Get address of local endpoint */
static pj_status_t network_get_localaddr(pj_ssl_sock_t *ssock,
					 pj_sockaddr_t *addr,
					 int *namelen)
{
    applessl_sock_t *assock = (applessl_sock_t *)ssock;
    nw_path_t path;
    nw_endpoint_t endpoint;
    const struct sockaddr *address;
    
    path = nw_connection_copy_current_path(assock->connection);
    if (!path)
    	return PJ_EINVALIDOP;
    
    endpoint = nw_path_copy_effective_local_endpoint(path);
    nw_release(path);
    if (!endpoint)
    	return PJ_EINVALIDOP;

    address = nw_endpoint_get_address(endpoint);
    if (address) {
    	pj_sockaddr_cp(addr, address);
    	*namelen = pj_sockaddr_get_addr_len(addr);
    }
    nw_release(endpoint);
    
    return PJ_SUCCESS;
}

static pj_status_t network_create_params(pj_ssl_sock_t * ssock,
				     	 const pj_sockaddr_t *localaddr,
				     	 pj_uint16_t port_range,
				     	 nw_parameters_t *p_params)
{
    applessl_sock_t *assock = (applessl_sock_t *)ssock;
    char ip_addr[PJ_INET6_ADDRSTRLEN];
    unsigned port;
    char port_str[PJ_INET6_ADDRSTRLEN];
    nw_endpoint_t local_endpoint;
    nw_parameters_t parameters;
    nw_parameters_configure_protocol_block_t configure_tls;
    nw_protocol_stack_t protocol_stack;
    nw_protocol_options_t ip_options;
    tls_protocol_version_t min_proto = tls_protocol_version_TLSv10;
    tls_protocol_version_t max_proto = tls_protocol_version_TLSv13;

    /* Set min and max protocol version */
    if (ssock->param.proto == PJ_SSL_SOCK_PROTO_DEFAULT) {
	ssock->param.proto = PJ_SSL_SOCK_PROTO_TLS1 |
	    		     PJ_SSL_SOCK_PROTO_TLS1_1 |
	    		     PJ_SSL_SOCK_PROTO_TLS1_2 |
	    		     PJ_SSL_SOCK_PROTO_TLS1_3;
    }

    if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_3) {
	max_proto = tls_protocol_version_TLSv13;
    } else if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_2) {
	max_proto = tls_protocol_version_TLSv12;
    } else if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_1) {
	max_proto = tls_protocol_version_TLSv11;
    } else if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1) {
	max_proto = tls_protocol_version_TLSv10;
    } else {
	PJ_LOG(3, (THIS_FILE, "Unsupported TLS protocol"));
	return PJ_EINVAL;
    }

    if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1) {
	min_proto = tls_protocol_version_TLSv10;
    } else if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_1) {
	min_proto = tls_protocol_version_TLSv11;
    } else if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_2) {
	min_proto = tls_protocol_version_TLSv12;
    } else if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_3) {
	min_proto = tls_protocol_version_TLSv13;
    }

    /* Set certificate */
    if (ssock->cert)  {
	pj_status_t status = create_identity_from_cert(assock, ssock->cert,
						       &assock->identity);
	if (status != PJ_SUCCESS)
    	    return status;
    }

    configure_tls = ^(nw_protocol_options_t tls_options)
    {
	sec_protocol_options_t sec_options;

	sec_options = nw_tls_copy_sec_protocol_options(tls_options);

    	/* Set identity */
    	if (ssock->cert && assock->identity) {
	    sec_protocol_options_set_local_identity(sec_options,
	    					    assock->identity);
	}

	sec_protocol_options_set_min_tls_protocol_version(sec_options,
	    						  min_proto);
	sec_protocol_options_set_max_tls_protocol_version(sec_options,
							  max_proto);

    	/* Set cipher list */
    	if (ssock->param.ciphers_num > 0) {
    	    unsigned i;    	
    	    for (i = 0; i < ssock->param.ciphers_num; i++) {
    	    	sec_protocol_options_append_tls_ciphersuite(sec_options,
	    	    (tls_ciphersuite_t)ssock->param.ciphers[i]);
	    }
	}
    
	if (!ssock->is_server && ssock->param.server_name.slen) {
	    sec_protocol_options_set_tls_server_name(sec_options,
	    	ssock->param.server_name.ptr);
	}
	
	sec_protocol_options_set_tls_renegotiation_enabled(sec_options,
							   true);
	
    	/* SSL verification options */
    	sec_protocol_options_set_peer_authentication_required(sec_options,
    	    true);

	/* Handshake flow:
	 * 1. Server's challenge block, provide server's trust
	 * 2. Client's verify block, to verify server's trust
	 * 3. Client's challenge block, provide client's trust
	 * 4. Only if client's trust is not NULL, server's verify block,
	 *    to verify client's trust.
	 */
        sec_protocol_options_set_challenge_block(sec_options,
	    ^(sec_protocol_metadata_t metadata,
	      sec_protocol_challenge_complete_t complete)
	{
	    complete(assock->identity);
	}, assock->queue);
	
	sec_protocol_options_set_verify_block(sec_options,
	    ^(sec_protocol_metadata_t metadata, sec_trust_t trust_ref,
	      sec_protocol_verify_complete_t complete)
	{
	    event_t event;
	    bool result = true;

    	    assock->trust = trust_ref? sec_trust_copy_ref(trust_ref): nil;

	    assock->cipher =
	      sec_protocol_metadata_get_negotiated_tls_ciphersuite(metadata);

	    /* For client, call on_connect_complete() callback first. */
	    if (!ssock->is_server) {
	    	event.type = EVENT_CONNECT;
	    	event.body.connect_ev.status = PJ_SUCCESS;
	    	event_manager_post_event(ssock, &event, PJ_FALSE);
	    }

	    event.type = EVENT_VERIFY_CERT;
	    event_manager_post_event(ssock, &event, PJ_FALSE);

	    /* Check the result of cert verification. */
	    if (ssock->verify_status != PJ_SSL_CERT_ESUCCESS) {
		if (ssock->param.verify_peer) {
        	    /* Verification failed. */
        	    result = false;
    		} else {
    		    /* When verification is not requested just return ok here,
    		     * however application can still get the verification status.
     		     */
    	    	    result = true;
    	    	}
    	    }

	    complete(result);
        }, assock->queue);

	nw_release(sec_options);
    };

    parameters = nw_parameters_create_secure_tcp(configure_tls,
    		     NW_PARAMETERS_DEFAULT_CONFIGURATION);

    protocol_stack = nw_parameters_copy_default_protocol_stack(parameters);
    ip_options = nw_protocol_stack_copy_internet_protocol(protocol_stack);
    if (ssock->param.sock_af == pj_AF_INET()) {
	nw_ip_options_set_version(ip_options, nw_ip_version_4);
    } else if (ssock->param.sock_af == pj_AF_INET6()) {
	nw_ip_options_set_version(ip_options, nw_ip_version_6);
    }
    nw_release(ip_options);
    nw_release(protocol_stack);
    
    if (ssock->is_server && ssock->param.reuse_addr) {
    	nw_parameters_set_reuse_local_address(parameters, true);
    }

    /* Create local endpoint.
     * Currently we ignore QoS and socket options.
     */
    pj_sockaddr_print(localaddr, ip_addr,sizeof(ip_addr),0);

    if (port_range) {
	pj_uint16_t max_try = MAX_BIND_RETRY;

	if (port_range && port_range < max_try) {
	    max_try = port_range;
	}
	for (; max_try; --max_try) {
	    pj_uint16_t base_port;

	    base_port = pj_sockaddr_get_port(localaddr);
	    port = (pj_uint16_t)(base_port + pj_rand() % (port_range + 1));
	    pj_utoa(port, port_str);
	    
	    local_endpoint = nw_endpoint_create_host(ip_addr, port_str);
	    if (local_endpoint)
	    	break;
	}
    } else {
    	port = pj_sockaddr_get_port(localaddr);
    	pj_utoa(port, port_str);

    	local_endpoint = nw_endpoint_create_host(ip_addr, port_str);
    }
    
    if (!local_endpoint) {
        PJ_LOG(2, (THIS_FILE, "Failed creating local endpoint"));
    	return PJ_EINVALIDOP;
    }

    nw_parameters_set_local_endpoint(parameters, local_endpoint);
    nw_release(local_endpoint);
    
    *p_params = parameters;
    return PJ_SUCCESS;
}

/* Setup assock's connection state callback and start the connection */
static pj_status_t network_setup_connection(pj_ssl_sock_t *ssock,
					    void *connection)
{
    applessl_sock_t *assock = (applessl_sock_t *)ssock;
    assock->connection = (nw_connection_t)connection;
    pj_status_t status;

    /* Initialize input circular buffer */
    status = circ_init(ssock->pool->factory, &ssock->circ_buf_input, 8192);
    if (status != PJ_SUCCESS)
        return status;

    /* Initialize output circular buffer */
    status = circ_init(ssock->pool->factory, &ssock->circ_buf_output, 8192);
    if (status != PJ_SUCCESS)
        return status;

    nw_connection_set_queue(assock->connection, assock->queue);

    nw_connection_set_state_changed_handler(assock->connection,
    	^(nw_connection_state_t state, nw_error_t error)
    {
    	pj_status_t status = PJ_SUCCESS;
    	pj_bool_t call_cb = PJ_FALSE;

	if (error && state != nw_connection_state_cancelled) {
    	    errno = nw_error_get_error_code(error);
            warn("Connection failed %p", assock);
            status = PJ_STATUS_FROM_OS(errno);
            call_cb = PJ_TRUE;	
	}

	if (state == nw_connection_state_ready) {
	    if (ssock->is_server) {
    		nw_protocol_definition_t tls_def;
    		nw_protocol_metadata_t prot_meta;
    		sec_protocol_metadata_t meta;
    		
    		tls_def = nw_protocol_copy_tls_definition();
    		prot_meta = nw_connection_copy_protocol_metadata(connection,
    								 tls_def);
		meta = nw_tls_copy_sec_protocol_metadata(prot_meta);
		assock->cipher =
	      	    sec_protocol_metadata_get_negotiated_tls_ciphersuite(meta);

		if (ssock->param.require_client_cert &&
		    !sec_protocol_metadata_access_peer_certificate_chain(
		    	meta, ^(sec_certificate_t certificate) {} ))
		{
		    status = PJ_EEOF;
		}
		nw_release(tls_def);
		nw_release(prot_meta);
		nw_release(meta);
	    }
	    call_cb = PJ_TRUE;
	} else if (state == nw_connection_state_cancelled) {
	    /* We release the reference in ssl_destroy() */
	    // nw_release(assock->connection);
	    // assock->connection = nil;
	}

	if (call_cb) {
	    event_t event;

	    event.type = EVENT_HANDSHAKE_COMPLETE;
	    event.body.handshake_ev.status = status;
	    event_manager_post_event(ssock, &event, PJ_TRUE);

	    if (ssock->is_server && status == PJ_SUCCESS) {
    	        status = network_start_read(ssock, ssock->param.async_cnt,
    		             (unsigned)ssock->param.read_buffer_size,
			     ssock->asock_rbuf, 0);
	    }
	}
    });

    nw_connection_start(assock->connection);
    
    return PJ_SUCCESS;
}

static pj_status_t network_start_accept(pj_ssl_sock_t *ssock,
			 		pj_pool_t *pool,
			  		const pj_sockaddr_t *localaddr,
			  		int addr_len,
			  		const pj_ssl_sock_param *newsock_param)
{
    applessl_sock_t *assock = (applessl_sock_t *)ssock;
    pj_status_t status;
    nw_parameters_t parameters = NULL;

    status = network_create_params(ssock, localaddr, 0, &parameters);
    if (status != PJ_SUCCESS)
    	return status;

    /* Create listener */
    assock->listener = nw_listener_create(parameters);
    nw_release(parameters);
    if (!assock->listener) {
        PJ_LOG(2, (THIS_FILE, "Failed creating listener"));
    	return PJ_EINVALIDOP;
    }

    nw_listener_set_queue(assock->listener, assock->queue);
    /* Hold a reference until cancelled */
    nw_retain(assock->listener);

    nw_listener_set_state_changed_handler(assock->listener, 
        ^(nw_listener_state_t state, nw_error_t error)
    {
	errno = error ? nw_error_get_error_code(error) : 0;
	if (state == nw_listener_state_failed) {
	    warn("listener failed\n");
	    pj_sockaddr_set_port(&ssock->local_addr, 0);
	    dispatch_semaphore_signal(assock->ev_semaphore);
	} else if (state == nw_listener_state_ready) {
    	    /* Update local port */
    	    pj_sockaddr_set_port(&ssock->local_addr,
    			         nw_listener_get_port(assock->listener));
	    dispatch_semaphore_signal(assock->ev_semaphore);
	} else if (state == nw_listener_state_cancelled) {
	    /* We release the reference in ssl_destroy() */
	    // nw_release(assock->listener);
	    // assock->listener = nil;
	}
    });

    nw_listener_set_new_connection_handler(assock->listener,
    	^(nw_connection_t connection)
    {
	nw_endpoint_t endpoint = nw_connection_copy_endpoint(connection);
	const struct sockaddr *address;
	event_t event;
	
	address = nw_endpoint_get_address(endpoint);

	event.type = EVENT_ACCEPT;
	event.body.accept_ev.newconn = connection;
	pj_sockaddr_cp(&event.body.accept_ev.src_addr, address);
	event.body.accept_ev.src_addr_len = pj_sockaddr_get_addr_len(address);
	event.body.accept_ev.status = PJ_SUCCESS;
	
	nw_retain(connection);
	event_manager_post_event(ssock, &event, PJ_TRUE);
	
	nw_release(endpoint);
    });

    /* Update local address */
    ssock->addr_len = addr_len;
    pj_sockaddr_cp(&ssock->local_addr, localaddr);

    /* Start accepting */
    pj_ssl_sock_param_copy(pool, &ssock->newsock_param, newsock_param);
    ssock->newsock_param.grp_lock = NULL;

    /* Start listening to the address */
    nw_listener_start(assock->listener);
    /* Wait until it's ready */
    dispatch_semaphore_wait(assock->ev_semaphore, DISPATCH_TIME_FOREVER);
    
    if (pj_sockaddr_get_port(&ssock->local_addr) == 0) {
    	/* Failed. */
    	status = PJ_EEOF;
    	goto on_error;
    }

    return PJ_SUCCESS;

on_error:
    ssl_reset_sock_state(ssock);
    return status;
}


static pj_status_t network_start_connect(pj_ssl_sock_t *ssock,
		       pj_ssl_start_connect_param *connect_param)
{
    char ip_addr[PJ_INET6_ADDRSTRLEN];
    unsigned port;
    char port_str[PJ_INET6_ADDRSTRLEN];
    nw_endpoint_t endpoint;
    nw_parameters_t parameters;
    nw_connection_t connection;
    pj_status_t status;

    pj_pool_t *pool = connect_param->pool;
    const pj_sockaddr_t *localaddr = connect_param->localaddr;
    pj_uint16_t port_range = connect_param->local_port_range;
    const pj_sockaddr_t *remaddr = connect_param->remaddr;
    int addr_len = connect_param->addr_len;

    PJ_ASSERT_RETURN(ssock && pool && localaddr && remaddr && addr_len,
		     PJ_EINVAL);

    status = network_create_params(ssock, localaddr, port_range,
    				   &parameters);
    if (status != PJ_SUCCESS)
    	return status;

    /* Create remote endpoint */
    pj_sockaddr_print(remaddr, ip_addr,sizeof(ip_addr),0);
    port = pj_sockaddr_get_port(remaddr);
    pj_utoa(port, port_str);

    endpoint = nw_endpoint_create_host(ip_addr, port_str);
    if (!endpoint) {
        PJ_LOG(2, (THIS_FILE, "Failed creating remote endpoint"));
    	nw_release(parameters);
    	return PJ_EINVALIDOP;
    }

    connection = nw_connection_create(endpoint, parameters);
    nw_release(endpoint);
    nw_release(parameters);
    if (!connection) {
        PJ_LOG(2, (THIS_FILE, "Failed creating connection"));
    	return PJ_EINVALIDOP;
    }

    /* Hold a reference until cancelled */
    nw_retain(connection);

    status = network_setup_connection(ssock, connection);
    if (status != PJ_SUCCESS)
    	return status;
    
    /* Save remote address */
    pj_sockaddr_cp(&ssock->rem_addr, remaddr);

    /* Update local address */
    ssock->addr_len = addr_len;
    pj_sockaddr_cp(&ssock->local_addr, localaddr);
    
    return PJ_EPENDING;
}


/*
 *******************************************************************
 * SSL functions.
 *******************************************************************
 */

static pj_ssl_sock_t *ssl_alloc(pj_pool_t *pool)
{
    applessl_sock_t *assock;
    
    /* Create event manager */
    if (event_manager_create() != PJ_SUCCESS)
    	return NULL;
    
    assock = PJ_POOL_ZALLOC_T(pool, applessl_sock_t);    

    assock->queue = dispatch_queue_create("ssl_queue", DISPATCH_QUEUE_SERIAL);
    assock->ev_semaphore = dispatch_semaphore_create(0);    
    if (!assock->queue || !assock->ev_semaphore) {
    	ssl_destroy(&assock->base);
    	return NULL;
    }
        
    return (pj_ssl_sock_t *)assock;
}

static pj_status_t ssl_create(pj_ssl_sock_t *ssock)
{
    /* Nothing to do here. SSL has been configured before connection
     * is started.
     */
    return PJ_SUCCESS;
}

/* Close sockets */
static void ssl_close_sockets(pj_ssl_sock_t *ssock)
{
    applessl_sock_t *assock = (applessl_sock_t *)ssock;

    if (assock->identity) {
   	nw_release(assock->identity);
   	assock->identity = nil;
    }

    /* This can happen when pj_ssl_sock_create() fails. */
    if (!ssock->write_mutex)
    	return;

    pj_lock_acquire(ssock->write_mutex);
    if (assock->connection) {
    	nw_connection_cancel(assock->connection);
    	nw_release(assock->connection);
    	assock->connection = nil;
    }
    pj_lock_release(ssock->write_mutex);
}

/* Destroy Apple SSL. */
static void ssl_destroy(pj_ssl_sock_t *ssock)
{
    applessl_sock_t *assock = (applessl_sock_t *)ssock;

    /* Cancel the connection and release it */
    if (assock->connection) {
    	nw_connection_force_cancel(assock->connection);
    	nw_release(assock->connection);
    	assock->connection = nil;
    }

    if (assock->listener) {
    	nw_listener_cancel(assock->listener);
	nw_release(assock->listener);
  	assock->listener = nil;
    }

    event_manager_remove_events(ssock);

    /* Important: if we are called from a blocking dispatch block,
     * we need to signal it before destroying ourselves.
     */
    if (assock->ev_semaphore) {
    	dispatch_semaphore_signal(assock->ev_semaphore);
    }

    if (assock->queue) {
    	dispatch_release(assock->queue);
    	assock->queue = NULL;
    }

    if (assock->ev_semaphore) {
    	dispatch_release(assock->ev_semaphore);
    	assock->ev_semaphore = nil;
    }

    /* Destroy circular buffers */
    circ_deinit(&ssock->circ_buf_input);
    circ_deinit(&ssock->circ_buf_output);
}


/* Reset socket state. */
static void ssl_reset_sock_state(pj_ssl_sock_t *ssock)
{
    pj_lock_acquire(ssock->circ_buf_output_mutex);
    ssock->ssl_state = SSL_STATE_NULL;
    pj_lock_release(ssock->circ_buf_output_mutex);

    ssl_close_sockets(ssock);
}


/* This function is taken from Apple's sslAppUtils.cpp (version 58286.41.2),
 * with some modifications.
 */
const char *sslGetCipherSuiteString(SSLCipherSuite cs)
{
    switch (cs) {
        /* TLS addenda using AES-CBC, RFC 3268 */
        case TLS_RSA_WITH_AES_128_CBC_SHA:          
            return "TLS_RSA_WITH_AES_128_CBC_SHA";
        case TLS_DH_DSS_WITH_AES_128_CBC_SHA:       
            return "TLS_DH_DSS_WITH_AES_128_CBC_SHA";
        case TLS_DH_RSA_WITH_AES_128_CBC_SHA:       
            return "TLS_DH_RSA_WITH_AES_128_CBC_SHA";
        case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:      
            return "TLS_DHE_DSS_WITH_AES_128_CBC_SHA";
        case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:      
            return "TLS_DHE_RSA_WITH_AES_128_CBC_SHA";
        case TLS_DH_anon_WITH_AES_128_CBC_SHA:      
            return "TLS_DH_anon_WITH_AES_128_CBC_SHA";
        case TLS_RSA_WITH_AES_256_CBC_SHA:          
            return "TLS_RSA_WITH_AES_256_CBC_SHA";
        case TLS_DH_DSS_WITH_AES_256_CBC_SHA:       
            return "TLS_DH_DSS_WITH_AES_256_CBC_SHA";
        case TLS_DH_RSA_WITH_AES_256_CBC_SHA:       
            return "TLS_DH_RSA_WITH_AES_256_CBC_SHA";
        case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:      
            return "TLS_DHE_DSS_WITH_AES_256_CBC_SHA";
        case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:      
            return "TLS_DHE_RSA_WITH_AES_256_CBC_SHA";
        case TLS_DH_anon_WITH_AES_256_CBC_SHA:      
            return "TLS_DH_anon_WITH_AES_256_CBC_SHA";

        /* ECDSA addenda, RFC 4492 */
        case TLS_ECDH_ECDSA_WITH_NULL_SHA:          
            return "TLS_ECDH_ECDSA_WITH_NULL_SHA";
        case TLS_ECDH_ECDSA_WITH_RC4_128_SHA:       
            return "TLS_ECDH_ECDSA_WITH_RC4_128_SHA";
        case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:  
            return "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA";
        case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:   
            return "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA";
        case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:   
            return "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA";
        case TLS_ECDHE_ECDSA_WITH_NULL_SHA:         
            return "TLS_ECDHE_ECDSA_WITH_NULL_SHA";
        case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:      
            return "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA";
        case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: 
            return "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA";
        case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:  
            return "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA";
        case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:  
            return "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA";
        case TLS_ECDH_RSA_WITH_NULL_SHA:            
            return "TLS_ECDH_RSA_WITH_NULL_SHA";
        case TLS_ECDH_RSA_WITH_RC4_128_SHA:         
            return "TLS_ECDH_RSA_WITH_RC4_128_SHA";
        case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:    
            return "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA";
        case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:     
            return "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA";
        case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:     
            return "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA";
        case TLS_ECDHE_RSA_WITH_NULL_SHA:           
            return "TLS_ECDHE_RSA_WITH_NULL_SHA";
        case TLS_ECDHE_RSA_WITH_RC4_128_SHA:        
            return "TLS_ECDHE_RSA_WITH_RC4_128_SHA";
        case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:   
            return "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA";
        case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:    
            return "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA";
        case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:    
            return "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA";
        case TLS_ECDH_anon_WITH_NULL_SHA:           
            return "TLS_ECDH_anon_WITH_NULL_SHA";
        case TLS_ECDH_anon_WITH_RC4_128_SHA:        
            return "TLS_ECDH_anon_WITH_RC4_128_SHA";
        case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA:   
            return "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA";
        case TLS_ECDH_anon_WITH_AES_128_CBC_SHA:    
            return "TLS_ECDH_anon_WITH_AES_128_CBC_SHA";
        case TLS_ECDH_anon_WITH_AES_256_CBC_SHA:    
            return "TLS_ECDH_anon_WITH_AES_256_CBC_SHA";

        /* TLS 1.2 addenda, RFC 5246 */
        case TLS_RSA_WITH_AES_128_CBC_SHA256:       
            return "TLS_RSA_WITH_AES_128_CBC_SHA256";
        case TLS_RSA_WITH_AES_256_CBC_SHA256:       
            return "TLS_RSA_WITH_AES_256_CBC_SHA256";
        case TLS_DH_DSS_WITH_AES_128_CBC_SHA256:    
            return "TLS_DH_DSS_WITH_AES_128_CBC_SHA256";
        case TLS_DH_RSA_WITH_AES_128_CBC_SHA256:    
            return "TLS_DH_RSA_WITH_AES_128_CBC_SHA256";
        case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:   
            return "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256";
        case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:   
            return "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256";
        case TLS_DH_DSS_WITH_AES_256_CBC_SHA256:    
            return "TLS_DH_DSS_WITH_AES_256_CBC_SHA256";
        case TLS_DH_RSA_WITH_AES_256_CBC_SHA256:    
            return "TLS_DH_RSA_WITH_AES_256_CBC_SHA256";
        case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:   
            return "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256";
        case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:   
            return "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256";
        case TLS_DH_anon_WITH_AES_128_CBC_SHA256:   
            return "TLS_DH_anon_WITH_AES_128_CBC_SHA256";
        case TLS_DH_anon_WITH_AES_256_CBC_SHA256:   
            return "TLS_DH_anon_WITH_AES_256_CBC_SHA256";

        /* TLS addenda using AES-GCM, RFC 5288 */
        case TLS_RSA_WITH_AES_128_GCM_SHA256:       
            return "TLS_RSA_WITH_AES_128_GCM_SHA256";
        case TLS_RSA_WITH_AES_256_GCM_SHA384:       
            return "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256";
        case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:   
            return "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256";
        case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:   
            return "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384";
        case TLS_DH_RSA_WITH_AES_128_GCM_SHA256:    
            return "TLS_DH_RSA_WITH_AES_128_GCM_SHA256";
        case TLS_DH_RSA_WITH_AES_256_GCM_SHA384:    
            return "TLS_DH_RSA_WITH_AES_256_GCM_SHA384";
        case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:   
            return "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256";
        case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:   
            return "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384";
        case TLS_DH_DSS_WITH_AES_128_GCM_SHA256:    
            return "TLS_DH_DSS_WITH_AES_128_GCM_SHA256";
        case TLS_DH_DSS_WITH_AES_256_GCM_SHA384:    
            return "TLS_DH_DSS_WITH_AES_256_GCM_SHA384";
        case TLS_DH_anon_WITH_AES_128_GCM_SHA256:   
            return "TLS_DH_anon_WITH_AES_128_GCM_SHA256";
        case TLS_DH_anon_WITH_AES_256_GCM_SHA384:   
            return "TLS_DH_anon_WITH_AES_256_GCM_SHA384";

        /* ECDSA addenda, RFC 5289 */
        case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:   
            return "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256";
        case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:   
            return "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384";
        case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:    
            return "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256";
        case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:    
            return "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384";
        case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:     
            return "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256";
        case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:     
            return "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384";
        case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:      
            return "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256";
        case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:      
            return "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384";
        case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:   
            return "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256";
        case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:   
            return "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384";
        case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:    
            return "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256";
        case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:    
            return "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384";
        case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:     
            return "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256";
        case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:     
            return "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384";
        case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:      
            return "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256";
        case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:      
            return "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384";

	case TLS_AES_128_GCM_SHA256:
	    return "TLS_AES_128_GCM_SHA256";	
	case TLS_AES_256_GCM_SHA384:
	    return "TLS_AES_256_GCM_SHA384";
	case TLS_CHACHA20_POLY1305_SHA256:
	    return "TLS_CHACHA20_POLY1305_SHA256";
	case TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
	    return "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256";
	case TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
	    return "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256";
	case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
	    return "TLS_RSA_WITH_3DES_EDE_CBC_SHA";
            
        default:
            return "TLS_CIPHER_STRING_UNKNOWN";
    }
}

static void ssl_ciphers_populate(void)
{
    /* SSLGetSupportedCiphers() is deprecated and we can't find
     * the replacement API, so we just list the valid ciphers here
     * taken from the tls_ciphersuite_t doc.
     */
    tls_ciphersuite_t ciphers[] = {
    	tls_ciphersuite_AES_128_GCM_SHA256,
	tls_ciphersuite_AES_256_GCM_SHA384,
	tls_ciphersuite_CHACHA20_POLY1305_SHA256,
    	tls_ciphersuite_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
	tls_ciphersuite_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
	tls_ciphersuite_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
	tls_ciphersuite_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
	tls_ciphersuite_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
	tls_ciphersuite_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
	tls_ciphersuite_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
	tls_ciphersuite_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
	tls_ciphersuite_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
	tls_ciphersuite_ECDHE_RSA_WITH_AES_128_CBC_SHA,
	tls_ciphersuite_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
	tls_ciphersuite_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
	tls_ciphersuite_ECDHE_RSA_WITH_AES_256_CBC_SHA,
	tls_ciphersuite_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
	tls_ciphersuite_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
	tls_ciphersuite_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
	tls_ciphersuite_RSA_WITH_3DES_EDE_CBC_SHA,
	tls_ciphersuite_RSA_WITH_AES_128_CBC_SHA,
	tls_ciphersuite_RSA_WITH_AES_128_CBC_SHA256,
	tls_ciphersuite_RSA_WITH_AES_128_GCM_SHA256,
	tls_ciphersuite_RSA_WITH_AES_256_CBC_SHA,
	tls_ciphersuite_RSA_WITH_AES_256_CBC_SHA256,
	tls_ciphersuite_RSA_WITH_AES_256_GCM_SHA384
    };
    if (!ssl_cipher_num) {
    	unsigned i;
    	
    	ssl_cipher_num = sizeof(ciphers)/sizeof(ciphers[0]);
	for (i = 0; i < ssl_cipher_num; i++) {
	    ssl_ciphers[i].id = (pj_ssl_cipher)ciphers[i];
	    ssl_ciphers[i].name = sslGetCipherSuiteString(ciphers[i]);
	}
    }
}


static pj_ssl_cipher ssl_get_cipher(pj_ssl_sock_t *ssock)
{
    applessl_sock_t *assock = (applessl_sock_t *)ssock;

    return (pj_ssl_cipher) assock->cipher;
}


#if !TARGET_OS_IPHONE
static void get_info_and_cn(CFArrayRef array, CFMutableStringRef info,
			    CFStringRef *cn)
{
    const void *keys[] = {kSecOIDOrganizationalUnitName, kSecOIDCountryName,
    			  kSecOIDStateProvinceName, kSecOIDLocalityName,
    			  kSecOIDOrganizationName, kSecOIDCommonName};
    const char *labels[] = { "OU=", "C=", "ST=", "L=", "O=", "CN="};
    pj_bool_t add_separator = PJ_FALSE;
    int i, n;

    *cn = NULL;
    for(i = 0; i < sizeof(keys)/sizeof(keys[0]);  i++) {
        for (n = 0 ; n < CFArrayGetCount(array); n++) {
            CFDictionaryRef dict;
            CFTypeRef dictkey;
            CFStringRef str;

            dict = CFArrayGetValueAtIndex(array, n);
            if (CFGetTypeID(dict) != CFDictionaryGetTypeID())
                continue;
            dictkey = CFDictionaryGetValue(dict, kSecPropertyKeyLabel);
            if (!CFEqual(dictkey, keys[i]))
                continue;
            str = (CFStringRef) CFDictionaryGetValue(dict,
            					     kSecPropertyKeyValue);
            					     
            if (CFStringGetLength(str) > 0) {
            	if (add_separator) {
                    CFStringAppendCString(info, "/", kCFStringEncodingUTF8);
                }
                CFStringAppendCString(info, labels[i], kCFStringEncodingUTF8);
                CFStringAppend(info, str);
		add_separator = PJ_TRUE;
		
		if (CFEqual(keys[i], kSecOIDCommonName))
		    *cn = str;
            }
        }
    }
}

static CFDictionaryRef get_cert_oid(SecCertificateRef cert, CFStringRef oid,
				    CFTypeRef *value)
{
    void *key[1];
    CFArrayRef key_arr;
    CFDictionaryRef vals, dict;

    key[0] = (void *)oid;
    key_arr = CFArrayCreate(NULL, (const void **)key, 1,
    			    &kCFTypeArrayCallBacks);

    vals = SecCertificateCopyValues(cert, key_arr, NULL);
    dict = CFDictionaryGetValue(vals, key[0]);
    *value = CFDictionaryGetValue(dict, kSecPropertyKeyValue);

    CFRelease(key_arr);

    return vals;
}

#endif

/* Get certificate info; in case the certificate info is already populated,
 * this function will check if the contents need updating by inspecting the
 * issuer and the serial number.
 */
static void get_cert_info(pj_pool_t *pool, pj_ssl_cert_info *ci,
			  SecCertificateRef cert)
{
    pj_bool_t update_needed;
    char buf[512];
    size_t bufsize = sizeof(buf);
    const pj_uint8_t *serial_no = NULL;
    size_t serialsize = 0;
    CFMutableStringRef issuer_info;
    CFStringRef str;
    CFDataRef serial = NULL;
#if !TARGET_OS_IPHONE
    CFStringRef issuer_cn = NULL;
    CFDictionaryRef dict;
#endif

    pj_assert(pool && ci && cert);

    /* Get issuer */
    issuer_info = CFStringCreateMutable(NULL, 0);
#if !TARGET_OS_IPHONE
{
    /* Unfortunately, unlike on Mac, on iOS we don't have these APIs
     * to query the certificate info such as the issuer, version,
     * validity, and alt names.
     */
    CFArrayRef issuer_vals;
    
    dict = get_cert_oid(cert, kSecOIDX509V1IssuerName,
    			(CFTypeRef *)&issuer_vals);
    if (dict) {
    	get_info_and_cn(issuer_vals, issuer_info, &issuer_cn);
    	if (issuer_cn)
    	    issuer_cn = CFStringCreateCopy(NULL, issuer_cn);
    	CFRelease(dict);
    }
}
#endif
    CFStringGetCString(issuer_info, buf, bufsize, kCFStringEncodingUTF8);

    /* Get serial no */
    if (__builtin_available(macOS 10.13, iOS 11.0, *)) {
	serial = SecCertificateCopySerialNumberData(cert, NULL);
    	if (serial) {
    	    serial_no = CFDataGetBytePtr(serial);
    	    serialsize = CFDataGetLength(serial);
    	}
    }

    /* Check if the contents need to be updated */
    update_needed = pj_strcmp2(&ci->issuer.info, buf) ||
                    pj_memcmp(ci->serial_no, serial_no, serialsize);
    if (!update_needed) {
        CFRelease(issuer_info);
        return;
    }

    /* Update cert info */

    pj_bzero(ci, sizeof(pj_ssl_cert_info));

    /* Version */
#if !TARGET_OS_IPHONE
{
    CFStringRef version;
    
    dict = get_cert_oid(cert, kSecOIDX509V1Version,
    			(CFTypeRef *)&version);
    if (dict) {
    	ci->version = CFStringGetIntValue(version);
    	CFRelease(dict);
    }
}
#endif

    /* Issuer */
    pj_strdup2(pool, &ci->issuer.info, buf);
#if !TARGET_OS_IPHONE
    if (issuer_cn) {
    	CFStringGetCString(issuer_cn, buf, bufsize, kCFStringEncodingUTF8);
    	pj_strdup2(pool, &ci->issuer.cn, buf);
    	CFRelease(issuer_cn);
    }
#endif
    CFRelease(issuer_info);

    /* Serial number */
    if (serial) {
    	if (serialsize > sizeof(ci->serial_no))
    	    serialsize = sizeof(ci->serial_no);
    	pj_memcpy(ci->serial_no, serial_no, serialsize);
    	CFRelease(serial);
    }

    /* Subject */
    str = SecCertificateCopySubjectSummary(cert);
    CFStringGetCString(str, buf, bufsize, kCFStringEncodingUTF8);
    pj_strdup2(pool, &ci->subject.cn, buf);
    CFRelease(str);
#if !TARGET_OS_IPHONE
{
    CFArrayRef subject;
    CFMutableStringRef subject_info;
    
    dict = get_cert_oid(cert, kSecOIDX509V1SubjectName,
    			(CFTypeRef *)&subject);
    if (dict) {
     	subject_info = CFStringCreateMutable(NULL, 0);

    	get_info_and_cn(subject, subject_info, &str);
    
    	CFStringGetCString(subject_info, buf, bufsize, kCFStringEncodingUTF8);
    	pj_strdup2(pool, &ci->subject.info, buf);
    
    	CFRelease(dict);
    	CFRelease(subject_info);
    }
}
#endif

    /* Validity */
#if !TARGET_OS_IPHONE
{
    CFNumberRef validity;
    double interval;
    
    dict = get_cert_oid(cert, kSecOIDX509V1ValidityNotBefore,
    			(CFTypeRef *)&validity);
    if (dict) {
        if (CFNumberGetValue(validity, CFNumberGetType(validity),
        		     &interval))
        {
            /* Darwin's absolute reference date is 1 Jan 2001 00:00:00 GMT */
    	    ci->validity.start.sec = (unsigned long)interval + 978278400L;
    	}
    	CFRelease(dict);
    }

    dict = get_cert_oid(cert, kSecOIDX509V1ValidityNotAfter,
    			(CFTypeRef *)&validity);
    if (dict) {
    	if (CFNumberGetValue(validity, CFNumberGetType(validity),
    			     &interval))
    	{
    	    ci->validity.end.sec = (unsigned long)interval + 978278400L;
    	}
    	CFRelease(dict);
    }
}
#endif

    /* Subject Alternative Name extension */
#if !TARGET_OS_IPHONE
{
    CFArrayRef altname;
    CFIndex i;

    dict = get_cert_oid(cert, kSecOIDSubjectAltName, (CFTypeRef *)&altname);
    if (!dict || !CFArrayGetCount(altname))
    	return;

    ci->subj_alt_name.entry = pj_pool_calloc(pool, CFArrayGetCount(altname),
					     sizeof(*ci->subj_alt_name.entry));

    for (i = 0; i < CFArrayGetCount(altname); ++i) {
    	CFDictionaryRef item;
    	CFStringRef label, value;
	pj_ssl_cert_name_type type = PJ_SSL_CERT_NAME_UNKNOWN;
        
        item = CFArrayGetValueAtIndex(altname, i);
        if (CFGetTypeID(item) != CFDictionaryGetTypeID())
            continue;
        
        label = (CFStringRef)CFDictionaryGetValue(item, kSecPropertyKeyLabel);
	if (CFGetTypeID(label) != CFStringGetTypeID())
	    continue;

        value = (CFStringRef)CFDictionaryGetValue(item, kSecPropertyKeyValue);

	if (!CFStringCompare(label, CFSTR("DNS Name"),
			     kCFCompareCaseInsensitive))
	{
	    if (CFGetTypeID(value) != CFStringGetTypeID())
	    	continue;
	    CFStringGetCString(value, buf, bufsize, kCFStringEncodingUTF8);
	    type = PJ_SSL_CERT_NAME_DNS;
	} else if (!CFStringCompare(label, CFSTR("IP Address"),
			     	    kCFCompareCaseInsensitive))
	{
	    if (CFGetTypeID(value) != CFStringGetTypeID())
	    	continue;
	    CFStringGetCString(value, buf, bufsize, kCFStringEncodingUTF8);
	    type = PJ_SSL_CERT_NAME_IP;
	} else if (!CFStringCompare(label, CFSTR("Email Address"),
			     	    kCFCompareCaseInsensitive))
	{
	    if (CFGetTypeID(value) != CFStringGetTypeID())
	    	continue;
	    CFStringGetCString(value, buf, bufsize, kCFStringEncodingUTF8);
	    type = PJ_SSL_CERT_NAME_RFC822;
	} else if (!CFStringCompare(label, CFSTR("URI"),
			     	    kCFCompareCaseInsensitive))
	{
	    CFStringRef uri;

	    if (CFGetTypeID(value) != CFURLGetTypeID())
	    	continue;
	    uri = CFURLGetString((CFURLRef)value);
	    CFStringGetCString(uri, buf, bufsize, kCFStringEncodingUTF8);
	    type = PJ_SSL_CERT_NAME_URI;
	}

	if (type != PJ_SSL_CERT_NAME_UNKNOWN) {
	    ci->subj_alt_name.entry[ci->subj_alt_name.cnt].type = type;
	    if (type == PJ_SSL_CERT_NAME_IP) {
	    	char ip_buf[PJ_INET6_ADDRSTRLEN+10];
	    	int len = CFStringGetLength(value);
		int af = pj_AF_INET();

		if (len == sizeof(pj_in6_addr)) af = pj_AF_INET6();
		pj_inet_ntop2(af, buf, ip_buf, sizeof(ip_buf));
		pj_strdup2(pool,
		    &ci->subj_alt_name.entry[ci->subj_alt_name.cnt].name,
		    ip_buf);
	    } else {
		pj_strdup2(pool,
	 	    &ci->subj_alt_name.entry[ci->subj_alt_name.cnt].name,
		    buf);
	    }
	    ci->subj_alt_name.cnt++;
	}
    }

    CFRelease(dict);
}
#endif
}

/* Update local & remote certificates info. This function should be
 * called after handshake successfully completed.
 */
static void ssl_update_certs_info(pj_ssl_sock_t *ssock)
{
    applessl_sock_t *assock = (applessl_sock_t *)ssock;
    SecTrustRef trust = assock->trust;
    CFIndex count;
    SecCertificateRef cert;

    pj_assert(ssock->ssl_state == SSL_STATE_ESTABLISHED);
    
    /* Get active local certificate */
    if (assock->identity) {
        CFArrayRef cert_arr;
        
    	cert_arr = sec_identity_copy_certificates_ref(assock->identity);
    	if (cert_arr) {
    	    count = CFArrayGetCount(cert_arr);
    	    if (count > 0) {
		CFTypeRef elmt;

    	    	elmt = (CFTypeRef) CFArrayGetValueAtIndex(cert_arr, 0);
    	    	if (CFGetTypeID(elmt) == SecCertificateGetTypeID()) {
    	    	    cert = (SecCertificateRef)elmt;
      	    	    get_cert_info(ssock->pool, &ssock->local_cert_info, cert);
      	    	}
    	    }    	    
    	    CFRelease(cert_arr);
    	}
    }

    /* Get active remote certificate */
    if (trust) {
    	count = SecTrustGetCertificateCount(trust);
    	if (count > 0) {
	    cert = SecTrustGetCertificateAtIndex(trust, 0);
      	    get_cert_info(ssock->pool, &ssock->remote_cert_info, cert);
    	}
    }
}

static void ssl_set_state(pj_ssl_sock_t *ssock, pj_bool_t is_server)
{
    PJ_UNUSED_ARG(ssock);
    PJ_UNUSED_ARG(is_server);
}

static void ssl_set_peer_name(pj_ssl_sock_t *ssock)
{
    /* Setting server name is done when configuring tls before connection
     * is started.
     */
    PJ_UNUSED_ARG(ssock);
}

static pj_status_t ssl_do_handshake(pj_ssl_sock_t *ssock)
{
    /* Nothing to do here, just return EPENDING. Handshake has
     * automatically been performed when starting a connection.
     */

    return PJ_EPENDING;
}

static pj_status_t ssl_read(pj_ssl_sock_t *ssock, void *data, int *size)
{
    pj_size_t circ_buf_size, read_size;

    pj_lock_acquire(ssock->circ_buf_input_mutex);

    if (circ_empty(&ssock->circ_buf_input)) {
        pj_lock_release(ssock->circ_buf_input_mutex);
        *size = 0;
	return PJ_SUCCESS;
    }

    circ_buf_size = circ_size(&ssock->circ_buf_input);
    read_size = PJ_MIN(circ_buf_size, *size);

    circ_read(&ssock->circ_buf_input, data, read_size);

    pj_lock_release(ssock->circ_buf_input_mutex);

    *size = read_size;

    return PJ_SUCCESS;
}

/*
 * Write the plain data to buffer. It will be encrypted later during
 * sending.
 */
static pj_status_t ssl_write(pj_ssl_sock_t *ssock, const void *data,
			     pj_ssize_t size, int *nwritten)
{
    pj_status_t status;

    status = circ_write(&ssock->circ_buf_output, data, size);
    *nwritten = (status == PJ_SUCCESS)? (int)size: 0;
    
    return status;
}

static pj_status_t ssl_renegotiate(pj_ssl_sock_t *ssock)
{
    PJ_UNUSED_ARG(ssock);

    /* According to the doc,
     * sec_protocol_options_set_tls_renegotiation_enabled() should
     * enable TLS session renegotiation for versions 1.2 and earlier.
     * But we can't trigger renegotiation manually, or can we?
     */
    return PJ_ENOTSUP;
}


#endif /* PJ_SSL_SOCK_IMP_APPLE */
