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

/* Set to 1 to enable debugging messages. */
#define SSL_DEBUG  0

#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_listener_state_t         lis_state;
    nw_connection_t             connection;
    nw_connection_state_t       con_state;
    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_DISCARD
} 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;

#if SSL_DEBUG
static pj_thread_desc queue_th_desc;
static pj_thread_t *queue_th;
#endif

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

#if SSL_DEBUG
    if (!pj_thread_is_registered()) {
        pj_bzero(queue_th_desc, sizeof(pj_thread_desc));
        pj_thread_register("sslq", queue_th_desc, &queue_th);
    }
    PJ_LOG(3, (THIS_FILE, "Posting event %p %d", ssock, event_item->type));
#endif

    if (ssock->is_closing || !ssock->pool || !mgr)
        return PJ_EGONE;

#if SSL_DEBUG
    PJ_LOG(3,(THIS_FILE, "Post event success %p %d",ssock, event_item->type));
#endif
    
    [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)) {
        pj_ssl_sock_t *ssock;
        applessl_sock_t * assock;
        event_t *event;
        pj_bool_t ret = PJ_TRUE, add_ref = PJ_FALSE;
        
        [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;
        ssock = event->ssock;
        assock = (applessl_sock_t *)ssock;
        pj_list_erase(event);

        if (ssock->is_closing || !ssock->pool ||
            (!ssock->is_server && !assock->connection) ||
            (ssock->is_server && !assock->listener && !assock->connection))
        {
            PJ_LOG(3, (THIS_FILE, "Warning: Discarding SSL event type %d of "
                       "a closing socket %p", event->type, ssock));
            event->type = EVENT_DISCARD;
        } else if (ssock->param.grp_lock) {
            if (pj_grp_lock_get_ref(ssock->param.grp_lock) > 0) {
                /* Prevent ssock from being destroyed while
                 * we are calling the callback.
                 */
                add_ref = PJ_TRUE;
                pj_grp_lock_add_ref(ssock->param.grp_lock);
            } else {
                PJ_LOG(3, (THIS_FILE, "Warning: Discarding SSL event type %d "
                           " of a destroyed socket %p", event->type, ssock));
                event->type = EVENT_DISCARD;
            }
        }

        [event_mgr->lock unlock];

        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 (event->type != EVENT_DISCARD && ret && !event->async && ret) {
            dispatch_semaphore_signal(assock->ev_semaphore);
        }
        
        /* Put the event into the free list to be reused */
        [event_mgr->lock lock];
        if (add_ref) {
            pj_grp_lock_dec_ref(ssock->param.grp_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 < (CFIndex)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);
                CFRetain(identity);
                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) {
#if SSL_DEBUG
            PJ_LOG(3, (THIS_FILE, "SSL trust evaluation: %d", trust_result));
#endif
            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)
{
    PJ_UNUSED_ARG(flags);
    applessl_sock_t *assock = (applessl_sock_t *)ssock;
    dispatch_data_t content;

    if (!assock->connection)
        return PJ_EGONE;
    
    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;

    if (!assock->connection)
        return PJ_EGONE;

    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 the context is marked as complete, and is the final context,
             * we're read-closed.
             */
            if (is_complete &&
                (context == NULL || nw_content_context_get_is_final(context)))
            {
                status = PJ_EEOF;
            } else 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;
                }
            }

            dispatch_block_t schedule_next_receive = 
            ^{
                /* If there was no error in receiving, request more data. */
                if (!error && !is_complete && status != PJ_EEOF) {
                    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)
                {
                    PJ_UNUSED_ARG(region);
                    PJ_UNUSED_ARG(offset);
                    /* 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_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,
                                            ssock->param.enable_renegotiation);
        /* This must be disabled, otherwise server may think this is
         * a resumption of a previously closed connection, and our
         * verify block may never be invoked!
         */
        sec_protocol_options_set_tls_resumption_enabled(sec_options, false);
        
        /* SSL peer authentication options */
        if (ssock->is_server && ssock->param.require_client_cert) {
            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)
        {
            PJ_UNUSED_ARG(metadata);
            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;
            pj_status_t status;
            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 && ssock->ssl_state == SSL_STATE_NULL) {
                if (!assock->connection)
                    complete(false);

                event.type = EVENT_CONNECT;
                event.body.connect_ev.status = PJ_SUCCESS;
                status = event_manager_post_event(ssock, &event, PJ_FALSE);
                if (status == PJ_EGONE)
                    complete(false);
            }

            event.type = EVENT_VERIFY_CERT;
            status = event_manager_post_event(ssock, &event, PJ_FALSE);
            if (status == PJ_EGONE)
                complete(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);

    assock->con_state = nw_connection_state_invalid;
    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 SSL_DEBUG
        if (!pj_thread_is_registered()) {
            pj_bzero(queue_th_desc, sizeof(pj_thread_desc));
            pj_thread_register("sslq", queue_th_desc, &queue_th);
        }
        PJ_LOG(3, (THIS_FILE, "SSL state change %p %d", assock, state));
#endif

        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);
            if (ssock->ssl_state == SSL_STATE_HANDSHAKING)
                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);
            }
        }

        assock->con_state = state;
    });

    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);

    assock->lis_state = nw_listener_state_invalid;
    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;
        }
        assock->lis_state = state;
    });

    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_CONCURRENT);
    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.
     */
    PJ_UNUSED_ARG(ssock);

    /* Suppress warnings */
    PJ_UNUSED_ARG(circ_reset);
    PJ_UNUSED_ARG(circ_read_cancel);

    return PJ_SUCCESS;
}

