/* 
 * Copyright (C) 2009-2011 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/compat/socket.h>
#include <pj/assert.h>
#include <pj/errno.h>
#include <pj/math.h>
#include <pj/pool.h>
#include <pj/sock.h>
#include <pj/string.h>

#include "os_symbian.h"
#include <securesocket.h>
#include <x509cert.h>
#include <e32des8.h>

#define THIS_FILE "ssl_sock_symbian.cpp"


/* Cipher name structure */
typedef struct cipher_name_t {
    pj_ssl_cipher    cipher;
    const char      *name;
} cipher_name_t;

/* Cipher name constants */
static cipher_name_t cipher_names[] =
{
    {PJ_TLS_UNKNOWN_CIPHER,                    "UNKNOWN"},
    {PJ_TLS_NULL_WITH_NULL_NULL,               "NULL"},

    /* TLS/SSLv3 */
    {PJ_TLS_RSA_WITH_NULL_MD5,                 "TLS_RSA_WITH_NULL_MD5"},
    {PJ_TLS_RSA_WITH_NULL_SHA,                 "TLS_RSA_WITH_NULL_SHA"},
    {PJ_TLS_RSA_WITH_NULL_SHA256,              "TLS_RSA_WITH_NULL_SHA256"},
    {PJ_TLS_RSA_WITH_RC4_128_MD5,              "TLS_RSA_WITH_RC4_128_MD5"},
    {PJ_TLS_RSA_WITH_RC4_128_SHA,              "TLS_RSA_WITH_RC4_128_SHA"},
    {PJ_TLS_RSA_WITH_3DES_EDE_CBC_SHA,         "TLS_RSA_WITH_3DES_EDE_CBC_SHA"},
    {PJ_TLS_RSA_WITH_AES_128_CBC_SHA,          "TLS_RSA_WITH_AES_128_CBC_SHA"},
    {PJ_TLS_RSA_WITH_AES_256_CBC_SHA,          "TLS_RSA_WITH_AES_256_CBC_SHA"},
    {PJ_TLS_RSA_WITH_AES_128_CBC_SHA256,       "TLS_RSA_WITH_AES_128_CBC_SHA256"},
    {PJ_TLS_RSA_WITH_AES_256_CBC_SHA256,       "TLS_RSA_WITH_AES_256_CBC_SHA256"},
    {PJ_TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA,      "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA"},
    {PJ_TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA,      "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA"},
    {PJ_TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,     "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA"},
    {PJ_TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA,     "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA"},
    {PJ_TLS_DH_DSS_WITH_AES_128_CBC_SHA,       "TLS_DH_DSS_WITH_AES_128_CBC_SHA"},
    {PJ_TLS_DH_RSA_WITH_AES_128_CBC_SHA,       "TLS_DH_RSA_WITH_AES_128_CBC_SHA"},
    {PJ_TLS_DHE_DSS_WITH_AES_128_CBC_SHA,      "TLS_DHE_DSS_WITH_AES_128_CBC_SHA"},
    {PJ_TLS_DHE_RSA_WITH_AES_128_CBC_SHA,      "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"},
    {PJ_TLS_DH_DSS_WITH_AES_256_CBC_SHA,       "TLS_DH_DSS_WITH_AES_256_CBC_SHA"},
    {PJ_TLS_DH_RSA_WITH_AES_256_CBC_SHA,       "TLS_DH_RSA_WITH_AES_256_CBC_SHA"},
    {PJ_TLS_DHE_DSS_WITH_AES_256_CBC_SHA,      "TLS_DHE_DSS_WITH_AES_256_CBC_SHA"},
    {PJ_TLS_DHE_RSA_WITH_AES_256_CBC_SHA,      "TLS_DHE_RSA_WITH_AES_256_CBC_SHA"},
    {PJ_TLS_DH_DSS_WITH_AES_128_CBC_SHA256,    "TLS_DH_DSS_WITH_AES_128_CBC_SHA256"},
    {PJ_TLS_DH_RSA_WITH_AES_128_CBC_SHA256,    "TLS_DH_RSA_WITH_AES_128_CBC_SHA256"},
    {PJ_TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,   "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256"},
    {PJ_TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,   "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256"},
    {PJ_TLS_DH_DSS_WITH_AES_256_CBC_SHA256,    "TLS_DH_DSS_WITH_AES_256_CBC_SHA256"},
    {PJ_TLS_DH_RSA_WITH_AES_256_CBC_SHA256,    "TLS_DH_RSA_WITH_AES_256_CBC_SHA256"},
    {PJ_TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,   "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256"},
    {PJ_TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,   "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256"},
    {PJ_TLS_DH_anon_WITH_RC4_128_MD5,          "TLS_DH_anon_WITH_RC4_128_MD5"},
    {PJ_TLS_DH_anon_WITH_3DES_EDE_CBC_SHA,     "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA"},
    {PJ_TLS_DH_anon_WITH_AES_128_CBC_SHA,      "TLS_DH_anon_WITH_AES_128_CBC_SHA"},
    {PJ_TLS_DH_anon_WITH_AES_256_CBC_SHA,      "TLS_DH_anon_WITH_AES_256_CBC_SHA"},
    {PJ_TLS_DH_anon_WITH_AES_128_CBC_SHA256,   "TLS_DH_anon_WITH_AES_128_CBC_SHA256"},
    {PJ_TLS_DH_anon_WITH_AES_256_CBC_SHA256,   "TLS_DH_anon_WITH_AES_256_CBC_SHA256"},

    /* TLS (deprecated) */
    {PJ_TLS_RSA_EXPORT_WITH_RC4_40_MD5,        "TLS_RSA_EXPORT_WITH_RC4_40_MD5"},
    {PJ_TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5,    "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5"},
    {PJ_TLS_RSA_WITH_IDEA_CBC_SHA,             "TLS_RSA_WITH_IDEA_CBC_SHA"},
    {PJ_TLS_RSA_EXPORT_WITH_DES40_CBC_SHA,     "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA"},
    {PJ_TLS_RSA_WITH_DES_CBC_SHA,              "TLS_RSA_WITH_DES_CBC_SHA"},
    {PJ_TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA,  "TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA"},
    {PJ_TLS_DH_DSS_WITH_DES_CBC_SHA,           "TLS_DH_DSS_WITH_DES_CBC_SHA"},
    {PJ_TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA,  "TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA"},
    {PJ_TLS_DH_RSA_WITH_DES_CBC_SHA,           "TLS_DH_RSA_WITH_DES_CBC_SHA"},
    {PJ_TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA, "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA"},
    {PJ_TLS_DHE_DSS_WITH_DES_CBC_SHA,          "TLS_DHE_DSS_WITH_DES_CBC_SHA"},
    {PJ_TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA, "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA"},
    {PJ_TLS_DHE_RSA_WITH_DES_CBC_SHA,          "TLS_DHE_RSA_WITH_DES_CBC_SHA"},
    {PJ_TLS_DH_anon_EXPORT_WITH_RC4_40_MD5,    "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5"},
    {PJ_TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA, "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA"},
    {PJ_TLS_DH_anon_WITH_DES_CBC_SHA,          "TLS_DH_anon_WITH_DES_CBC_SHA"},

    /* SSLv3 */
    {PJ_SSL_FORTEZZA_KEA_WITH_NULL_SHA,        "SSL_FORTEZZA_KEA_WITH_NULL_SHA"},
    {PJ_SSL_FORTEZZA_KEA_WITH_FORTEZZA_CBC_SHA,"SSL_FORTEZZA_KEA_WITH_FORTEZZA_CBC_SHA"},
    {PJ_SSL_FORTEZZA_KEA_WITH_RC4_128_SHA,     "SSL_FORTEZZA_KEA_WITH_RC4_128_SHA"},

    /* SSLv2 */
    {PJ_SSL_CK_RC4_128_WITH_MD5,               "SSL_CK_RC4_128_WITH_MD5"},
    {PJ_SSL_CK_RC4_128_EXPORT40_WITH_MD5,      "SSL_CK_RC4_128_EXPORT40_WITH_MD5"},
    {PJ_SSL_CK_RC2_128_CBC_WITH_MD5,           "SSL_CK_RC2_128_CBC_WITH_MD5"},
    {PJ_SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5,  "SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5"},
    {PJ_SSL_CK_IDEA_128_CBC_WITH_MD5,          "SSL_CK_IDEA_128_CBC_WITH_MD5"},
    {PJ_SSL_CK_DES_64_CBC_WITH_MD5,            "SSL_CK_DES_64_CBC_WITH_MD5"},
    {PJ_SSL_CK_DES_192_EDE3_CBC_WITH_MD5,      "SSL_CK_DES_192_EDE3_CBC_WITH_MD5"}
};


