Ticket #957:
 - Added features in secure socket: handshake timeout timer, certificate info, renegotiation API.
 - Added unit test for secure socket, along with testing purpose certificate & private key.
 - Updated build configs for secure socket.



git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@2970 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjlib/build/Makefile b/pjlib/build/Makefile
index 439c7fa..a3482e0 100644
--- a/pjlib/build/Makefile
+++ b/pjlib/build/Makefile
@@ -26,7 +26,7 @@
 	guid.o hash.o ip_helper_generic.o list.o lock.o log.o os_time_common.o \
 	pool.o pool_buf.o pool_caching.o pool_dbg.o rand.o \
 	rbtree.o sock_common.o sock_qos_common.o sock_qos_bsd.o \
-	string.o timer.o types.o
+	ssl_sock_common.o ssl_sock_ossl.o string.o timer.o types.o
 export PJLIB_CFLAGS += $(_CFLAGS)
 
 ###############################################################################
@@ -37,7 +37,7 @@
 		    fifobuf.o file.o hash_test.o ioq_perf.o ioq_udp.o \
 		    ioq_unreg.o ioq_tcp.o \
 		    list.o mutex.o os.o pool.o pool_perf.o rand.o rbtree.o \
-		    select.o sleep.o sock.o sock_perf.o \
+		    select.o sleep.o sock.o sock_perf.o ssl_sock.o \
 		    string.o test.o thread.o timer.o timestamp.o \
 		    udp_echo_srv_sync.o udp_echo_srv_ioqueue.o \
 		    util.o
diff --git a/pjlib/build/cacert.pem b/pjlib/build/cacert.pem
new file mode 100644
index 0000000..0a2ee45
--- /dev/null
+++ b/pjlib/build/cacert.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDXzCCAkegAwIBAgIJAPqAwYU5OQLXMA0GCSqGSIb3DQEBBQUAMEYxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMQ4wDAYDVQQKDAVwanNpcDESMBAG
+A1UEAwwJcGpzaXAubGFiMB4XDTA5MTAyMjE3MTczN1oXDTE5MTAyMDE3MTczN1ow
+RjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxDjAMBgNVBAoMBXBq
+c2lwMRIwEAYDVQQDDAlwanNpcC5sYWIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQDWWvnL+oSC0Q6OwGLt2YuhXTEzIVv3B+SGQ7tajB6H3WXVeq+1NmU9
+Yzca33g4FRrU7n4smYmKLzm1aniBhNmJjA+t+gbyizKnLMaCLoG52tUoULcANGKU
+aGwlmvZFugDn2eVg6UfUfRzEGbV3q3a/PzSsOEPwsMeF3YMQJPhkoyPQLtWgUXgP
+89Nyq3XjGGtw/qmUgQjE8a6/P0yXc+myI0hmApmZ9nB3YmlB5W3q6WoU2gGhLXf4
+12rH/LgdnPhM4ijS554Kv9EcUDdQTTrm6bYg66tj+qTet7DolUOlTZ3vKpuCK3tt
+eK9CbNPVzsMsB3yCALSLzQ347pIwfLaJAgMBAAGjUDBOMB0GA1UdDgQWBBRE/VNp
+kNQmLEXKQ+NM4bOVj95zYTAfBgNVHSMEGDAWgBRE/VNpkNQmLEXKQ+NM4bOVj95z
+YTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQCj/gzJKTOZDEBD+zr7
+lvbVctiYE9o8ObxZQsnl/6zI2V9H/2yc1sqQyjBupzw6c37ehvk30yIyUfD3ts87
+2xaJ5VgtgUI3FI5DQ+ASyQXmDawUEmXIGqHb2gDrXBQLd6uMpvNDNW+7TouuniyA
+F12JUCNITeXaVJ0c8d4A9J9DlszBfYUzI45yIQu1gbpnpH74Sp/hG77EMxrRau+x
+EFFmV7gAmkCgOBnXm8jTKqNre/GfLfO7w2xoLsubSLnK46U3iLGBIJJRVGu3UQuN
+k1o7CiIKf0SSWj1bQI99ipTj8obBKRqj1nSbgKF/U6FIfd8DGcVvbJCSAG2czzyA
+5tdA
+-----END CERTIFICATE-----
diff --git a/pjlib/build/pjlib.dsp b/pjlib/build/pjlib.dsp
index 2b04256..541a2d8 100644
--- a/pjlib/build/pjlib.dsp
+++ b/pjlib/build/pjlib.dsp
@@ -353,6 +353,14 @@
 # End Source File

 # Begin Source File

 

+SOURCE=..\src\pj\ssl_sock_common.c

+# End Source File

+# Begin Source File

+

+SOURCE=..\src\pj\ssl_sock_ossl.c

+# End Source File

+# Begin Source File

+

 SOURCE=..\src\pj\string.c

 # End Source File

 # Begin Source File

@@ -605,6 +613,10 @@
 # End Source File

 # Begin Source File

 

+SOURCE=..\include\pj\ssl_sock.h

+# End Source File

+# Begin Source File

+

 SOURCE=..\include\pj\string.h

 # End Source File

 # Begin Source File

diff --git a/pjlib/build/pjlib.vcproj b/pjlib/build/pjlib.vcproj
index 7f4ee00..6e8b5bf 100644
--- a/pjlib/build/pjlib.vcproj
+++ b/pjlib/build/pjlib.vcproj
@@ -5645,6 +5645,14 @@
 				</FileConfiguration>

 			</File>

 			<File

+				RelativePath="..\src\pj\ssl_sock_common.c"

+				>

+			</File>

+			<File

+				RelativePath="..\src\pj\ssl_sock_ossl.c"

+				>

+			</File>

+			<File

 				RelativePath="..\src\pj\string.c"

 				>

 				<FileConfiguration

@@ -11638,6 +11646,10 @@
 				>

 			</File>

 			<File

+				RelativePath="..\include\pj\ssl_sock.h"

+				>

+			</File>

+			<File

 				RelativePath="..\include\pj\string.h"

 				>

 			</File>

diff --git a/pjlib/build/pjlib_test.dsp b/pjlib/build/pjlib_test.dsp
index 8252f12..067dea1 100644
--- a/pjlib/build/pjlib_test.dsp
+++ b/pjlib/build/pjlib_test.dsp
@@ -74,7 +74,7 @@
 # ADD BSC32 /nologo

 LINK32=link.exe

 # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept

-# ADD LINK32 netapi32.lib mswsock.lib ws2_32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /out:"../bin/pjlib-test-i386-win32-vc6-debug.exe" /pdbtype:sept

+# ADD LINK32 netapi32.lib mswsock.lib ws2_32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib  libeay32MT.lib ssleay32MT.lib /nologo /subsystem:console /debug /machine:I386 /out:"../bin/pjlib-test-i386-win32-vc6-debug.exe" /pdbtype:sept

 

 !ENDIF 

 

@@ -193,6 +193,10 @@
 # End Source File

 # Begin Source File

 

+SOURCE="..\src\pjlib-test\ssl_sock.c"

+# End Source File

+# Begin Source File

+

 SOURCE="..\src\pjlib-test\string.c"

 # End Source File

 # Begin Source File

diff --git a/pjlib/build/pjlib_test.vcproj b/pjlib/build/pjlib_test.vcproj
index 6156d7c..050f679 100644
--- a/pjlib/build/pjlib_test.vcproj
+++ b/pjlib/build/pjlib_test.vcproj
Binary files differ
diff --git a/pjlib/build/privkey.pem b/pjlib/build/privkey.pem
new file mode 100644
index 0000000..3241be2
--- /dev/null
+++ b/pjlib/build/privkey.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA1lr5y/qEgtEOjsBi7dmLoV0xMyFb9wfkhkO7Woweh91l1Xqv
+tTZlPWM3Gt94OBUa1O5+LJmJii85tWp4gYTZiYwPrfoG8osypyzGgi6BudrVKFC3
+ADRilGhsJZr2RboA59nlYOlH1H0cxBm1d6t2vz80rDhD8LDHhd2DECT4ZKMj0C7V
+oFF4D/PTcqt14xhrcP6plIEIxPGuvz9Ml3PpsiNIZgKZmfZwd2JpQeVt6ulqFNoB
+oS13+Ndqx/y4HZz4TOIo0ueeCr/RHFA3UE065um2IOurY/qk3rew6JVDpU2d7yqb
+git7bXivQmzT1c7DLAd8ggC0i80N+O6SMHy2iQIDAQABAoIBAQCAke7Ujz2d7WDq
+9LAh8+NRdUFGZtLvd9d1RPkCVZsWaRBknIL5kVfmGzV5M+K62MXQRACAJdOeg7b8
+fpErNpD4dH8PHjG+lwlZxnyGpvh+jqhd1xP81m7ujzeW0ry2k9tpNYPkveespyJy
+6Oy0i67dBT9FsTXnD1GNlJDBRTuLuEkTTBqbn/2s3+gUfChJ4HPmYMeO9HU4PcfM
+yUsHatBiIkXiCKdDZVMDr5AUVD0Wo3uHPGJ8ZreURAjH+ldG09+/EsVoPberbrDZ
+ZxJ70VKG+ZZTY8HZr9OsZhDZDrHiw9PdG7Hvg7bCvv+gDzZ/z8F+7YHjRjmD5Tp5
+Ex5hDco1AoGBAPfdfzwmqb79AXwYH0HZkkl2EXpzbR9LRgvWlOMSN3GlZhusvGQR
+up6iGk9QnmoEtQS2IAuK4JT3r+yoM/20Nadq5ZUpkZ49SHuZ6+eZpotv3Fh8Oay8
+TAi2vBGM7EQPUOjPOWMRaYGBz3FT/GvUGPTeQ8jYt1gy8F18+A8xD8pTAoGBAN1j
+7+yTly+M47U6mIUXcwoelaS4f/kMcwKHO0O182S4ktfjzc3TpQbHm68ws1rB3iFZ
+SFOP/d04tVxZqPBhN2SpXRHGqTJxXthdTbu3scLMedlf4jY11SRiHX4PDnoBQ1GJ
+NpdkMoex25Fw3AqSVpP61zo8sJkqpqjFfeQDbfgzAoGBAKyGx1ZmDwc6cjsfSzp5
+p+JsRVQ3XcBHk9UPooi/mEoJd55RyLvav0xFxwxoMCvZZOqHnpyKKTJniVOv7Khu
+NF55AJ6n1Y0QWRB3ngWSJKOv0+7fYQHD+yShlRyeO6JQCuBRxT8Y0phrc6oNbIjd
+lBV1VDdL6aqBol9gagWg/72zAoGBAK1rAx1F3z+YFSZ459AZNjvPCVkmTNhBMDXi
+yEGZ3TYgfqYuA6AfET3mTcVFWLjW87EbxtPuDuWi7i2Q7gydmk53fDfYbeDdfXXu
+YF2S3uPAWBI2UXQ1ZuhBEukT0jsvkhPkb6bXDd3NLDkZNsPxLXBtJPqxX4QbLME3
+Mg3RweqRAoGAJ4iXP2b4XWhg17qpDtpn8nhFxLNdhxdaDSKRL8oKQLteds3wS0fi
+ZlaU1P9a3ygTpNquKlmdLJTQEDAVjV5DDlrQAPxtSSytHulNzXRMQFaeydar3Ssv
+J07BPdQs6JEgV071rGGzBcL8ulo7qCdnGxU6GmhLkS4MBbTuqR6jmgU=
+-----END RSA PRIVATE KEY-----
diff --git a/pjlib/include/pj/compat/os_auto.h.in b/pjlib/include/pj/compat/os_auto.h.in
index 2e8562e..f474d60 100644
--- a/pjlib/include/pj/compat/os_auto.h.in
+++ b/pjlib/include/pj/compat/os_auto.h.in
@@ -181,5 +181,11 @@
  */
 #undef PJ_THREAD_ALLOCATE_STACK
 