static void close_connection(applessl_sock_t *assock)
{
    if (assock->connection) {
        unsigned i;
        nw_connection_t conn = assock->connection;
        
        assock->connection = nil;
        nw_connection_force_cancel(conn);

        /* We need to wait until the connection is at cancelled state,
         * otherwise events will still be delivered even though we
         * already force cancel and release the connection.
         */
        for (i = 0; i < 40; i++) {
            if (assock->con_state == nw_connection_state_cancelled) break;
            pj_thread_sleep(50);
        }

        event_manager_remove_events(&assock->base);

        if (assock->con_state != nw_connection_state_cancelled) {
            PJ_LOG(3, (THIS_FILE, "Warning: Failed to cancel SSL connection "
                                  "%p %d", assock, assock->con_state));
        }

        nw_connection_set_state_changed_handler(conn, nil);
        nw_release(conn);

#if SSL_DEBUG
        PJ_LOG(3, (THIS_FILE, "SSL connection %p closed", assock));
#endif

    }
}

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

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

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

    pj_lock_acquire(ssock->write_mutex);
    close_connection(assock);
    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;

    close_connection(assock);

    if (assock->listener) {
        unsigned i;
        
        nw_listener_set_new_connection_handler(assock->listener, nil);
        nw_listener_cancel(assock->listener);

        for (i = 0; i < 20; i++) {
            if (assock->lis_state == nw_listener_state_cancelled) break;
            pj_thread_sleep(50);
        }
        if (assock->lis_state != nw_listener_state_cancelled) {
            PJ_LOG(3, (THIS_FILE, "Warning: Failed to cancel SSL listener "
                                  "%p %d", assock, assock->lis_state));
        }
        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);
    
    PJ_LOG(4, (THIS_FILE, "SSL %p destroyed", ssock));
}


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

#if SSL_DEBUG
    PJ_LOG(3, (THIS_FILE, "SSL reset sock state %p", ssock));
#endif

    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 = PJ_ARRAY_SIZE(ciphers);
        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 < (int)PJ_ARRAY_SIZE(keys); 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]);
    if (!dict) {
        CFRelease(key_arr);
        CFRelease(vals);
        return NULL;
    }

    *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.
     */
    PJ_UNUSED_ARG(ssock);
    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, (pj_size_t)*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 */