/* Get cipher name string */
static const char* get_cipher_name(pj_ssl_cipher cipher)
{
    unsigned i, n;

    n = PJ_ARRAY_SIZE(cipher_names);
    for (i = 0; i < n; ++i) {
       if (cipher == cipher_names[i].cipher)
           return cipher_names[i].name;
    }

    return "CIPHER_UNKNOWN";
}

typedef void (*CPjSSLSocket_cb)(int err, void *key);

class CPjSSLSocketReader : public CActive
{
public:
    static CPjSSLSocketReader *NewL(CSecureSocket &sock) 
    {
        CPjSSLSocketReader *self = new (ELeave) 
                                   CPjSSLSocketReader(sock);
        CleanupStack::PushL(self);
        self->ConstructL();
        CleanupStack::Pop(self);
        return self;
    }

    ~CPjSSLSocketReader() {
        Cancel();
    }

    /* Asynchronous read from the socket. */
    int Read(CPjSSLSocket_cb cb, void *key, TPtr8 &data, TUint flags)
    {
        PJ_ASSERT_RETURN(!IsActive(), PJ_EBUSY);
        
        cb_ = cb;
        key_ = key;
        sock_.RecvOneOrMore(data, iStatus, len_received_);
        SetActive();
        
        return PJ_EPENDING;
    }

private:
    CSecureSocket       &sock_;
    CPjSSLSocket_cb      cb_;
    void                *key_;
    TSockXfrLength       len_received_; /* not really useful? */

    void DoCancel() {
        sock_.CancelAll();
    }
    
    void RunL() {
        (*cb_)(iStatus.Int(), key_);
    }

    CPjSSLSocketReader(CSecureSocket &sock) : 
        CActive(0), sock_(sock), cb_(NULL), key_(NULL) 
    {}
    
    void ConstructL() {
        CActiveScheduler::Add(this);
    }
};

class CPjSSLSocket : public CActive
{
public:
    enum ssl_state {
        SSL_STATE_NULL,
        SSL_STATE_CONNECTING,
        SSL_STATE_HANDSHAKING,
        SSL_STATE_ESTABLISHED
    };
    
    static CPjSSLSocket *NewL(const TDesC8 &ssl_proto,
                              pj_qos_type qos_type,
                              const pj_qos_params &qos_params) 
    {
        CPjSSLSocket *self = new (ELeave) CPjSSLSocket(qos_type, qos_params);
        CleanupStack::PushL(self);
        self->ConstructL(ssl_proto);
        CleanupStack::Pop(self);
        return self;
    }

    ~CPjSSLSocket() {
        Cancel();
        CleanupSubObjects();
    }

    int Connect(CPjSSLSocket_cb cb, void *key, const TInetAddr &local_addr, 
                const TInetAddr &rem_addr, 
                const TDesC8 &servername = TPtrC8(NULL,0),
                const TDesC8 &ciphers = TPtrC8(NULL,0));
    int Send(CPjSSLSocket_cb cb, void *key, const TDesC8 &aDesc, TUint flags);
    int SendSync(const TDesC8 &aDesc, TUint flags);

    CPjSSLSocketReader* GetReader();
    enum ssl_state GetState() const { return state_; }
    const TInetAddr* GetLocalAddr() const { return &local_addr_; }
    int GetCipher(TDes8 &cipher) const {
        if (securesock_)
            return securesock_->CurrentCipherSuite(cipher);
        return KErrNotFound;
    }
    const CX509Certificate *GetPeerCert() {
        if (securesock_)
            return securesock_->ServerCert();
        return NULL;
    }

private:
    enum ssl_state       state_;
    pj_sock_t            sock_;
    CSecureSocket       *securesock_;
    bool                 is_connected_;
    
    pj_qos_type          qos_type_;
    pj_qos_params        qos_params_;
                              
    CPjSSLSocketReader  *reader_;
    TBuf<32>             ssl_proto_;
    TInetAddr            rem_addr_;
    TPtrC8               servername_;
    TPtrC8               ciphers_;
    TInetAddr            local_addr_;
    TSockXfrLength       sent_len_;

    CPjSSLSocket_cb      cb_;
    void                *key_;
    
    void DoCancel();
    void RunL();

    CPjSSLSocket(pj_qos_type qos_type, const pj_qos_params &qos_params) :
        CActive(0), state_(SSL_STATE_NULL), sock_(PJ_INVALID_SOCKET), 
        securesock_(NULL), is_connected_(false),
        qos_type_(qos_type), qos_params_(qos_params),
        reader_(NULL),  cb_(NULL), key_(NULL)
    {}
    