+/* SSL socket availability. */
+#ifndef PJ_HAS_SSL_SOCK
+#undef PJ_HAS_SSL_SOCK
+#endif
+
+
 #endif	/* __PJ_COMPAT_OS_AUTO_H__ */
 
diff --git a/pjlib/include/pj/config.h b/pjlib/include/pj/config.h
index 71ca8a0..ab37d2d 100644
--- a/pjlib/include/pj/config.h
+++ b/pjlib/include/pj/config.h
@@ -779,6 +779,18 @@
 #endif
 
 
+/**
+ * Enable secure socket. For most platforms, this is implemented using
+ * OpenSSL, so this will require OpenSSL to be installed. For Symbian
+ * platform, this is implemented natively using CSecureSocket.
+ *
+ * Default: 0 (for now)
+ */
+#ifndef PJ_HAS_SSL_SOCK
+#  define PJ_HAS_SSL_SOCK	    0
+#endif
+
+
 /** @} */
 
 /********************************************************************
diff --git a/pjlib/include/pj/ssl_sock.h b/pjlib/include/pj/ssl_sock.h
index ce319d4..ad4556e 100644
--- a/pjlib/include/pj/ssl_sock.h
+++ b/pjlib/include/pj/ssl_sock.h
@@ -60,12 +60,27 @@
 
 
 /**
+ * Describe structure of certificate info.
+ */
+typedef struct pj_ssl_cert_info {
+    pj_str_t	subject;	    /**< Subject.		*/
+    pj_str_t	issuer;		    /**< Issuer.		*/
+    unsigned	version;	    /**< Certificate version.	*/
+    pj_time_val	validity_start;	    /**< Validity start.	*/
+    pj_time_val	validity_end;	    /**< Validity end.		*/
+    pj_bool_t	validity_use_gmt;   /**< Flag if validity date/time 
+					 use GMT.		*/
+} pj_ssl_cert_info;
+
+
+/**
  * Create credential from files.
  *
  * @param CA_file	The file of trusted CA list.
  * @param cert_file	The file of certificate.
  * @param privkey_file	The file of private key.
  * @param privkey_pass	The password of private key, if any.
+ * @param p_cert	Pointer to credential instance to be created.
  *
  * @return		PJ_SUCCESS when successful.
  */
@@ -322,24 +337,38 @@
      * handshaking has been done successfully.
      */
     pj_bool_t established;
+
     /**
      * Describes secure socket protocol being used.
      */
     pj_ssl_sock_proto proto;
+
     /**
      * Describes cipher suite being used, this will only be set when connection
      * is established.
      */
     pj_ssl_cipher cipher;
+
     /**
      * Describes local address.
      */
     pj_sockaddr local_addr;
+
     /**
      * Describes remote address.
      */
     pj_sockaddr remote_addr;
    
+    /**
+     * Describes active local certificate info.
+     */
+    pj_ssl_cert_info local_cert_info;
+   
+    /**
+     * Describes active remote certificate info.
+     */
+    pj_ssl_cert_info remote_cert_info;
+   
 } pj_ssl_sock_info;
 
 
@@ -369,6 +398,13 @@
     pj_ioqueue_t *ioqueue;
 
     /**
+     * Specify the timer heap to use. Secure socket uses the timer to provide
+     * auto cancelation on asynchronous operation when it takes longer time 
+     * than specified timeout period, e.g: security negotiation timeout.
+     */
+    pj_timer_heap_t *timer_heap;
+
+    /**
      * Specify secure socket callbacks, see #pj_ssl_sock_cb.
      */
     pj_ssl_sock_cb cb;
@@ -430,13 +466,12 @@
     pj_bool_t whole_data;
 
     /**
-     * Specify buffer size for delayed send operation. This setting is only
-     * applied for some platforms that restrict more than one outstanding 
-     * send operation at a time, e.g: Symbian. So delaying/buffering send 
-     * mechanism is used to allow application to send data anytime without 
-     * worrying about current outstanding send operations.
+     * Specify buffer size for sending operation. Buffering sending data
+     * is used for allowing application to perform multiple outstanding 
+     * send operations. Whenever application specifies this setting too
+     * small, sending operation may return PJ_ENOMEM.
      *  
-     * Default value is 0, except for Symbian 8192 bytes.
+     * Default value is 8192 bytes.
      */
     pj_size_t send_buffer_size;
 
@@ -495,7 +530,7 @@
      *
      * Default value is zero/not-set.
      */
-    pj_str_t servername;
+    pj_str_t server_name;
     
 } pj_ssl_sock_param;
 
@@ -691,12 +726,12 @@
  * @param size		The size of the data.
  * @param flags		Flags to be given to pj_ioqueue_send().
  *
- *
  * @return		PJ_SUCCESS if data has been sent immediately, or
- *			PJ_EPENDING if data cannot be sent immediately. In
- *			this case the \a on_data_sent() callback will be
- *			called when data is actually sent. Any other return
- *			value indicates error condition.
+ *			PJ_EPENDING if data cannot be sent immediately or
+ *			PJ_ENOMEM when sending buffer could not handle all
+ *			queued data, see \a send_buffer_size. The callback
+ *			\a on_data_sent() will be called when data is actually
+ *			sent. Any other return value indicates error condition.
  */
 PJ_DECL(pj_status_t) pj_ssl_sock_send(pj_ssl_sock_t *ssock,
 				      pj_ioqueue_op_key_t *send_key,
@@ -786,6 +821,23 @@
 
 
 /**
+ * Starts SSL/TLS renegotiation over an already established SSL connection
+ * for this socket. This operation is performed transparently, no callback 
+ * will be called once the renegotiation completed successfully. However, 
+ * when the renegotiation fails, the connection will be closed and callback
+ * \a on_data_read() will be invoked with non-PJ_SUCCESS status code.
+ *
+ * @param ssock		The secure socket.
+ *
+ * @return		PJ_SUCCESS if renegotiation is completed immediately,
+ *			or PJ_EPENDING if renegotiation has been started and
+ *			waiting for completion, or the appropriate error code 
+ *			on failure.
+ */
+PJ_DECL(pj_status_t) pj_ssl_sock_renegotiate(pj_ssl_sock_t *ssock);
+
+
+/**
  * @}
  */
 
diff --git a/pjlib/include/pjlib.h b/pjlib/include/pjlib.h
index 648c594..0709a09 100644
--- a/pjlib/include/pjlib.h
+++ b/pjlib/include/pjlib.h
@@ -52,6 +52,7 @@
 #include <pj/sock.h>
 #include <pj/sock_qos.h>
 #include <pj/sock_select.h>
+#include <pj/ssl_sock.h>
 #include <pj/string.h>
 #include <pj/timer.h>
 #include <pj/unicode.h>
diff --git a/pjlib/src/pj/ssl_sock_ossl.c b/pjlib/src/pj/ssl_sock_ossl.c
index 9c6c80c..eca60d2 100644
--- a/pjlib/src/pj/ssl_sock_ossl.c
+++ b/pjlib/src/pj/ssl_sock_ossl.c
@@ -25,12 +25,14 @@
 #include <pj/lock.h>
 #include <pj/log.h>
 #include <pj/math.h>
+#include <pj/os.h>
 #include <pj/pool.h>
 #include <pj/string.h>
+#include <pj/timer.h>
 
 
 /* Only build when PJ_HAS_SSL_SOCK is enabled */
-//#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK!=0
+#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK!=0
 
 #define THIS_FILE		"ssl_sock_ossl.c"
 
@@ -129,10 +131,14 @@
     pj_ssl_sock_t	 *parent;
     pj_ssl_sock_param	  param;
     pj_ssl_cert_t	 *cert;
+    
+    pj_ssl_cert_info	  local_cert_info;
+    pj_ssl_cert_info	  remote_cert_info;
 
     pj_bool_t		  is_server;
     enum ssl_state	  ssl_state;
     pj_ioqueue_op_key_t	  handshake_op_key;
+    pj_timer_entry	  handshake_timer;
 
     pj_sock_t		  sock;
     pj_activesock_t	 *asock;
@@ -179,56 +185,57 @@
  *******************************************************************
  */
 
-/* ssl_report_error() */
-static void ssl_report_error(const char *sender, int level, 
-			     pj_status_t status,
-			     const char *format, ...)
+/**
+ * Mapping from OpenSSL error codes to pjlib error space.
+ */
+
+#define PJ_SSL_ERRNO_START		(PJ_ERRNO_START_USER + \
+					 PJ_ERRNO_SPACE_SIZE*6)
+
+#define PJ_SSL_ERRNO_SPACE_SIZE		5000
+
+#define PJ_STATUS_FROM_OSSL(ossl_err)	(ossl_err == SSL_ERROR_NONE? \
+					 PJ_SUCCESS : \
+					 (PJ_SSL_ERRNO_START + ossl_err))
+
+#define PJ_STATUS_TO_OSSL(status)	(status == PJ_SUCCESS? \
+					 SSL_ERROR_NONE : \
+					(status - PJ_SSL_ERRNO_START))
+
+
+/*
+ * Get error string of OpenSSL.
+ */
+static pj_str_t ssl_strerror(pj_status_t status, 
+			     char *buf, pj_size_t bufsize)
 {
-    va_list marker;
+    pj_str_t errstr;
+    unsigned long ssl_err = PJ_STATUS_TO_OSSL(status);
 
-    va_start(marker, format);
+#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
 
-#if PJ_LOG_MAX_LEVEL > 0
-    if (status != PJ_SUCCESS) {
-	char err_format[PJ_ERR_MSG_SIZE + 512];
-	int len;
+    ERR_error_string_n(ssl_err, buf, bufsize);
+    errstr = pj_str(buf);
 
-	len = pj_ansi_snprintf(err_format, sizeof(err_format),
-			       "%s: ", format);
-	pj_strerror(status, err_format+len, sizeof(err_format)-len);
-	
-	pj_log(sender, level, err_format, marker);
+#else
 
-    } else {
-	unsigned long ssl_err;
+    errstr.ptr = buf;
+    errstr.slen = pj_ansi_snprintf(buf, bufsize, 
+				   "Unknown OpenSSL error %d",
+				   ssl_err);
 
-	ssl_err = ERR_get_error();
+#endif	/* PJ_HAS_ERROR_STRING */
 
-	if (ssl_err == 0) {
-	    pj_log(sender, level, format, marker);
-	} else {
-	    char err_format[512];
-	    int len;
-
-	    len = pj_ansi_snprintf(err_format, sizeof(err_format),
-				   "%s: ", format);
-	    ERR_error_string(ssl_err, err_format+len);
-	    
-	    pj_log(sender, level, err_format, marker);
-	}
-    }
-#endif
-
-    va_end(marker);
+    return errstr;
 }
 
 