    void ConstructL(const TDesC8 &ssl_proto) {
        ssl_proto_.Copy(ssl_proto);
        CActiveScheduler::Add(this);
    }

    void CleanupSubObjects() {
        delete reader_;
        reader_ = NULL;
        if (securesock_) {
            if (state_ == SSL_STATE_ESTABLISHED)
                securesock_->Close();
            delete securesock_;
            securesock_ = NULL;
        }
        if (sock_ != PJ_INVALID_SOCKET) {
            pj_sock_close(sock_);
            sock_ = PJ_INVALID_SOCKET;
        }           
    }
};

int CPjSSLSocket::Connect(CPjSSLSocket_cb cb, void *key, 
                          const TInetAddr &local_addr, 
                          const TInetAddr &rem_addr,
                          const TDesC8 &servername,
                          const TDesC8 &ciphers)
{
    pj_status_t status;
    
    PJ_ASSERT_RETURN(state_ == SSL_STATE_NULL, PJ_EINVALIDOP);
    
    status = pj_sock_socket(rem_addr.Family(), pj_SOCK_STREAM(), 0, &sock_);
    if (status != PJ_SUCCESS)
        return status;

    // Apply QoS
    status = pj_sock_apply_qos2(sock_, qos_type_, &qos_params_, 
                                2,  THIS_FILE, NULL);
    
    RSocket &rSock = ((CPjSocket*)sock_)->Socket();

    local_addr_ = local_addr;
    
    if (!local_addr_.IsUnspecified()) {
        TInt err = rSock.Bind(local_addr_);
        if (err != KErrNone)
            return PJ_RETURN_OS_ERROR(err);
    }
    
    cb_ = cb;
    key_ = key;
    rem_addr_ = rem_addr;
    
    /* Note: the following members only keep the pointer, not the data */
    servername_.Set(servername);
    ciphers_.Set(ciphers);

    rSock.Connect(rem_addr_, iStatus);
    SetActive();
    state_ = SSL_STATE_CONNECTING;
    
    rSock.LocalName(local_addr_);

    return PJ_EPENDING;
}

int CPjSSLSocket::Send(CPjSSLSocket_cb cb, void *key, const TDesC8 &aDesc, 
                       TUint flags)
{
    PJ_UNUSED_ARG(flags);

    PJ_ASSERT_RETURN(state_ == SSL_STATE_ESTABLISHED, PJ_EINVALIDOP);
    
    if (IsActive())
        return PJ_EBUSY;
    
    cb_ = cb;
    key_ = key;
    
    securesock_->Send(aDesc, iStatus, sent_len_);
    SetActive();
    
    return PJ_EPENDING;
}

int CPjSSLSocket::SendSync(const TDesC8 &aDesc, TUint flags)
{
    PJ_UNUSED_ARG(flags);

    PJ_ASSERT_RETURN(state_ == SSL_STATE_ESTABLISHED, PJ_EINVALIDOP);
    
    TRequestStatus reqStatus;
    securesock_->Send(aDesc, reqStatus, sent_len_);
    User::WaitForRequest(reqStatus);
    
    return PJ_RETURN_OS_ERROR(reqStatus.Int());
}

CPjSSLSocketReader* CPjSSLSocket::GetReader()
{
    PJ_ASSERT_RETURN(state_ == SSL_STATE_ESTABLISHED, NULL);
    
    if (reader_)
        return reader_;
    
    TRAPD(err,  reader_ = CPjSSLSocketReader::NewL(*securesock_));
    if (err != KErrNone)
        return NULL;
    
    return reader_;
}

void CPjSSLSocket::DoCancel()
{
    /* Operation to be cancelled depends on current state */
    switch (state_) {
    case SSL_STATE_CONNECTING:
        {
            RSocket &rSock = ((CPjSocket*)sock_)->Socket();

            rSock.CancelConnect();
            CleanupSubObjects();
            state_ = SSL_STATE_NULL;
        }
        break;
    case SSL_STATE_HANDSHAKING:
        {
            securesock_->CancelHandshake();
            CleanupSubObjects();
            state_ = SSL_STATE_NULL;
        }
        break;
    case SSL_STATE_ESTABLISHED:
        securesock_->CancelSend();
        break;
    default:
        break;
    }
}

void CPjSSLSocket::RunL()
{
    switch (state_) {
    case SSL_STATE_CONNECTING:
        if (iStatus != KErrNone) {
            CleanupSubObjects();
            state_ = SSL_STATE_NULL;
            /* Dispatch connect failure notification */
            if (cb_) (*cb_)(iStatus.Int(), key_);
        } else {
            RSocket &rSock = ((CPjSocket*)sock_)->Socket();

            /* Get local addr */
            rSock.LocalName(local_addr_);
            
            /* Prepare and start handshake */
            securesock_ = CSecureSocket::NewL(rSock, ssl_proto_);
            securesock_->SetDialogMode(EDialogModeAttended);
            if (servername_.Length() > 0)
                securesock_->SetOpt(KSoSSLDomainName, KSolInetSSL,
                                    servername_);
            if (ciphers_.Length() > 0)
                securesock_->SetAvailableCipherSuites(ciphers_);

            // FlushSessionCache() seems to also fire signals to all 
            // completed AOs (something like CActiveScheduler::RunIfReady())
            // which may cause problem, e.g: we've experienced that when 
            // SSL timeout is set to 1s, the SSL timeout timer fires up
            // at this point and securesock_ instance gets deleted here!
            // So be careful using this. And we don't think we need it here.
            //securesock_->FlushSessionCache();

            securesock_->StartClientHandshake(iStatus);
            SetActive();
            state_ = SSL_STATE_HANDSHAKING;
        }
        break;
    case SSL_STATE_HANDSHAKING:
        if (iStatus == KErrNone) {
            state_ = SSL_STATE_ESTABLISHED;
        } else {
            state_ = SSL_STATE_NULL;
            CleanupSubObjects();
        }
        /* Dispatch connect status notification */
        if (cb_) (*cb_)(iStatus.Int(), key_);
        break;
    case SSL_STATE_ESTABLISHED:
        /* Dispatch data sent notification */
        if (cb_) (*cb_)(iStatus.Int(), key_);
        break;
    default:
        pj_assert(0);
        break;
    }
}

typedef void (*CPjTimer_cb)(void *user_data);

class CPjTimer : public CActive 
{
public:
    CPjTimer(const pj_time_val *delay, CPjTimer_cb cb, void *user_data) : 
        CActive(0), cb_(cb), user_data_(user_data)
    {
        CActiveScheduler::Add(this);

        rtimer_.CreateLocal();
        pj_int32_t interval = PJ_TIME_VAL_MSEC(*delay) * 1000;
        if (interval < 0) {
            interval = 0;
        }
        rtimer_.After(iStatus, interval);
        SetActive();
    }
    