-
 /* OpenSSL library initialization counter */
 static int openssl_init_count;
+static int openssl_reg_strerr;
 
 /* OpenSSL available ciphers */
-static pj_ssl_cipher openssl_ciphers[64];
+static pj_ssl_cipher openssl_ciphers[100];
 static unsigned openssl_cipher_num;
 
 
@@ -238,6 +245,18 @@
     if (++openssl_init_count != 1)
 	return PJ_SUCCESS;
 
+    /* Register error subsystem */
+    if (!openssl_reg_strerr) {
+	pj_status_t status;
+
+	openssl_reg_strerr = 1;
+	status = pj_register_strerror(PJ_SSL_ERRNO_START, 
+				      PJ_SSL_ERRNO_SPACE_SIZE, 
+				      &ssl_strerror);
+	pj_assert(status == PJ_SUCCESS);
+    }
+
+    /* Init OpenSSL lib */
     SSL_library_init();
     SSL_load_error_strings();
     OpenSSL_add_all_algorithms();
@@ -344,17 +363,14 @@
 	ssl_method = (SSL_METHOD*)DTLSv1_method();
 	break;
     default:
-	ssl_report_error(THIS_FILE, 4, PJ_EINVAL,
-			 "Error creating SSL context");
 	return PJ_EINVAL;
     }
 
     /* Create SSL context for the listener */
     ctx = SSL_CTX_new(ssl_method);
     if (ctx == NULL) {
-	ssl_report_error(THIS_FILE, 4, PJ_SUCCESS,
-			 "Error creating SSL context");
-	return PJ_EINVAL;
+	PJ_LOG(1,(ssock->pool->obj_name, "Error creating OpenSSL context"));
+	return PJ_STATUS_FROM_OSSL(ERR_get_error());
     }
 
     /* Apply credentials */
@@ -365,15 +381,11 @@
 	    rc = SSL_CTX_load_verify_locations(ctx, cert->CA_file.ptr, NULL);
 
 	    if (rc != 1) {
-		ssl_report_error(THIS_FILE, 4, PJ_SUCCESS,
-				 "Error loading/verifying CA list file '%s'",
-				 cert->CA_file.ptr);
+		PJ_LOG(1,(ssock->pool->obj_name, "Error loading CA list file "
+			  "'%s'", cert->CA_file.ptr));
 		SSL_CTX_free(ctx);
-		return PJ_EINVAL;
+		return PJ_STATUS_FROM_OSSL(ERR_get_error());
 	    }
-
-	    PJ_LOG(5,(THIS_FILE, "CA file successfully loaded from '%s'",
-		      cert->CA_file.ptr));
 	}
     
 	/* Set password callback */
@@ -390,15 +402,11 @@
 	    rc = SSL_CTX_use_certificate_chain_file(ctx, cert->cert_file.ptr);
 
 	    if(rc != 1) {
-		ssl_report_error(THIS_FILE, 4, PJ_SUCCESS,
-				 "Error loading certificate chain file '%s'",
-				 cert->cert_file.ptr);
+		PJ_LOG(1,(ssock->pool->obj_name, "Error loading certificate "
+			  "chain file '%s'", cert->cert_file.ptr));
 		SSL_CTX_free(ctx);
-		return PJ_EINVAL;
+		return PJ_STATUS_FROM_OSSL(ERR_get_error());
 	    }
-
-	    PJ_LOG(5,(THIS_FILE, "TLS certificate successfully loaded from '%s'",
-		      cert->cert_file.ptr));
 	}
 
 
@@ -409,15 +417,11 @@
 					     SSL_FILETYPE_PEM);
 
 	    if(rc != 1) {
-		ssl_report_error(THIS_FILE, 4, PJ_SUCCESS,
-				 "Error adding private key from '%s'",
-				 cert->privkey_file.ptr);
+		PJ_LOG(1,(ssock->pool->obj_name, "Error adding private key "
+			  "from '%s'", cert->privkey_file.ptr));
 		SSL_CTX_free(ctx);
-		return PJ_EINVAL;
+		return PJ_STATUS_FROM_OSSL(ERR_get_error());
 	    }
-
-	    PJ_LOG(5,(THIS_FILE, "Private key successfully loaded from '%s'",
-		      cert->privkey_file.ptr));
 	}
     }
 
@@ -430,12 +434,10 @@
     }
 
     if (ssock->is_server && ssock->param.require_client_cert)
-	mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
+	mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT | SSL_VERIFY_PEER;
 
     SSL_CTX_set_verify(ctx, mode, NULL);
 
-    PJ_LOG(5,(THIS_FILE, "Verification mode set to %d", mode));
-
     *p_ctx = ctx;
 
     return PJ_SUCCESS;
@@ -473,6 +475,10 @@
 	ssock->asock = NULL;
 	ssock->sock = PJ_INVALID_SOCKET;
     }
+    if (ssock->sock != PJ_INVALID_SOCKET) {
+	pj_sock_close(ssock->sock);
+	ssock->sock = PJ_INVALID_SOCKET;
+    }
 }
 
 
@@ -528,16 +534,131 @@
 
     /* Finally, set chosen cipher list */
     ret = SSL_set_cipher_list(ssock->ossl_ssl, buf);
-    if (ret < 1) {
-	ssl_report_error(THIS_FILE, 4, PJ_SUCCESS,
-			 "Error setting cipher list");
-	return PJ_EINVAL;
-    }
+    if (ret < 1)
+	return PJ_STATUS_FROM_OSSL(SSL_get_error(ssock->ossl_ssl, ret));
 
     return PJ_SUCCESS;
 }
 
 
+/* Parse OpenSSL ASN1_TIME to pj_time_val and GMT info */
+static pj_bool_t parse_ossl_asn1_time(pj_time_val *tv, pj_bool_t *gmt,
+				      const ASN1_TIME *tm)
+{
+    unsigned long parts[7] = {0};
+    char *p, *end;
+    unsigned len;
+    pj_bool_t utc;
+    pj_parsed_time pt;
+    int i;
+
+    utc = tm->type == V_ASN1_UTCTIME;
+    p = (char*)tm->data;
+    len = tm->length;
+    end = p + len - 1;
+
+    /* GMT */
+    *gmt = (*end == 'Z');
+
+    /* parse parts */
+    for (i = 0; i < 7 && p < end; ++i) {
+	pj_str_t st;
+
+	if (i==0 && !utc) {
+	    /* 4 digits year part for non-UTC time format */
+	    st.slen = 4;
+	} else if (i==6) {
+	    /* fraction of seconds */
+	    if (*p == '.') ++p;
+	    st.slen = end - p + 1;
+	} else {
+	    /* other parts always 2 digits length */
+	    st.slen = 2;
+	}
+	st.ptr = p;
+
+	parts[i] = pj_strtoul(&st);
+	p += st.slen;
+    }
+
+    /* encode parts to pj_time_val */
+    pt.year = parts[0];
+    if (utc)
+	pt.year += (pt.year < 50)? 2000:1900;
+    pt.mon = parts[1] - 1;
+    pt.day = parts[2];
+    pt.hour = parts[3];
+    pt.min = parts[4];
+    pt.sec = parts[5];
+    pt.msec = parts[6];
+
+    pj_time_encode(&pt, tv);
+
+    return PJ_TRUE;
+}
+
+
+/* Get certificate info from OpenSSL X509 */
+static void get_cert_info(pj_pool_t *pool, pj_ssl_cert_info *ci, X509 *x)
+{
+    pj_ssl_cert_info info;
+    char buf1[256];
+    char buf2[256];
+
+    pj_assert(pool && ci);
+
+    if (!x) {
+	pj_bzero(ci, sizeof(pj_ssl_cert_info));
+	return;
+    }
+
+    pj_bzero(&info, sizeof(info));
+
+    /* Populate cert info */
+    info.subject = pj_str(X509_NAME_oneline(X509_get_subject_name(x),buf1,
+					    sizeof(buf1)));
+    info.issuer = pj_str(X509_NAME_oneline(X509_get_issuer_name(x), buf2,
+					   sizeof(buf2)));
+    info.version = X509_get_version(x) + 1;
+    parse_ossl_asn1_time(&info.validity_start, &info.validity_use_gmt,
+			 X509_get_notBefore(x));
+    parse_ossl_asn1_time(&info.validity_end, &info.validity_use_gmt,
+			 X509_get_notAfter(x));
+
+    /* Update certificate info */
+    if (pj_strcmp(&ci->subject, &info.subject))
+	pj_strdup(pool, &ci->subject, &info.subject);
+    if (pj_strcmp(&ci->issuer, &info.issuer))
+	pj_strdup(pool, &ci->issuer, &info.issuer);
+    ci->version = info.version;
+    ci->validity_start = info.validity_start;
+    ci->validity_end = info.validity_end;
+    ci->validity_use_gmt = info.validity_use_gmt;
+}
+
+
+/* Update local & remote certificates info. This function should be
+ * called after handshake or renegotiation successfully completed.
+ */
+static void update_certs_info(pj_ssl_sock_t *ssock)
+{
+    X509 *x;
+
+    pj_assert(ssock->ssl_state == SSL_STATE_ESTABLISHED);
+
+    /* Active local certificate */
+    x = SSL_get_certificate(ssock->ossl_ssl);
+    get_cert_info(ssock->pool, &ssock->local_cert_info, x);
+    /* Don't free local's X509! */
+
+    /* Active remote certificate */
+    x = SSL_get_peer_certificate(ssock->ossl_ssl);
+    get_cert_info(ssock->pool, &ssock->remote_cert_info, x);
+    /* Free peer's X509 */
+    X509_free(x);
+}
+
+
 /* When handshake completed:
  * - notify application
  * - if handshake failed, reset SSL state
@@ -546,6 +667,14 @@
 static pj_bool_t on_handshake_complete(pj_ssl_sock_t *ssock, 
 				       pj_status_t status)
 {
+    /* Cancel handshake timer */
+    if (ssock->param.timer_heap)
+	pj_timer_heap_cancel(ssock->param.timer_heap, &ssock->handshake_timer);
+
+    /* Update certificates info on successful handshake */
+    if (status == PJ_SUCCESS)
+	update_certs_info(ssock);
+
     /* Accepting */
     if (ssock->is_server) {
 	if (status != PJ_SUCCESS) {
@@ -707,6 +836,22 @@
     return status;
 }
 
+
+static void handshake_timeout_cb(pj_timer_heap_t *th,
+				 struct pj_timer_entry *te)
+{
+    pj_ssl_sock_t *ssock = (pj_ssl_sock_t*)te->user_data;
+
+    PJ_UNUSED_ARG(th);
+
+    PJ_LOG(1,(ssock->pool->obj_name, "SSL handshake timeout after %d.%ds",
+	      ssock->param.timeout.sec, ssock->param.timeout.msec));
+
+    on_handshake_complete(ssock, PJ_ETIMEDOUT);
+}
+
+
+/* Asynchronouse handshake */
 static pj_status_t do_handshake(pj_ssl_sock_t *ssock)
 {
     pj_status_t status;
@@ -721,10 +866,8 @@
 	if (err != SSL_ERROR_NONE && err != SSL_ERROR_WANT_READ) 
 	{
 	    /* Handshake fails */
-	    ssl_report_error(THIS_FILE, 4, PJ_SUCCESS, 
-			     "SSL_do_handshake()");
 	    pj_lock_release(ssock->write_mutex);
-	    return PJ_ECANCELLED;
+	    return PJ_STATUS_FROM_OSSL(err);
 	}
     }
 
@@ -766,15 +909,13 @@
     pj_size_t nwritten;
 
     /* Socket error or closed */
-    if (data == NULL || size < 0)
-	goto on_error;
-
-    /* Consume the whole data */
-    nwritten = BIO_write(ssock->ossl_rbio, data, size);
-    if (nwritten < size) {
-	status = PJ_ENOMEM;
-	ssl_report_error(THIS_FILE, 4, PJ_SUCCESS, "BIO_write()");
-	goto on_error;
+    if (data && size > 0) {
+	/* Consume the whole data */
+	nwritten = BIO_write(ssock->ossl_rbio, data, size);
+	if (nwritten < size) {
+	    status = PJ_STATUS_FROM_OSSL(ERR_get_error());
+	    goto on_error;
+	}
     }
 
     /* Check if SSL handshake hasn't finished yet */
@@ -802,15 +943,16 @@
 	     * is on progress, so let's protect it with write mutex.
 	     */
 	    pj_lock_acquire(ssock->write_mutex);
-
 	    size_ = SSL_read(ssock->ossl_ssl, data_, size_);
-	    if (size_ > 0) {
-		pj_lock_release(ssock->write_mutex);
+	    pj_lock_release(ssock->write_mutex);
+
+	    if (size_ > 0 || status != PJ_SUCCESS) {
 		if (ssock->param.cb.on_data_read) {
 		    pj_bool_t ret;
 		    pj_size_t remainder_ = 0;
 
-		    buf->len += size_;
+		    if (size_ > 0)
+			buf->len += size_;
     		
 		    ret = (*ssock->param.cb.on_data_read)(ssock, buf->data,
 							  buf->len, status,
@@ -825,7 +967,18 @@
 		     */
 		    buf->len = remainder_;
 		}
+
+		/* Active socket signalled connection closed/error, this has
+		 * been signalled to the application along with any remaining
+		 * buffer. So, let's just reset SSL socket now.
+		 */
+		if (status != PJ_SUCCESS) {
+		    reset_ssl_sock_state(ssock);
+		    return PJ_FALSE;
+		}
+
 	    } else {
+
 		int err = SSL_get_error(ssock->ossl_ssl, size);
 		
 		/* SSL might just return SSL_ERROR_WANT_READ in 
@@ -833,26 +986,43 @@
 		 */
 		if (err != SSL_ERROR_NONE && err != SSL_ERROR_WANT_READ)
 		{
+		    char errmsg[PJ_ERR_MSG_SIZE];
+
+		    pj_strerror(status, errmsg, sizeof(errmsg));
+		    PJ_LOG(1,(ssock->pool->obj_name, "SSL_read() failed: %s",
+			      errmsg));
+
 		    /* Reset SSL socket state, then return PJ_FALSE */
-		    ssl_report_error(THIS_FILE, 4, PJ_SUCCESS, "SSL_read()");
-		    pj_lock_release(ssock->write_mutex);
 		    reset_ssl_sock_state(ssock);
-		    return PJ_FALSE;
+		    goto on_error;
 		}
 
-		/* SSL may write something in case of re-negotiation */
-		status = flush_write_bio(ssock, &ssock->handshake_op_key, 0, 0);
-		pj_lock_release(ssock->write_mutex);
-		if (status != PJ_SUCCESS && status != PJ_EPENDING)
-		    goto on_error;
+		status = do_handshake(ssock);
+		if (status == PJ_SUCCESS) {
+		    /* Renegotiation completed */
 
-		/* If re-negotiation has been completed, start flushing
-		 * delayed send.
-		 */
-		if (!SSL_renegotiate_pending(ssock->ossl_ssl)) {
+		    /* Update certificates */
+		    update_certs_info(ssock);
+
+		    pj_lock_acquire(ssock->write_mutex);
 		    status = flush_delayed_send(ssock);
-		    if (status != PJ_SUCCESS && status != PJ_EPENDING)
+		    pj_lock_release(ssock->write_mutex);
+
+		    if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+			char errmsg[PJ_ERR_MSG_SIZE];
+
+			pj_strerror(status, errmsg, sizeof(errmsg));
+			PJ_LOG(1,(ssock->pool->obj_name, "Failed to flush "
+				  "delayed send: %s", errmsg));
 			goto on_error;
+		    }
+		} else if (status != PJ_EPENDING) {
+		    char errmsg[PJ_ERR_MSG_SIZE];
+
+		    pj_strerror(status, errmsg, sizeof(errmsg));
+		    PJ_LOG(1,(ssock->pool->obj_name, "Renegotiation failed: "
+			      "%s", errmsg));
+		    goto on_error;
 		}
 
 		break;
@@ -943,9 +1113,13 @@
     pj_activesock_cfg asock_cfg;
     unsigned i;
     pj_status_t status;
+    char buf[64];
 
     PJ_UNUSED_ARG(src_addr_len);
 
+    PJ_LOG(4,(ssock_parent->pool->obj_name, "Incoming connection from %s",
+	      pj_sockaddr_print(src_addr, buf, sizeof(buf), 3)));
+
     /* Create new SSL socket instance */
     status = pj_ssl_sock_create(ssock_parent->pool, &ssock_parent->param,
 				&ssock);
@@ -981,9 +1155,8 @@
     /* Create SSL instance */
     ssock->ossl_ssl = SSL_new(ssock->ossl_ctx);
     if (ssock->ossl_ssl == NULL) {
-	ssl_report_error(THIS_FILE, 4, PJ_SUCCESS,
-			 "Error creating SSL connection object");
-	status = PJ_EINVAL;
+	PJ_LOG(1,(ssock->pool->obj_name, "Error creating SSL instance"));
+	status = PJ_STATUS_FROM_OSSL(ERR_get_error());
 	goto on_return;
     }
 
@@ -1049,6 +1222,16 @@
     ssock->write_state.start = ssock->write_state.buf;
     ssock->write_state.len = 0;
 
+    /* Start handshake timer */
+    if (ssock->param.timer_heap && (ssock->param.timeout.sec != 0 ||
+	ssock->param.timeout.msec != 0))
+    {
+	pj_timer_entry_init(&ssock->handshake_timer, 0, ssock, 
+			    &handshake_timeout_cb);
+	pj_timer_heap_schedule(ssock->param.timer_heap, &ssock->handshake_timer,
+			       &ssock->param.timeout);
+    }
+
     /* Start SSL handshake */
     ssock->ssl_state = SSL_STATE_HANDSHAKING;
     SSL_set_accept_state(ssock->ossl_ssl);
@@ -1088,9 +1271,8 @@
     /* Create SSL instance */
     ssock->ossl_ssl = SSL_new(ssock->ossl_ctx);
     if (ssock->ossl_ssl == NULL) {
-	ssl_report_error(THIS_FILE, 4, PJ_SUCCESS,
-			 "Error creating SSL connection object");
-	status = PJ_EINVAL;
+	PJ_LOG(1,(ssock->pool->obj_name, "Error creating SSL instance"));
+	status = PJ_STATUS_FROM_OSSL(ERR_get_error());
 	goto on_return;
     }
 
@@ -1134,9 +1316,37 @@
     ssock->write_state.start = ssock->write_state.buf;
     ssock->write_state.len = 0;
 
+    /* Start handshake timer */
+    if (ssock->param.timer_heap && (ssock->param.timeout.sec != 0 ||
+	ssock->param.timeout.msec != 0))
+    {
+	pj_timer_entry_init(&ssock->handshake_timer, 0, ssock, 
+			    &handshake_timeout_cb);
+	pj_timer_heap_schedule(ssock->param.timer_heap,
+			       &ssock->handshake_timer,
+			       &ssock->param.timeout);
+    }
+
+#ifdef SSL_set_tlsext_host_name
+    /* Set server name to connect */
+    if (ssock->param.server_name.slen) {
+	/* Server name is null terminated already */
+	if (!SSL_set_tlsext_host_name(ssock->ossl_ssl, 
+				      ssock->param.server_name.ptr))
+	{
+	    char err_str[PJ_ERR_MSG_SIZE];
+
+	    ERR_error_string_n(ERR_get_error(), err_str, sizeof(err_str));
+	    PJ_LOG(3,(ssock->pool->obj_name, "SSL_set_tlsext_host_name() "
+		"failed: %s", err_str));
+	}
+    }
+#endif
+
     /* Start SSL handshake */
     ssock->ssl_state = SSL_STATE_HANDSHAKING;
     SSL_set_connect_state(ssock->ossl_ssl);
+
     status = do_handshake(ssock);
     if (status != PJ_EPENDING)
 	goto on_return;
@@ -1185,13 +1395,18 @@
 					    pj_pool_t *pool,
 					    const pj_ssl_cert_t *cert)
 {
+    pj_ssl_cert_t *cert_;
+
     PJ_ASSERT_RETURN(ssock && pool && cert, PJ_EINVAL);
 
-    ssock->cert = PJ_POOL_ZALLOC_T(pool, pj_ssl_cert_t);
-    pj_strdup_with_null(pool, &ssock->cert->CA_file, &cert->CA_file);
-    pj_strdup_with_null(pool, &ssock->cert->cert_file, &cert->cert_file);
-    pj_strdup_with_null(pool, &ssock->cert->privkey_file, &cert->privkey_file);
-    pj_strdup_with_null(pool, &ssock->cert->privkey_pass, &cert->privkey_pass);
+    cert_ = PJ_POOL_ZALLOC_T(pool, pj_ssl_cert_t);
+    pj_memcpy(cert_, cert, sizeof(cert));
+    pj_strdup_with_null(pool, &cert_->CA_file, &cert->CA_file);
+    pj_strdup_with_null(pool, &cert_->cert_file, &cert->cert_file);
+    pj_strdup_with_null(pool, &cert_->privkey_file, &cert->privkey_file);
+    pj_strdup_with_null(pool, &cert_->privkey_pass, &cert->privkey_pass);
+
+    ssock->cert = cert_;
 
     return PJ_SUCCESS;
 }
@@ -1262,8 +1477,10 @@
 	for (i = 0; i < param->ciphers_num; ++i)
 	    ssock->param.ciphers[i] = param->ciphers[i];
     }
-    pj_strdup_with_null(pool, &ssock->param.servername, 
-			&param->servername);
+
+    /* Server name must be null-terminated */
+    pj_strdup_with_null(pool, &ssock->param.server_name, 
+			&param->server_name);
 
     /* Finally */
     *p_ssock = ssock;
@@ -1345,6 +1562,10 @@
 
 	/* Remote address */
 	pj_sockaddr_cp(&info->remote_addr, &ssock->rem_addr);
+
+	/* Certificates info */
+	info->local_cert_info = ssock->local_cert_info;
+	info->remote_cert_info = ssock->remote_cert_info;
     }
 
     return PJ_SUCCESS;
@@ -1489,11 +1710,11 @@
 	    /* Re-negotiation is on progress, flush re-negotiation data */
 	    status = flush_write_bio(ssock, &ssock->handshake_op_key, 0, 0);
 	    if (status == PJ_SUCCESS || status == PJ_EPENDING)
+		/* Just return PJ_EBUSY when re-negotiation is on progress */
 		status = PJ_EBUSY;
 	} else {
 	    /* Some problem occured */
-	    ssl_report_error(THIS_FILE, 4, PJ_SUCCESS, "SSL_write()");
-	    status = PJ_ECANCELLED;
+	    status = PJ_STATUS_FROM_OSSL(err);
 	}
     } else {
 	/* nwritten < *size, shouldn't happen, unless write BIO cannot hold 
@@ -1776,5 +1997,25 @@
 }
 
 
-//#endif  /* PJ_HAS_SSL_SOCK */
+PJ_DEF(pj_status_t) pj_ssl_sock_renegotiate(pj_ssl_sock_t *ssock)
+{
+    int ret;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(ssock->ssl_state == SSL_STATE_ESTABLISHED, PJ_EINVALIDOP);
+
+    if (SSL_renegotiate_pending(ssock->ossl_ssl))
+	return PJ_EPENDING;
+
+    ret = SSL_renegotiate(ssock->ossl_ssl);
+    if (ret <= 0) {
+	status = PJ_STATUS_FROM_OSSL(SSL_get_error(ssock->ossl_ssl, ret));
+    } else {
+	status = do_handshake(ssock);
+    }
+
+    return status;
+}
+
+#endif  /* PJ_HAS_SSL_SOCK */
 
diff --git a/pjlib/src/pj/ssl_sock_symbian.cpp b/pjlib/src/pj/ssl_sock_symbian.cpp
index 06165f3..0619fd9 100644
--- a/pjlib/src/pj/ssl_sock_symbian.cpp
+++ b/pjlib/src/pj/ssl_sock_symbian.cpp
@@ -499,7 +499,7 @@
 	for (i = 0; i < param->ciphers_num; ++i)
 	    ssock->ciphers[i] = param->ciphers[i];
     }