    ~CPjTimer() { Cancel(); }
    
private:        
    RTimer               rtimer_;
    CPjTimer_cb          cb_;
    void                *user_data_;
    
    void RunL() { if (cb_) (*cb_)(user_data_); }
    void DoCancel() { rtimer_.Cancel(); }
};

/*
 * Structure of recv/read state.
 */
typedef struct read_state_t {
    TPtr8               *read_buf;
    TPtr8               *orig_buf;
    pj_uint32_t          flags;    
} read_state_t;

/*
 * Structure of send/write data.
 */
typedef struct write_data_t {
    pj_size_t            len;
    pj_ioqueue_op_key_t *key;
    pj_size_t            data_len;
    char                 data[1];
} write_data_t;

/*
 * Structure of send/write state.
 */
typedef struct write_state_t {
    char                *buf;
    pj_size_t            max_len;    
    char                *start;
    pj_size_t            len;
    write_data_t        *current_data;
    TPtrC8               send_ptr;
} write_state_t;

/*
 * Secure socket structure definition.
 */
struct pj_ssl_sock_t
{
    pj_pool_t           *pool;
    pj_ssl_sock_cb       cb;
    void                *user_data;
    
    pj_bool_t            established;
    write_state_t        write_state;
    read_state_t         read_state;
    CPjTimer            *connect_timer;

    CPjSSLSocket        *sock;
    int                  sock_af;
    int                  sock_type;
    pj_sockaddr          local_addr;
    pj_sockaddr          rem_addr;

    /* QoS settings */
    pj_qos_type          qos_type;
    pj_qos_params        qos_params;
    pj_bool_t            qos_ignore_error;


    pj_ssl_sock_proto    proto;
    pj_time_val          timeout;
    pj_str_t             servername;
    pj_str_t             ciphers;
    pj_ssl_cert_info     remote_cert_info;
};


static pj_str_t get_cert_name(char *buf, unsigned buf_len,
                              const CX500DistinguishedName &name)
{
    TInt i;
    TUint8 *p;
    TInt l = buf_len;
    
    p = (TUint8*)buf;
    for(i = 0; i < name.Count(); ++i) {
        const CX520AttributeTypeAndValue &attr = name.Element(i);

        /* Print element separator */
        *p++ = '/';
        if (0 == --l) break;

        /* Print the type. */
        TPtr8 type(p, l);
        type.Copy(attr.Type());
        p += type.Length();
        l -= type.Length();
        if (0 >= --l) break;

        /* Print equal sign */
        *p++ = '=';
        if (0 == --l) break;
        
        /* Print the value. Let's just get the raw data here */
        TPtr8 value(p, l);
        value.Copy(attr.EncodedValue().Mid(2));
        p += value.Length();
        l -= value.Length();
        if (0 >= --l) break;
    }
    
    pj_str_t src;
    pj_strset(&src, buf, buf_len - l);
    
    return src;
}
                            
/* Get certificate info from CX509Certificate.
 */
static void get_cert_info(pj_pool_t *pool, pj_ssl_cert_info *ci,
                          const CX509Certificate *x)
{
    enum { tmp_buf_len = 512 };
    char *tmp_buf;
    unsigned len;
    
    pj_assert(pool && ci && x);
    
    /* Init */
    tmp_buf = new char[tmp_buf_len];
    pj_bzero(ci, sizeof(*ci));
    
    /* Version */
    ci->version = x->Version();
    
    /* Serial number */
    len = x->SerialNumber().Length();
    if (len > sizeof(ci->serial_no)) 
        len = sizeof(ci->serial_no);
    pj_memcpy(ci->serial_no + sizeof(ci->serial_no) - len, 
              x->SerialNumber().Ptr(), len);
    
    /* Subject */
    {
        HBufC *subject = NULL;
        TRAPD(err, subject = x->SubjectL());
        if (err == KErrNone) {
            TPtr16 ptr16(subject->Des());
            len = ptr16.Length();
            TPtr8 ptr8((TUint8*)pj_pool_alloc(pool, len), len);
            ptr8.Copy(ptr16);
            pj_strset(&ci->subject.cn, (char*)ptr8.Ptr(), ptr8.Length());
        }
        pj_str_t tmp = get_cert_name(tmp_buf, tmp_buf_len,
                                     x->SubjectName());
        pj_strdup(pool, &ci->subject.info, &tmp);
    }

    /* Issuer */
    {
        HBufC *issuer = NULL;
        TRAPD(err, issuer = x->IssuerL());
        if (err == KErrNone) {
            TPtr16 ptr16(issuer->Des());
            len = ptr16.Length();
            TPtr8 ptr8((TUint8*)pj_pool_alloc(pool, len), len);
            ptr8.Copy(ptr16);
            pj_strset(&ci->issuer.cn, (char*)ptr8.Ptr(), ptr8.Length());
        }
        pj_str_t tmp = get_cert_name(tmp_buf, tmp_buf_len,
                                     x->IssuerName());
        pj_strdup(pool, &ci->issuer.info, &tmp);
    }
    
    /* Validity */
    const CValidityPeriod &valid_period = x->ValidityPeriod();
    TTime base_time(TDateTime(1970, EJanuary, 0, 0, 0, 0, 0));
    TTimeIntervalSeconds tmp_sec;
    valid_period.Start().SecondsFrom(base_time, tmp_sec);
    ci->validity.start.sec = tmp_sec.Int(); 
    valid_period.Finish().SecondsFrom(base_time, tmp_sec);
    ci->validity.end.sec = tmp_sec.Int();
    
    /* Deinit */
    delete [] tmp_buf;
}


/* Update certificates info. This function should be called after handshake
 * or renegotiation successfully completed.
 */
static void update_certs_info(pj_ssl_sock_t *ssock)
{
    const CX509Certificate *x;

    pj_assert(ssock && ssock->sock &&
              ssock->sock->GetState() == CPjSSLSocket::SSL_STATE_ESTABLISHED);
        
    /* Active remote certificate */
    x = ssock->sock->GetPeerCert();
    if (x) {
        get_cert_info(ssock->pool, &ssock->remote_cert_info, x);
    } else {
        pj_bzero(&ssock->remote_cert_info, sizeof(pj_ssl_cert_info));
    }
}


/* Available ciphers */
static unsigned ciphers_num_ = 0;
static struct ciphers_t
{
    pj_ssl_cipher    id;
    const char      *name;
} ciphers_[64];