-    pj_strdup_with_null(pool, &ssock->servername, &param->servername);
+    pj_strdup_with_null(pool, &ssock->servername, &param->server_name);
 
     /* Finally */
     *p_ssock = ssock;
@@ -1085,3 +1085,9 @@
     return status;
 }
 
+
+PJ_DEF(pj_status_t) pj_ssl_sock_renegotiate(pj_ssl_sock_t *ssock)
+{
+    PJ_UNUSED_ARG(ssock);
+    return PJ_ENOTSUP;
+}
diff --git a/pjlib/src/pjlib-test/ssl_sock.c b/pjlib/src/pjlib-test/ssl_sock.c
new file mode 100644
index 0000000..8fd42ef
--- /dev/null
+++ b/pjlib/src/pjlib-test/ssl_sock.c
@@ -0,0 +1,820 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 "test.h"
+#include <pjlib.h>
+
+#define ECHO_SERVER_NAME	    "localhost"
+#define ECHO_SERVER_ADDR	    "localhost"
+#define ECHO_SERVER_PORT	    12345
+
+#define CERT_DIR		    "..\\build\\"
+#define CERT_CA_FILE		    NULL
+#define CERT_FILE		    CERT_DIR "cacert.pem"
+#define CERT_PRIVKEY_FILE	    CERT_DIR "privkey.pem"
+#define CERT_PRIVKEY_PASS	    ""
+
+
+#if INCLUDE_SSLSOCK_TEST
+
+
+struct send_key {
+    pj_ioqueue_op_key_t	op_key;
+};
+
+
+static int get_cipher_list(void) {
+    pj_status_t status;
+    pj_ssl_cipher ciphers[100];
+    unsigned cipher_num;
+    unsigned i;
+
+    cipher_num = PJ_ARRAY_SIZE(ciphers);
+    status = pj_ssl_cipher_get_availables(ciphers, &cipher_num);
+    if (status != PJ_SUCCESS) {
+	app_perror("...FAILED to get available ciphers", status);
+	return -10;
+    }
+
+    PJ_LOG(3, ("", "...Found %u ciphers:", cipher_num));
+    for (i = 0; i < cipher_num; ++i) {
+	const char* st;
+	st = pj_ssl_cipher_name(ciphers[i]);
+	if (st == NULL)
+	    st = "[Unknown]";
+
+	PJ_LOG(3, ("", "...%3u: 0x%08x=%s", i+1, ciphers[i], st));
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+struct test_state
+{
+    pj_pool_t	   *pool;	    /* pool				    */
+    pj_bool_t	    echo;	    /* echo received data		    */
+    pj_status_t	    err;	    /* error flag			    */
+    unsigned	    sent;	    /* bytes sent			    */
+    unsigned	    recv;	    /* bytes received			    */
+    pj_uint8_t	    read_buf[256];  /* read buffer			    */
+    pj_bool_t	    done;	    /* test done flag			    */
+    char	   *send_str;	    /* data to send once connected	    */
+    unsigned	    send_str_len;   /* send data length			    */
+    pj_bool_t	    check_echo;	    /* flag to compare sent & echoed data   */
+    const char	   *check_echo_ptr; /* pointer/cursor for comparing data    */
+    struct send_key send_key;	    /* send op key			    */
+};
+
+static void dump_cert_info(const char *prefix, const pj_ssl_cert_info *ci)
+{
+    const char *wdays[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
+    pj_parsed_time pt1;
+    pj_parsed_time pt2;
+
+    pj_time_decode(&ci->validity_start, &pt1);
+    pj_time_decode(&ci->validity_end, &pt2);
+
+    PJ_LOG(3, ("", "%sSubject    : %.*s", prefix, ci->subject.slen, ci->subject.ptr));
+    PJ_LOG(3, ("", "%sIssuer     : %.*s", prefix, ci->issuer.slen, ci->issuer.ptr));
+    PJ_LOG(3, ("", "%sVersion    : v%d", prefix, ci->version));
+    PJ_LOG(3, ("", "%sValid from : %s %4d-%02d-%02d %02d:%02d:%02d.%03d %s", 
+		   prefix, wdays[pt1.wday], pt1.year, pt1.mon+1, pt1.day,
+		   pt1.hour, pt1.min, pt1.sec, pt1.msec,
+		   (ci->validity_use_gmt? "GMT":"")));
+    PJ_LOG(3, ("", "%sValid to   : %s %4d-%02d-%02d %02d:%02d:%02d.%03d %s", 
+		   prefix, wdays[pt2.wday], pt2.year, pt2.mon+1, pt2.day,
+		   pt2.hour, pt2.min, pt2.sec, pt2.msec,
+		   (ci->validity_use_gmt? "GMT":"")));
+}
+
+
+static pj_bool_t ssl_on_connect_complete(pj_ssl_sock_t *ssock,
+					 pj_status_t status)
+{
+    struct test_state *st = (struct test_state*) 
+		    	    pj_ssl_sock_get_user_data(ssock);
+    void *read_buf[1];
+    pj_ssl_sock_info info;
+    const char *tmp_st;
+    char buf[64];
+
+    if (status != PJ_SUCCESS) {
+	app_perror("...ERROR ssl_on_connect_complete()", status);
+	goto on_return;
+    }
+
+    status = pj_ssl_sock_get_info(ssock, &info);
+    if (status != PJ_SUCCESS) {
+	app_perror("...ERROR pj_ssl_sock_get_info()", status);
+	goto on_return;
+    }
+
+    pj_sockaddr_print((pj_sockaddr_t*)&info.remote_addr, buf, sizeof(buf), 1);
+    PJ_LOG(3, ("", "...Connected to %s!", buf));
+
+    /* Print cipher name */
+    tmp_st = pj_ssl_cipher_name(info.cipher);
+    if (tmp_st == NULL)
+	tmp_st = "[Unknown]";
+    PJ_LOG(3, ("", ".....Cipher: %s", tmp_st));
+
+    /* Print certificates info */
+    if (info.local_cert_info.subject.slen) {
+	PJ_LOG(3, ("", ".....Local certificate info:"));
+	dump_cert_info(".......", &info.local_cert_info);
+    }
+    if (info.remote_cert_info.subject.slen) {
+	PJ_LOG(3, ("", ".....Remote certificate info:"));
+	dump_cert_info(".......", &info.remote_cert_info);
+    }
+
+    /* Start sending data */
+    while (st->sent < st->send_str_len) {
+	pj_ssize_t size;
+
+	size = st->send_str_len - st->sent;
+	status = pj_ssl_sock_send(ssock, (pj_ioqueue_op_key_t*)&st->send_key, 
+				  st->send_str + st->sent, &size, 0);
+	if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+	    app_perror("...ERROR pj_ssl_sock_send()", status);
+	    goto on_return;
+	}
+
+	if (status == PJ_SUCCESS)
+	    st->sent += size;
+	else
+	    break;
+    }
+
+    /* Start reading data */
+    read_buf[0] = st->read_buf;
+    status = pj_ssl_sock_start_read2(ssock, st->pool, sizeof(st->read_buf), (void**)read_buf, 0);
+    if (status != PJ_SUCCESS  && status != PJ_EPENDING) {
+	app_perror("...ERROR pj_ssl_sock_start_read2()", status);
+	goto on_return;
+    }
+
+on_return:
+    st->err = status;
+    return PJ_TRUE;
+}
+
+
+static pj_bool_t ssl_on_accept_complete(pj_ssl_sock_t *ssock,
+					pj_ssl_sock_t *newsock,
+					const pj_sockaddr_t *src_addr,
+					int src_addr_len)
+{
+    struct test_state *st = (struct test_state*) 
+			    pj_ssl_sock_get_user_data(ssock);
+    void *read_buf[1];
+    pj_ssl_sock_info info;
+    pj_status_t status;
+    const char *tmp_st;
+    char buf[64];
+
+    PJ_UNUSED_ARG(src_addr_len);
+
+    status = pj_ssl_sock_get_info(newsock, &info);
+    if (status != PJ_SUCCESS) {
+	app_perror("...ERROR pj_ssl_sock_get_info()", status);
+	goto on_return;
+    }
+
+    pj_sockaddr_print(src_addr, buf, sizeof(buf), 1);
+    PJ_LOG(3, ("", "...Accepted connection from %s", buf));
+
+    /* Print cipher name */
+    tmp_st = pj_ssl_cipher_name(info.cipher);
+    if (tmp_st == NULL)
+	tmp_st = "[Unknown]";
+    PJ_LOG(3, ("", ".....Cipher: %s", tmp_st));
+
+    /* Print certificates info */
+    if (info.local_cert_info.subject.slen) {
+	PJ_LOG(3, ("", ".....Local certificate info:"));
+	dump_cert_info(".......", &info.local_cert_info);
+    }
+    if (info.remote_cert_info.subject.slen) {
+	PJ_LOG(3, ("", ".....Remote certificate info:"));
+	dump_cert_info(".......", &info.remote_cert_info);
+    }
+
+    pj_ssl_sock_set_user_data(newsock, st);
+
+    /* Start sending data */
+    while (st->sent < st->send_str_len) {
+	pj_ssize_t size;
+
+	size = st->send_str_len - st->sent;
+	status = pj_ssl_sock_send(ssock, (pj_ioqueue_op_key_t*)&st->send_key, 
+				  st->send_str + st->sent, &size, 0);
+	if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+	    app_perror("...ERROR pj_ssl_sock_send()", status);
+	    goto on_return;
+	}
+
+	if (status == PJ_SUCCESS)
+	    st->sent += size;
+	else
+	    break;
+    }
+
+    /* Start reading data */
+    read_buf[0] = st->read_buf;
+    status = pj_ssl_sock_start_read2(newsock, st->pool, sizeof(st->read_buf), (void**)read_buf, 0);
+    if (status != PJ_SUCCESS  && status != PJ_EPENDING) {
+	app_perror("...ERROR pj_ssl_sock_start_read2()", status);
+	goto on_return;
+    }
+
+on_return:
+    st->err = status;
+    return PJ_TRUE;
+}
+
+static pj_bool_t ssl_on_data_read(pj_ssl_sock_t *ssock,
+				  void *data,
+				  pj_size_t size,
+				  pj_status_t status,
+				  pj_size_t *remainder)
+{
+    struct test_state *st = (struct test_state*) 
+			     pj_ssl_sock_get_user_data(ssock);
+
+    PJ_UNUSED_ARG(remainder);
+    PJ_UNUSED_ARG(data);
+
+    if (size > 0) {
+	pj_size_t consumed;
+
+	/* Set random remainder */
+	*remainder = pj_rand() % 100;
+
+	/* Apply zero remainder if:
+	 * - remainder is less than size, or
+	 * - connection closed/error
+	 * - echo/check_eco set
+	 */
+	if (*remainder > size || status != PJ_SUCCESS || st->echo || st->check_echo)
+	    *remainder = 0;
+
+	consumed = size - *remainder;
+	st->recv += consumed;
+
+	//printf("%.*s", consumed, (char*)data);
+
+	pj_memmove(data, (char*)data + consumed, *remainder);
+
+	/* Echo data when specified to */
+	if (st->echo) {
+	    pj_ssize_t size_ = consumed;
+	    status = pj_ssl_sock_send(ssock, (pj_ioqueue_op_key_t*)&st->send_key, data, &size_, 0);
+	    if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+		app_perror("...ERROR pj_ssl_sock_send()", status);
+		goto on_return;
+	    }
+
+	    if (status == PJ_SUCCESS)
+		st->sent += size_;
+	}
+
+	/* Verify echoed data when specified to */
+	if (st->check_echo) {
+	    if (!st->check_echo_ptr)
+		st->check_echo_ptr = st->send_str;
+
+	    if (pj_memcmp(st->check_echo_ptr, data, consumed)) {
+		status = PJ_EINVAL;
+		app_perror("...ERROR echoed data not exact", status);
+		goto on_return;
+	    }
+	    st->check_echo_ptr += consumed;
+
+	    if (st->send_str_len == st->recv)
+		st->done = PJ_TRUE;
+	}
+    }
+
+    if (status != PJ_SUCCESS) {
+	if (status == PJ_EEOF) {
+	    status = PJ_SUCCESS;
+	    st->done = PJ_TRUE;
+	} else {
+	    app_perror("...ERROR ssl_on_data_read()", status);
+	}
+    }
+
+on_return:
+    st->err = status;
+    return PJ_TRUE;
+}
+
+static pj_bool_t ssl_on_data_sent(pj_ssl_sock_t *ssock,
+				  pj_ioqueue_op_key_t *op_key,
+				  pj_ssize_t sent)
+{
+    struct test_state *st = (struct test_state*)
+			     pj_ssl_sock_get_user_data(ssock);
+    PJ_UNUSED_ARG(op_key);
+
+    if (sent < 1) {
+	st->err++;
+    } else {
+	st->sent += sent;
+
+	/* Send more if any */
+	while (st->sent < st->send_str_len) {
+	    pj_ssize_t size;
+	    pj_status_t status;
+
+	    size = st->send_str_len - st->sent;
+	    status = pj_ssl_sock_send(ssock, (pj_ioqueue_op_key_t*)&st->send_key, 
+				      st->send_str + st->sent, &size, 0);
+	    if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+		app_perror("...ERROR pj_ssl_sock_send()", status);
+		st->err++;
+		break;
+	    }
+
+	    if (status == PJ_SUCCESS)
+		st->sent += size;
+	    else
+		break;
+	}
+    }
+
+    return PJ_TRUE;
+}
+
+#define HTTP_REQ		"GET / HTTP/1.0\r\n\r\n";
+#define HTTP_SERVER_ADDR	"trac.pjsip.org"
+#define HTTP_SERVER_PORT	443
+
+static int https_client_test(void)
+{
+    pj_pool_t *pool = NULL;
+    pj_ioqueue_t *ioqueue = NULL;
+    pj_ssl_sock_t *ssock = NULL;
+    pj_ssl_sock_param param;
+    pj_status_t status;
+    struct test_state state = {0};
+    pj_sockaddr local_addr, rem_addr;
+    pj_str_t tmp_st;
+
+    pool = pj_pool_create(mem, "http_get", 256, 256, NULL);
+
+    status = pj_ioqueue_create(pool, 4, &ioqueue);
+    if (status != PJ_SUCCESS) {
+	goto on_return;
+    }
+
+    state.pool = pool;
+    state.send_str = HTTP_REQ;
+    state.send_str_len = pj_ansi_strlen(state.send_str);
+
+    pj_ssl_sock_param_default(&param);
+    param.cb.on_connect_complete = &ssl_on_connect_complete;
+    param.cb.on_data_read = &ssl_on_data_read;
+    param.cb.on_data_sent = &ssl_on_data_sent;
+    param.ioqueue = ioqueue;
+    param.user_data = &state;
+    param.server_name = pj_str((char*)HTTP_SERVER_ADDR);
+
+    status = pj_ssl_sock_create(pool, &param, &ssock);
+    if (status != PJ_SUCCESS) {
+	goto on_return;
+    }
+
+    pj_sockaddr_init(PJ_AF_INET, &local_addr, pj_strset2(&tmp_st, "0.0.0.0"), 0);
+    pj_sockaddr_init(PJ_AF_INET, &rem_addr, pj_strset2(&tmp_st, HTTP_SERVER_ADDR), HTTP_SERVER_PORT);
+    status = pj_ssl_sock_start_connect(ssock, pool, &local_addr, &rem_addr, sizeof(rem_addr));
+    if (status == PJ_SUCCESS) {
+	ssl_on_connect_complete(ssock, PJ_SUCCESS);
+    } else if (status == PJ_EPENDING) {
+	status = PJ_SUCCESS;
+    } else {
+	goto on_return;
+    }
+
+    /* Wait until everything has been sent/received */
+    while (state.err == PJ_SUCCESS && !state.done) {
+#ifdef PJ_SYMBIAN
+	pj_symbianos_poll(-1, 1000);
+#else
+	pj_time_val delay = {0, 100};
+	pj_ioqueue_poll(ioqueue, &delay);
+#endif
+    }
+
+    if (state.err) {
+	status = state.err;
+	goto on_return;
+    }
+
+    PJ_LOG(3, ("", "...Done!"));
+    PJ_LOG(3, ("", ".....Sent/recv: %d/%d bytes", state.sent, state.recv));
+
+on_return:
+    if (ssock)
+	pj_ssl_sock_close(ssock);
+    if (ioqueue)
+	pj_ioqueue_destroy(ioqueue);
+    if (pool)
+	pj_pool_release(pool);
+
+    return status;
+}
+
+
+static int echo_test(pj_ssl_sock_proto proto, pj_ssl_cipher srv_cipher,
+		     pj_ssl_cipher cli_cipher)
+{
+    pj_pool_t *pool = NULL;
+    pj_ioqueue_t *ioqueue = NULL;
+    pj_ssl_sock_t *ssock_serv = NULL;
+    pj_ssl_sock_t *ssock_cli = NULL;
+    pj_ssl_sock_param param;
+    struct test_state state_serv = { 0 };
+    struct test_state state_cli = { 0 };
+    pj_sockaddr local_addr, rem_addr;
+    pj_str_t tmp_st;
+    pj_ssl_cipher ciphers[1];
+    pj_ssl_cert_t *cert = NULL;
+    pj_status_t status;
+
+    pool = pj_pool_create(mem, "echo", 256, 256, NULL);
+
+    status = pj_ioqueue_create(pool, 4, &ioqueue);
+    if (status != PJ_SUCCESS) {
+	goto on_return;
+    }
+
+    /* Set cert */
+    {
+	pj_str_t tmp1, tmp2, tmp3, tmp4;
+
+	status = pj_ssl_cert_load_from_files(pool, 
+					     pj_strset2(&tmp1, (char*)CERT_CA_FILE), 
+					     pj_strset2(&tmp2, (char*)CERT_FILE), 
+					     pj_strset2(&tmp3, (char*)CERT_PRIVKEY_FILE), 
+					     pj_strset2(&tmp4, (char*)CERT_PRIVKEY_PASS), 
+					     &cert);
+	if (status != PJ_SUCCESS) {
+	    goto on_return;
+	}
+    }
+
+    pj_ssl_sock_param_default(&param);
+    param.proto = proto;
+    param.cb.on_accept_complete = &ssl_on_accept_complete;
+    param.cb.on_connect_complete = &ssl_on_connect_complete;
+    param.cb.on_data_read = &ssl_on_data_read;
+    param.cb.on_data_sent = &ssl_on_data_sent;
+    param.ioqueue = ioqueue;
+    param.ciphers_num = 1;
+    param.ciphers = ciphers;
+
+    /* SERVER */
+    param.user_data = &state_serv;
+    ciphers[0] = srv_cipher;
+
+    state_serv.pool = pool;
+    state_serv.echo = PJ_TRUE;
+
+    status = pj_ssl_sock_create(pool, &param, &ssock_serv);
+    if (status != PJ_SUCCESS) {
+	goto on_return;
+    }
+
+    status = pj_ssl_sock_set_certificate(ssock_serv, pool, cert);
+    if (status != PJ_SUCCESS) {
+	goto on_return;
+    }
+
+    pj_sockaddr_init(PJ_AF_INET, &local_addr, pj_strset2(&tmp_st, ECHO_SERVER_ADDR), ECHO_SERVER_PORT);
+    status = pj_ssl_sock_start_accept(ssock_serv, pool, &local_addr, sizeof(local_addr));
+    if (status != PJ_SUCCESS) {
+	goto on_return;
+    }
+
+    /* CLIENT */
+    param.user_data = &state_cli;
+    ciphers[0] = cli_cipher;
+
+    state_cli.pool = pool;
+    state_cli.check_echo = PJ_TRUE;
+
+    {
+	pj_time_val now;
+
+	pj_gettimeofday(&now);
+	pj_srand((pj_rand()%now.sec) * (pj_rand()%now.msec));
+	state_cli.send_str_len = (pj_rand() % 5 + 1) * 1024 + pj_rand() % 1024;
+    }
+    state_cli.send_str = pj_pool_alloc(pool, state_cli.send_str_len);
+    {
+	unsigned i;
+	for (i = 0; i < state_cli.send_str_len; ++i)
+	    state_cli.send_str[i] = (char)(pj_rand() % 256);
+    }
+
+    status = pj_ssl_sock_create(pool, &param, &ssock_cli);
+    if (status != PJ_SUCCESS) {
+	goto on_return;
+    }
+
+    pj_sockaddr_init(PJ_AF_INET, &local_addr, pj_strset2(&tmp_st, "0.0.0.0"), 0);
+    pj_sockaddr_init(PJ_AF_INET, &rem_addr, pj_strset2(&tmp_st, ECHO_SERVER_ADDR), ECHO_SERVER_PORT);
+    status = pj_ssl_sock_start_connect(ssock_cli, pool, &local_addr, &rem_addr, sizeof(rem_addr));
+    if (status == PJ_SUCCESS) {
+	ssl_on_connect_complete(ssock_cli, PJ_SUCCESS);
+    } else if (status == PJ_EPENDING) {
+	status = PJ_SUCCESS;
+    } else {
+	goto on_return;
+    }
+
+    /* Wait until everything has been sent/received or error */
+    while (!state_serv.err && !state_cli.err && !state_serv.done && !state_cli.done)
+    {
+#ifdef PJ_SYMBIAN
+	pj_symbianos_poll(-1, 1000);
+#else
+	pj_time_val delay = {0, 100};
+	pj_ioqueue_poll(ioqueue, &delay);
+#endif
+    }
+
+    if (state_serv.err || state_cli.err) {
+	if (state_serv.err != PJ_SUCCESS)
+	    status = state_serv.err;
+	else
+	    status = state_cli.err;
+
+	goto on_return;
+    }
+
+    PJ_LOG(3, ("", "...Done!"));
+    PJ_LOG(3, ("", ".....Server sent/recv: %d/%d bytes", state_serv.sent, state_serv.recv));
+    PJ_LOG(3, ("", ".....Client sent/recv: %d/%d bytes", state_cli.sent, state_cli.recv));
+
+on_return:
+    if (ssock_serv) 
+	pj_ssl_sock_close(ssock_serv);
+    if (ssock_cli)
+	pj_ssl_sock_close(ssock_cli);
+    if (ioqueue)
+	pj_ioqueue_destroy(ioqueue);
+    if (pool)
+	pj_pool_release(pool);
+
+    return status;
+}
+
+
+static pj_bool_t asock_on_data_read(pj_activesock_t *asock,
+				    void *data,
+				    pj_size_t size,
+				    pj_status_t status,
+				    pj_size_t *remainder)
+{
+    struct test_state *st = (struct test_state*)
+			     pj_activesock_get_user_data(asock);
+
+    PJ_UNUSED_ARG(data);
+    PJ_UNUSED_ARG(size);
+    PJ_UNUSED_ARG(remainder);
+
+    if (status != PJ_SUCCESS) {
+	if (status == PJ_EEOF) {
+	    status = PJ_SUCCESS;
+	    st->done = PJ_TRUE;
+	} else {
+	    app_perror("...ERROR asock_on_data_read()", status);
+	}
+    }
+
+    st->err = status;
+
+    return PJ_TRUE;
+}
+
+
+static pj_bool_t asock_on_connect_complete(pj_activesock_t *asock,
+					   pj_status_t status)
+{
+    struct test_state *st = (struct test_state*)
+			     pj_activesock_get_user_data(asock);
+
+    if (status == PJ_SUCCESS) {
+	status = pj_activesock_start_read(asock, st->pool, 1, 0);
+    }
+
+    st->err = status;
+
+    return PJ_TRUE;
+}
+
+
+/* Set ms_timeout to 0 to disable timer */
+static int client_non_ssl(unsigned ms_timeout)
+{
+    pj_pool_t *pool = NULL;
+    pj_ioqueue_t *ioqueue = NULL;
+    pj_timer_heap_t *timer = NULL;
+    pj_ssl_sock_t *ssock_serv = NULL;
+    pj_activesock_t *asock_cli = NULL;
+    pj_activesock_cb asock_cb = { 0 };
+    pj_sock_t sock = PJ_INVALID_SOCKET;
+    pj_ssl_sock_param param;
+    struct test_state state_serv = { 0 };
+    struct test_state state_cli = { 0 };
+    pj_sockaddr listen_addr;
+    pj_str_t tmp_st;
+    pj_ssl_cert_t *cert = NULL;
+    pj_status_t status;
+
+    pool = pj_pool_create(mem, "echo", 256, 256, NULL);
+
+    status = pj_ioqueue_create(pool, 4, &ioqueue);
+    if (status != PJ_SUCCESS) {
+	goto on_return;
+    }
+
+    status = pj_timer_heap_create(pool, 4, &timer);
+    if (status != PJ_SUCCESS) {
+	goto on_return;
+    }
+
+    /* Set cert */
+    {
+	pj_str_t tmp1, tmp2, tmp3, tmp4;
+	status = pj_ssl_cert_load_from_files(pool, 
+					     pj_strset2(&tmp1, (char*)CERT_CA_FILE), 
+					     pj_strset2(&tmp2, (char*)CERT_FILE), 
+					     pj_strset2(&tmp3, (char*)CERT_PRIVKEY_FILE), 
+					     pj_strset2(&tmp4, (char*)CERT_PRIVKEY_PASS), 
+					     &cert);
+	if (status != PJ_SUCCESS) {
+	    goto on_return;
+	}
+    }
+
+    pj_ssl_sock_param_default(&param);
+    param.cb.on_accept_complete = &ssl_on_accept_complete;
+    param.cb.on_data_read = &ssl_on_data_read;
+    param.cb.on_data_sent = &ssl_on_data_sent;
+    param.ioqueue = ioqueue;
+    param.timeout.sec = 0;
+    param.timeout.msec = ms_timeout;
+    param.timer_heap = timer;
+    pj_time_val_normalize(&param.timeout);
+
+    /* SERVER */
+    param.user_data = &state_serv;
+    state_serv.pool = pool;
+
+    status = pj_ssl_sock_create(pool, &param, &ssock_serv);
+    if (status != PJ_SUCCESS) {
+	goto on_return;
+    }
+
+    status = pj_ssl_sock_set_certificate(ssock_serv, pool, cert);
+    if (status != PJ_SUCCESS) {
+	goto on_return;
+    }
+
+    pj_sockaddr_init(PJ_AF_INET, &listen_addr, pj_strset2(&tmp_st, ECHO_SERVER_ADDR), ECHO_SERVER_PORT);
+    status = pj_ssl_sock_start_accept(ssock_serv, pool, &listen_addr, sizeof(listen_addr));
+    if (status != PJ_SUCCESS) {
+	goto on_return;
+    }
+
+    /* CLIENT */
+    state_cli.pool = pool;
+    status = pj_sock_socket(pj_AF_INET(), pj_SOCK_STREAM(), 0, &sock);
+    if (status != PJ_SUCCESS) {
+	goto on_return;
+    }
+
+    asock_cb.on_connect_complete = &asock_on_connect_complete;
+    asock_cb.on_data_read = &asock_on_data_read;
+    status = pj_activesock_create(pool, sock, pj_SOCK_STREAM(), NULL, 
+				  ioqueue, &asock_cb, &state_cli, &asock_cli);
+    if (status != PJ_SUCCESS) {
+	goto on_return;
+    }
+
+    status = pj_activesock_start_connect(asock_cli, pool, (pj_sockaddr_t*)&listen_addr, 
+					 pj_sockaddr_get_len(&listen_addr));
+    if (status == PJ_SUCCESS) {
+	asock_on_connect_complete(asock_cli, PJ_SUCCESS);
+    } else if (status == PJ_EPENDING) {
+	status = PJ_SUCCESS;
+    } else {
+	goto on_return;
+    }
+
+    /* Wait until everything has been sent/received or error */
+    while (!state_serv.err && !state_cli.err && !state_serv.done && !state_cli.done)
+    {
+#ifdef PJ_SYMBIAN
+	pj_symbianos_poll(-1, 1000);
+#else
+	pj_time_val delay = {0, 100};
+	pj_ioqueue_poll(ioqueue, &delay);
+	pj_timer_heap_poll(timer, &delay);
+#endif
+    }
+
+    if (state_serv.err || state_cli.err) {
+	if (state_serv.err != PJ_SUCCESS)
+	    status = state_serv.err;
+	else
+	    status = state_cli.err;
+
+	goto on_return;
+    }
+
+    PJ_LOG(3, ("", "...Done!"));
+
+on_return:
+    if (ssock_serv) 
+	pj_ssl_sock_close(ssock_serv);
+    if (asock_cli)
+	pj_activesock_close(asock_cli);
+    if (timer)
+	pj_timer_heap_destroy(timer);
+    if (ioqueue)
+	pj_ioqueue_destroy(ioqueue);
+    if (pool)
+	pj_pool_release(pool);
+
+    return status;
+}
+
+
+int ssl_sock_test(void)
+{
+    int ret;
+
+    PJ_LOG(3,("", "..get cipher list test"));
+    ret = get_cipher_list();
+    if (ret != 0)
+	return ret;
+
+#if 0
+    PJ_LOG(3,("", "..https client test"));
+    ret = https_client_test();
+    if (ret != 0)
+	return ret;
+#endif
+
+    PJ_LOG(3,("", "..echo test w/ TLSv1 and TLS_RSA_WITH_DES_CBC_SHA cipher"));
+    ret = echo_test(PJ_SSL_SOCK_PROTO_TLS1, TLS_RSA_WITH_DES_CBC_SHA, TLS_RSA_WITH_DES_CBC_SHA);
+    if (ret != 0)
+	return ret;
+
+    PJ_LOG(3,("", "..echo test w/ SSLv23 and TLS_RSA_WITH_AES_256_CBC_SHA cipher"));
+    ret = echo_test(PJ_SSL_SOCK_PROTO_SSL23, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA);
+    if (ret != 0)
+	return ret;
+
+    PJ_LOG(3,("", "..echo test w/ incompatible ciphers"));
+    ret = echo_test(PJ_SSL_SOCK_PROTO_DEFAULT, TLS_RSA_WITH_DES_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA);
+    if (ret == 0)
+	return -10;
+
+    PJ_LOG(3,("", "..client non-SSL timeout in 5 secs"));
+    ret = client_non_ssl(5000);
+    if (ret != 0)
+	return ret;
+
+    return 0;
+}
+
+#else	/* INCLUDE_SSLSOCK_TEST */
+/* To prevent warning about "translation unit is empty"
+ * when this test is disabled. 
+ */
+int dummy_ssl_sock_test;
+#endif	/* INCLUDE_SSLSOCK_TEST */
+
diff --git a/pjlib/src/pjlib-test/test.c b/pjlib/src/pjlib-test/test.c
index b3e2951..9d7c1ba 100644
--- a/pjlib/src/pjlib-test/test.c
+++ b/pjlib/src/pjlib-test/test.c
@@ -167,6 +167,10 @@
     DO_TEST( file_test() );
 #endif
 