/*
 * Get cipher list supported by SSL/TLS backend.
 */
PJ_DEF(pj_status_t) pj_ssl_cipher_get_availables (pj_ssl_cipher ciphers[],
                                                  unsigned *cipher_num)
{
    unsigned i;

    PJ_ASSERT_RETURN(ciphers && cipher_num, PJ_EINVAL);
    
    if (ciphers_num_ == 0) {
        RSocket sock;
        CSecureSocket *secure_sock;
        TPtrC16 proto(_L16("TLS1.0"));

        secure_sock = CSecureSocket::NewL(sock, proto);
        if (secure_sock) {
            TBuf8<128> ciphers_buf(0);
            secure_sock->AvailableCipherSuites(ciphers_buf);
            
            ciphers_num_ = ciphers_buf.Length() / 2;
            if (ciphers_num_ > PJ_ARRAY_SIZE(ciphers_))
                ciphers_num_ = PJ_ARRAY_SIZE(ciphers_);
            for (i = 0; i < ciphers_num_; ++i) {
                ciphers_[i].id = (pj_ssl_cipher)(ciphers_buf[i*2]*10 + 
                                                 ciphers_buf[i*2+1]);
                ciphers_[i].name = get_cipher_name(ciphers_[i].id);
            }
        }
        
        delete secure_sock;
    }
    
    if (ciphers_num_ == 0) {
        *cipher_num = 0;
        return PJ_ENOTFOUND;
    }
    
    *cipher_num = PJ_MIN(*cipher_num, ciphers_num_);
    for (i = 0; i < *cipher_num; ++i)
        ciphers[i] = ciphers_[i].id;
    
    return PJ_SUCCESS;
}


/* Get cipher name string */
PJ_DEF(const char*) pj_ssl_cipher_name(pj_ssl_cipher cipher)
{
    unsigned i;

    if (ciphers_num_ == 0) {
        pj_ssl_cipher c[1];
        i = 0;
        pj_ssl_cipher_get_availables(c, &i);
    }
        
    for (i = 0; i < ciphers_num_; ++i) {
        if (cipher == ciphers_[i].id)
            return ciphers_[i].name;
    }

    return NULL;
}


/* Get cipher identifier */
PJ_DEF(pj_ssl_cipher) pj_ssl_cipher_id(const char *cipher_name)
{
    unsigned i;
    
    if (ciphers_num_ == 0) {
        pj_ssl_cipher c[1];
        i = 0;
        pj_ssl_cipher_get_availables(c, &i);
    }
    
    for (i = 0; i < ciphers_num_; ++i) {
        if (!pj_ansi_stricmp(ciphers_[i].name, cipher_name))
            return ciphers_[i].id;
    }

    return PJ_TLS_UNKNOWN_CIPHER;
}


/* Check if the specified cipher is supported by SSL/TLS backend. */
PJ_DEF(pj_bool_t) pj_ssl_cipher_is_supported(pj_ssl_cipher cipher)
{
    unsigned i;

    if (ciphers_num_ == 0) {
        pj_ssl_cipher c[1];
        i = 0;
        pj_ssl_cipher_get_availables(c, &i);
    }
        
    for (i = 0; i < ciphers_num_; ++i) {
        if (cipher == ciphers_[i].id)
            return PJ_TRUE;
    }

    return PJ_FALSE;
}


/*
 * Create SSL socket instance. 
 */