+#if INCLUDE_SSLSOCK_TEST
+    DO_TEST( ssl_sock_test() );
+#endif
+
 #if INCLUDE_ECHO_SERVER
     //echo_server();
     //echo_srv_sync();
diff --git a/pjlib/src/pjlib-test/test.h b/pjlib/src/pjlib-test/test.h
index 0f128b0..2d65d15 100644
--- a/pjlib/src/pjlib-test/test.h
+++ b/pjlib/src/pjlib-test/test.h
@@ -54,6 +54,7 @@
 #define INCLUDE_UDP_IOQUEUE_TEST    GROUP_NETWORK
 #define INCLUDE_TCP_IOQUEUE_TEST    GROUP_NETWORK
 #define INCLUDE_ACTIVESOCK_TEST	    GROUP_NETWORK
+#define INCLUDE_SSLSOCK_TEST	    (PJ_HAS_SSL_SOCK && GROUP_NETWORK)
 #define INCLUDE_IOQUEUE_PERF_TEST   (PJ_HAS_THREADS && GROUP_NETWORK)
 #define INCLUDE_IOQUEUE_UNREG_TEST  (PJ_HAS_THREADS && GROUP_NETWORK)
 #define INCLUDE_FILE_TEST           GROUP_FILE
@@ -96,6 +97,7 @@
 extern int ioqueue_perf_test(void);
 extern int activesock_test(void);
 extern int file_test(void);
+extern int ssl_sock_test(void);
 
 extern int echo_server(void);
 extern int echo_client(int sock_type, const char *server, int port);
@@ -104,6 +106,7 @@
 extern int udp_echo_srv_ioqueue(void);
 extern int echo_srv_common_loop(pj_atomic_t *bytes_counter);
 
+
 extern pj_pool_factory *mem;
 
 extern int          test_main(void);