PJ_DEF(pj_status_t) pj_ssl_sock_create (pj_pool_t *pool,
                                        const pj_ssl_sock_param *param,
                                        pj_ssl_sock_t **p_ssock)
{
    pj_ssl_sock_t *ssock;

    PJ_ASSERT_RETURN(param->async_cnt == 1, PJ_EINVAL);
    PJ_ASSERT_RETURN(pool && param && p_ssock, PJ_EINVAL);

    /* Allocate secure socket */
    ssock = PJ_POOL_ZALLOC_T(pool, pj_ssl_sock_t);
    
    /* Allocate write buffer */
    ssock->write_state.buf = (char*)pj_pool_alloc(pool, 
                                                  param->send_buffer_size);
    ssock->write_state.max_len = param->send_buffer_size;
    ssock->write_state.start = ssock->write_state.buf;
    
    /* Init secure socket */
    ssock->pool = pool;
    ssock->sock_af = param->sock_af;
    ssock->sock_type = param->sock_type;
    ssock->cb = param->cb;
    ssock->user_data = param->user_data;
    ssock->timeout = param->timeout;
    if (param->ciphers_num > 0) {
        /* Cipher list in Symbian is represented as array of two-octets. */
        ssock->ciphers.slen = param->ciphers_num*2;
        ssock->ciphers.ptr  = (char*)pj_pool_alloc(pool, ssock->ciphers.slen);
        pj_uint8_t *c = (pj_uint8_t*)ssock->ciphers.ptr;
        for (unsigned i = 0; i < param->ciphers_num; ++i) {
            *c++ = (pj_uint8_t)(param->ciphers[i] & 0xFF00) >> 8;
            *c++ = (pj_uint8_t)(param->ciphers[i] & 0xFF);
        }
    }
    pj_strdup_with_null(pool, &ssock->servername, &param->server_name);

    ssock->qos_type = param->qos_type;
    ssock->qos_ignore_error = param->qos_ignore_error;
    pj_memcpy(&ssock->qos_params, &param->qos_params,
              sizeof(param->qos_params));

    /* Finally */
    *p_ssock = ssock;

    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pj_ssl_cert_load_from_files(pj_pool_t *pool,
                                                const pj_str_t *CA_file,
                                                const pj_str_t *cert_file,
                                                const pj_str_t *privkey_file,
                                                const pj_str_t *privkey_pass,
                                                pj_ssl_cert_t **p_cert)
{
    return pj_ssl_cert_load_from_files2(pool, CA_file, NULL, cert_file,
                                        privkey_file, privkey_pass, p_cert);
}

PJ_DEF(pj_status_t) pj_ssl_cert_load_from_files2(pj_pool_t *pool,
                                                 const pj_str_t *CA_file,
                                                 const pj_str_t *CA_path,
                                                 const pj_str_t *cert_file,
                                                 const pj_str_t *privkey_file,
                                                 const pj_str_t *privkey_pass,
                                                 pj_ssl_cert_t **p_cert)
{
    PJ_UNUSED_ARG(pool);
    PJ_UNUSED_ARG(CA_file);
    PJ_UNUSED_ARG(CA_path);
    PJ_UNUSED_ARG(cert_file);
    PJ_UNUSED_ARG(privkey_file);
    PJ_UNUSED_ARG(privkey_pass);
    PJ_UNUSED_ARG(p_cert);
    return PJ_ENOTSUP;
}

/*
 * Set SSL socket credential.
 */
PJ_DEF(pj_status_t) pj_ssl_sock_set_certificate(
                                            pj_ssl_sock_t *ssock,
                                            pj_pool_t *pool,
                                            const pj_ssl_cert_t *cert)
{
    PJ_UNUSED_ARG(ssock);
    PJ_UNUSED_ARG(pool);
    PJ_UNUSED_ARG(cert);
    return PJ_ENOTSUP;
}

/*
 * Close the SSL socket.
 */
PJ_DEF(pj_status_t) pj_ssl_sock_close(pj_ssl_sock_t *ssock)
{
    PJ_ASSERT_RETURN(ssock, PJ_EINVAL);
    
    delete ssock->connect_timer;
    ssock->connect_timer = NULL;
    
    delete ssock->sock;
    ssock->sock = NULL;

    delete ssock->read_state.read_buf;
    delete ssock->read_state.orig_buf;
    ssock->read_state.read_buf = NULL;
    ssock->read_state.orig_buf = NULL;
    
    return PJ_SUCCESS;
}


/*
 * Associate arbitrary data with the SSL socket.
 */
PJ_DEF(pj_status_t) pj_ssl_sock_set_user_data (pj_ssl_sock_t *ssock,
                                               void *user_data)
{
    PJ_ASSERT_RETURN(ssock, PJ_EINVAL);
    
    ssock->user_data = user_data;
    
    return PJ_SUCCESS;
}
                                               

/*
 * Retrieve the user data previously associated with this SSL
 * socket.
 */
PJ_DEF(void*) pj_ssl_sock_get_user_data(pj_ssl_sock_t *ssock)
{
    PJ_ASSERT_RETURN(ssock, NULL);
    
    return ssock->user_data;
}


/*
 * Retrieve the local address and port used by specified SSL socket.
 */
PJ_DEF(pj_status_t) pj_ssl_sock_get_info (pj_ssl_sock_t *ssock,
                                          pj_ssl_sock_info *info)
{
    PJ_ASSERT_RETURN(ssock && info, PJ_EINVAL);
    
    pj_bzero(info, sizeof(*info));
    
    info->established = ssock->established;
    
    /* Local address */
    if (ssock->sock) {
        const TInetAddr* local_addr_ = ssock->sock->GetLocalAddr();
        int addrlen = sizeof(pj_sockaddr);
        pj_status_t status;
        
        status = PjSymbianOS::Addr2pj(*local_addr_, info->local_addr, &addrlen);
        if (status != PJ_SUCCESS)
            return status;
    } else {
        pj_sockaddr_cp(&info->local_addr, &ssock->local_addr);
    }

    if (info->established) {
        /* Cipher suite */
        TBuf8<4> cipher;
        if (ssock->sock->GetCipher(cipher) == KErrNone) {
            info->cipher = (pj_ssl_cipher)cipher[1]; 
        }

        /* Remote address */
        pj_sockaddr_cp((pj_sockaddr_t*)&info->remote_addr, 
                       (pj_sockaddr_t*)&ssock->rem_addr);
        
        /* Certificates info */
        info->remote_cert_info = &ssock->remote_cert_info;
    }

    /* Protocol */
    info->proto = ssock->proto;
    
    return PJ_SUCCESS;
}


/*
 * Starts read operation on this SSL socket.
 */
PJ_DEF(pj_status_t) pj_ssl_sock_start_read (pj_ssl_sock_t *ssock,
                                            pj_pool_t *pool,
                                            unsigned buff_size,
                                            pj_uint32_t flags)
{
    PJ_ASSERT_RETURN(ssock && pool && buff_size, PJ_EINVAL);
    PJ_ASSERT_RETURN(ssock->established, PJ_EINVALIDOP);

    /* Reading is already started */
    if (ssock->read_state.orig_buf) {
        return PJ_SUCCESS;
    }

    void *readbuf[1];
    readbuf[0] = pj_pool_alloc(pool, buff_size);
    return pj_ssl_sock_start_read2(ssock, pool, buff_size, readbuf, flags);
}

static void read_cb(int err, void *key)
{
    pj_ssl_sock_t *ssock = (pj_ssl_sock_t*)key;
    pj_status_t status;

    status = (err == KErrNone)? PJ_SUCCESS : PJ_RETURN_OS_ERROR(err);

    /* Check connection status */
    if (err == KErrEof || !PjSymbianOS::Instance()->IsConnectionUp() ||
        !ssock->established) 
    {
        status = PJ_EEOF;
    }
    
    /* Notify data arrival */
    if (ssock->cb.on_data_read) {
        pj_size_t remainder = 0;
        char *data = (char*)ssock->read_state.orig_buf->Ptr();
        pj_size_t data_len = ssock->read_state.read_buf->Length() + 
                             ssock->read_state.read_buf->Ptr() -
                             ssock->read_state.orig_buf->Ptr();
        
        if (data_len > 0) {
            /* Notify received data */
            pj_bool_t ret = (*ssock->cb.on_data_read)(ssock, data, data_len, 
                                                      status, &remainder);
            if (!ret) {
                /* We've been destroyed */
                return;
            }
            
            /* Calculate available data for next READ operation */
            if (remainder > 0) {
                pj_size_t data_maxlen = ssock->read_state.orig_buf->MaxLength();
                
                /* There is some data left unconsumed by application, we give
                 * smaller buffer for next READ operation.
                 */
                ssock->read_state.read_buf->Set((TUint8*)data+remainder, 0, 
                                                data_maxlen - remainder);
            } else {
                /* Give all buffer for next READ operation. 
                 */
                ssock->read_state.read_buf->Set(*ssock->read_state.orig_buf);
            }
        }
    }

    if (status == PJ_SUCCESS) {
        /* Perform the "next" READ operation */
        CPjSSLSocketReader *reader = ssock->sock->GetReader(); 
        ssock->read_state.read_buf->SetLength(0);
        status = reader->Read(&read_cb, ssock, *ssock->read_state.read_buf, 
                              ssock->read_state.flags);
    }
    
    /* Connection closed or something goes wrong */
    if (status != PJ_SUCCESS && status != PJ_EPENDING) {
        /* Notify error */
        if (ssock->cb.on_data_read) {
            pj_bool_t ret = (*ssock->cb.on_data_read)(ssock, NULL, 0, 
                                                      status, NULL);
            if (!ret) {
                /* We've been destroyed */
                return;
            }
        }
        
        delete ssock->read_state.read_buf;
        delete ssock->read_state.orig_buf;
        ssock->read_state.read_buf = NULL;
        ssock->read_state.orig_buf = NULL;
        ssock->established = PJ_FALSE;
    }
}

/*
 * Same as #pj_ssl_sock_start_read(), except that the application
 * supplies the buffers for the read operation so that the acive socket
 * does not have to allocate the buffers.
 */
PJ_DEF(pj_status_t) pj_ssl_sock_start_read2 (pj_ssl_sock_t *ssock,
                                             pj_pool_t *pool,
                                             unsigned buff_size,
                                             void *readbuf[],
                                             pj_uint32_t flags)
{
    PJ_ASSERT_RETURN(ssock && buff_size && readbuf, PJ_EINVAL);
    PJ_ASSERT_RETURN(ssock->established, PJ_EINVALIDOP);
    
    /* Return failure if access point is marked as down by app. */
    PJ_SYMBIAN_CHECK_CONNECTION();
    
    /* Reading is already started */
    if (ssock->read_state.orig_buf) {
        return PJ_SUCCESS;
    }
    
    PJ_UNUSED_ARG(pool);

    /* Get reader instance */
    CPjSSLSocketReader *reader = ssock->sock->GetReader();
    if (!reader)
        return PJ_ENOMEM;
    
    /* We manage two buffer pointers here:
     * 1. orig_buf keeps the orginal buffer address (and its max length).
     * 2. read_buf provides buffer for READ operation, mind that there may be
     *    some remainder data left by application.
     */
    ssock->read_state.read_buf = new TPtr8((TUint8*)readbuf[0], 0, buff_size);
    ssock->read_state.orig_buf = new TPtr8((TUint8*)readbuf[0], 0, buff_size);
    ssock->read_state.flags = flags;
    
    pj_status_t status;
    status = reader->Read(&read_cb, ssock, *ssock->read_state.read_buf, 
                          ssock->read_state.flags);
    
    if (status != PJ_SUCCESS && status != PJ_EPENDING) {
        delete ssock->read_state.read_buf;
        delete ssock->read_state.orig_buf;
        ssock->read_state.read_buf = NULL;
        ssock->read_state.orig_buf = NULL;
        
        return status;
    }
    
    return PJ_SUCCESS;
}

/*
 * Same as pj_ssl_sock_start_read(), except that this function is used
 * only for datagram sockets, and it will trigger \a on_data_recvfrom()
 * callback instead.
 */
PJ_DEF(pj_status_t) pj_ssl_sock_start_recvfrom (pj_ssl_sock_t *ssock,
                                                pj_pool_t *pool,
                                                unsigned buff_size,
                                                pj_uint32_t flags)
{
    PJ_UNUSED_ARG(ssock);
    PJ_UNUSED_ARG(pool);
    PJ_UNUSED_ARG(buff_size);
    PJ_UNUSED_ARG(flags);
    return PJ_ENOTSUP;
}

/*
 * Same as #pj_ssl_sock_start_recvfrom() except that the recvfrom() 
 * operation takes the buffer from the argument rather than creating
 * new ones.
 */
PJ_DEF(pj_status_t) pj_ssl_sock_start_recvfrom2 (pj_ssl_sock_t *ssock,
                                                 pj_pool_t *pool,
                                                 unsigned buff_size,
                                                 void *readbuf[],
                                                 pj_uint32_t flags)
{
    PJ_UNUSED_ARG(ssock);
    PJ_UNUSED_ARG(pool);
    PJ_UNUSED_ARG(buff_size);
    PJ_UNUSED_ARG(readbuf);
    PJ_UNUSED_ARG(flags);
    return PJ_ENOTSUP;
}

static void send_cb(int err, void *key)
{
    pj_ssl_sock_t *ssock = (pj_ssl_sock_t*)key;
    write_state_t *st = &ssock->write_state;

    /* Check connection status */
    if (err != KErrNone || !PjSymbianOS::Instance()->IsConnectionUp() ||
        !ssock->established) 
    {
        ssock->established = PJ_FALSE;
        return;
    }

    /* Remove sent data from buffer */
    st->start += st->current_data->len;
    st->len -= st->current_data->len;

    /* Reset current outstanding send */
    st->current_data = NULL;

    /* Let's check if there is pending data to send */
    if (st->len) {
        write_data_t *wdata = (write_data_t*)st->start;
        pj_status_t status;
        
        st->send_ptr.Set((TUint8*)wdata->data, (TInt)wdata->data_len);
        st->current_data = wdata;
        status = ssock->sock->Send(&send_cb, ssock, st->send_ptr, 0);
        if (status != PJ_EPENDING) {
            ssock->established = PJ_FALSE;
            st->len = 0;
            return;
        }
    } else {
        /* Buffer empty, reset the start position */
        st->start = st->buf;
    }
}

/*
 * Send data using the socket.
 */
PJ_DEF(pj_status_t) pj_ssl_sock_send (pj_ssl_sock_t *ssock,
                                      pj_ioqueue_op_key_t *send_key,
                                      const void *data,
                                      pj_ssize_t *size,
                                      unsigned flags)
{
    PJ_CHECK_STACK();
    PJ_ASSERT_RETURN(ssock && data && size, PJ_EINVAL);
    PJ_ASSERT_RETURN(ssock->write_state.max_len == 0 || 
                     ssock->write_state.max_len >= (pj_size_t)*size, 
                     PJ_ETOOSMALL);
    
    /* Check connection status */
    if (!PjSymbianOS::Instance()->IsConnectionUp() || !ssock->established) 
    {
        ssock->established = PJ_FALSE;
        return PJ_ECANCELLED;
    }

    write_state_t *st = &ssock->write_state;
    
    /* Synchronous mode */
    if (st->max_len == 0) {
        st->send_ptr.Set((TUint8*)data, (TInt)*size);
        return ssock->sock->SendSync(st->send_ptr, flags);
    }

    /* CSecureSocket only allows one outstanding send operation, so
     * we use buffering mechanism to allow application to perform send 
     * operations at any time.
     */
    
    pj_size_t needed_len = *size + sizeof(write_data_t) - 1;
    
    /* Align needed_len to be multiplication of 4 */
    needed_len = ((needed_len + 3) >> 2) << 2; 

    /* Block until there is buffer slot available and contiguous! */
    while (st->start + st->len + needed_len > st->buf + st->max_len) {
        pj_symbianos_poll(-1, -1);
    }

    /* Push back the send data into the buffer */
    write_data_t *wdata = (write_data_t*)(st->start + st->len);
    
    wdata->len = needed_len;
    wdata->key = send_key;
    wdata->data_len = (pj_size_t)*size;
    pj_memcpy(wdata->data, data, *size);
    st->len += needed_len;

    /* If no outstanding send, send it */
    if (st->current_data == NULL) {
        pj_status_t status;
            
        wdata = (write_data_t*)st->start;
        st->current_data = wdata;
        st->send_ptr.Set((TUint8*)wdata->data, (TInt)wdata->data_len);
        status = ssock->sock->Send(&send_cb, ssock, st->send_ptr, flags);
        
        if (status != PJ_EPENDING) {
            *size = -status;
            return status;
        }
    }
    
    return PJ_SUCCESS;
}

/*
 * Send datagram using the socket.
 */
PJ_DEF(pj_status_t) pj_ssl_sock_sendto (pj_ssl_sock_t *ssock,
                                        pj_ioqueue_op_key_t *send_key,
                                        const void *data,
                                        pj_ssize_t *size,
                                        unsigned flags,
                                        const pj_sockaddr_t *addr,
                                        int addr_len)
{
    PJ_UNUSED_ARG(ssock);
    PJ_UNUSED_ARG(send_key);
    PJ_UNUSED_ARG(data);
    PJ_UNUSED_ARG(size);
    PJ_UNUSED_ARG(flags);
    PJ_UNUSED_ARG(addr);
    PJ_UNUSED_ARG(addr_len);
    return PJ_ENOTSUP;
}

/*
 * Starts asynchronous socket accept() operations on this SSL socket. 
 */
PJ_DEF(pj_status_t) pj_ssl_sock_start_accept (pj_ssl_sock_t *ssock,
                                              pj_pool_t *pool,
                                              const pj_sockaddr_t *local_addr,
                                              int addr_len)
{
    PJ_UNUSED_ARG(ssock);
    PJ_UNUSED_ARG(pool);
    PJ_UNUSED_ARG(local_addr);
    PJ_UNUSED_ARG(addr_len);
    
    return PJ_ENOTSUP;
}

static void connect_cb(int err, void *key)
{
    pj_ssl_sock_t *ssock = (pj_ssl_sock_t*)key;
    pj_status_t status;
    
    if (ssock->connect_timer) {
        delete ssock->connect_timer;
        ssock->connect_timer = NULL;
    }

    status = (err == KErrNone)? PJ_SUCCESS : PJ_RETURN_OS_ERROR(err);
    if (status == PJ_SUCCESS) {
        ssock->established = PJ_TRUE;
        update_certs_info(ssock);
    } else {
        delete ssock->sock;
        ssock->sock = NULL;
        if (err == KErrTimedOut) status = PJ_ETIMEDOUT;
    }
    
    if (ssock->cb.on_connect_complete) {
        pj_bool_t ret = (*ssock->cb.on_connect_complete)(ssock, status);
        if (!ret) {
            /* We've been destroyed */
            return;
        }
    }
}

static void connect_timer_cb(void *key)
{
    connect_cb(KErrTimedOut, key);
}

/*
 * Starts asynchronous socket connect() operation and SSL/TLS handshaking 
 * for this socket. Once the connection is done (either successfully or not),
 * the \a on_connect_complete() callback will be called.
 */
PJ_DEF(pj_status_t) pj_ssl_sock_start_connect (pj_ssl_sock_t *ssock,
                                               pj_pool_t *pool,
                                               const pj_sockaddr_t *localaddr,
                                               const pj_sockaddr_t *remaddr,
                                               int addr_len)
{
    CPjSSLSocket *sock = NULL;
    pj_status_t status;
    
    PJ_ASSERT_RETURN(ssock && pool && localaddr && remaddr && addr_len,
                     PJ_EINVAL);

    /* Check connection status */
    PJ_SYMBIAN_CHECK_CONNECTION();
    
    if (ssock->sock != NULL) {
        CPjSSLSocket::ssl_state state = ssock->sock->GetState();
        switch (state) {
        case CPjSSLSocket::SSL_STATE_ESTABLISHED:
            return PJ_SUCCESS;
        default:
            return PJ_EPENDING;
        }
    }

    /* Set SSL protocol */
    TPtrC8 proto;
    
    if (ssock->proto == PJ_SSL_SOCK_PROTO_DEFAULT)
        ssock->proto = PJ_SSL_SOCK_PROTO_TLS1;

    /* CSecureSocket only support TLS1.0 and SSL3.0 */
    if (ssock->proto & PJ_SSL_SOCK_PROTO_TLS1==PJ_SSL_SOCK_PROTO_TLS1) {
        proto.Set((const TUint8*)"TLS1.0", 6);
    } else if (ssock->proto & PJ_SSL_SOCK_PROTO_SSL3==PJ_SSL_SOCK_PROTO_SSL3) {
        proto.Set((const TUint8*)"SSL3.0", 6);
    } else {
        return PJ_ENOTSUP;
    }

    /* Prepare addresses */
    TInetAddr localaddr_, remaddr_;
    status = PjSymbianOS::pj2Addr(*(pj_sockaddr*)localaddr, addr_len, 
                                  localaddr_);
    if (status != PJ_SUCCESS)
        return status;
    
    status = PjSymbianOS::pj2Addr(*(pj_sockaddr*)remaddr, addr_len,
                                  remaddr_);
    if (status != PJ_SUCCESS)
        return status;

    pj_sockaddr_cp((pj_sockaddr_t*)&ssock->rem_addr, remaddr);

    /* Init SSL engine */
    TRAPD(err, sock = CPjSSLSocket::NewL(proto, ssock->qos_type, 
                                         ssock->qos_params));
    if (err != KErrNone)
        return PJ_ENOMEM;
    
    if (ssock->timeout.sec != 0 || ssock->timeout.msec != 0) {
        ssock->connect_timer = new CPjTimer(&ssock->timeout, 
                                            &connect_timer_cb, ssock);
    }
    
    /* Convert server name to Symbian descriptor */
    TPtrC8 servername_((TUint8*)ssock->servername.ptr, 
                       ssock->servername.slen);
    
    /* Convert cipher list to Symbian descriptor */
    TPtrC8 ciphers_((TUint8*)ssock->ciphers.ptr, 
                    ssock->ciphers.slen);
    
    /* Try to connect */
    status = sock->Connect(&connect_cb, ssock, localaddr_, remaddr_,
                           servername_, ciphers_);
    if (status != PJ_SUCCESS && status != PJ_EPENDING) {
        delete sock;
        return status;
    }

    ssock->sock = sock;
    return status;
}


PJ_DEF(pj_status_t) pj_ssl_sock_renegotiate(pj_ssl_sock_t *ssock)
{
    PJ_UNUSED_ARG(ssock);
    return PJ_ENOTSUP;
}
