[arch-commits] Commit in (15 files)
Baptiste Jonglez
zorun at archlinux.org
Sun Dec 31 18:25:21 UTC 2017
Date: Sunday, December 31, 2017 @ 18:25:20
Author: zorun
Revision: 277599
pjproject-savoirfairelinux: Import from AUR
Added:
pjproject-savoirfairelinux/
pjproject-savoirfairelinux/trunk/
pjproject-savoirfairelinux/trunk/PKGBUILD
pjproject-savoirfairelinux/trunk/add_dtls_transport.patch
pjproject-savoirfairelinux/trunk/endianness.patch
pjproject-savoirfairelinux/trunk/fix_base64.patch
pjproject-savoirfairelinux/trunk/fix_ioqueue_ipv6_sendto.patch
pjproject-savoirfairelinux/trunk/fix_turn_fallback.patch
pjproject-savoirfairelinux/trunk/gnutls.patch
pjproject-savoirfairelinux/trunk/ice_config.patch
pjproject-savoirfairelinux/trunk/ipv6.patch
pjproject-savoirfairelinux/trunk/multiple_listeners.patch
pjproject-savoirfairelinux/trunk/notestsapps.patch
pjproject-savoirfairelinux/trunk/pj_ice_sess.patch
pjproject-savoirfairelinux/trunk/rfc6062.patch
-------------------------------+
PKGBUILD | 79
add_dtls_transport.patch | 63
endianness.patch | 19
fix_base64.patch | 20
fix_ioqueue_ipv6_sendto.patch | 19
fix_turn_fallback.patch | 53
gnutls.patch | 3295 ++++++++++++++++++++++++++++++++++++++++
ice_config.patch | 46
ipv6.patch | 11
multiple_listeners.patch | 66
notestsapps.patch | 97 +
pj_ice_sess.patch | 29
rfc6062.patch | 919 +++++++++++
13 files changed, 4716 insertions(+)
Added: pjproject-savoirfairelinux/trunk/PKGBUILD
===================================================================
--- pjproject-savoirfairelinux/trunk/PKGBUILD (rev 0)
+++ pjproject-savoirfairelinux/trunk/PKGBUILD 2017-12-31 18:25:20 UTC (rev 277599)
@@ -0,0 +1,79 @@
+# Maintainer: Baptiste Jonglez <baptiste--aur at jonglez dot org>
+
+_pkgname=pjproject
+pkgname=${_pkgname}-savoirfairelinux
+pkgver=2.6
+pkgrel=6
+pkgdesc="Open source SIP stack and media stack, built with patches from SavoirFaire Linux (mostly GnuTLS support)"
+arch=('i686' 'x86_64')
+url="http://www.pjsip.org/"
+license=('GPL')
+depends=('util-linux' 'gnutls' 'portaudio' 'speex' 'alsa-lib' 'libsamplerate' 'ffmpeg' 'libsrtp' 'opus')
+provides=("${_pkgname}=${pkgver}")
+conflicts=("${_pkgname}")
+source=("http://www.pjsip.org/release/${pkgver}/${_pkgname}-${pkgver}.tar.bz2"
+ endianness.patch
+ gnutls.patch
+ notestsapps.patch
+ fix_base64.patch
+ ipv6.patch
+ ice_config.patch
+ multiple_listeners.patch
+ pj_ice_sess.patch
+ fix_turn_fallback.patch
+ fix_ioqueue_ipv6_sendto.patch
+ add_dtls_transport.patch
+ rfc6062.patch)
+sha256sums=('2f5a1da1c174d845871c758bd80fbb580fca7799d3cfaa0d3c4e082b5161c7b4'
+ '294d9fba18a8c903979de2c9b531a3ca32a28f90658bf0613a32ebfa7d5e3a69'
+ '8dedb243a390af62741d4e3c0ea6b7f970cd6d67934f03d7ef8b69c745a6523b'
+ 'f88f3e73d2f62ae60d93e84e08f98da7d5febe93f1f390286cafa106178c4f27'
+ '25c808206aa5028f29f66ea5364b93be94d0d5feac7d97165cd4ba3493aae6ec'
+ 'fdf64f3260aecbbc5433ae784e12dde462f1a15361f14c5cd0f7be0a3d13f802'
+ '12fa1dd99fda01e49df862e02f3ae64f1d31290400236bd50fa930fd20f3b799'
+ '7aabc43556456085ca1bb9e17ef11ae5f4701dd392028335a65c06fd2bc1f6f8'
+ '62ae6bb0bca51196e82984d6b0851a87421307ab0631d92e32966d2a656e7349'
+ '5289671ee86618d665556e5591ef5409354611a6cc0512bfe4100bc6b94fea67'
+ '15524714c2bb48612b6504864b279fc56af1d756a197a8afc8141556889cfb70'
+ '5b901de12d49d79aba8777f3c7b59d703603be5ed2e1be6696f5d01be584e739'
+ '21b1cf20d343e283498ace80ede97ce00062697a26c71ece09cea38a0278ad24')
+
+prepare() {
+ cd "${srcdir}/${_pkgname}-${pkgver}"
+ for patch in endianness.patch gnutls.patch notestsapps.patch fix_base64.patch \
+ ipv6.patch ice_config.patch multiple_listeners.patch \
+ pj_ice_sess.patch fix_turn_fallback.patch \
+ fix_ioqueue_ipv6_sendto.patch add_dtls_transport.patch \
+ rfc6062.patch
+ do
+ msg2 "Applying patch $patch"
+ patch -p1 < ../"$patch"
+ done
+}
+
+build() {
+ cd "${srcdir}/${_pkgname}-${pkgver}"
+ export CXXFLAGS="${CXXFLAGS} -DNDEBUG -fPIC -DPJSIP_MAX_PKT_LEN=8000"
+ export CFLAGS="${CXXFLAGS}"
+ ./configure \
+ --prefix=/usr \
+ --enable-shared \
+ --enable-ssl=gnutls \
+ --with-external-speex \
+ --with-external-srtp \
+ --with-external-pa \
+ --with-external-gsm \
+ --enable-ext-sound \
+ --disable-oss \
+ --disable-opencore-amr \
+ --disable-v4l2 \
+ --disable-video \
+ --disable-sound
+ make dep
+ make
+}
+
+package() {
+ cd "${srcdir}/${_pkgname}-${pkgver}"
+ make DESTDIR="${pkgdir}" install
+}
Added: pjproject-savoirfairelinux/trunk/add_dtls_transport.patch
===================================================================
--- pjproject-savoirfairelinux/trunk/add_dtls_transport.patch (rev 0)
+++ pjproject-savoirfairelinux/trunk/add_dtls_transport.patch 2017-12-31 18:25:20 UTC (rev 277599)
@@ -0,0 +1,63 @@
+--- a/pjsip/src/pjsip/sip_transport.c
++++ b/pjsip/src/pjsip/sip_transport.c
+@@ -183,6 +183,13 @@
+ PJSIP_TRANSPORT_RELIABLE | PJSIP_TRANSPORT_SECURE
+ },
+ {
++ PJSIP_TRANSPORT_DTLS,
++ 5061,
++ {"DTLS", 4},
++ "DTLS transport",
++ PJSIP_TRANSPORT_SECURE
++ },
++ {
+ PJSIP_TRANSPORT_SCTP,
+ 5060,
+ {"SCTP", 4},
+@@ -224,6 +231,13 @@
+ "TLS IPv6 transport",
+ PJSIP_TRANSPORT_RELIABLE | PJSIP_TRANSPORT_SECURE
+ },
++ {
++ PJSIP_TRANSPORT_DTLS6,
++ 5061,
++ {"DTLS", 4},
++ "DTLS IPv6 transport",
++ PJSIP_TRANSPORT_SECURE
++ },
+ };
+
+ static void tp_state_callback(pjsip_transport *tp,
+@@ -249,7 +263,7 @@
+ */
+ PJ_DEF(pj_status_t) pjsip_transport_register_type( unsigned tp_flag,
+ const char *tp_name,
+- int def_port,
++ int def_port,
+ int *p_tp_type)
+ {
+ unsigned i;
+--- a/pjsip/include/pjsip/sip_types.h
++++ b/pjsip/include/pjsip/sip_types.h
+@@ -73,6 +73,9 @@
+ /** TLS. */
+ PJSIP_TRANSPORT_TLS,
+
++ /** DTLS. */
++ PJSIP_TRANSPORT_DTLS,
++
+ /** SCTP. */
+ PJSIP_TRANSPORT_SCTP,
+
+@@ -95,7 +98,10 @@
+ PJSIP_TRANSPORT_TCP6 = PJSIP_TRANSPORT_TCP + PJSIP_TRANSPORT_IPV6,
+
+ /** TLS over IPv6 */
+- PJSIP_TRANSPORT_TLS6 = PJSIP_TRANSPORT_TLS + PJSIP_TRANSPORT_IPV6
++ PJSIP_TRANSPORT_TLS6 = PJSIP_TRANSPORT_TLS + PJSIP_TRANSPORT_IPV6,
++
++ /** DTLS over IPv6 */
++ PJSIP_TRANSPORT_DTLS6 = PJSIP_TRANSPORT_DTLS + PJSIP_TRANSPORT_IPV6
+
+ } pjsip_transport_type_e;
+
Added: pjproject-savoirfairelinux/trunk/endianness.patch
===================================================================
--- pjproject-savoirfairelinux/trunk/endianness.patch (rev 0)
+++ pjproject-savoirfairelinux/trunk/endianness.patch 2017-12-31 18:25:20 UTC (rev 277599)
@@ -0,0 +1,19 @@
+diff --git a/pjlib/include/pj/config.h b/pjlib/include/pj/config.h
+index 10f86fd..4ace1bc 100644
+--- a/pjlib/include/pj/config.h
++++ b/pjlib/include/pj/config.h
+@@ -245,7 +245,13 @@
+ # define PJ_M_NAME "armv4"
+ # define PJ_HAS_PENTIUM 0
+ # if !PJ_IS_LITTLE_ENDIAN && !PJ_IS_BIG_ENDIAN
+-# error Endianness must be declared for this processor
++# if defined(__GNUC__)
++# include <endian.h>
++# define PJ_IS_LITTLE_ENDIAN __BYTE_ORDER__ == __LITTLE_ENDIAN__
++# define PJ_IS_BIG_ENDIAN __BYTE_ORDER__ == __BIG_ENDIAN__
++# else
++# error Endianness must be declared for this processor
++# endif
+ # endif
+
+ #elif defined (PJ_M_POWERPC) || defined(__powerpc) || defined(__powerpc__) || \
Added: pjproject-savoirfairelinux/trunk/fix_base64.patch
===================================================================
--- pjproject-savoirfairelinux/trunk/fix_base64.patch (rev 0)
+++ pjproject-savoirfairelinux/trunk/fix_base64.patch 2017-12-31 18:25:20 UTC (rev 277599)
@@ -0,0 +1,20 @@
+--- a/pjlib-util/src/pjlib-util/base64.c 2017-05-03 10:29:07.200417026 -0400
++++ b/pjlib-util/src/pjlib-util/base64.c 2017-05-03 10:28:30.344335390 -0400
+@@ -131,7 +131,7 @@
+
+ PJ_ASSERT_RETURN(input && out && out_len, PJ_EINVAL);
+
+- while (buf[len-1] == '=' && len)
++ while (len && buf[len-1] == '=')
+ --len;
+
+ PJ_ASSERT_RETURN(*out_len >= PJ_BASE64_TO_BASE256_LEN(len),
+@@ -161,7 +161,7 @@
+ out[j++] = (pj_uint8_t)(((c[2] & 0x03)<<6) | (c[3] & 0x3F));
+ }
+
+- pj_assert(j < *out_len);
++ pj_assert(j <= *out_len);
+ *out_len = j;
+
+ return PJ_SUCCESS;
Added: pjproject-savoirfairelinux/trunk/fix_ioqueue_ipv6_sendto.patch
===================================================================
--- pjproject-savoirfairelinux/trunk/fix_ioqueue_ipv6_sendto.patch (rev 0)
+++ pjproject-savoirfairelinux/trunk/fix_ioqueue_ipv6_sendto.patch 2017-12-31 18:25:20 UTC (rev 277599)
@@ -0,0 +1,19 @@
+--- a/pjlib/src/pj/ioqueue_common_abs.c 2015-11-05 23:18:46.000000000 -0500
++++ b/pjlib/src/pj/ioqueue_common_abs.c 2016-10-21 13:49:09.183662433 -0400
+@@ -1048,5 +1048,6 @@
+ * Check that address storage can hold the address parameter.
+ */
+- PJ_ASSERT_RETURN(addrlen <= (int)sizeof(pj_sockaddr_in), PJ_EBUG);
++ PJ_ASSERT_RETURN((((pj_sockaddr*)addr)->addr.sa_family == pj_AF_INET() && addrlen <= (int)sizeof(pj_sockaddr_in)) ||
++ (((pj_sockaddr*)addr)->addr.sa_family == pj_AF_INET6() && addrlen <= (int)sizeof(pj_sockaddr_in6)), PJ_EBUG);
+
+ /*
+--- a/pjlib/src/pj/ioqueue_common_abs.h 2013-02-21 06:18:36.000000000 -0500
++++ b/pjlib/src/pj/ioqueue_common_abs.h 2016-10-21 14:04:04.148928591 -0400
+@@ -64,5 +64,5 @@
+ pj_ssize_t written;
+ unsigned flags;
+- pj_sockaddr_in rmt_addr;
++ pj_sockaddr rmt_addr;
+ int rmt_addrlen;
+ };
Added: pjproject-savoirfairelinux/trunk/fix_turn_fallback.patch
===================================================================
--- pjproject-savoirfairelinux/trunk/fix_turn_fallback.patch (rev 0)
+++ pjproject-savoirfairelinux/trunk/fix_turn_fallback.patch 2017-12-31 18:25:20 UTC (rev 277599)
@@ -0,0 +1,53 @@
+--- a/pjnath/src/pjnath/turn_session.c 2016-09-19 18:21:09.073614574 -0400
++++ b/pjnath/src/pjnath/turn_session.c 2016-09-19 18:21:30.648631620 -0400
+@@ -653,3 +653,3 @@
+
+- cnt = PJ_TURN_MAX_DNS_SRV_CNT;
++ cnt = 1;
+ ai = (pj_addrinfo*)
+--- a/pjnath/src/pjnath/ice_strans.c 2016-09-19 18:36:04.180104330 -0400
++++ b/pjnath/src/pjnath/ice_strans.c 2016-09-19 18:37:10.614136809 -0400
+@@ -1304,2 +1304,5 @@
+
++ if (!comp->turn[n].sock)
++ continue;
++
+ /* Gather remote addresses for this component */
+@@ -1995,4 +1998,37 @@
+ sess_init_update(comp->ice_st);
+
++ } else if ((old_state == PJ_TURN_STATE_RESOLVING || old_state == PJ_TURN_STATE_ALLOCATING) &&
++ new_state >= PJ_TURN_STATE_DEALLOCATING)
++ {
++ pj_ice_sess_cand *cand = NULL;
++ unsigned i;
++
++ /* DNS resolution has failed! */
++ ++comp->turn[tp_idx].err_cnt;
++
++ /* Unregister ourself from the TURN relay */
++ pj_turn_sock_set_user_data(turn_sock, NULL);
++ comp->turn[tp_idx].sock = NULL;
++
++ /* Wait until initialization completes */
++ pj_grp_lock_acquire(comp->ice_st->grp_lock);
++
++ /* Find relayed candidate in the component */
++ for (i=0; i<comp->cand_cnt; ++i) {
++ if (comp->cand_list[i].type == PJ_ICE_CAND_TYPE_RELAYED &&
++ comp->cand_list[i].transport_id == data->transport_id)
++ {
++ cand = &comp->cand_list[i];
++ break;
++ }
++ }
++ pj_assert(cand != NULL);
++
++ pj_grp_lock_release(comp->ice_st->grp_lock);
++
++ cand->status = old_state == PJ_TURN_STATE_RESOLVING ? PJ_ERESOLVE : PJ_EINVALIDOP;
++
++ sess_init_update(comp->ice_st);
++
+ } else if (new_state >= PJ_TURN_STATE_DEALLOCATING) {
+ pj_turn_session_info info;
Added: pjproject-savoirfairelinux/trunk/gnutls.patch
===================================================================
--- pjproject-savoirfairelinux/trunk/gnutls.patch (rev 0)
+++ pjproject-savoirfairelinux/trunk/gnutls.patch 2017-12-31 18:25:20 UTC (rev 277599)
@@ -0,0 +1,3295 @@
+Copyright (c) 2014-2017 Savoir-faire Linux Inc.
+
+ssl_sock: add gnutls backend
+
+This backend is mutually exclusive with the OpenSSL one, but completely
+compatible, and conformant to the PJSIP API. Also avoids any license issues
+when linking statically.
+
+The configure script is updated to select either OpenSSL or GnuTLS
+with --enable-ssl[='...'] and a new symbol (PJ_HAS_TLS_SOCK) is introduced
+to identify which backend is in use.
+
+Written by
+Vittorio Giovara <vittorio.giovara at savoirfairelinux.com>
+Philippe Proulx <philippe.proulx at savoirfairelinux.com> and
+Adrien Béraud <adrien.beraud at savoirfairelinux.com>
+on behalf of Savoir-faire Linux.
+
+---
+diff -ru a/aconfigure b/aconfigure
+--- a/aconfigure 2017-01-25 06:23:08.000000000 -0500
++++ b/aconfigure 2017-06-08 13:51:11.146810527 -0400
+@@ -644,6 +644,8 @@
+ libcrypto_present
+ libssl_present
+ openssl_h_present
++libgnutls_present
++gnutls_h_present
+ ac_ssl_has_aes_gcm
+ ac_no_ssl
+ ac_openh264_ldflags
+@@ -1482,8 +1484,8 @@
+ package and samples location using IPPROOT and
+ IPPSAMPLES env var or with --with-ipp and
+ --with-ipp-samples options
+- --disable-ssl Exclude SSL support the build (default: autodetect)
+-
++ --enable-ssl=backend Select 'gnutls' or 'openssl' (default) to provide
++ SSL support (autodetect)
+ --disable-opencore-amr Exclude OpenCORE AMR support from the build
+ (default: autodetect)
+
+@@ -7787,17 +7789,149 @@
+
+ # Check whether --enable-ssl was given.
+ if test "${enable_ssl+set}" = set; then :
+- enableval=$enable_ssl;
+- if test "$enable_ssl" = "no"; then
+- ac_no_ssl=1
+- { $as_echo "$as_me:${as_lineno-$LINENO}: result: Checking if SSL support is disabled... yes" >&5
++ enableval=$enable_ssl; if test "x$enableval" = "xgnutls"; then
++ ssl_backend="gnutls"
++ else
++ ssl_backend="openssl"
++ fi
++fi
++
++
++if test "x$enable_ssl" = "xno"; then
++ ac_no_ssl=1
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: Checking if SSL support is disabled... yes" >&5
+ $as_echo "Checking if SSL support is disabled... yes" >&6; }
+- fi
++else
++ if test "x$with_ssl" != "xno" -a "x$with_ssl" != "x"; then
++ CFLAGS="$CFLAGS -I$with_ssl/include"
++ LDFLAGS="$LDFLAGS -L$with_ssl/lib"
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: Using SSL prefix... $with_ssl" >&5
++$as_echo "Using SSL prefix... $with_ssl" >&6; }
++ fi
++ if test "x$ssl_backend" = "xgnutls"; then
++ for ac_prog in $host-pkg-config pkg-config "python pkgconfig.py"
++do
++ # Extract the first word of "$ac_prog", so it can be a program name with args.
++set dummy $ac_prog; ac_word=$2
++{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
++$as_echo_n "checking for $ac_word... " >&6; }
++if ${ac_cv_prog_PKG_CONFIG+:} false; then :
++ $as_echo_n "(cached) " >&6
++else
++ if test -n "$PKG_CONFIG"; then
++ ac_cv_prog_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test.
++else
++as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
++for as_dir in $PATH
++do
++ IFS=$as_save_IFS
++ test -z "$as_dir" && as_dir=.
++ for ac_exec_ext in '' $ac_executable_extensions; do
++ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
++ ac_cv_prog_PKG_CONFIG="$ac_prog"
++ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
++ break 2
++ fi
++done
++ done
++IFS=$as_save_IFS
++
++fi
++fi
++PKG_CONFIG=$ac_cv_prog_PKG_CONFIG
++if test -n "$PKG_CONFIG"; then
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5
++$as_echo "$PKG_CONFIG" >&6; }
++else
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
++$as_echo "no" >&6; }
++fi
++
++
++ test -n "$PKG_CONFIG" && break
++done
++test -n "$PKG_CONFIG" || PKG_CONFIG="none"
++
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: checking for GnuTLS installations.." >&5
++$as_echo "checking for GnuTLS installations.." >&6; }
++
++
++ ac_fn_c_check_header_mongrel "$LINENO" "gnutls/gnutls.h" "ac_cv_header_gnutls_gnutls_h" "$ac_includes_default"
++if test "x$ac_cv_header_gnutls_gnutls_h" = xyes; then :
++ gnutls_h_present=1
++fi
++
+
++
++ if test "$PKG_CONFIG" != "none"; then
++ if $PKG_CONFIG --exists gnutls; then
++ LIBS="$LIBS `$PKG_CONFIG --libs gnutls`"
++ libgnutls_present=1
++ else
++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for gnutls_certificate_set_x509_system_trust in -lgnutls" >&5
++$as_echo_n "checking for gnutls_certificate_set_x509_system_trust in -lgnutls... " >&6; }
++if ${ac_cv_lib_gnutls_gnutls_certificate_set_x509_system_trust+:} false; then :
++ $as_echo_n "(cached) " >&6
++else
++ ac_check_lib_save_LIBS=$LIBS
++LIBS="-lgnutls $LIBS"
++cat confdefs.h - <<_ACEOF >conftest.$ac_ext
++/* end confdefs.h. */
++
++/* Override any GCC internal prototype to avoid an error.
++ Use char because int might match the return type of a GCC
++ builtin and then its argument prototype would still apply. */
++#ifdef __cplusplus
++extern "C"
++#endif
++char gnutls_certificate_set_x509_system_trust ();
++int
++main ()
++{
++return gnutls_certificate_set_x509_system_trust ();
++ ;
++ return 0;
++}
++_ACEOF
++if ac_fn_c_try_link "$LINENO"; then :
++ ac_cv_lib_gnutls_gnutls_certificate_set_x509_system_trust=yes
+ else
++ ac_cv_lib_gnutls_gnutls_certificate_set_x509_system_trust=no
++fi
++rm -f core conftest.err conftest.$ac_objext \
++ conftest$ac_exeext conftest.$ac_ext
++LIBS=$ac_check_lib_save_LIBS
++fi
++{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_gnutls_gnutls_certificate_set_x509_system_trust" >&5
++$as_echo "$ac_cv_lib_gnutls_gnutls_certificate_set_x509_system_trust" >&6; }
++if test "x$ac_cv_lib_gnutls_gnutls_certificate_set_x509_system_trust" = xyes; then :
++ libgnutls_present=1 &&
++ LIBS="$LIBS -lgnutls"
++fi
+
++ fi
++ else
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: *** Warning: neither pkg-config nor python is available, disabling gnutls. ***" >&5
++$as_echo "*** Warning: neither pkg-config nor python is available, disabling gnutls. ***" >&6; }
++ fi
++
++ if test "x$gnutls_h_present" = "x1" -a "x$libgnutls_present" = "x1"; then
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: GnuTLS library found, SSL support enabled" >&5
++$as_echo "GnuTLS library found, SSL support enabled" >&6; }
++ # PJSIP_HAS_TLS_TRANSPORT setting follows PJ_HAS_SSL_SOCK
++ #AC_DEFINE(PJSIP_HAS_TLS_TRANSPORT, 1)
++ $as_echo "#define PJ_HAS_SSL_SOCK 1" >>confdefs.h
++
++ $as_echo "#define PJ_HAS_TLS_SOCK 1" >>confdefs.h
++
++ else
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: ** No GnuTLS libraries found, disabling SSL support **" >&5
++$as_echo "** No GnuTLS libraries found, disabling SSL support **" >&6; }
++ fi
++ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: checking for OpenSSL installations.." >&5
+ $as_echo "checking for OpenSSL installations.." >&6; }
++
+ if test "x$with_ssl" != "xno" -a "x$with_ssl" != "x"; then
+ CFLAGS="$CFLAGS -I$with_ssl/include"
+ LDFLAGS="$LDFLAGS -L$with_ssl/lib"
+@@ -7971,11 +8105,10 @@
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: ** OpenSSL libraries not found, disabling SSL support **" >&5
+ $as_echo "** OpenSSL libraries not found, disabling SSL support **" >&6; }
+ fi
+-
++ fi
+ fi
+
+
+-
+ # Check whether --with-opencore-amrnb was given.
+ if test "${with_opencore_amrnb+set}" = set; then :
+ withval=$with_opencore_amrnb; as_fn_error $? "This option is obsolete and replaced by --with-opencore-amr=DIR" "$LINENO" 5
+diff -ru a/aconfigure.ac b/aconfigure.ac
+--- a/aconfigure.ac 2017-01-25 06:23:08.000000000 -0500
++++ b/aconfigure.ac 2017-06-08 13:28:17.138135490 -0400
+@@ -1533,18 +1533,59 @@
+ dnl # Include SSL support
+ AC_SUBST(ac_no_ssl)
+ AC_SUBST(ac_ssl_has_aes_gcm,0)
+-AC_ARG_ENABLE(ssl,
+- AS_HELP_STRING([--disable-ssl],
+- [Exclude SSL support the build (default: autodetect)])
+- ,
+- [
+- if test "$enable_ssl" = "no"; then
+- [ac_no_ssl=1]
+- AC_MSG_RESULT([Checking if SSL support is disabled... yes])
+- fi
+- ],
+- [
++AC_ARG_ENABLE([ssl],
++ AS_HELP_STRING([--enable-ssl[=backend]],
++ [Select 'gnutls' or 'openssl' (default) to provide SSL support (autodetect)]),
++ [ if test "x$enableval" = "xgnutls"; then
++ [ssl_backend="gnutls"]
++ else
++ [ssl_backend="openssl"]
++ fi ])
++
++if test "x$enable_ssl" = "xno"; then
++ [ac_no_ssl=1]
++ AC_MSG_RESULT([Checking if SSL support is disabled... yes])
++else
++ if test "x$with_ssl" != "xno" -a "x$with_ssl" != "x"; then
++ CFLAGS="$CFLAGS -I$with_ssl/include"
++ LDFLAGS="$LDFLAGS -L$with_ssl/lib"
++ AC_MSG_RESULT([Using SSL prefix... $with_ssl])
++ fi
++ if test "x$ssl_backend" = "xgnutls"; then
++ AC_CHECK_PROGS(PKG_CONFIG,
++ $host-pkg-config pkg-config "python pkgconfig.py",
++ none)
++ AC_MSG_RESULT([checking for GnuTLS installations..])
++ AC_SUBST(gnutls_h_present)
++ AC_SUBST(libgnutls_present)
++ AC_CHECK_HEADER(gnutls/gnutls.h, [gnutls_h_present=1])
++
++ if test "$PKG_CONFIG" != "none"; then
++ if $PKG_CONFIG --exists gnutls; then
++ LIBS="$LIBS `$PKG_CONFIG --libs gnutls`"
++ libgnutls_present=1
++ else
++ AC_CHECK_LIB(gnutls,
++ gnutls_certificate_set_x509_system_trust,
++ [libgnutls_present=1 &&
++ LIBS="$LIBS -lgnutls"])
++ fi
++ else
++ AC_MSG_RESULT([*** Warning: neither pkg-config nor python is available, disabling gnutls. ***])
++ fi
++
++ if test "x$gnutls_h_present" = "x1" -a "x$libgnutls_present" = "x1"; then
++ AC_MSG_RESULT([GnuTLS library found, SSL support enabled])
++ # PJSIP_HAS_TLS_TRANSPORT setting follows PJ_HAS_SSL_SOCK
++ #AC_DEFINE(PJSIP_HAS_TLS_TRANSPORT, 1)
++ AC_DEFINE(PJ_HAS_SSL_SOCK, 1)
++ AC_DEFINE(PJ_HAS_TLS_SOCK, 1)
++ else
++ AC_MSG_RESULT([** No GnuTLS libraries found, disabling SSL support **])
++ fi
++ else
+ AC_MSG_RESULT([checking for OpenSSL installations..])
++
+ if test "x$with_ssl" != "xno" -a "x$with_ssl" != "x"; then
+ CFLAGS="$CFLAGS -I$with_ssl/include"
+ LDFLAGS="$LDFLAGS -L$with_ssl/lib"
+@@ -1578,7 +1619,8 @@
+ else
+ AC_MSG_RESULT([** OpenSSL libraries not found, disabling SSL support **])
+ fi
+- ])
++ fi
++fi
+
+ dnl # Obsolete option --with-opencore-amrnb
+ AC_ARG_WITH(opencore-amrnb,
+diff -ru a/pjlib/build/Makefile b/pjlib/build/Makefile
+--- a/pjlib/build/Makefile 2016-10-05 05:52:39.000000000 -0400
++++ b/pjlib/build/Makefile 2017-06-08 13:30:20.702138591 -0400
+@@ -35,7 +35,7 @@
+ guid.o hash.o ip_helper_generic.o list.o lock.o log.o os_time_common.o \
+ os_info.o pool.o pool_buf.o pool_caching.o pool_dbg.o rand.o \
+ rbtree.o sock_common.o sock_qos_common.o \
+- ssl_sock_common.o ssl_sock_ossl.o ssl_sock_dump.o \
++ ssl_sock_common.o ssl_sock_ossl.o ssl_sock_gtls.o ssl_sock_dump.o \
+ string.o timer.o types.o
+ export PJLIB_CFLAGS += $(_CFLAGS)
+ export PJLIB_CXXFLAGS += $(_CXXFLAGS)
+diff -ru a/pjlib/include/pj/compat/os_auto.h.in b/pjlib/include/pj/compat/os_auto.h.in
+--- a/pjlib/include/pj/compat/os_auto.h.in 2017-01-24 00:36:50.000000000 -0500
++++ b/pjlib/include/pj/compat/os_auto.h.in 2017-06-08 13:31:04.976064779 -0400
+@@ -219,6 +219,9 @@
+ #ifndef PJ_HAS_SSL_SOCK
+ #undef PJ_HAS_SSL_SOCK
+ #endif
++#ifndef PJ_HAS_TLS_SOCK
++#undef PJ_HAS_TLS_SOCK
++#endif
+
+
+ #endif /* __PJ_COMPAT_OS_AUTO_H__ */
+diff -ru a/pjlib/include/pj/config.h b/pjlib/include/pj/config.h
+--- a/pjlib/include/pj/config.h 2017-01-25 21:29:59.000000000 -0500
++++ b/pjlib/include/pj/config.h 2017-06-08 13:34:27.642149351 -0400
+@@ -888,7 +888,7 @@
+
+ /**
+ * Enable secure socket. For most platforms, this is implemented using
+- * OpenSSL, so this will require OpenSSL to be installed. For Symbian
++ * OpenSSL or GnuTLS, so this will require OpenSSL or GnuTLS to be installed. For Symbian
+ * platform, this is implemented natively using CSecureSocket.
+ *
+ * Default: 0 (for now)
+@@ -896,6 +896,10 @@
+ #ifndef PJ_HAS_SSL_SOCK
+ # define PJ_HAS_SSL_SOCK 0
+ #endif
++// When set to 1 secure sockets will use the GnuTLS backend than OpenSSL
++#ifndef PJ_HAS_TLS_SOCK
++# define PJ_HAS_TLS_SOCK 0
++#endif
+
+
+ /**
+diff -ru a/pjlib/include/pj/ssl_sock.h b/pjlib/include/pj/ssl_sock.h
+--- a/pjlib/include/pj/ssl_sock.h 2016-10-27 03:58:01.000000000 -0400
++++ b/pjlib/include/pj/ssl_sock.h 2017-06-08 13:36:16.448510381 -0400
+@@ -184,6 +184,10 @@
+ pj_str_t raw; /**< Raw certificate in PEM format, only
+ available for remote certificate. */
+
++ struct {
++ unsigned cnt; /**< # of entry */
++ pj_str_t* cert_raw;
++ } raw_chain;
+ } pj_ssl_cert_info;
+
+
+diff -ru a/pjlib/src/pj/ssl_sock_common.c b/pjlib/src/pj/ssl_sock_common.c
+--- a/pjlib/src/pj/ssl_sock_common.c 2016-10-27 03:58:01.000000000 -0400
++++ b/pjlib/src/pj/ssl_sock_common.c 2017-06-08 13:37:17.171037628 -0400
+@@ -35,7 +35,12 @@
+ param->async_cnt = 1;
+ param->concurrency = -1;
+ param->whole_data = PJ_TRUE;
++#if defined(PJ_HAS_TLS_SOCK) && PJ_HAS_TLS_SOCK == 1
++ // GnuTLS is allowed to send bigger chunks
++ param->send_buffer_size = 65536;
++#else
+ param->send_buffer_size = 8192;
++#endif
+ #if !defined(PJ_SYMBIAN) || PJ_SYMBIAN==0
+ param->read_buffer_size = 1500;
+ #endif
+diff --git a/pjlib/src/pj/ssl_sock_gtls.c b/pjlib/src/pj/ssl_sock_gtls.c
+new file mode 100644
+index 0000000..37bcaba
+--- /dev/null
++++ b/pjlib/src/pj/ssl_sock_gtls.c
+@@ -0,0 +1,2877 @@
++/* $Id$ */
++/*
++ * Copyright (C) 2014-2016 Savoir-faire Linux. (https://www.savoirfairelinux.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/activesock.h>
++#include <pj/compat/socket.h>
++#include <pj/assert.h>
++#include <pj/errno.h>
++#include <pj/list.h>
++#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>
++#include <pj/file_io.h>
++
++#if GNUTLS_VERSION_NUMBER < 0x030306 && !defined(_MSC_VER)
++#include <dirent.h>
++#endif
++
++#include <errno.h>
++
++/* Only build when PJ_HAS_SSL_SOCK and PJ_HAS_TLS_SOCK are enabled */
++#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 && \
++ defined(PJ_HAS_TLS_SOCK) && PJ_HAS_TLS_SOCK != 0
++
++#define THIS_FILE "ssl_sock_gtls.c"
++
++/* Workaround for ticket #985 */
++#define DELAYED_CLOSE_TIMEOUT 200
++
++/* Maximum ciphers */
++#define MAX_CIPHERS 100
++
++/* Standard trust locations */
++#define TRUST_STORE_FILE1 "/etc/ssl/certs/ca-certificates.crt"
++#define TRUST_STORE_FILE2 "/etc/ssl/certs/ca-bundle.crt"
++
++/* Debugging output level for GnuTLS only */
++#define GNUTLS_LOG_LEVEL 0
++
++/* GnuTLS includes */
++#include <gnutls/gnutls.h>
++#include <gnutls/x509.h>
++#include <gnutls/abstract.h>
++
++#ifdef _MSC_VER
++# pragma comment( lib, "libgnutls")
++#endif
++
++
++/* TLS state enumeration. */
++enum tls_connection_state {
++ TLS_STATE_NULL,
++ TLS_STATE_HANDSHAKING,
++ TLS_STATE_ESTABLISHED
++};
++
++/* Internal timer types. */
++enum timer_id {
++ TIMER_NONE,
++ TIMER_HANDSHAKE_TIMEOUT,
++ TIMER_CLOSE
++};
++
++/* Structure of SSL socket read buffer. */
++typedef struct read_data_t {
++ void *data;
++ pj_size_t len;
++} read_data_t;
++
++/*
++ * Get the offset of pointer to read-buffer of SSL socket from read-buffer
++ * of active socket. Note that both SSL socket and active socket employ
++ * different but correlated read-buffers (as much as async_cnt for each),
++ * and to make it easier/faster to find corresponding SSL socket's read-buffer
++ * from known active socket's read-buffer, the pointer of corresponding
++ * SSL socket's read-buffer is stored right after the end of active socket's
++ * read-buffer.
++ */
++#define OFFSET_OF_READ_DATA_PTR(ssock, asock_rbuf) \
++ (read_data_t**) \
++ ((pj_int8_t *)(asock_rbuf) + \
++ ssock->param.read_buffer_size)
++
++/* Structure of SSL socket write data. */
++typedef struct write_data_t {
++ PJ_DECL_LIST_MEMBER(struct write_data_t);
++ pj_ioqueue_op_key_t key;
++ pj_size_t record_len;
++ pj_ioqueue_op_key_t *app_key;
++ pj_size_t plain_data_len;
++ pj_size_t data_len;
++ unsigned flags;
++ union {
++ char content[1];
++ const char *ptr;
++ } data;
++} write_data_t;
++
++
++/* Structure of SSL socket write buffer (circular buffer). */
++typedef struct send_buf_t {
++ char *buf;
++ pj_size_t max_len;
++ char *start;
++ pj_size_t len;
++} send_buf_t;
++
++
++/* Circular buffer object */
++typedef struct circ_buf_t {
++ pj_size_t cap; /* maximum number of elements (must be power of 2) */
++ pj_size_t readp; /* index of oldest element */
++ pj_size_t writep; /* index at which to write new element */
++ pj_size_t size; /* number of elements */
++ pj_uint8_t *buf; /* data buffer */
++ pj_pool_t *pool; /* where new allocations will take place */
++} circ_buf_t;
++
++
++/* Secure socket structure definition. */
++struct pj_ssl_sock_t {
++ pj_pool_t *pool;
++ pj_ssl_sock_t *parent;
++ pj_ssl_sock_param param;
++ pj_ssl_sock_param newsock_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 tls_connection_state connection_state;
++ pj_ioqueue_op_key_t handshake_op_key;
++ pj_timer_entry timer;
++ pj_status_t verify_status;
++
++ int last_err;
++
++ pj_sock_t sock;
++ pj_activesock_t *asock;
++
++ pj_sockaddr local_addr;
++ pj_sockaddr rem_addr;
++ int addr_len;
++
++ pj_bool_t read_started;
++ pj_size_t read_size;
++ pj_uint32_t read_flags;
++ void **asock_rbuf;
++ read_data_t *ssock_rbuf;
++
++ write_data_t write_pending; /* list of pending writes */
++ write_data_t write_pending_empty; /* cache for write_pending */
++ pj_bool_t flushing_write_pend; /* flag of flushing is ongoing */
++ send_buf_t send_buf;
++ write_data_t send_pending; /* list of pending write to network */
++
++ gnutls_session_t session;
++ gnutls_certificate_credentials_t xcred;
++
++ circ_buf_t circ_buf_input;
++ pj_lock_t *circ_buf_input_mutex;
++
++ circ_buf_t circ_buf_output;
++ pj_lock_t *circ_buf_output_mutex;
++
++ int tls_init_count; /* library initialization counter */
++};
++
++
++/* Certificate/credential structure definition. */
++struct pj_ssl_cert_t {
++ pj_str_t CA_file;
++ pj_str_t CA_path;
++ pj_str_t cert_file;
++ pj_str_t privkey_file;
++ pj_str_t privkey_pass;
++};
++
++/* GnuTLS available ciphers */
++static unsigned tls_available_ciphers;
++
++/* Array of id/names for available ciphers */
++static struct tls_ciphers_t {
++ pj_ssl_cipher id;
++ const char *name;
++} tls_ciphers[MAX_CIPHERS];
++
++/* Last error reported somehow */
++static int tls_last_error;
++
++
++/*
++ *******************************************************************
++ * Circular buffer functions.
++ *******************************************************************
++ */
++
++static pj_status_t circ_init(pj_pool_factory *factory,
++ circ_buf_t *cb, pj_size_t cap)
++{
++ cb->cap = cap;
++ cb->readp = 0;
++ cb->writep = 0;
++ cb->size = 0;
++
++ /* Initial pool holding the buffer elements */
++ cb->pool = pj_pool_create(factory, "tls-circ%p", cap, cap, NULL);
++ if (!cb->pool)
++ return PJ_ENOMEM;
++
++ /* Allocate circular buffer */
++ cb->buf = pj_pool_alloc(cb->pool, cap);
++ if (!cb->buf) {
++ pj_pool_release(cb->pool);
++ return PJ_ENOMEM;
++ }
++
++ return PJ_SUCCESS;
++}
++
++static void circ_deinit(circ_buf_t *cb)
++{
++ if (cb->pool) {
++ pj_pool_release(cb->pool);
++ cb->pool = NULL;
++ }
++}
++
++static pj_bool_t circ_empty(const circ_buf_t *cb)
++{
++ return cb->size == 0;
++}
++
++static pj_size_t circ_size(const circ_buf_t *cb)
++{
++ return cb->size;
++}
++
++static pj_size_t circ_avail(const circ_buf_t *cb)
++{
++ return cb->cap - cb->size;
++}
++
++static void circ_read(circ_buf_t *cb, pj_uint8_t *dst, pj_size_t len)
++{
++ pj_size_t size_after = cb->cap - cb->readp;
++ pj_size_t tbc = PJ_MIN(size_after, len);
++ pj_size_t rem = len - tbc;
++
++ pj_memcpy(dst, cb->buf + cb->readp, tbc);
++ pj_memcpy(dst + tbc, cb->buf, rem);
++
++ cb->readp += len;
++ cb->readp &= (cb->cap - 1);
++
++ cb->size -= len;
++}
++
++static pj_status_t circ_write(circ_buf_t *cb,
++ const pj_uint8_t *src, pj_size_t len)
++{
++ /* Overflow condition: resize */
++ if (len > circ_avail(cb)) {
++ /* Minimum required capacity */
++ pj_size_t min_cap = len + cb->size;
++
++ /* Next 32-bit power of two */
++ min_cap--;
++ min_cap |= min_cap >> 1;
++ min_cap |= min_cap >> 2;
++ min_cap |= min_cap >> 4;
++ min_cap |= min_cap >> 8;
++ min_cap |= min_cap >> 16;
++ min_cap++;
++
++ /* Create a new pool to hold a bigger buffer, using the same factory */
++ pj_pool_t *pool = pj_pool_create(cb->pool->factory, "tls-circ%p",
++ min_cap, min_cap, NULL);
++ if (!pool)
++ return PJ_ENOMEM;
++
++ /* Allocate our new buffer */
++ pj_uint8_t *buf = pj_pool_alloc(pool, min_cap);
++ if (!buf) {
++ pj_pool_release(pool);
++ return PJ_ENOMEM;
++ }
++
++ /* Save old size, which we shall restore after the next read */
++ pj_size_t old_size = cb->size;
++
++ /* Copy old data into beginning of new buffer */
++ circ_read(cb, buf, cb->size);
++
++ /* Restore old size now */
++ cb->size = old_size;
++
++ /* Release the previous pool */
++ pj_pool_release(cb->pool);
++
++ /* Update circular buffer members */
++ cb->pool = pool;
++ cb->buf = buf;
++ cb->readp = 0;
++ cb->writep = cb->size;
++ cb->cap = min_cap;
++ }
++
++ pj_size_t size_after = cb->cap - cb->writep;
++ pj_size_t tbc = PJ_MIN(size_after, len);
++ pj_size_t rem = len - tbc;
++
++ pj_memcpy(cb->buf + cb->writep, src, tbc);
++ pj_memcpy(cb->buf, src + tbc, rem);
++
++ cb->writep += len;
++ cb->writep &= (cb->cap - 1);
++
++ cb->size += len;
++
++ return PJ_SUCCESS;
++}
++
++
++/*
++ *******************************************************************
++ * Static/internal functions.
++ *******************************************************************
++ */
++
++/* Convert from GnuTLS error to pj_status_t. */
++static pj_status_t tls_status_from_err(pj_ssl_sock_t *ssock, int err)
++{
++ pj_status_t status;
++
++ switch (err) {
++ case GNUTLS_E_SUCCESS:
++ status = PJ_SUCCESS;
++ break;
++ case GNUTLS_E_MEMORY_ERROR:
++ status = PJ_ENOMEM;
++ break;
++ case GNUTLS_E_LARGE_PACKET:
++ status = PJ_ETOOBIG;
++ break;
++ case GNUTLS_E_NO_CERTIFICATE_FOUND:
++ status = PJ_ENOTFOUND;
++ break;
++ case GNUTLS_E_SESSION_EOF:
++ status = PJ_EEOF;
++ break;
++ case GNUTLS_E_HANDSHAKE_TOO_LARGE:
++ status = PJ_ETOOBIG;
++ break;
++ case GNUTLS_E_EXPIRED:
++ status = PJ_EGONE;
++ break;
++ case GNUTLS_E_TIMEDOUT:
++ status = PJ_ETIMEDOUT;
++ break;
++ case GNUTLS_E_PREMATURE_TERMINATION:
++ status = PJ_ECANCELLED;
++ break;
++ case GNUTLS_E_INTERNAL_ERROR:
++ case GNUTLS_E_UNIMPLEMENTED_FEATURE:
++ status = PJ_EBUG;
++ break;
++ case GNUTLS_E_AGAIN:
++ case GNUTLS_E_INTERRUPTED:
++ case GNUTLS_E_REHANDSHAKE:
++ status = PJ_EPENDING;
++ break;
++ case GNUTLS_E_TOO_MANY_EMPTY_PACKETS:
++ case GNUTLS_E_TOO_MANY_HANDSHAKE_PACKETS:
++ case GNUTLS_E_RECORD_LIMIT_REACHED:
++ status = PJ_ETOOMANY;
++ break;
++ case GNUTLS_E_UNSUPPORTED_VERSION_PACKET:
++ case GNUTLS_E_UNSUPPORTED_SIGNATURE_ALGORITHM:
++ case GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE:
++ case GNUTLS_E_X509_UNSUPPORTED_ATTRIBUTE:
++ case GNUTLS_E_X509_UNSUPPORTED_EXTENSION:
++ case GNUTLS_E_X509_UNSUPPORTED_CRITICAL_EXTENSION:
++ status = PJ_ENOTSUP;
++ break;
++ case GNUTLS_E_INVALID_SESSION:
++ case GNUTLS_E_INVALID_REQUEST:
++ case GNUTLS_E_INVALID_PASSWORD:
++ case GNUTLS_E_ILLEGAL_PARAMETER:
++ case GNUTLS_E_RECEIVED_ILLEGAL_EXTENSION:
++ case GNUTLS_E_UNEXPECTED_PACKET:
++ case GNUTLS_E_UNEXPECTED_PACKET_LENGTH:
++ case GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET:
++ case GNUTLS_E_UNWANTED_ALGORITHM:
++ case GNUTLS_E_USER_ERROR:
++ status = PJ_EINVAL;
++ break;
++ default:
++ status = PJ_EUNKNOWN;
++ break;
++ }
++
++ /* Not thread safe */
++ tls_last_error = err;
++ if (ssock)
++ ssock->last_err = err;
++ return status;
++}
++
++
++/* Get error string from GnuTLS using tls_last_error */
++static pj_str_t tls_strerror(pj_status_t status,
++ char *buf, pj_size_t bufsize)
++{
++ pj_str_t errstr;
++ const char *tmp = gnutls_strerror(tls_last_error);
++
++#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
++ if (tmp) {
++ pj_ansi_strncpy(buf, tmp, bufsize);
++ errstr = pj_str(buf);
++ return errstr;
++ }
++#endif /* PJ_HAS_ERROR_STRING */
++
++ errstr.ptr = buf;
++ errstr.slen = pj_ansi_snprintf(buf, bufsize, "GnuTLS error %d: %s",
++ tls_last_error, tmp);
++ if (errstr.slen < 1 || errstr.slen >= (int) bufsize)
++ errstr.slen = bufsize - 1;
++
++ return errstr;
++}
++
++
++/* Initialize GnuTLS. */
++static pj_status_t tls_init(void)
++{
++ /* Register error subsystem */
++ pj_status_t status = pj_register_strerror(PJ_ERRNO_START_USER +
++ PJ_ERRNO_SPACE_SIZE * 6,
++ PJ_ERRNO_SPACE_SIZE,
++ &tls_strerror);
++ pj_assert(status == PJ_SUCCESS);
++
++ /* Init GnuTLS library */
++ int ret = gnutls_global_init();
++ if (ret < 0)
++ return tls_status_from_err(NULL, ret);
++
++ /* Init available ciphers */
++ if (!tls_available_ciphers) {
++ unsigned int i;
++
++ for (i = 0; ; i++) {
++ unsigned char id[2];
++ const char *suite = gnutls_cipher_suite_info(i, (unsigned char *)id,
++ NULL, NULL, NULL, NULL);
++ tls_ciphers[i].id = 0;
++ /* usually the array size is bigger than the number of available
++ * ciphers anyway, so by checking here we can exit the loop as soon
++ * as either all ciphers have been added or the array is full */
++ if (suite && i < PJ_ARRAY_SIZE(tls_ciphers)) {
++ tls_ciphers[i].id = (pj_ssl_cipher)
++ (pj_uint32_t) ((id[0] << 8) | id[1]);
++ tls_ciphers[i].name = suite;
++ } else
++ break;
++ }
++
++ tls_available_ciphers = i;
++ }
++
++ return PJ_SUCCESS;
++}
++
++
++/* Shutdown GnuTLS */
++static void tls_deinit(void)
++{
++ gnutls_global_deinit();
++}
++
++
++/* Callback invoked every time a certificate has to be validated. */
++static int tls_cert_verify_cb(gnutls_session_t session)
++{
++ pj_ssl_sock_t *ssock;
++ unsigned int status;
++ int ret;
++
++ /* Get SSL socket instance */
++ ssock = (pj_ssl_sock_t *)gnutls_session_get_ptr(session);
++ pj_assert(ssock);
++
++ /* Support only x509 format */
++ ret = gnutls_certificate_type_get(session) != GNUTLS_CRT_X509;
++ if (ret < 0) {
++ ssock->verify_status |= PJ_SSL_CERT_EINVALID_FORMAT;
++ return GNUTLS_E_CERTIFICATE_ERROR;
++ }
++
++ /* Store verification status */
++ ret = gnutls_certificate_verify_peers2(session, &status);
++ if (ret < 0) {
++ ssock->verify_status |= PJ_SSL_CERT_EUNKNOWN;
++ return GNUTLS_E_CERTIFICATE_ERROR;
++ }
++ if (ssock->param.verify_peer) {
++ if (status & GNUTLS_CERT_INVALID) {
++ if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
++ ssock->verify_status |= PJ_SSL_CERT_EISSUER_NOT_FOUND;
++ else if (status & GNUTLS_CERT_EXPIRED ||
++ status & GNUTLS_CERT_NOT_ACTIVATED)
++ ssock->verify_status |= PJ_SSL_CERT_EVALIDITY_PERIOD;
++ else if (status & GNUTLS_CERT_SIGNER_NOT_CA ||
++ status & GNUTLS_CERT_INSECURE_ALGORITHM)
++ ssock->verify_status |= PJ_SSL_CERT_EUNTRUSTED;
++ else if (status & GNUTLS_CERT_UNEXPECTED_OWNER ||
++ status & GNUTLS_CERT_MISMATCH)
++ ssock->verify_status |= PJ_SSL_CERT_EISSUER_MISMATCH;
++ else if (status & GNUTLS_CERT_REVOKED)
++ ssock->verify_status |= PJ_SSL_CERT_EREVOKED;
++ else
++ ssock->verify_status |= PJ_SSL_CERT_EUNKNOWN;
++
++ return GNUTLS_E_CERTIFICATE_ERROR;
++ }
++
++ /* When verification is not requested just return ok here, however
++ * applications can still get the verification status. */
++ gnutls_x509_crt_t cert;
++ unsigned int cert_list_size;
++ const gnutls_datum_t *cert_list;
++ int ret;
++
++ ret = gnutls_x509_crt_init(&cert);
++ if (ret < 0)
++ goto out;
++
++ cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
++ if (cert_list == NULL) {
++ ret = GNUTLS_E_NO_CERTIFICATE_FOUND;
++ goto out;
++ }
++
++ /* TODO: verify whole chain perhaps? */
++ ret = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER);
++ if (ret < 0)
++ ret = gnutls_x509_crt_import(cert, &cert_list[0],
++ GNUTLS_X509_FMT_PEM);
++ if (ret < 0) {
++ ssock->verify_status |= PJ_SSL_CERT_EINVALID_FORMAT;
++ goto out;
++ }
++ ret = gnutls_x509_crt_check_hostname(cert, ssock->param.server_name.ptr);
++ if (ret < 0)
++ goto out;
++
++ gnutls_x509_crt_deinit(cert);
++
++ /* notify GnuTLS to continue handshake normally */
++ return GNUTLS_E_SUCCESS;
++
++out:
++ tls_last_error = ret;
++ ssock->verify_status |= PJ_SSL_CERT_EUNKNOWN;
++ return GNUTLS_E_CERTIFICATE_ERROR;
++ }
++
++ return GNUTLS_E_SUCCESS;
++}
++
++
++/* gnutls_handshake() and gnutls_record_send() will call this function to
++ * send/write (encrypted) data */
++static ssize_t tls_data_push(gnutls_transport_ptr_t ptr,
++ const void *data, size_t len)
++{
++ pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)ptr;
++
++ pj_lock_acquire(ssock->circ_buf_output_mutex);
++ if (circ_write(&ssock->circ_buf_output, data, len) != PJ_SUCCESS) {
++ pj_lock_release(ssock->circ_buf_output_mutex);
++
++ gnutls_transport_set_errno(ssock->session, ENOMEM);
++ return -1;
++ }
++
++ pj_lock_release(ssock->circ_buf_output_mutex);
++
++ return len;
++}
++
++
++/* gnutls_handshake() and gnutls_record_recv() will call this function to
++ * receive/read (encrypted) data */
++static ssize_t tls_data_pull(gnutls_transport_ptr_t ptr,
++ void *data, pj_size_t len)
++{
++ pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)ptr;
++
++ pj_lock_acquire(ssock->circ_buf_input_mutex);
++
++ if (circ_empty(&ssock->circ_buf_input)) {
++ pj_lock_release(ssock->circ_buf_input_mutex);
++
++ /* Data buffers not yet filled */
++ gnutls_transport_set_errno(ssock->session, EAGAIN);
++ return -1;
++ }
++
++ pj_size_t circ_buf_size = circ_size(&ssock->circ_buf_input);
++ pj_size_t read_size = PJ_MIN(circ_buf_size, len);
++
++ circ_read(&ssock->circ_buf_input, data, read_size);
++
++ pj_lock_release(ssock->circ_buf_input_mutex);
++
++ return read_size;
++}
++
++
++/* Append a string to the priority string, only once. */
++static pj_status_t tls_str_append_once(pj_str_t *dst, pj_str_t *src)
++{
++ if (pj_strstr(dst, src) == NULL) {
++ /* Check buffer size */
++ if (dst->slen + src->slen + 3 > 1024)
++ return PJ_ETOOMANY;
++
++ pj_strcat2(dst, ":+");
++ pj_strcat(dst, src);
++ }
++ return PJ_SUCCESS;
++}
++
++
++/* Generate priority string with user preference order. */
++static pj_status_t tls_priorities_set(pj_ssl_sock_t *ssock)
++{
++ char buf[1024];
++ char priority_buf[256];
++ pj_str_t cipher_list;
++ pj_str_t compression = pj_str("COMP-NULL");
++ pj_str_t server = pj_str(":%SERVER_PRECEDENCE");
++ int i, j, ret;
++ pj_str_t priority;
++ const char *err;
++
++ pj_strset(&cipher_list, buf, 0);
++ pj_strset(&priority, priority_buf, 0);
++
++ /* For each level, enable only the requested protocol */
++ pj_strcat2(&priority, "NORMAL:");
++ if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_2) {
++ pj_strcat2(&priority, "+VERS-TLS1.2:");
++ }
++ if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_1) {
++ pj_strcat2(&priority, "+VERS-TLS1.1:");
++ }
++ if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1) {
++ pj_strcat2(&priority, "+VERS-TLS1.0:");
++ }
++ pj_strcat2(&priority, "-VERS-SSL3.0:");
++ pj_strcat2(&priority, "%LATEST_RECORD_VERSION");
++
++ pj_strcat(&cipher_list, &priority);
++ for (i = 0; i < ssock->param.ciphers_num; i++) {
++ for (j = 0; ; j++) {
++ pj_ssl_cipher c;
++ const char *suite;
++ unsigned char id[2];
++ gnutls_protocol_t proto;
++ gnutls_kx_algorithm_t kx;
++ gnutls_mac_algorithm_t mac;
++ gnutls_cipher_algorithm_t algo;
++
++ suite = gnutls_cipher_suite_info(j, (unsigned char *)id,
++ &kx, &algo, &mac, &proto);
++ if (!suite)
++ break;
++
++ c = (pj_ssl_cipher) (pj_uint32_t) ((id[0] << 8) | id[1]);
++ if (ssock->param.ciphers[i] == c) {
++ char temp[256];
++ pj_str_t cipher_entry;
++
++ /* Protocol version */
++ pj_strset(&cipher_entry, temp, 0);
++ pj_strcat2(&cipher_entry, "VERS-");
++ pj_strcat2(&cipher_entry, gnutls_protocol_get_name(proto));
++ ret = tls_str_append_once(&cipher_list, &cipher_entry);
++ if (ret != PJ_SUCCESS)
++ return ret;
++
++ /* Cipher */
++ pj_strset(&cipher_entry, temp, 0);
++ pj_strcat2(&cipher_entry, gnutls_cipher_get_name(algo));
++ ret = tls_str_append_once(&cipher_list, &cipher_entry);
++ if (ret != PJ_SUCCESS)
++ return ret;
++
++ /* Mac */
++ pj_strset(&cipher_entry, temp, 0);
++ pj_strcat2(&cipher_entry, gnutls_mac_get_name(mac));
++ ret = tls_str_append_once(&cipher_list, &cipher_entry);
++ if (ret != PJ_SUCCESS)
++ return ret;
++
++ /* Key exchange */
++ pj_strset(&cipher_entry, temp, 0);
++ pj_strcat2(&cipher_entry, gnutls_kx_get_name(kx));
++ ret = tls_str_append_once(&cipher_list, &cipher_entry);
++ if (ret != PJ_SUCCESS)
++ return ret;
++
++ /* Compression is always disabled */
++ /* Signature is level-default */
++ break;
++ }
++ }
++ }
++
++ /* Disable compression, it's a TLS-only extension after all */
++ tls_str_append_once(&cipher_list, &compression);
++
++ /* Server will be the one deciding which crypto to use */
++ if (ssock->is_server) {
++ if (cipher_list.slen + server.slen + 1 > sizeof(buf))
++ return PJ_ETOOMANY;
++ else
++ pj_strcat(&cipher_list, &server);
++ }
++
++ /* End the string and print it */
++ cipher_list.ptr[cipher_list.slen] = '\0';
++ PJ_LOG(5, (ssock->pool->obj_name, "Priority string: %s", cipher_list.ptr));
++
++ /* Set our priority string */
++ ret = gnutls_priority_set_direct(ssock->session,
++ cipher_list.ptr, &err);
++ if (ret < 0) {
++ tls_last_error = GNUTLS_E_INVALID_REQUEST;
++ return PJ_EINVAL;
++ }
++
++ return PJ_SUCCESS;
++}
++
++
++/* Load root CA file or load the installed ones. */
++static pj_status_t tls_trust_set(pj_ssl_sock_t *ssock)
++{
++ int ntrusts = 0;
++ int err;
++
++ err = gnutls_certificate_set_x509_system_trust(ssock->xcred);
++ if (err > 0)
++ ntrusts += err;
++ err = gnutls_certificate_set_x509_trust_file(ssock->xcred,
++ TRUST_STORE_FILE1,
++ GNUTLS_X509_FMT_PEM);
++ if (err > 0)
++ ntrusts += err;
++
++ err = gnutls_certificate_set_x509_trust_file(ssock->xcred,
++ TRUST_STORE_FILE2,
++ GNUTLS_X509_FMT_PEM);
++ if (err > 0)
++ ntrusts += err;
++
++ if (ntrusts > 0)
++ return PJ_SUCCESS;
++ else if (!ntrusts)
++ return PJ_ENOTFOUND;
++ else
++ return PJ_EINVAL;
++}
++
++#if GNUTLS_VERSION_NUMBER < 0x030306
++
++#ifdef _POSIX_PATH_MAX
++# define GNUTLS_PATH_MAX _POSIX_PATH_MAX
++#else
++# define GNUTLS_PATH_MAX 256
++#endif
++
++static
++int gnutls_certificate_set_x509_trust_dir(gnutls_certificate_credentials_t cred, const char *dirname, unsigned type)
++{
++ DIR *dirp;
++ struct dirent *d;
++ int ret;
++ int r = 0;
++ char path[GNUTLS_PATH_MAX];
++#ifndef _WIN32
++ struct dirent e;
++#endif
++
++ dirp = opendir(dirname);
++ if (dirp != NULL) {
++ do {
++#ifdef _WIN32
++ d = readdir(dirp);
++ if (d != NULL) {
++#else
++ ret = readdir_r(dirp, &e, &d);
++ if (ret == 0 && d != NULL
++#ifdef _DIRENT_HAVE_D_TYPE
++ && (d->d_type == DT_REG || d->d_type == DT_LNK || d->d_type == DT_UNKNOWN)
++#endif
++ ) {
++#endif
++ snprintf(path, sizeof(path), "%s/%s",
++ dirname, d->d_name);
++
++ ret = gnutls_certificate_set_x509_trust_file(cred, path, type);
++ if (ret >= 0)
++ r += ret;
++ }
++ }
++ while (d != NULL);
++ closedir(dirp);
++ }
++
++ return r;
++}
++
++#endif
++
++/* Create and initialize new GnuTLS context and instance */
++static pj_status_t tls_open(pj_ssl_sock_t *ssock)
++{
++ pj_ssl_cert_t *cert;
++ pj_status_t status;
++ int ret;
++
++ pj_assert(ssock);
++
++ cert = ssock->cert;
++
++ /* Even if reopening is harmless, having one instance only simplifies
++ * deallocating it later on */
++ if (!ssock->tls_init_count) {
++ ssock->tls_init_count++;
++ ret = tls_init();
++ if (ret < 0)
++ return ret;
++ } else
++ return PJ_SUCCESS;
++
++ /* Start this socket session */
++ ret = gnutls_init(&ssock->session, ssock->is_server ? GNUTLS_SERVER
++ : GNUTLS_CLIENT);
++ if (ret < 0)
++ goto out;
++
++ /* Set the ssock object to be retrieved by transport (send/recv) and by
++ * user data from this session */
++ gnutls_transport_set_ptr(ssock->session,
++ (gnutls_transport_ptr_t) (uintptr_t) ssock);
++ gnutls_session_set_ptr(ssock->session,
++ (gnutls_transport_ptr_t) (uintptr_t) ssock);
++
++ /* Initialize input circular buffer */
++ status = circ_init(ssock->pool->factory, &ssock->circ_buf_input, 512);
++ if (status != PJ_SUCCESS)
++ return status;
++
++ /* Initialize output circular buffer */
++ status = circ_init(ssock->pool->factory, &ssock->circ_buf_output, 512);
++ if (status != PJ_SUCCESS)
++ return status;
++
++ /* Set the callback that allows GnuTLS to PUSH and PULL data
++ * TO and FROM the transport layer */
++ gnutls_transport_set_push_function(ssock->session, tls_data_push);
++ gnutls_transport_set_pull_function(ssock->session, tls_data_pull);
++
++ /* Determine which cipher suite to support */
++ status = tls_priorities_set(ssock);
++ if (status != PJ_SUCCESS)
++ return status;
++
++ /* Allocate credentials for handshaking and transmission */
++ ret = gnutls_certificate_allocate_credentials(&ssock->xcred);
++ if (ret < 0)
++ goto out;
++ gnutls_certificate_set_verify_function(ssock->xcred, tls_cert_verify_cb);
++
++ /* Load system trust file(s) */
++ status = tls_trust_set(ssock);
++ if (status != PJ_SUCCESS)
++ return status;
++
++ /* Load user-provided CA, certificate and key if available */
++ if (cert) {
++ /* Load CA if one is specified. */
++ if (cert->CA_file.slen) {
++ ret = gnutls_certificate_set_x509_trust_file(ssock->xcred,
++ cert->CA_file.ptr,
++ GNUTLS_X509_FMT_PEM);
++ if (ret < 0)
++ ret = gnutls_certificate_set_x509_trust_file(ssock->xcred,
++ cert->CA_file.ptr,
++ GNUTLS_X509_FMT_DER);
++ if (ret < 0)
++ goto out;
++ }
++ if (cert->CA_path.slen) {
++ ret = gnutls_certificate_set_x509_trust_dir(ssock->xcred,
++ cert->CA_path.ptr,
++ GNUTLS_X509_FMT_PEM);
++ if (ret < 0)
++ ret = gnutls_certificate_set_x509_trust_dir(ssock->xcred,
++ cert->CA_path.ptr,
++ GNUTLS_X509_FMT_DER);
++ if (ret < 0)
++ goto out;
++ }
++
++ /* Load certificate, key and pass if one is specified */
++ if (cert->cert_file.slen && cert->privkey_file.slen) {
++ const char *prikey_file = cert->privkey_file.ptr;
++ const char *prikey_pass = cert->privkey_pass.slen
++ ? cert->privkey_pass.ptr
++ : NULL;
++ ret = gnutls_certificate_set_x509_key_file2(ssock->xcred,
++ cert->cert_file.ptr,
++ prikey_file,
++ GNUTLS_X509_FMT_PEM,
++ prikey_pass,
++ 0);
++ if (ret != GNUTLS_E_SUCCESS)
++ ret = gnutls_certificate_set_x509_key_file2(ssock->xcred,
++ cert->cert_file.ptr,
++ prikey_file,
++ GNUTLS_X509_FMT_DER,
++ prikey_pass,
++ 0);
++ if (ret < 0)
++ goto out;
++ }
++ }
++
++ /* Require client certificate if asked */
++ if (ssock->is_server && ssock->param.require_client_cert)
++ gnutls_certificate_server_set_request(ssock->session,
++ GNUTLS_CERT_REQUIRE);
++
++ /* Finally set credentials for this session */
++ ret = gnutls_credentials_set(ssock->session,
++ GNUTLS_CRD_CERTIFICATE, ssock->xcred);
++ if (ret < 0)
++ goto out;
++
++ ret = GNUTLS_E_SUCCESS;
++out:
++ return tls_status_from_err(ssock, ret);
++}
++
++
++/* Destroy GnuTLS credentials and session. */
++static void tls_close(pj_ssl_sock_t *ssock)
++{
++ if (ssock->session) {
++ gnutls_bye(ssock->session, GNUTLS_SHUT_RDWR);
++ gnutls_deinit(ssock->session);
++ ssock->session = NULL;
++ }
++
++ if (ssock->xcred) {
++ gnutls_certificate_free_credentials(ssock->xcred);
++ ssock->xcred = NULL;
++ }
++
++ /* Free GnuTLS library */
++ if (ssock->tls_init_count) {
++ ssock->tls_init_count--;
++ tls_deinit();
++ }
++
++ /* Destroy circular buffers */
++ circ_deinit(&ssock->circ_buf_input);
++ circ_deinit(&ssock->circ_buf_output);
++}
++
++
++/* Reset socket state. */
++static void tls_sock_reset(pj_ssl_sock_t *ssock)
++{
++ ssock->connection_state = TLS_STATE_NULL;
++
++ tls_close(ssock);
++
++ if (ssock->asock) {
++ pj_activesock_close(ssock->asock);
++ ssock->asock = NULL;
++ ssock->sock = PJ_INVALID_SOCKET;
++ }
++ if (ssock->sock != PJ_INVALID_SOCKET) {
++ pj_sock_close(ssock->sock);
++ ssock->sock = PJ_INVALID_SOCKET;
++ }
++
++ ssock->last_err = tls_last_error = GNUTLS_E_SUCCESS;
++}
++
++
++/* Get Common Name field string from a general name string */
++static void tls_cert_get_cn(const pj_str_t *gen_name, pj_str_t *cn)
++{
++ pj_str_t CN_sign = {"CN=", 3};
++ char *p, *q;
++
++ pj_bzero(cn, sizeof(cn));
++
++ p = pj_strstr(gen_name, &CN_sign);
++ if (!p)
++ return;
++
++ p += 3; /* shift pointer to value part */
++ pj_strset(cn, p, gen_name->slen - (p - gen_name->ptr));
++ q = pj_strchr(cn, ',');
++ if (q)
++ cn->slen = q - p;
++}
++
++
++/* Get certificate info; in case the certificate info is already populated,
++ * this function will check if the contents need updating by inspecting the
++ * issuer and the serial number. */
++static void tls_cert_get_info(pj_pool_t *pool, pj_ssl_cert_info *ci, gnutls_x509_crt_t cert)
++{
++ pj_bool_t update_needed;
++ char buf[512] = { 0 };
++ size_t bufsize = sizeof(buf);
++ pj_uint8_t serial_no[64] = { 0 }; /* should be >= sizeof(ci->serial_no) */
++ size_t serialsize = sizeof(serial_no);
++ size_t len = sizeof(buf);
++ int i, ret, seq = 0;
++ pj_ssl_cert_name_type type;
++
++ pj_assert(pool && ci && cert);
++
++ /* Get issuer */
++ gnutls_x509_crt_get_issuer_dn(cert, buf, &bufsize);
++
++ /* Get serial no */
++ gnutls_x509_crt_get_serial(cert, serial_no, &serialsize);
++
++ /* Check if the contents need to be updated */
++ update_needed = pj_strcmp2(&ci->issuer.info, buf) ||
++ pj_memcmp(ci->serial_no, serial_no, serialsize);
++ if (!update_needed)
++ return;
++
++ /* Update cert info */
++
++ pj_bzero(ci, sizeof(pj_ssl_cert_info));
++
++ /* Version */
++ ci->version = gnutls_x509_crt_get_version(cert);
++
++ /* Issuer */
++ pj_strdup2(pool, &ci->issuer.info, buf);
++ tls_cert_get_cn(&ci->issuer.info, &ci->issuer.cn);
++
++ /* Serial number */
++ pj_memcpy(ci->serial_no, serial_no, sizeof(ci->serial_no));
++
++ /* Subject */
++ bufsize = sizeof(buf);
++ gnutls_x509_crt_get_dn(cert, buf, &bufsize);
++ pj_strdup2(pool, &ci->subject.info, buf);
++ tls_cert_get_cn(&ci->subject.info, &ci->subject.cn);
++
++ /* Validity */
++ ci->validity.end.sec = gnutls_x509_crt_get_expiration_time(cert);
++ ci->validity.start.sec = gnutls_x509_crt_get_activation_time(cert);
++ ci->validity.gmt = 0;
++
++ /* Subject Alternative Name extension */
++ if (ci->version >= 3) {
++ char out[256] = { 0 };
++ /* Get the number of all alternate names so that we can allocate
++ * the correct number of bytes in subj_alt_name */
++ while (gnutls_x509_crt_get_subject_alt_name(cert, seq, out, &len,
++ NULL) != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
++ seq++;
++
++ ci->subj_alt_name.entry = pj_pool_calloc(pool, seq,
++ sizeof(*ci->subj_alt_name.entry));
++ if (!ci->subj_alt_name.entry) {
++ tls_last_error = GNUTLS_E_MEMORY_ERROR;
++ return;
++ }
++
++ /* Now populate the alternative names */
++ for (i = 0; i < seq; i++) {
++ len = sizeof(out) - 1;
++ ret = gnutls_x509_crt_get_subject_alt_name(cert, i, out, &len, NULL);
++ switch (ret) {
++ case GNUTLS_SAN_IPADDRESS:
++ type = PJ_SSL_CERT_NAME_IP;
++ pj_inet_ntop2(len == sizeof(pj_in6_addr) ? pj_AF_INET6()
++ : pj_AF_INET(),
++ out, buf, sizeof(buf));
++ break;
++ case GNUTLS_SAN_URI:
++ type = PJ_SSL_CERT_NAME_URI;
++ break;
++ case GNUTLS_SAN_RFC822NAME:
++ type = PJ_SSL_CERT_NAME_RFC822;
++ break;
++ case GNUTLS_SAN_DNSNAME:
++ type = PJ_SSL_CERT_NAME_DNS;
++ break;
++ default:
++ type = PJ_SSL_CERT_NAME_UNKNOWN;
++ break;
++ }
++
++ if (len && type != PJ_SSL_CERT_NAME_UNKNOWN) {
++ ci->subj_alt_name.entry[ci->subj_alt_name.cnt].type = type;
++ pj_strdup2(pool,
++ &ci->subj_alt_name.entry[ci->subj_alt_name.cnt].name,
++ type == PJ_SSL_CERT_NAME_IP ? buf : out);
++ ci->subj_alt_name.cnt++;
++ }
++ }
++ /* TODO: if no DNS alt. names were found, we could check against
++ * the commonName as per RFC3280. */
++ }
++}
++
++static void tls_cert_get_chain_raw(pj_pool_t *pool, pj_ssl_cert_info *ci, const gnutls_datum_t *certs, size_t certs_num)
++{
++ size_t i=0;
++ ci->raw_chain.cert_raw = pj_pool_calloc(pool, certs_num, sizeof(*ci->raw_chain.cert_raw));
++ ci->raw_chain.cnt = certs_num;
++ for (i=0; i < certs_num; ++i) {
++ const pj_str_t crt_raw = {(const char*)certs[i].data, (pj_ssize_t)certs[i].size};
++ pj_strdup(pool, ci->raw_chain.cert_raw+i, &crt_raw);
++ }
++}
++
++/* Update local & remote certificates info. This function should be
++ * called after handshake or renegotiation successfully completed. */
++static void tls_cert_update(pj_ssl_sock_t *ssock)
++{
++ gnutls_x509_crt_t cert = NULL;
++ const gnutls_datum_t *us;
++ const gnutls_datum_t *certs;
++ unsigned int certslen = 0;
++ int ret = GNUTLS_CERT_INVALID;
++
++ pj_assert(ssock->connection_state == TLS_STATE_ESTABLISHED);
++
++ /* Get active local certificate */
++ us = gnutls_certificate_get_ours(ssock->session);
++ if (!us)
++ goto us_out;
++
++ ret = gnutls_x509_crt_init(&cert);
++ if (ret < 0)
++ goto us_out;
++ ret = gnutls_x509_crt_import(cert, us, GNUTLS_X509_FMT_DER);
++ if (ret < 0)
++ ret = gnutls_x509_crt_import(cert, us, GNUTLS_X509_FMT_PEM);
++ if (ret < 0)
++ goto us_out;
++
++ tls_cert_get_info(ssock->pool, &ssock->local_cert_info, cert);
++ tls_cert_get_chain_raw(ssock->pool, &ssock->local_cert_info, us, 1);
++
++us_out:
++ tls_last_error = ret;
++ if (cert)
++ gnutls_x509_crt_deinit(cert);
++ else
++ pj_bzero(&ssock->local_cert_info, sizeof(pj_ssl_cert_info));
++
++ cert = NULL;
++
++ /* Get active remote certificate */
++ certs = gnutls_certificate_get_peers(ssock->session, &certslen);
++ if (certs == NULL || certslen == 0)
++ goto peer_out;
++
++ ret = gnutls_x509_crt_init(&cert);
++ if (ret < 0)
++ goto peer_out;
++
++ ret = gnutls_x509_crt_import(cert, certs, GNUTLS_X509_FMT_PEM);
++ if (ret < 0)
++ ret = gnutls_x509_crt_import(cert, certs, GNUTLS_X509_FMT_DER);
++ if (ret < 0)
++ goto peer_out;
++
++ tls_cert_get_info(ssock->pool, &ssock->remote_cert_info, cert);
++ tls_cert_get_chain_raw(ssock->pool, &ssock->remote_cert_info, certs, certslen);
++
++peer_out:
++ tls_last_error = ret;
++ if (cert)
++ gnutls_x509_crt_deinit(cert);
++ else
++ pj_bzero(&ssock->remote_cert_info, sizeof(pj_ssl_cert_info));
++}
++
++
++/* When handshake completed:
++ * - notify application
++ * - if handshake failed, reset SSL state
++ * - return PJ_FALSE when SSL socket instance is destroyed by application. */
++static pj_bool_t on_handshake_complete(pj_ssl_sock_t *ssock,
++ pj_status_t status)
++{
++ pj_bool_t ret = PJ_TRUE;
++
++ /* Cancel handshake timer */
++ if (ssock->timer.id == TIMER_HANDSHAKE_TIMEOUT) {
++ pj_timer_heap_cancel(ssock->param.timer_heap, &ssock->timer);
++ ssock->timer.id = TIMER_NONE;
++ }
++
++ /* Update certificates info on successful handshake */
++ if (status == PJ_SUCCESS)
++ tls_cert_update(ssock);
++
++ /* Accepting */
++ if (ssock->is_server) {
++ if (status != PJ_SUCCESS) {
++ /* Handshake failed in accepting, destroy our self silently. */
++
++ char errmsg[PJ_ERR_MSG_SIZE];
++ char buf[PJ_INET6_ADDRSTRLEN + 10];
++
++ pj_strerror(status, errmsg, sizeof(errmsg));
++ PJ_LOG(3, (ssock->pool->obj_name,
++ "Handshake failed in accepting %s: %s",
++ pj_sockaddr_print(&ssock->rem_addr, buf, sizeof(buf), 3),
++ errmsg));
++
++ /* Workaround for ticket #985 */
++#if (defined(PJ_WIN32) && PJ_WIN32 != 0) || (defined(PJ_WIN64) && PJ_WIN64 != 0)
++ if (ssock->param.timer_heap) {
++ pj_time_val interval = {0, DELAYED_CLOSE_TIMEOUT};
++
++ tls_sock_reset(ssock);
++
++ ssock->timer.id = TIMER_CLOSE;
++ pj_time_val_normalize(&interval);
++ if (pj_timer_heap_schedule(ssock->param.timer_heap,
++ &ssock->timer, &interval) != 0)
++ {
++ ssock->timer.id = TIMER_NONE;
++ pj_ssl_sock_close(ssock);
++ }
++ } else
++#endif /* PJ_WIN32 */
++ {
++ pj_ssl_sock_close(ssock);
++ }
++
++ return PJ_FALSE;
++ }
++ /* Notify application the newly accepted SSL socket */
++ if (ssock->param.cb.on_accept_complete)
++ ret = (*ssock->param.cb.on_accept_complete)
++ (ssock->parent, ssock, (pj_sockaddr_t*)&ssock->rem_addr,
++ pj_sockaddr_get_len((pj_sockaddr_t*)&ssock->rem_addr));
++
++ } else { /* Connecting */
++ /* On failure, reset SSL socket state first, as app may try to
++ * reconnect in the callback. */
++ if (status != PJ_SUCCESS) {
++ /* Server disconnected us, possibly due to negotiation failure */
++ tls_sock_reset(ssock);
++ }
++ if (ssock->param.cb.on_connect_complete) {
++
++ ret = (*ssock->param.cb.on_connect_complete)(ssock, status);
++ }
++ }
++
++ return ret;
++}
++
++static write_data_t *alloc_send_data(pj_ssl_sock_t *ssock, pj_size_t len)
++{
++ send_buf_t *send_buf = &ssock->send_buf;
++ pj_size_t avail_len, skipped_len = 0;
++ char *reg1, *reg2;
++ pj_size_t reg1_len, reg2_len;
++ write_data_t *p;
++
++ /* Check buffer availability */
++ avail_len = send_buf->max_len - send_buf->len;
++ if (avail_len < len)
++ return NULL;
++
++ /* If buffer empty, reset start pointer and return it */
++ if (send_buf->len == 0) {
++ send_buf->start = send_buf->buf;
++ send_buf->len = len;
++ p = (write_data_t*)send_buf->start;
++ goto init_send_data;
++ }
++
++ /* Free space may be wrapped/splitted into two regions, so let's
++ * analyze them if any region can hold the write data. */
++ reg1 = send_buf->start + send_buf->len;
++ if (reg1 >= send_buf->buf + send_buf->max_len)
++ reg1 -= send_buf->max_len;
++ reg1_len = send_buf->max_len - send_buf->len;
++ if (reg1 + reg1_len > send_buf->buf + send_buf->max_len) {
++ reg1_len = send_buf->buf + send_buf->max_len - reg1;
++ reg2 = send_buf->buf;
++ reg2_len = send_buf->start - send_buf->buf;
++ } else {
++ reg2 = NULL;
++ reg2_len = 0;
++ }
++
++ /* More buffer availability check, note that the write data must be in
++ * a contigue buffer. */
++ avail_len = PJ_MAX(reg1_len, reg2_len);
++ if (avail_len < len)
++ return NULL;
++
++ /* Get the data slot */
++ if (reg1_len >= len) {
++ p = (write_data_t*)reg1;
++ } else {
++ p = (write_data_t*)reg2;
++ skipped_len = reg1_len;
++ }
++
++ /* Update buffer length */
++ send_buf->len += len + skipped_len;
++
++init_send_data:
++ /* Init the new send data */
++ pj_bzero(p, sizeof(*p));
++ pj_list_init(p);
++ pj_list_push_back(&ssock->send_pending, p);
++
++ return p;
++}
++
++static void free_send_data(pj_ssl_sock_t *ssock, write_data_t *wdata)
++{
++ send_buf_t *buf = &ssock->send_buf;
++ write_data_t *spl = &ssock->send_pending;
++
++ pj_assert(!pj_list_empty(&ssock->send_pending));
++
++ /* Free slot from the buffer */
++ if (spl->next == wdata && spl->prev == wdata) {
++ /* This is the only data, reset the buffer */
++ buf->start = buf->buf;
++ buf->len = 0;
++ } else if (spl->next == wdata) {
++ /* This is the first data, shift start pointer of the buffer and
++ * adjust the buffer length.
++ */
++ buf->start = (char*)wdata->next;
++ if (wdata->next > wdata) {
++ buf->len -= ((char*)wdata->next - buf->start);
++ } else {
++ /* Overlapped */
++ pj_size_t right_len, left_len;
++ right_len = buf->buf + buf->max_len - (char*)wdata;
++ left_len = (char*)wdata->next - buf->buf;
++ buf->len -= (right_len + left_len);
++ }
++ } else if (spl->prev == wdata) {
++ /* This is the last data, just adjust the buffer length */
++ if (wdata->prev < wdata) {
++ pj_size_t jump_len;
++ jump_len = (char*)wdata -
++ ((char*)wdata->prev + wdata->prev->record_len);
++ buf->len -= (wdata->record_len + jump_len);
++ } else {
++ /* Overlapped */
++ pj_size_t right_len, left_len;
++ right_len = buf->buf + buf->max_len -
++ ((char*)wdata->prev + wdata->prev->record_len);
++ left_len = (char*)wdata + wdata->record_len - buf->buf;
++ buf->len -= (right_len + left_len);
++ }
++ }
++ /* For data in the middle buffer, just do nothing on the buffer. The slot
++ * will be freed later when freeing the first/last data. */
++
++ /* Remove the data from send pending list */
++ pj_list_erase(wdata);
++}
++
++#if 0
++/* Just for testing send buffer alloc/free */
++#include <pj/rand.h>
++pj_status_t pj_ssl_sock_ossl_test_send_buf(pj_pool_t *pool)
++{
++ enum { MAX_CHUNK_NUM = 20 };
++ unsigned chunk_size, chunk_cnt, i;
++ write_data_t *wdata[MAX_CHUNK_NUM] = {0};
++ pj_time_val now;
++ pj_ssl_sock_t *ssock = NULL;
++ pj_ssl_sock_param param;
++ pj_status_t status;
++
++ pj_gettimeofday(&now);
++ pj_srand((unsigned)now.sec);
++
++ pj_ssl_sock_param_default(¶m);
++ status = pj_ssl_sock_create(pool, ¶m, &ssock);
++ if (status != PJ_SUCCESS) {
++ return status;
++ }
++
++ if (ssock->send_buf.max_len == 0) {
++ ssock->send_buf.buf = (char *)
++ pj_pool_alloc(ssock->pool,
++ ssock->param.send_buffer_size);
++ ssock->send_buf.max_len = ssock->param.send_buffer_size;
++ ssock->send_buf.start = ssock->send_buf.buf;
++ ssock->send_buf.len = 0;
++ }
++
++ chunk_size = ssock->param.send_buffer_size / MAX_CHUNK_NUM / 2;
++ chunk_cnt = 0;
++ for (i = 0; i < MAX_CHUNK_NUM; i++) {
++ wdata[i] = alloc_send_data(ssock, pj_rand() % chunk_size + 321);
++ if (wdata[i])
++ chunk_cnt++;
++ else
++ break;
++ }
++
++ while (chunk_cnt) {
++ i = pj_rand() % MAX_CHUNK_NUM;
++ if (wdata[i]) {
++ free_send_data(ssock, wdata[i]);
++ wdata[i] = NULL;
++ chunk_cnt--;
++ }
++ }
++
++ if (ssock->send_buf.len != 0)
++ status = PJ_EBUG;
++
++ pj_ssl_sock_close(ssock);
++ return status;
++}
++#endif
++
++/* Flush write circular buffer to network socket. */
++static pj_status_t flush_circ_buf_output(pj_ssl_sock_t *ssock,
++ pj_ioqueue_op_key_t *send_key,
++ pj_size_t orig_len, unsigned flags)
++{
++ pj_ssize_t len;
++ write_data_t *wdata;
++ pj_size_t needed_len;
++ pj_status_t status;
++
++ pj_lock_acquire(ssock->circ_buf_output_mutex);
++
++ /* Check if there is data in the circular buffer, flush it if any */
++ if (circ_empty(&ssock->circ_buf_output)) {
++ pj_lock_release(ssock->circ_buf_output_mutex);
++
++ return PJ_SUCCESS;
++ }
++
++ len = circ_size(&ssock->circ_buf_output);
++
++ /* Calculate buffer size needed, and align it to 8 */
++ needed_len = len + sizeof(write_data_t);
++ needed_len = ((needed_len + 7) >> 3) << 3;
++
++ /* Allocate buffer for send data */
++ wdata = alloc_send_data(ssock, needed_len);
++ if (wdata == NULL) {
++ pj_lock_release(ssock->circ_buf_output_mutex);
++ return PJ_ENOMEM;
++ }
++
++ /* Copy the data and set its properties into the send data */
++ pj_ioqueue_op_key_init(&wdata->key, sizeof(pj_ioqueue_op_key_t));
++ wdata->key.user_data = wdata;
++ wdata->app_key = send_key;
++ wdata->record_len = needed_len;
++ wdata->data_len = len;
++ wdata->plain_data_len = orig_len;
++ wdata->flags = flags;
++ circ_read(&ssock->circ_buf_output, (pj_uint8_t *)&wdata->data, len);
++
++ /* Ticket #1573: Don't hold mutex while calling PJLIB socket send(). */
++ pj_lock_release(ssock->circ_buf_output_mutex);
++
++ /* Send it */
++ if (ssock->param.sock_type == pj_SOCK_STREAM()) {
++ status = pj_activesock_send(ssock->asock, &wdata->key,
++ wdata->data.content, &len,
++ flags);
++ } else {
++ status = pj_activesock_sendto(ssock->asock, &wdata->key,
++ wdata->data.content, &len,
++ flags,
++ (pj_sockaddr_t*)&ssock->rem_addr,
++ ssock->addr_len);
++ }
++
++ if (status != PJ_EPENDING) {
++ /* When the sending is not pending, remove the wdata from send
++ * pending list. */
++ pj_lock_acquire(ssock->circ_buf_output_mutex);
++ free_send_data(ssock, wdata);
++ pj_lock_release(ssock->circ_buf_output_mutex);
++ }
++
++ return status;
++}
++
++static void on_timer(pj_timer_heap_t *th, struct pj_timer_entry *te)
++{
++ pj_ssl_sock_t *ssock = (pj_ssl_sock_t*)te->user_data;
++ int timer_id = te->id;
++
++ te->id = TIMER_NONE;
++
++ PJ_UNUSED_ARG(th);
++
++ switch (timer_id) {
++ case TIMER_HANDSHAKE_TIMEOUT:
++ PJ_LOG(1, (ssock->pool->obj_name, "TLS timeout after %d.%ds",
++ ssock->param.timeout.sec, ssock->param.timeout.msec));
++
++ on_handshake_complete(ssock, PJ_ETIMEDOUT);
++ break;
++ case TIMER_CLOSE:
++ pj_ssl_sock_close(ssock);
++ break;
++ default:
++ pj_assert(!"Unknown timer");
++ break;
++ }
++}
++
++
++/* Try to perform an asynchronous handshake */
++static pj_status_t tls_try_handshake(pj_ssl_sock_t *ssock)
++{
++ int ret;
++ pj_status_t status;
++
++ /* Perform SSL handshake */
++ ret = gnutls_handshake(ssock->session);
++
++ status = flush_circ_buf_output(ssock, &ssock->handshake_op_key, 0, 0);
++ if (status != PJ_SUCCESS)
++ return status;
++
++ if (ret == GNUTLS_E_SUCCESS) {
++ /* System are GO */
++ ssock->connection_state = TLS_STATE_ESTABLISHED;
++ status = PJ_SUCCESS;
++ } else if (!gnutls_error_is_fatal(ret)) {
++ /* Non fatal error, retry later (busy or again) */
++ status = PJ_EPENDING;
++ } else {
++ /* Fatal error invalidates session, no fallback */
++ status = PJ_EINVAL;
++ }
++
++ tls_last_error = ret;
++
++ return status;
++}
++
++
++/*
++ *******************************************************************
++ * Active socket callbacks.
++ *******************************************************************
++ */
++
++/* PJ_TRUE asks the socket to read more data, PJ_FALSE takes it off the queue */
++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)
++{
++ pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)
++ pj_activesock_get_user_data(asock);
++
++ pj_size_t app_remainder = 0;
++
++ if (data && size > 0) {
++ /* Push data into input circular buffer (for GnuTLS) */
++ pj_lock_acquire(ssock->circ_buf_input_mutex);
++ circ_write(&ssock->circ_buf_input, data, size);
++ pj_lock_release(ssock->circ_buf_input_mutex);
++ }
++
++ /* Check if SSL handshake hasn't finished yet */
++ if (ssock->connection_state == TLS_STATE_HANDSHAKING) {
++ pj_bool_t ret = PJ_TRUE;
++
++ if (status == PJ_SUCCESS)
++ status = tls_try_handshake(ssock);
++
++ /* Not pending is either success or failed */
++ if (status != PJ_EPENDING)
++ ret = on_handshake_complete(ssock, status);
++
++ return ret;
++ }
++
++ /* See if there is any decrypted data for the application */
++ if (ssock->read_started) {
++ do {
++ /* Get read data structure at the end of the data */
++ read_data_t *app_read_data = *(OFFSET_OF_READ_DATA_PTR(ssock, data));
++ int app_data_size = (int)(ssock->read_size - app_read_data->len);
++
++ /* Decrypt received data using GnuTLS (will read our input
++ * circular buffer) */
++ int decrypted_size = gnutls_record_recv(ssock->session,
++ ((read_data_t *)app_read_data->data) +
++ app_read_data->len,
++ app_data_size);
++
++ if (decrypted_size > 0 || status != PJ_SUCCESS) {
++ if (ssock->param.cb.on_data_read) {
++ pj_bool_t ret;
++ app_remainder = 0;
++
++ if (decrypted_size > 0)
++ app_read_data->len += decrypted_size;
++
++ ret = (*ssock->param.cb.on_data_read)(ssock,
++ app_read_data->data,
++ app_read_data->len,
++ status,
++ &app_remainder);
++
++ if (!ret) {
++ /* We've been destroyed */
++ return PJ_FALSE;
++ }
++
++ /* Application may have left some data to be consumed
++ * later as remainder */
++ app_read_data->len = app_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) {
++ tls_sock_reset(ssock);
++ return PJ_FALSE;
++ }
++ } else if (decrypted_size == 0) {
++ /* Nothing more to read */
++
++ return PJ_TRUE;
++ } else if (decrypted_size == GNUTLS_E_AGAIN ||
++ decrypted_size == GNUTLS_E_INTERRUPTED) {
++ return PJ_TRUE;
++ } else if (decrypted_size == GNUTLS_E_REHANDSHAKE) {
++ /* Seems like we are renegotiating */
++ pj_status_t try_handshake_status = tls_try_handshake(ssock);
++
++ /* Not pending is either success or failed */
++ if (try_handshake_status != PJ_EPENDING) {
++ if (!on_handshake_complete(ssock, try_handshake_status)) {
++ return PJ_FALSE;
++ }
++ }
++
++ if (try_handshake_status != PJ_SUCCESS &&
++ try_handshake_status != PJ_EPENDING) {
++ return PJ_FALSE;
++ }
++ } else if (!gnutls_error_is_fatal(decrypted_size)) {
++ /* non-fatal error, let's just continue */
++ } else {
++ return PJ_FALSE;
++ }
++ } while (PJ_TRUE);
++ }
++
++ return PJ_TRUE;
++}
++
++
++/* Callback every time new data is available from the active socket */
++static pj_bool_t asock_on_data_sent(pj_activesock_t *asock,
++ pj_ioqueue_op_key_t *send_key,
++ pj_ssize_t sent)
++{
++ pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)pj_activesock_get_user_data(asock);
++
++ PJ_UNUSED_ARG(send_key);
++ PJ_UNUSED_ARG(sent);
++
++ if (ssock->connection_state == TLS_STATE_HANDSHAKING) {
++ /* Initial handshaking */
++ pj_status_t status = tls_try_handshake(ssock);
++
++ /* Not pending is either success or failed */
++ if (status != PJ_EPENDING)
++ return on_handshake_complete(ssock, status);
++
++ } else if (send_key != &ssock->handshake_op_key) {
++ /* Some data has been sent, notify application */
++ write_data_t *wdata = (write_data_t*)send_key->user_data;
++ if (ssock->param.cb.on_data_sent) {
++ pj_bool_t ret;
++ pj_ssize_t sent_len;
++
++ sent_len = sent > 0 ? wdata->plain_data_len : sent;
++
++ ret = (*ssock->param.cb.on_data_sent)(ssock, wdata->app_key,
++ sent_len);
++ if (!ret) {
++ /* We've been destroyed */
++ return PJ_FALSE;
++ }
++ }
++
++ /* Update write buffer state */
++ pj_lock_acquire(ssock->circ_buf_output_mutex);
++ free_send_data(ssock, wdata);
++ pj_lock_release(ssock->circ_buf_output_mutex);
++ } else {
++ /* SSL re-negotiation is on-progress, just do nothing */
++ /* FIXME: check if this is valid for GnuTLS too */
++ }
++
++ return PJ_TRUE;
++}
++
++
++/* Callback every time a new connection has been accepted (server) */
++static pj_bool_t asock_on_accept_complete(pj_activesock_t *asock,
++ pj_sock_t newsock,
++ const pj_sockaddr_t *src_addr,
++ int src_addr_len)
++{
++ pj_ssl_sock_t *ssock_parent = (pj_ssl_sock_t *)
++ pj_activesock_get_user_data(asock);
++
++ pj_ssl_sock_t *ssock;
++ pj_activesock_cb asock_cb;
++ pj_activesock_cfg asock_cfg;
++ unsigned int i;
++ pj_status_t status;
++
++ PJ_UNUSED_ARG(src_addr_len);
++
++ /* Create new SSL socket instance */
++ status = pj_ssl_sock_create(ssock_parent->pool, &ssock_parent->newsock_param,
++ &ssock);
++ if (status != PJ_SUCCESS)
++ goto on_return;
++
++ /* Update new SSL socket attributes */
++ ssock->sock = newsock;
++ ssock->parent = ssock_parent;
++ ssock->is_server = PJ_TRUE;
++ if (ssock_parent->cert) {
++ status = pj_ssl_sock_set_certificate(ssock, ssock->pool,
++ ssock_parent->cert);
++ if (status != PJ_SUCCESS)
++ goto on_return;
++ }
++
++ /* Apply QoS, if specified */
++ status = pj_sock_apply_qos2(ssock->sock, ssock->param.qos_type,
++ &ssock->param.qos_params, 1,
++ ssock->pool->obj_name, NULL);
++ if (status != PJ_SUCCESS && !ssock->param.qos_ignore_error)
++ goto on_return;
++
++ /* Update local address */
++ ssock->addr_len = src_addr_len;
++ status = pj_sock_getsockname(ssock->sock, &ssock->local_addr,
++ &ssock->addr_len);
++ if (status != PJ_SUCCESS) {
++ /* This fails on few envs, e.g: win IOCP, just tolerate this and
++ * use parent local address instead.
++ */
++ pj_sockaddr_cp(&ssock->local_addr, &ssock_parent->local_addr);
++ }
++
++ /* Set remote address */
++ pj_sockaddr_cp(&ssock->rem_addr, src_addr);
++
++ /* Create SSL context */
++ status = tls_open(ssock);
++ if (status != PJ_SUCCESS)
++ goto on_return;
++
++ /* Prepare read buffer */
++ ssock->asock_rbuf = (void **)pj_pool_calloc(ssock->pool,
++ ssock->param.async_cnt,
++ sizeof(void*));
++ if (!ssock->asock_rbuf)
++ return PJ_ENOMEM;
++
++ for (i = 0; i < ssock->param.async_cnt; ++i) {
++ ssock->asock_rbuf[i] = (void *)pj_pool_alloc(
++ ssock->pool,
++ ssock->param.read_buffer_size +
++ sizeof(read_data_t*));
++ if (!ssock->asock_rbuf[i])
++ return PJ_ENOMEM;
++ }
++
++ /* Create active socket */
++ pj_activesock_cfg_default(&asock_cfg);
++ asock_cfg.async_cnt = ssock->param.async_cnt;
++ asock_cfg.concurrency = ssock->param.concurrency;
++ asock_cfg.whole_data = PJ_TRUE;
++
++ pj_bzero(&asock_cb, sizeof(asock_cb));
++ asock_cb.on_data_read = asock_on_data_read;
++ asock_cb.on_data_sent = asock_on_data_sent;
++
++ status = pj_activesock_create(ssock->pool,
++ ssock->sock,
++ ssock->param.sock_type,
++ &asock_cfg,
++ ssock->param.ioqueue,
++ &asock_cb,
++ ssock,
++ &ssock->asock);
++
++ if (status != PJ_SUCCESS)
++ goto on_return;
++
++ /* Start reading */
++ status = pj_activesock_start_read2(ssock->asock, ssock->pool,
++ (unsigned)ssock->param.read_buffer_size,
++ ssock->asock_rbuf,
++ PJ_IOQUEUE_ALWAYS_ASYNC);
++ if (status != PJ_SUCCESS)
++ goto on_return;
++
++ /* Prepare write/send state */
++ pj_assert(ssock->send_buf.max_len == 0);
++ ssock->send_buf.buf = (char *)pj_pool_alloc(ssock->pool,
++ ssock->param.send_buffer_size);
++ if (!ssock->send_buf.buf)
++ return PJ_ENOMEM;
++
++ ssock->send_buf.max_len = ssock->param.send_buffer_size;
++ ssock->send_buf.start = ssock->send_buf.buf;
++ ssock->send_buf.len = 0;
++
++ /* Start handshake timer */
++ if (ssock->param.timer_heap &&
++ (ssock->param.timeout.sec != 0 || ssock->param.timeout.msec != 0)) {
++ pj_assert(ssock->timer.id == TIMER_NONE);
++ ssock->timer.id = TIMER_HANDSHAKE_TIMEOUT;
++ status = pj_timer_heap_schedule(ssock->param.timer_heap,
++ &ssock->timer,
++ &ssock->param.timeout);
++ if (status != PJ_SUCCESS)
++ ssock->timer.id = TIMER_NONE;
++ }
++
++ /* Start SSL handshake */
++ ssock->connection_state = TLS_STATE_HANDSHAKING;
++
++ status = tls_try_handshake(ssock);
++
++on_return:
++ if (ssock && status != PJ_EPENDING)
++ on_handshake_complete(ssock, status);
++
++ /* Must return PJ_TRUE whatever happened, as active socket must
++ * continue listening.
++ */
++ return PJ_TRUE;
++}
++
++
++/* Callback every time a new connection has been completed (client) */
++static pj_bool_t asock_on_connect_complete (pj_activesock_t *asock,
++ pj_status_t status)
++{
++ pj_ssl_sock_t *ssock = (pj_ssl_sock_t*)
++ pj_activesock_get_user_data(asock);
++
++ unsigned int i;
++ int ret;
++
++ if (status != PJ_SUCCESS)
++ goto on_return;
++
++ /* Update local address */
++ ssock->addr_len = sizeof(pj_sockaddr);
++ status = pj_sock_getsockname(ssock->sock, &ssock->local_addr,
++ &ssock->addr_len);
++ if (status != PJ_SUCCESS)
++ goto on_return;
++
++ /* Create SSL context */
++ status = tls_open(ssock);
++ if (status != PJ_SUCCESS)
++ goto on_return;
++
++ /* Prepare read buffer */
++ ssock->asock_rbuf = (void **)pj_pool_calloc(ssock->pool,
++ ssock->param.async_cnt,
++ sizeof(void *));
++ if (!ssock->asock_rbuf)
++ return PJ_ENOMEM;
++
++ for (i = 0; i < ssock->param.async_cnt; ++i) {
++ ssock->asock_rbuf[i] = (void *)pj_pool_alloc(
++ ssock->pool,
++ ssock->param.read_buffer_size +
++ sizeof(read_data_t *));
++ if (!ssock->asock_rbuf[i])
++ return PJ_ENOMEM;
++ }
++
++ /* Start read */
++ status = pj_activesock_start_read2(ssock->asock, ssock->pool,
++ (unsigned) ssock->param.read_buffer_size,
++ ssock->asock_rbuf,
++ PJ_IOQUEUE_ALWAYS_ASYNC);
++ if (status != PJ_SUCCESS)
++ goto on_return;
++
++ /* Prepare write/send state */
++ pj_assert(ssock->send_buf.max_len == 0);
++ ssock->send_buf.buf = (char *)pj_pool_alloc(ssock->pool,
++ ssock->param.send_buffer_size);
++ if (!ssock->send_buf.buf)
++ return PJ_ENOMEM;
++
++ ssock->send_buf.max_len = ssock->param.send_buffer_size;
++ ssock->send_buf.start = ssock->send_buf.buf;
++ ssock->send_buf.len = 0;
++
++ /* Set server name to connect */
++ if (ssock->param.server_name.slen) {
++ /* Server name is null terminated already */
++ ret = gnutls_server_name_set(ssock->session, GNUTLS_NAME_DNS,
++ ssock->param.server_name.ptr,
++ ssock->param.server_name.slen);
++ if (ret < 0) {
++ PJ_LOG(3, (ssock->pool->obj_name,
++ "gnutls_server_name_set() failed: %s",
++ gnutls_strerror(ret)));
++ }
++ }
++
++ /* Start handshake */
++ ssock->connection_state = TLS_STATE_HANDSHAKING;
++
++ status = tls_try_handshake(ssock);
++ if (status != PJ_EPENDING)
++ goto on_return;
++
++ return PJ_TRUE;
++
++on_return:
++ return on_handshake_complete(ssock, status);
++}
++
++static void tls_ciphers_fill(void)
++{
++ if (!tls_available_ciphers) {
++ tls_init();
++ tls_deinit();
++ }
++}
++
++/*
++ *******************************************************************
++ * API
++ *******************************************************************
++ */
++
++/* Load credentials from files. */
++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);
++}
++
++/* Load credentials from files. */
++PJ_DECL(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_ssl_cert_t *cert;
++
++ PJ_ASSERT_RETURN(pool && (CA_file || CA_path) && cert_file &&
++ privkey_file,
++ PJ_EINVAL);
++
++ cert = PJ_POOL_ZALLOC_T(pool, pj_ssl_cert_t);
++ if (CA_file) {
++ pj_strdup_with_null(pool, &cert->CA_file, CA_file);
++ }
++ if (CA_path) {
++ pj_strdup_with_null(pool, &cert->CA_path, CA_path);
++ }
++ pj_strdup_with_null(pool, &cert->cert_file, cert_file);
++ pj_strdup_with_null(pool, &cert->privkey_file, privkey_file);
++ pj_strdup_with_null(pool, &cert->privkey_pass, privkey_pass);
++
++ *p_cert = cert;
++
++ return PJ_SUCCESS;
++}
++
++/* Store credentials. */
++PJ_DECL(pj_status_t) pj_ssl_sock_set_certificate(pj_ssl_sock_t *ssock,
++ pj_pool_t *pool,
++ const pj_ssl_cert_t *cert)
++{
++ pj_ssl_cert_t *cert_;
++
++ PJ_ASSERT_RETURN(ssock && pool && cert, PJ_EINVAL);
++
++ 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_->CA_path, &cert->CA_path);
++ 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;
++}
++
++
++/* Get available ciphers. */
++PJ_DEF(pj_status_t) pj_ssl_cipher_get_availables(pj_ssl_cipher ciphers[],
++ unsigned *cipher_num)
++{
++ unsigned int i;
++
++ PJ_ASSERT_RETURN(ciphers && cipher_num, PJ_EINVAL);
++
++ tls_ciphers_fill();
++
++ if (!tls_available_ciphers) {
++ *cipher_num = 0;
++ return PJ_ENOTFOUND;
++ }
++
++ *cipher_num = PJ_MIN(*cipher_num, tls_available_ciphers);
++
++ for (i = 0; i < *cipher_num; ++i)
++ ciphers[i] = tls_ciphers[i].id;
++
++ return PJ_SUCCESS;
++}
++
++
++/* Get cipher name string. */
++PJ_DEF(const char *)pj_ssl_cipher_name(pj_ssl_cipher cipher)
++{
++ unsigned int i;
++
++ tls_ciphers_fill();
++
++ for (i = 0; i < tls_available_ciphers; ++i) {
++ if (cipher == tls_ciphers[i].id)
++ return tls_ciphers[i].name;
++ }
++
++ return NULL;
++}
++
++
++/* Get cipher identifier. */
++PJ_DEF(pj_ssl_cipher) pj_ssl_cipher_id(const char *cipher_name)
++{
++ unsigned int i;
++
++ tls_ciphers_fill();
++
++ for (i = 0; i < tls_available_ciphers; ++i) {
++ if (!pj_ansi_stricmp(tls_ciphers[i].name, cipher_name))
++ return tls_ciphers[i].id;
++ }
++
++ return PJ_TLS_UNKNOWN_CIPHER;
++}
++
++
++/* Check if the specified cipher is supported by the TLS backend. */
++PJ_DEF(pj_bool_t) pj_ssl_cipher_is_supported(pj_ssl_cipher cipher)
++{
++ unsigned int i;
++
++ tls_ciphers_fill();
++
++ for (i = 0; i < tls_available_ciphers; ++i) {
++ if (cipher == tls_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_status_t status;
++
++ PJ_ASSERT_RETURN(pool && param && p_ssock, PJ_EINVAL);
++ PJ_ASSERT_RETURN(param->sock_type == pj_SOCK_STREAM(), PJ_ENOTSUP);
++
++ pool = pj_pool_create(pool->factory, "tls%p", 512, 512, NULL);
++
++ /* Create secure socket */
++ ssock = PJ_POOL_ZALLOC_T(pool, pj_ssl_sock_t);
++ ssock->pool = pool;
++ ssock->sock = PJ_INVALID_SOCKET;
++ ssock->connection_state = TLS_STATE_NULL;
++ pj_list_init(&ssock->write_pending);
++ pj_list_init(&ssock->write_pending_empty);
++ pj_list_init(&ssock->send_pending);
++ pj_timer_entry_init(&ssock->timer, 0, ssock, &on_timer);
++ pj_ioqueue_op_key_init(&ssock->handshake_op_key,
++ sizeof(pj_ioqueue_op_key_t));
++
++ /* Create secure socket mutex */
++ status = pj_lock_create_recursive_mutex(pool, pool->obj_name,
++ &ssock->circ_buf_output_mutex);
++ if (status != PJ_SUCCESS)
++ return status;
++
++ /* Create input circular buffer mutex */
++ status = pj_lock_create_simple_mutex(pool, pool->obj_name,
++ &ssock->circ_buf_input_mutex);
++ if (status != PJ_SUCCESS)
++ return status;
++
++ /* Create output circular buffer mutex */
++ status = pj_lock_create_simple_mutex(pool, pool->obj_name,
++ &ssock->circ_buf_output_mutex);
++ if (status != PJ_SUCCESS)
++ return status;
++
++ /* Init secure socket param */
++ ssock->param = *param;
++ ssock->param.read_buffer_size = ((ssock->param.read_buffer_size + 7) >> 3) << 3;
++
++ if (param->ciphers_num > 0) {
++ unsigned int i;
++ ssock->param.ciphers = (pj_ssl_cipher *)
++ pj_pool_calloc(pool, param->ciphers_num,
++ sizeof(pj_ssl_cipher));
++ if (!ssock->param.ciphers)
++ return PJ_ENOMEM;
++
++ for (i = 0; i < param->ciphers_num; ++i)
++ ssock->param.ciphers[i] = param->ciphers[i];
++ }
++
++ /* Server name must be null-terminated */
++ pj_strdup_with_null(pool, &ssock->param.server_name, ¶m->server_name);
++
++ /* Finally */
++ *p_ssock = ssock;
++
++ return PJ_SUCCESS;
++}
++
++
++/*
++ * Close the secure socket. This will unregister the socket from the
++ * ioqueue and ultimately close the socket.
++ */
++PJ_DEF(pj_status_t) pj_ssl_sock_close(pj_ssl_sock_t *ssock)
++{
++ pj_pool_t *pool;
++
++ PJ_ASSERT_RETURN(ssock, PJ_EINVAL);
++
++ if (!ssock->pool)
++ return PJ_SUCCESS;
++
++ if (ssock->timer.id != TIMER_NONE) {
++ pj_timer_heap_cancel(ssock->param.timer_heap, &ssock->timer);
++ ssock->timer.id = TIMER_NONE;
++ }
++
++ tls_sock_reset(ssock);
++
++ pj_lock_destroy(ssock->circ_buf_output_mutex);
++ pj_lock_destroy(ssock->circ_buf_input_mutex);
++
++ pool = ssock->pool;
++ ssock->pool = NULL;
++ if (pool)
++ pj_pool_release(pool);
++
++ return PJ_SUCCESS;
++}
++
++
++/* Associate arbitrary data with the secure 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->param.user_data = user_data;
++ return PJ_SUCCESS;
++}
++
++
++/* Retrieve the user data previously associated with this secure socket. */
++PJ_DEF(void *)pj_ssl_sock_get_user_data(pj_ssl_sock_t *ssock)
++{
++ PJ_ASSERT_RETURN(ssock, NULL);
++
++ return ssock->param.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_bzero(info, sizeof(*info));
++
++ /* Established flag */
++ info->established = (ssock->connection_state == TLS_STATE_ESTABLISHED);
++
++ /* Protocol */
++ info->proto = ssock->param.proto;
++
++ /* Local address */
++ pj_sockaddr_cp(&info->local_addr, &ssock->local_addr);
++
++ if (info->established) {
++ int i;
++ gnutls_cipher_algorithm_t lookup;
++ gnutls_cipher_algorithm_t cipher;
++
++ /* Current cipher */
++ cipher = gnutls_cipher_get(ssock->session);
++ for (i = 0; ; i++) {
++ unsigned char id[2];
++ const char *suite = gnutls_cipher_suite_info(i, (unsigned char *)id,
++ NULL, &lookup, NULL,
++ NULL);
++ if (suite) {
++ if (lookup == cipher) {
++ info->cipher = (pj_uint32_t) ((id[0] << 8) | id[1]);
++ break;
++ }
++ } else
++ break;
++ }
++
++ /* 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;
++
++ /* Verification status */
++ info->verify_status = ssock->verify_status;
++ }
++
++ /* Last known GnuTLS error code */
++ info->last_native_err = ssock->last_err;
++
++ return PJ_SUCCESS;
++}
++
++
++/* Starts read operation on this secure 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)
++{
++ void **readbuf;
++ unsigned int i;
++
++ PJ_ASSERT_RETURN(ssock && pool && buff_size, PJ_EINVAL);
++ PJ_ASSERT_RETURN(ssock->connection_state == TLS_STATE_ESTABLISHED,
++ PJ_EINVALIDOP);
++
++ readbuf = (void**) pj_pool_calloc(pool, ssock->param.async_cnt,
++ sizeof(void *));
++ if (!readbuf)
++ return PJ_ENOMEM;
++
++ for (i = 0; i < ssock->param.async_cnt; ++i) {
++ readbuf[i] = pj_pool_alloc(pool, buff_size);
++ if (!readbuf[i])
++ return PJ_ENOMEM;
++ }
++
++ return pj_ssl_sock_start_read2(ssock, pool, buff_size, readbuf, flags);
++}
++
++
++/*
++ * 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)
++{
++ unsigned int i;
++
++ PJ_ASSERT_RETURN(ssock && pool && buff_size && readbuf, PJ_EINVAL);
++ PJ_ASSERT_RETURN(ssock->connection_state == TLS_STATE_ESTABLISHED,
++ PJ_EINVALIDOP);
++
++ /* Create SSL socket read buffer */
++ ssock->ssock_rbuf = (read_data_t*)pj_pool_calloc(pool,
++ ssock->param.async_cnt,
++ sizeof(read_data_t));
++ if (!ssock->ssock_rbuf)
++ return PJ_ENOMEM;
++
++ /* Store SSL socket read buffer pointer in the activesock read buffer */
++ for (i = 0; i < ssock->param.async_cnt; ++i) {
++ read_data_t **p_ssock_rbuf =
++ OFFSET_OF_READ_DATA_PTR(ssock, ssock->asock_rbuf[i]);
++
++ ssock->ssock_rbuf[i].data = readbuf[i];
++ ssock->ssock_rbuf[i].len = 0;
++
++ *p_ssock_rbuf = &ssock->ssock_rbuf[i];
++ }
++
++ ssock->read_size = buff_size;
++ ssock->read_started = PJ_TRUE;
++ ssock->read_flags = flags;
++
++ 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;
++}
++
++
++/*
++ * Write the plain data to GnuTLS, it will be encrypted by gnutls_record_send()
++ * and sent via tls_data_push. Note that re-negotitation may be on progress, so
++ * sending data should be delayed until re-negotiation is completed.
++ */
++static pj_status_t tls_write(pj_ssl_sock_t *ssock,
++ pj_ioqueue_op_key_t *send_key,
++ const void *data, pj_ssize_t size, unsigned flags)
++{
++ pj_status_t status;
++ int nwritten;
++ pj_ssize_t total_written = 0;
++
++ /* Ask GnuTLS to encrypt our plaintext now. GnuTLS will use the push
++ * callback to actually write the encrypted bytes into our output circular
++ * buffer. GnuTLS may refuse to "send" everything at once, but since we are
++ * not really sending now, we will just call it again now until it succeeds
++ * (or fails in a fatal way). */
++ while (total_written < size) {
++ /* Try encrypting using GnuTLS */
++ nwritten = gnutls_record_send(ssock->session, ((read_data_t *)data) + total_written,
++ size);
++
++ if (nwritten > 0) {
++ /* Good, some data was encrypted and written */
++ total_written += nwritten;
++ } else {
++ /* Normally we would have to retry record_send but our internal
++ * state has not changed, so we have to ask for more data first.
++ * We will just try again later, although this should never happen.
++ */
++ return tls_status_from_err(ssock, nwritten);
++ }
++ }
++
++ /* All encrypted data is written to the output circular buffer;
++ * now send it on the socket (or notify problem). */
++ if (total_written == size)
++ status = flush_circ_buf_output(ssock, send_key, size, flags);
++ else
++ status = PJ_ENOMEM;
++
++ return status;
++}
++
++
++/* Flush delayed data sending in the write pending list. */
++static pj_status_t flush_delayed_send(pj_ssl_sock_t *ssock)
++{
++ /* Check for another ongoing flush */
++ if (ssock->flushing_write_pend) {
++ return PJ_EBUSY;
++ }
++
++ pj_lock_acquire(ssock->circ_buf_output_mutex);
++
++ /* Again, check for another ongoing flush */
++ if (ssock->flushing_write_pend) {
++ pj_lock_release(ssock->circ_buf_output_mutex);
++ return PJ_EBUSY;
++ }
++
++ /* Set ongoing flush flag */
++ ssock->flushing_write_pend = PJ_TRUE;
++
++ while (!pj_list_empty(&ssock->write_pending)) {
++ write_data_t *wp;
++ pj_status_t status;
++
++ wp = ssock->write_pending.next;
++
++ /* Ticket #1573: Don't hold mutex while calling socket send. */
++ pj_lock_release(ssock->circ_buf_output_mutex);
++
++ status = tls_write(ssock, &wp->key, wp->data.ptr,
++ wp->plain_data_len, wp->flags);
++ if (status != PJ_SUCCESS) {
++ /* Reset ongoing flush flag first. */
++ ssock->flushing_write_pend = PJ_FALSE;
++ return status;
++ }
++
++ pj_lock_acquire(ssock->circ_buf_output_mutex);
++ pj_list_erase(wp);
++ pj_list_push_back(&ssock->write_pending_empty, wp);
++ }
++
++ /* Reset ongoing flush flag */
++ ssock->flushing_write_pend = PJ_FALSE;
++
++ pj_lock_release(ssock->circ_buf_output_mutex);
++
++ return PJ_SUCCESS;
++}
++
++
++/* Sending is delayed, push back the sending data into pending list. */
++static pj_status_t delay_send(pj_ssl_sock_t *ssock,
++ pj_ioqueue_op_key_t *send_key,
++ const void *data, pj_ssize_t size,
++ unsigned flags)
++{
++ write_data_t *wp;
++
++ pj_lock_acquire(ssock->circ_buf_output_mutex);
++
++ /* Init write pending instance */
++ if (!pj_list_empty(&ssock->write_pending_empty)) {
++ wp = ssock->write_pending_empty.next;
++ pj_list_erase(wp);
++ } else {
++ wp = PJ_POOL_ZALLOC_T(ssock->pool, write_data_t);
++ }
++
++ wp->app_key = send_key;
++ wp->plain_data_len = size;
++ wp->data.ptr = data;
++ wp->flags = flags;
++
++ pj_list_push_back(&ssock->write_pending, wp);
++
++ pj_lock_release(ssock->circ_buf_output_mutex);
++
++ /* Must return PJ_EPENDING */
++ return PJ_EPENDING;
++}
++
++
++/**
++ * 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_status_t status;
++
++ PJ_ASSERT_RETURN(ssock && data && size && (*size > 0), PJ_EINVAL);
++ PJ_ASSERT_RETURN(ssock->connection_state==TLS_STATE_ESTABLISHED,
++ PJ_EINVALIDOP);
++
++ /* Flush delayed send first. Sending data might be delayed when
++ * re-negotiation is on-progress. */
++ status = flush_delayed_send(ssock);
++ if (status == PJ_EBUSY) {
++ /* Re-negotiation or flushing is on progress, delay sending */
++ status = delay_send(ssock, send_key, data, *size, flags);
++ goto on_return;
++ } else if (status != PJ_SUCCESS) {
++ goto on_return;
++ }
++
++ /* Write data to SSL */
++ status = tls_write(ssock, send_key, data, *size, flags);
++ if (status == PJ_EBUSY) {
++ /* Re-negotiation is on progress, delay sending */
++ status = delay_send(ssock, send_key, data, *size, flags);
++ }
++
++on_return:
++ return status;
++}
++
++
++/**
++ * 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 secure socket.
++ */
++PJ_DEF(pj_status_t) pj_ssl_sock_start_accept (pj_ssl_sock_t *ssock,
++ pj_pool_t *pool,
++ const pj_sockaddr_t *localaddr,
++ int addr_len)
++{
++ return pj_ssl_sock_start_accept2(ssock, pool, localaddr, addr_len,
++ &ssock->param);
++}
++
++/**
++ * Starts asynchronous socket accept() operations on this secure socket.
++ */
++PJ_DEF(pj_status_t) pj_ssl_sock_start_accept2 (pj_ssl_sock_t *ssock,
++ pj_pool_t *pool,
++ const pj_sockaddr_t *localaddr,
++ int addr_len,
++ const pj_ssl_sock_param *newsock_param)
++{
++ pj_activesock_cb asock_cb;
++ pj_activesock_cfg asock_cfg;
++ pj_status_t status;
++
++ PJ_ASSERT_RETURN(ssock && pool && localaddr && addr_len, PJ_EINVAL);
++
++ /* Verify new socket parameters */
++ if (newsock_param->grp_lock != ssock->param.grp_lock ||
++ newsock_param->sock_af != ssock->param.sock_af ||
++ newsock_param->sock_type != ssock->param.sock_type)
++ {
++ return PJ_EINVAL;
++ }
++
++ /* Create socket */
++ status = pj_sock_socket(ssock->param.sock_af, ssock->param.sock_type, 0,
++ &ssock->sock);
++ if (status != PJ_SUCCESS)
++ goto on_error;
++
++ /* Apply SO_REUSEADDR */
++ if (ssock->param.reuse_addr) {
++ int enabled = 1;
++ status = pj_sock_setsockopt(ssock->sock, pj_SOL_SOCKET(),
++ pj_SO_REUSEADDR(),
++ &enabled, sizeof(enabled));
++ if (status != PJ_SUCCESS) {
++ PJ_PERROR(4,(ssock->pool->obj_name, status,
++ "Warning: error applying SO_REUSEADDR"));
++ }
++ }
++
++ /* Apply QoS, if specified */
++ status = pj_sock_apply_qos2(ssock->sock, ssock->param.qos_type,
++ &ssock->param.qos_params, 2,
++ ssock->pool->obj_name, NULL);
++ if (status != PJ_SUCCESS && !ssock->param.qos_ignore_error)
++ goto on_error;
++
++ /* Bind socket */
++ status = pj_sock_bind(ssock->sock, localaddr, addr_len);
++ if (status != PJ_SUCCESS)
++ goto on_error;
++
++ /* Start listening to the address */
++ status = pj_sock_listen(ssock->sock, PJ_SOMAXCONN);
++ if (status != PJ_SUCCESS)
++ goto on_error;
++
++ /* Create active socket */
++ pj_activesock_cfg_default(&asock_cfg);
++ asock_cfg.async_cnt = ssock->param.async_cnt;
++ asock_cfg.concurrency = ssock->param.concurrency;
++ asock_cfg.whole_data = PJ_TRUE;
++
++ pj_bzero(&asock_cb, sizeof(asock_cb));
++ asock_cb.on_accept_complete = asock_on_accept_complete;
++
++ status = pj_activesock_create(pool,
++ ssock->sock,
++ ssock->param.sock_type,
++ &asock_cfg,
++ ssock->param.ioqueue,
++ &asock_cb,
++ ssock,
++ &ssock->asock);
++
++ if (status != PJ_SUCCESS)
++ goto on_error;
++
++ /* Start accepting */
++ pj_ssl_sock_param_copy(pool, &ssock->newsock_param, newsock_param);
++ status = pj_activesock_start_accept(ssock->asock, pool);
++ if (status != PJ_SUCCESS)
++ goto on_error;
++
++ /* Update local address */
++ ssock->addr_len = addr_len;
++ status = pj_sock_getsockname(ssock->sock, &ssock->local_addr,
++ &ssock->addr_len);
++ if (status != PJ_SUCCESS)
++ pj_sockaddr_cp(&ssock->local_addr, localaddr);
++
++ ssock->is_server = PJ_TRUE;
++
++ return PJ_SUCCESS;
++
++on_error:
++ tls_sock_reset(ssock);
++ return status;
++}
++
++
++/**
++ * Starts asynchronous socket connect() operation.
++ */
++PJ_DECL(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)
++{
++ pj_activesock_cb asock_cb;
++ pj_activesock_cfg asock_cfg;
++ pj_status_t status;
++
++ PJ_ASSERT_RETURN(ssock && pool && localaddr && remaddr && addr_len,
++ PJ_EINVAL);
++
++ /* Create socket */
++ status = pj_sock_socket(ssock->param.sock_af, ssock->param.sock_type, 0,
++ &ssock->sock);
++ if (status != PJ_SUCCESS)
++ goto on_error;
++
++ /* Apply QoS, if specified */
++ status = pj_sock_apply_qos2(ssock->sock, ssock->param.qos_type,
++ &ssock->param.qos_params, 2,
++ ssock->pool->obj_name, NULL);
++ if (status != PJ_SUCCESS && !ssock->param.qos_ignore_error)
++ goto on_error;
++
++ /* Bind socket */
++ status = pj_sock_bind(ssock->sock, localaddr, addr_len);
++ if (status != PJ_SUCCESS)
++ goto on_error;
++
++ /* Create active socket */
++ pj_activesock_cfg_default(&asock_cfg);
++ asock_cfg.async_cnt = ssock->param.async_cnt;
++ asock_cfg.concurrency = ssock->param.concurrency;
++ asock_cfg.whole_data = PJ_TRUE;
++
++ pj_bzero(&asock_cb, sizeof(asock_cb));
++ asock_cb.on_connect_complete = asock_on_connect_complete;
++ asock_cb.on_data_read = asock_on_data_read;
++ asock_cb.on_data_sent = asock_on_data_sent;
++
++ status = pj_activesock_create(pool,
++ ssock->sock,
++ ssock->param.sock_type,
++ &asock_cfg,
++ ssock->param.ioqueue,
++ &asock_cb,
++ ssock,
++ &ssock->asock);
++
++ if (status != PJ_SUCCESS)
++ goto on_error;
++
++ /* Save remote address */
++ pj_sockaddr_cp(&ssock->rem_addr, remaddr);
++
++ /* Start timer */
++ if (ssock->param.timer_heap &&
++ (ssock->param.timeout.sec != 0 || ssock->param.timeout.msec != 0))
++ {
++ pj_assert(ssock->timer.id == TIMER_NONE);
++ ssock->timer.id = TIMER_HANDSHAKE_TIMEOUT;
++ status = pj_timer_heap_schedule(ssock->param.timer_heap,
++ &ssock->timer,
++ &ssock->param.timeout);
++ if (status != PJ_SUCCESS)
++ ssock->timer.id = TIMER_NONE;
++ }
++
++ status = pj_activesock_start_connect(ssock->asock, pool, remaddr,
++ addr_len);
++
++ if (status == PJ_SUCCESS)
++ asock_on_connect_complete(ssock->asock, PJ_SUCCESS);
++ else if (status != PJ_EPENDING)
++ goto on_error;
++
++ /* Update local address */
++ ssock->addr_len = addr_len;
++ status = pj_sock_getsockname(ssock->sock, &ssock->local_addr,
++ &ssock->addr_len);
++ /* Note that we may not get an IP address here. This can
++ * happen for example on Windows, where getsockname()
++ * would return 0.0.0.0 if socket has just started the
++ * async connect. In this case, just leave the local
++ * address with 0.0.0.0 for now; it will be updated
++ * once the socket is established.
++ */
++
++ /* Update socket state */
++ ssock->is_server = PJ_FALSE;
++
++ return PJ_EPENDING;
++
++on_error:
++ tls_sock_reset(ssock);
++ return status;
++}
++
++
++PJ_DEF(pj_status_t) pj_ssl_sock_renegotiate(pj_ssl_sock_t *ssock)
++{
++ int status;
++
++ /* Nothing established yet */
++ PJ_ASSERT_RETURN(ssock->connection_state == TLS_STATE_ESTABLISHED,
++ PJ_EINVALIDOP);
++
++ /* Cannot renegotiate; we're a client */
++ /* FIXME: in fact maybe that's not true */
++ PJ_ASSERT_RETURN(!ssock->is_server, PJ_EINVALIDOP);
++
++ /* First call gnutls_rehandshake() to see if this is even possible */
++ status = gnutls_rehandshake(ssock->session);
++
++ if (status == GNUTLS_E_SUCCESS) {
++ /* Rehandshake is possible, so try a GnuTLS handshake now. The eventual
++ * gnutls_record_recv() calls could return a few specific values during
++ * this state:
++ *
++ * - GNUTLS_E_REHANDSHAKE: rehandshake message processing
++ * - GNUTLS_E_WARNING_ALERT_RECEIVED: client does not wish to
++ * renegotiate
++ */
++ ssock->connection_state = TLS_STATE_HANDSHAKING;
++ status = tls_try_handshake(ssock);
++
++ return status;
++ } else {
++ return tls_status_from_err(ssock, status);
++ }
++}
++
++#endif /* PJ_HAS_SSL_SOCK */
+diff -ru a/pjlib/src/pj/ssl_sock_ossl.c b/pjlib/src/pj/ssl_sock_ossl.c
+--- a/pjlib/src/pj/ssl_sock_ossl.c 2017-01-24 00:41:05.000000000 -0500
++++ b/pjlib/src/pj/ssl_sock_ossl.c 2017-06-08 13:42:15.188809557 -0400
+@@ -32,8 +32,10 @@
+ #include <pj/timer.h>
+
+
+-/* Only build when PJ_HAS_SSL_SOCK is enabled */
+-#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK!=0
++/* Only build when PJ_HAS_SSL_SOCK is enabled and when PJ_HAS_TLS_SOCK is
++ * disabled (meaning GnuTLS is off) */
++#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 && \
++ defined(PJ_HAS_TLS_SOCK) && PJ_HAS_TLS_SOCK == 0
+
+ #define THIS_FILE "ssl_sock_ossl.c"
+
+diff -ru a/pjmedia/src/pjmedia/transport_srtp.c b/pjmedia/src/pjmedia/transport_srtp.c
+--- a/pjmedia/src/pjmedia/transport_srtp.c 2017-01-10 23:38:29.000000000 -0500
++++ b/pjmedia/src/pjmedia/transport_srtp.c 2017-06-08 13:43:29.727001721 -0400
+@@ -30,7 +30,8 @@
+
+ #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+
+-#if defined(PJ_HAS_SSL_SOCK) && (PJ_HAS_SSL_SOCK != 0)
++#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 && \
++ defined(PJ_HAS_TLS_SOCK) && PJ_HAS_TLS_SOCK == 0
+ # include <openssl/rand.h>
+
+ /* Suppress compile warning of OpenSSL deprecation (OpenSSL is deprecated
+@@ -1147,7 +1148,8 @@
+ key_ok = PJ_TRUE;
+
+
+-#if defined(PJ_HAS_SSL_SOCK) && (PJ_HAS_SSL_SOCK != 0)
++#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 && \
++ defined(PJ_HAS_TLS_SOCK) && PJ_HAS_TLS_SOCK == 0
+
+ /* Include OpenSSL libraries for MSVC */
+ # ifdef _MSC_VER
+
Added: pjproject-savoirfairelinux/trunk/ice_config.patch
===================================================================
--- pjproject-savoirfairelinux/trunk/ice_config.patch (rev 0)
+++ pjproject-savoirfairelinux/trunk/ice_config.patch 2017-12-31 18:25:20 UTC (rev 277599)
@@ -0,0 +1,46 @@
+Description:
+ Increase PJ_ICE_MAX_CAND to 256. Upstream's default, 16, is
+ not enough to handle cases with numerous local/remote candidates
+ like the case when multiple hosts in IPv6 and IPv4 exists.
+ Tests show that 40 entries are needed with 5 local host IPs.
+ .
+ Increase PJ_ICE_MAX_CHECKS, which would be way too low for the
+ number of candidates.
+ .
+ Increase PJ_ICE_MAX_STUN to 3:
+ - One for IPv4
+ - One for IPv6
+ - One set by the user
+ .
+ Increase PJ_ICE_COMP_BITS so that we can have more components:
+ - 2 Audio components
+ - 2 Video components
+Author: Guillaume Roguez <guillaume.roguez at savoirfairelinux.com>
+
+--- a/pjnath/include/pjnath/config.h
++++ b/pjnath/include/pjnath/config.h
+@@ -233,3 +233,3 @@
+ #ifndef PJ_ICE_MAX_CAND
+-# define PJ_ICE_MAX_CAND 16
++# define PJ_ICE_MAX_CAND 256
+ #endif
+@@ -243,3 +243,3 @@
+ #ifndef PJ_ICE_ST_MAX_CAND
+-# define PJ_ICE_ST_MAX_CAND 8
++# define PJ_ICE_ST_MAX_CAND 32
+ #endif
+@@ -254,3 +254,3 @@
+ #ifndef PJ_ICE_MAX_STUN
+-# define PJ_ICE_MAX_STUN 2
++# define PJ_ICE_MAX_STUN 3
+ #endif
+@@ -274,3 +274,3 @@
+ #ifndef PJ_ICE_COMP_BITS
+-# define PJ_ICE_COMP_BITS 1
++# define PJ_ICE_COMP_BITS 2
+ #endif
+@@ -325,3 +325,3 @@
+ #ifndef PJ_ICE_MAX_CHECKS
+-# define PJ_ICE_MAX_CHECKS 32
++# define PJ_ICE_MAX_CHECKS 150
+ #endif
Added: pjproject-savoirfairelinux/trunk/ipv6.patch
===================================================================
--- pjproject-savoirfairelinux/trunk/ipv6.patch (rev 0)
+++ pjproject-savoirfairelinux/trunk/ipv6.patch 2017-12-31 18:25:20 UTC (rev 277599)
@@ -0,0 +1,11 @@
+--- a/pjlib/include/pj/config.h
++++ b/pjlib/include/pj/config.h
+@@ -549,7 +549,7 @@
+ * Default: 0 (disabled, for now)
+ */
+ #ifndef PJ_HAS_IPV6
+-# define PJ_HAS_IPV6 0
++# define PJ_HAS_IPV6 1
+ #endif
+
+ /**
Added: pjproject-savoirfairelinux/trunk/multiple_listeners.patch
===================================================================
--- pjproject-savoirfairelinux/trunk/multiple_listeners.patch (rev 0)
+++ pjproject-savoirfairelinux/trunk/multiple_listeners.patch 2017-12-31 18:25:20 UTC (rev 277599)
@@ -0,0 +1,66 @@
+diff --git a/pjproject/pjsip/src/pjsip/sip_transport.c b/pjproject_new/pjsip/src/pjsip/sip_transport.c
+index 4b2cc1a..22e3603 100644
+--- a/pjsip/src/pjsip/sip_transport.c
++++ b/pjsip/src/pjsip/sip_transport.c
+@@ -1248,22 +1248,22 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_register_tpfactory( pjsip_tpmgr *mgr,
+
+ pj_lock_acquire(mgr->lock);
+
+- /* Check that no factory with the same type has been registered. */
++ /* Check that no factory with the same type and bound address has been registered. */
+ status = PJ_SUCCESS;
+ for (p=mgr->factory_list.next; p!=&mgr->factory_list; p=p->next) {
+- if (p->type == tpf->type) {
+- status = PJSIP_ETYPEEXISTS;
+- break;
+- }
+- if (p == tpf) {
+- status = PJ_EEXISTS;
+- break;
+- }
++ if (p->type == tpf->type && !pj_sockaddr_cmp(&tpf->local_addr, &p->local_addr)) {
++ status = PJSIP_ETYPEEXISTS;
++ break;
++ }
++ if (p == tpf) {
++ status = PJ_EEXISTS;
++ break;
++ }
+ }
+
+ if (status != PJ_SUCCESS) {
+- pj_lock_release(mgr->lock);
+- return status;
++ pj_lock_release(mgr->lock);
++ return status;
+ }
+
+ pj_list_insert_before(&mgr->factory_list, tpf);
+@@ -2047,13 +2047,11 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport2(pjsip_tpmgr *mgr,
+ pj_memcpy(&key.rem_addr, remote, addr_len);
+
+ transport = (pjsip_transport*)
+- pj_hash_get(mgr->table, &key, key_len, NULL);
+-
++ pj_hash_get(mgr->table, &key, key_len, NULL);
++ unsigned flag = pjsip_transport_get_flag_from_type(type);
+ if (transport == NULL) {
+- unsigned flag = pjsip_transport_get_flag_from_type(type);
+ const pj_sockaddr *remote_addr = (const pj_sockaddr*)remote;
+
+-
+ /* Ignore address for loop transports. */
+ if (type == PJSIP_TRANSPORT_LOOP ||
+ type == PJSIP_TRANSPORT_LOOP_DGRAM)
+@@ -2135,6 +2135,11 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport2(pjsip_tpmgr *mgr,
+ }
+
+ } else {
++ /* Make sure we don't use another factory than the one given if secure flag is set */
++ if (flag & PJSIP_TRANSPORT_SECURE) {
++ TRACE_((THIS_FILE, "Can't create new TLS transport with no provided suitable TLS listener."));
++ return PJSIP_ETPNOTSUITABLE;
++ }
+
+ /* Find factory with type matches the destination type */
+ factory = mgr->factory_list.next;
Added: pjproject-savoirfairelinux/trunk/notestsapps.patch
===================================================================
--- pjproject-savoirfairelinux/trunk/notestsapps.patch (rev 0)
+++ pjproject-savoirfairelinux/trunk/notestsapps.patch 2017-12-31 18:25:20 UTC (rev 277599)
@@ -0,0 +1,97 @@
+diff --git a/Makefile b/Makefile
+index 33a4e6b..a486eb7 100644
+--- a/Makefile
++++ b/Makefile
+@@ -4,7 +4,7 @@ include build/host-$(HOST_NAME).mak
+ include version.mak
+
+ LIB_DIRS = pjlib/build pjlib-util/build pjnath/build third_party/build pjmedia/build pjsip/build
+-DIRS = $(LIB_DIRS) pjsip-apps/build $(EXTRA_DIRS)
++DIRS = $(LIB_DIRS) $(EXTRA_DIRS)
+
+ ifdef MINSIZE
+ MAKE_FLAGS := MINSIZE=1
+diff --git a/pjlib-util/build/Makefile b/pjlib-util/build/Makefile
+index cb601cb..862a78a 100644
+--- a/pjlib-util/build/Makefile
++++ b/pjlib-util/build/Makefile
+@@ -54,7 +54,6 @@ export UTIL_TEST_OBJS += xml.o encryption.o stun.o resolver_test.o test.o \
+ export UTIL_TEST_CFLAGS += $(_CFLAGS)
+ export UTIL_TEST_CXXFLAGS += $(_CXXFLAGS)
+ export UTIL_TEST_LDFLAGS += $(PJLIB_UTIL_LDLIB) $(PJLIB_LDLIB) $(_LDFLAGS)
+-export UTIL_TEST_EXE:=pjlib-util-test-$(TARGET_NAME)$(HOST_EXE)
+
+
+ export CC_OUT CC AR RANLIB HOST_MV HOST_RM HOST_RMDIR HOST_MKDIR OBJEXT LD LDOUT
+diff --git a/pjlib/build/Makefile b/pjlib/build/Makefile
+index 1e64950..a75fa65 100644
+--- a/pjlib/build/Makefile
++++ b/pjlib/build/Makefile
+@@ -56,7 +56,6 @@ export TEST_OBJS += activesock.o atomic.o echo_clt.o errno.o exception.o \
+ export TEST_CFLAGS += $(_CFLAGS)
+ export TEST_CXXFLAGS += $(_CXXFLAGS)
+ export TEST_LDFLAGS += $(PJLIB_LDLIB) $(_LDFLAGS)
+-export TEST_EXE := pjlib-test-$(TARGET_NAME)$(HOST_EXE)
+
+
+ export CC_OUT CC AR RANLIB HOST_MV HOST_RM HOST_RMDIR HOST_MKDIR OBJEXT LD LDOUT
+diff --git a/pjmedia/build/Makefile b/pjmedia/build/Makefile
+index 8012cb7..2ca283a 100644
+--- a/pjmedia/build/Makefile
++++ b/pjmedia/build/Makefile
+@@ -165,7 +165,6 @@ export PJMEDIA_TEST_LDFLAGS += $(PJMEDIA_CODEC_LDLIB) \
+ $(PJLIB_UTIL_LDLIB) \
+ $(PJNATH_LDLIB) \
+ $(_LDFLAGS)
+-export PJMEDIA_TEST_EXE:=pjmedia-test-$(TARGET_NAME)$(HOST_EXE)
+
+
+ export CC_OUT CC AR RANLIB HOST_MV HOST_RM HOST_RMDIR HOST_MKDIR OBJEXT LD LDOUT
+diff --git a/pjnath/build/Makefile b/pjnath/build/Makefile
+index 1bc08b5..109f79b 100644
+--- a/pjnath/build/Makefile
++++ b/pjnath/build/Makefile
+@@ -54,7 +54,6 @@ export PJNATH_TEST_OBJS += ice_test.o stun.o sess_auth.o server.o concur_test.o
+ export PJNATH_TEST_CFLAGS += $(_CFLAGS)
+ export PJNATH_TEST_CXXFLAGS += $(_CXXFLAGS)
+ export PJNATH_TEST_LDFLAGS += $(PJNATH_LDLIB) $(PJLIB_UTIL_LDLIB) $(PJLIB_LDLIB) $(_LDFLAGS)
+-export PJNATH_TEST_EXE:=pjnath-test-$(TARGET_NAME)$(HOST_EXE)
+
+
+ ###############################################################################
+@@ -65,7 +64,6 @@ export PJTURN_CLIENT_OBJS += client_main.o
+ export PJTURN_CLIENT_CFLAGS += $(_CFLAGS)
+ export PJTURN_CLIENT_CXXFLAGS += $(_CXXFLAGS)
+ export PJTURN_CLIENT_LDFLAGS += $(PJNATH_LDLIB) $(PJLIB_UTIL_LDLIB) $(PJLIB_LDLIB) $(_LDFLAGS)
+-export PJTURN_CLIENT_EXE:=pjturn-client-$(TARGET_NAME)$(HOST_EXE)
+
+ ###############################################################################
+ # Defines for building TURN server application
+@@ -76,7 +74,6 @@ export PJTURN_SRV_OBJS += allocation.o auth.o listener_udp.o \
+ export PJTURN_SRV_CFLAGS += $(_CFLAGS)
+ export PJTURN_SRV_CXXFLAGS += $(_CXXFLAGS)
+ export PJTURN_SRV_LDFLAGS += $(PJNATH_LDLIB) $(PJLIB_UTIL_LDLIB) $(PJLIB_LDLIB) $(_LDFLAGS)
+-export PJTURN_SRV_EXE:=pjturn-srv-$(TARGET_NAME)$(HOST_EXE)
+
+
+
+diff --git a/pjsip/build/Makefile b/pjsip/build/Makefile
+index d2a5c2a..7e2ec60 100644
+--- a/pjsip/build/Makefile
++++ b/pjsip/build/Makefile
+@@ -165,7 +165,6 @@ export PJSUA2_TEST_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \
+ export PJSUA2_TEST_CFLAGS += $(_CFLAGS) $(PJ_VIDEO_CFLAGS)
+ export PJSUA2_TEST_CXXFLAGS = $(PJSUA2_LIB_CFLAGS)
+ export PJSUA2_TEST_LDFLAGS += $(PJ_LDXXFLAGS) $(PJ_LDXXLIBS) $(LDFLAGS)
+-export PJSUA2_TEST_EXE := pjsua2-test-$(TARGET_NAME)$(HOST_EXE)
+
+ export CC_OUT CC AR RANLIB HOST_MV HOST_RM HOST_RMDIR HOST_MKDIR OBJEXT LD LDOUT
+
+@@ -195,7 +194,6 @@ export TEST_LDFLAGS += $(PJSIP_LDLIB) \
+ $(PJLIB_UTIL_LDLIB) \
+ $(PJNATH_LDLIB) \
+ $(_LDFLAGS)
+-export TEST_EXE := pjsip-test-$(TARGET_NAME)$(HOST_EXE)
+
+
+ export CC_OUT CC AR RANLIB HOST_MV HOST_RM HOST_RMDIR HOST_MKDIR OBJEXT LD LDOUT
Added: pjproject-savoirfairelinux/trunk/pj_ice_sess.patch
===================================================================
--- pjproject-savoirfairelinux/trunk/pj_ice_sess.patch (rev 0)
+++ pjproject-savoirfairelinux/trunk/pj_ice_sess.patch 2017-12-31 18:25:20 UTC (rev 277599)
@@ -0,0 +1,29 @@
+Description: Expose the ice session to allow adding candidates.
+ .
+ Ring has other ways than stun to determine its IP address.
+ .
+ See ice_transport.cpp for usage details.
+Author: Stepan Salenikovich <stepan.salenikovich at savoirfairelinux.com>
+
+--- a/pjnath/include/pjnath/ice_strans.h
++++ b/pjnath/include/pjnath/ice_strans.h
+@@ -845,6 +845,8 @@ PJ_DECL(pj_status_t) pj_ice_strans_sendt
+ int dst_addr_len);
+
+
++PJ_DECL(pj_ice_sess *) pj_ice_strans_get_ice_sess(pj_ice_strans *ice_st);
++
+ /**
+ * @}
+ */
+--- a/pjnath/src/pjnath/ice_strans.c
++++ b/pjnath/src/pjnath/ice_strans.c
+@@ -1243,6 +1243,11 @@ PJ_DEF(pj_status_t) pj_ice_strans_sendto
+ return PJ_EINVALIDOP;
+ }
+
++PJ_DECL(pj_ice_sess *) pj_ice_strans_get_ice_sess( pj_ice_strans *ice_st )
++{
++ return ice_st->ice;
++}
++
Added: pjproject-savoirfairelinux/trunk/rfc6062.patch
===================================================================
--- pjproject-savoirfairelinux/trunk/rfc6062.patch (rev 0)
+++ pjproject-savoirfairelinux/trunk/rfc6062.patch 2017-12-31 18:25:20 UTC (rev 277599)
@@ -0,0 +1,919 @@
+Copyright (c) 2017 Savoir-faire Linux Inc.
+
+turn: rfc 6062 support
+
+Current implementation of TURN and STUN doesn't support TCP as a valid
+peer connection. This connection is defined by the rfc 6062.
+This patch is an implementation proposal of this rfc into PJNATH.
+Modifications are API backware compatible, not ABI.
+Users must rebuild their code.
+
+Written by
+Guillaume Roguez <guillaume.roguez at savoirfairelinux.com>
+on behalf of Savoir-faire Linux.
+
+----
+
+--- a/pjnath/include/pjnath/config.h
++++ b/pjnath/include/pjnath/config.h
+@@ -220,6 +220,14 @@
+ # define PJ_TURN_KEEP_ALIVE_SEC 15
+ #endif
+
++/**
++ * Maximal number of TCP data connection that a client can open/accept with
++ * peers.
++ */
++#ifndef PJ_TURN_MAX_TCP_CNX
++# define PJ_TURN_MAX_TCP_CNX 32
++#endif
++
+
+ /* **************************************************************************
+ * ICE CONFIGURATION
+--- a/pjnath/include/pjnath/stun_msg.h
++++ b/pjnath/include/pjnath/stun_msg.h
+@@ -92,6 +92,21 @@
+ */
+ PJ_STUN_CHANNEL_BIND_METHOD = 9,
+
++ /*
++ * STUN/TURN Connect as defined by RFC 6062
++ */
++ PJ_STUN_CONNECT_METHOD = 10,
++
++ /*
++ * STUN/TURN ConnectionBind as defined by RFC 6062
++ */
++ PJ_STUN_CONNECTION_BIND_METHOD = 11,
++
++ /*
++ * STUN/TURN ConnectionAttempt as defined by RFC 6062
++ */
++ PJ_STUN_CONNECTION_ATTEMPT_METHOD = 12,
++
+ /**
+ * All known methods.
+ */
+@@ -261,6 +276,16 @@
+ */
+ PJ_STUN_DATA_INDICATION = 0x0017,
+
++ /**
++ * STUN/TURN ConnectBind Request
++ */
++ PJ_STUN_CONNECTION_BIND_REQUEST = 0x000b,
++
++ /**
++ * TURN ConnectionAttempt indication
++ */
++ PJ_STUN_CONNECTION_ATTEMPT_INDICATION = 0x001c,
++
+
+ /**
+ * TURN CreatePermission request
+@@ -333,6 +358,7 @@
+ PJ_STUN_ATTR_XOR_REFLECTED_FROM = 0x0023,/**< XOR-REFLECTED-FROM */
+ PJ_STUN_ATTR_PRIORITY = 0x0024,/**< PRIORITY */
+ PJ_STUN_ATTR_USE_CANDIDATE = 0x0025,/**< USE-CANDIDATE */
++ PJ_STUN_ATTR_CONNECTION_ID = 0x002a,/**< CONNECTION-ID */
+ PJ_STUN_ATTR_ICMP = 0x0030,/**< ICMP (TURN) */
+
+ PJ_STUN_ATTR_END_MANDATORY_ATTR,
+--- a/pjnath/include/pjnath/stun_session.h
++++ b/pjnath/include/pjnath/stun_session.h
+@@ -751,6 +751,13 @@
+ pj_stun_tx_data *tdata);
+
+
++PJ_DEF(void) pj_stun_session_get_server_cred(
++ pj_stun_session *sess,
++ pj_pool_t *pool, pj_str_t *nonce, pj_str_t *realm);
++
++PJ_DEF(void) pj_stun_session_set_server_cred(
++ pj_stun_session *sess, const pj_str_t *nonce, pj_str_t *realm);
++
+ /**
+ * @}
+ */
+--- a/pjnath/include/pjnath/turn_session.h
++++ b/pjnath/include/pjnath/turn_session.h
+@@ -184,6 +184,12 @@
+ PJ_TURN_STATE_ALLOCATING,
+
+ /**
++ * TURN session has issued CONNECTION-BIND request and is waiting for response
++ * from the TURN server.
++ */
++ PJ_TURN_STATE_CONNECTION_BINDING,
++
++ /**
+ * TURN session has successfully allocated relay resoruce and now is
+ * ready to be used.
+ */
+@@ -298,6 +304,21 @@
+ pj_turn_state_t old_state,
+ pj_turn_state_t new_state);
+
++ /**
++ * Notification when TURN session get a ConnectionAttempt indication.
++ *
++ * @param sess The TURN session.
++ * @param conn_id The connection-id to use for connection binding.
++ * @param peer_addr Peer address that tried to connect on the TURN server.
++ * @param addr_len Length of the peer address.
++
++ */
++ void (*on_peer_connection)(pj_turn_session *sess,
++ pj_uint32_t conn_id,
++ const pj_sockaddr_t *peer_addr,
++ unsigned addr_len,
++ pj_status_t status);
++
+ } pj_turn_session_cb;
+
+
+@@ -339,6 +360,14 @@
+ */
+ int af;
+
++ /**
++ * Type of connection to from TURN server to peer.
++ *
++ * Supported values: PJ_TURN_TP_UDP (rfc 5766), PJ_TURN_TP_TLS (rfc 6062)
++ *
++ * Default is PJ_TURN_TP_UDP.
++ */
++ pj_turn_tp_type peer_conn_type;
+
+ } pj_turn_alloc_param;
+
+@@ -741,6 +770,15 @@
+ pj_size_t pkt_len,
+ pj_size_t *parsed_len);
+
++/**
++ * rfc6062
++ */
++PJ_DEF(void) pj_turn_session_get_server_cred(
++ pj_turn_session *sess,
++ pj_pool_t *pool, pj_str_t *nonce, pj_str_t *realm);
++
++PJ_DEF(void) pj_turn_session_set_server_cred(
++ pj_turn_session *sess, const pj_str_t *nonce, pj_str_t *realm);
+
+ /**
+ * @}
+--- a/pjnath/include/pjnath/turn_sock.h
++++ b/pjnath/include/pjnath/turn_sock.h
+@@ -98,6 +98,23 @@
+ pj_turn_state_t old_state,
+ pj_turn_state_t new_state);
+
++ /**
++ * Notification when TURN session get a ConnectionAttempt indication.
++ *
++ * @param turn_sock The TURN client transport.
++ * @param conn_id The connection-id to use for connection binding.
++ * @param peer_addr Peer address that tried to connect on the TURN server.
++ * @param addr_len Length of the peer address.
++ * @param status PJ_SUCCESS when connection is made, or any errors
++ * if the connection has failed (or if the peer has
++ * disconnected after an established connection).
++ */
++ void (*on_peer_connection)(pj_turn_sock *turn_sock,
++ pj_uint32_t conn_id,
++ const pj_sockaddr_t *peer_addr,
++ unsigned addr_len,
++ pj_status_t status);
++
+ } pj_turn_sock_cb;
+
+
+@@ -446,6 +463,13 @@
+ const pj_sockaddr_t *peer,
+ unsigned addr_len);
+
++/**
++ * RFC 6062
++ */
++PJ_DECL(pj_status_t) pj_turn_connect_peer(pj_turn_sock *sock,
++ pj_uint32_t conn_id,
++ const pj_sockaddr_t *peer_addr,
++ unsigned addr_len);
+
+ /**
+ * @}
+--- a/pjnath/src/pjnath/stun_msg.c
++++ b/pjnath/src/pjnath/stun_msg.c
+@@ -45,6 +45,9 @@
+ "Data", /* 7 */
+ "CreatePermission", /* 8 */
+ "ChannelBind", /* 9 */
++ "Connect", /* 10 */
++ "ConnectionBind", /* 11 */
++ "ConnectionAttempt", /* 12 */
+ };
+
+ static struct
+@@ -476,11 +479,11 @@
+ NULL
+ },
+ {
+- /* ID 0x002a is not assigned */
+- NULL,
+- NULL,
+- NULL,
+- NULL
++ /* PJ_STUN_ATTR_CONNECTION_ID, */
++ "CONNECTION-ID",
++ &decode_uint_attr,
++ &encode_uint_attr,
++ &clone_uint_attr
+ },
+ {
+ /* ID 0x002b is not assigned */
+--- a/pjnath/src/pjnath/stun_session.c
++++ b/pjnath/src/pjnath/stun_session.c
+@@ -1511,3 +1511,14 @@
+ return status;
+ }
+
++PJ_DEF(void) pj_stun_session_get_server_cred(pj_stun_session *sess, pj_pool_t *pool, pj_str_t *nonce, pj_str_t *realm)
++{
++ pj_strdup(pool, nonce, &sess->next_nonce);
++ pj_strdup(pool, realm, &sess->server_realm);
++}
++
++PJ_DEF(void) pj_stun_session_set_server_cred(pj_stun_session *sess, const pj_str_t *nonce, pj_str_t *realm)
++{
++ pj_strdup(sess->pool, &sess->next_nonce, nonce);
++ pj_strdup(sess->pool, &sess->server_realm, realm);
++}
+--- a/pjnath/src/pjnath/turn_session.c
++++ b/pjnath/src/pjnath/turn_session.c
+@@ -42,6 +42,7 @@
+ "Resolving",
+ "Resolved",
+ "Allocating",
++ "TcpBinding",
+ "Ready",
+ "Deallocating",
+ "Deallocated",
+@@ -208,6 +209,7 @@
+ PJ_DEF(void) pj_turn_alloc_param_default(pj_turn_alloc_param *prm)
+ {
+ pj_bzero(prm, sizeof(*prm));
++ prm->peer_conn_type = PJ_TURN_TP_UDP;
+ }
+
+ /*
+@@ -403,6 +405,11 @@
+ sess->pending_destroy = PJ_TRUE;
+ can_destroy = PJ_FALSE;
+ break;
++ case PJ_TURN_STATE_CONNECTION_BINDING:
++ /* We need to wait until connection binding complete */
++ sess->pending_destroy = PJ_TRUE;
++ can_destroy = PJ_FALSE;
++ break;
+ case PJ_TURN_STATE_READY:
+ /* Send REFRESH with LIFETIME=0 */
+ can_destroy = PJ_FALSE;
+@@ -719,6 +726,9 @@
+ PJ_ASSERT_RETURN(sess->state>PJ_TURN_STATE_NULL &&
+ sess->state<=PJ_TURN_STATE_RESOLVED,
+ PJ_EINVALIDOP);
++ PJ_ASSERT_RETURN(param->peer_conn_type == PJ_TURN_TP_UDP ||
++ param->peer_conn_type == PJ_TURN_TP_TCP,
++ PJ_EINVAL);
+
+ /* Verify address family in allocation param */
+ if (param && param->af) {
+@@ -756,7 +766,7 @@
+ /* MUST include REQUESTED-TRANSPORT attribute */
+ pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg,
+ PJ_STUN_ATTR_REQ_TRANSPORT,
+- PJ_STUN_SET_RT_PROTO(PJ_TURN_TP_UDP));
++ PJ_STUN_SET_RT_PROTO(param->peer_conn_type));
+
+ /* Include BANDWIDTH if requested */
+ if (sess->alloc_param.bandwidth > 0) {
+@@ -994,6 +1004,13 @@
+ }
+ }
+
++ /* rfc6062: direct send if peer connection is TCP */
++ if (sess->alloc_param.peer_conn_type == PJ_TURN_TP_TCP) {
++ status = sess->cb.on_send_pkt(sess, pkt, pkt_len,
++ addr, addr_len);
++ goto on_return;
++ }
++
+ /* See if the peer is bound to a channel number */
+ ch = lookup_ch_by_addr(sess, addr, pj_sockaddr_get_len(addr),
+ PJ_FALSE, PJ_FALSE);
+@@ -1670,6 +1687,33 @@
+
+ sess = (pj_turn_session*)pj_stun_session_get_user_data(stun);
+
++ if (msg->hdr.type == PJ_STUN_CONNECTION_ATTEMPT_INDICATION) {
++ pj_stun_uint_attr *connection_id_attr;
++ /* Get CONNECTION-ID attribute */
++ connection_id_attr = (pj_stun_uint_attr*)
++ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_CONNECTION_ID, 0);
++
++ /* Get XOR-PEER-ADDRESS attribute */
++ peer_attr = (pj_stun_xor_peer_addr_attr*)
++ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_XOR_PEER_ADDR, 0);
++
++ /* Must have both XOR-PEER-ADDRESS and CONNECTION-ID attributes */
++ if (!peer_attr || !connection_id_attr) {
++ PJ_LOG(4,(sess->obj_name,
++ "Received ConnectionAttempt indication with missing attributes"));
++ return PJ_EINVALIDOP;
++ }
++
++ /* Notify application */
++ if (sess->cb.on_peer_connection) {
++ (*sess->cb.on_peer_connection)(sess, connection_id_attr->value,
++ &peer_attr->sockaddr,
++ pj_sockaddr_get_len(&peer_attr->sockaddr),
++ PJ_SUCCESS);
++ }
++ return PJ_SUCCESS;
++ }
++
+ /* Expecting Data Indication only */
+ if (msg->hdr.type != PJ_STUN_DATA_INDICATION) {
+ PJ_LOG(4,(sess->obj_name, "Unexpected STUN %s indication",
+@@ -2089,3 +2133,16 @@
+ pj_grp_lock_release(sess->grp_lock);
+ }
+
++PJ_DEF(void) pj_turn_session_get_server_cred(pj_turn_session *sess,
++ pj_pool_t *pool, pj_str_t *nonce,
++ pj_str_t *realm)
++{
++ pj_stun_session_get_server_cred(sess->stun, pool, nonce, realm);
++}
++
++PJ_DEF(void) pj_turn_session_set_server_cred(pj_turn_session *sess,
++ const pj_str_t *nonce,
++ pj_str_t *realm)
++{
++ pj_stun_session_set_server_cred(sess->stun, nonce, realm);
++}
+--- a/pjnath/src/pjnath/turn_sock.c
++++ b/pjnath/src/pjnath/turn_sock.c
+@@ -35,9 +35,29 @@
+
+ enum { MAX_BIND_RETRY = 100 };
+
++enum { CONNECTION_USED = (1<<0), /* TCP connection slot is used or free */
++ CONNECTION_READY = (1<<1) /* TCP connection bind and ready to use for data transfer */
++};
+
+ #define INIT 0x1FFFFFFF
+
++/**
++ * pj_turn_tcp_data_connection contains information on TCP connection open between
++ * the client and the turn server, conveying data from/to a specific peer.
++ * notes: part of RFC 6062 support
++ */
++typedef struct pj_turn_tcp_data_connection
++{
++ pj_uint32_t id; /* identity of this connection as given by the TURN server */
++ pj_uint32_t flags; /* 0 or CONNECTION_USED */
++ pj_sockaddr peer_addr; /* mapped address of connected peer */
++ unsigned peer_addr_len;
++ pj_activesock_t *active_tcp_sock; /* socket between client and TURN server */
++ pj_ioqueue_op_key_t send_key;
++ pj_stun_session *stun_sess; /* STUN session used to send ConnectBind msg */
++ pj_turn_sock *turn_sock; /* up link */
++} pj_turn_tcp_data_connection;
++
+ struct pj_turn_sock
+ {
+ pj_pool_t *pool;
+@@ -59,6 +79,11 @@
+ pj_turn_tp_type conn_type;
+ pj_activesock_t *active_sock;
+ pj_ioqueue_op_key_t send_key;
++
++ /* RFC 6062 */
++ pj_stun_auth_cred cred; /* saved from control connection */
++ pj_size_t tcp_cnx_count; /* number of elements in tcp_cnx */
++ pj_turn_tcp_data_connection tcp_cnx[PJ_TURN_MAX_TCP_CNX]; /* peer dedicated data connections throught the TURN server */
+ };
+
+
+@@ -82,6 +107,12 @@
+ static void turn_on_state(pj_turn_session *sess,
+ pj_turn_state_t old_state,
+ pj_turn_state_t new_state);
++static void turn_on_peer_connection(pj_turn_session *sess,
++ pj_uint32_t conn_id,
++ const pj_sockaddr_t *peer_addr,
++ unsigned addr_len,
++ pj_status_t status);
++
+
+ static pj_bool_t on_data_read(pj_activesock_t *asock,
+ void *data,
+@@ -97,6 +127,26 @@
+ static void destroy(pj_turn_sock *turn_sock);
+ static void timer_cb(pj_timer_heap_t *th, pj_timer_entry *e);
+
++static pj_bool_t on_peer_data_read(pj_activesock_t *asock,
++ void *data,
++ pj_size_t size,
++ pj_status_t status,
++ pj_size_t *remainder);
++static pj_bool_t on_peer_connect_complete(pj_activesock_t *asock,
++ pj_status_t status);
++static pj_status_t on_tcp_stun_send_msg(pj_stun_session *sess,
++ void *token,
++ const void *pkt,
++ pj_size_t pkt_size,
++ const pj_sockaddr_t *dst_addr,
++ unsigned addr_len);
++static void on_tcp_stun_request_complete(pj_stun_session *sess,
++ pj_status_t status,
++ void *token,
++ pj_stun_tx_data *tdata,
++ const pj_stun_msg *response,
++ const pj_sockaddr_t *src_addr,
++ unsigned src_addr_len);
+
+ /* Init config */
+ PJ_DEF(void) pj_turn_sock_cfg_default(pj_turn_sock_cfg *cfg)
+@@ -193,6 +243,7 @@
+ sess_cb.on_channel_bound = &turn_on_channel_bound;
+ sess_cb.on_rx_data = &turn_on_rx_data;
+ sess_cb.on_state = &turn_on_state;
++ sess_cb.on_peer_connection = &turn_on_peer_connection;
+ status = pj_turn_session_create(cfg, pool->obj_name, af, conn_type,
+ turn_sock->grp_lock, &sess_cb, 0,
+ turn_sock, &turn_sock->sess);
+@@ -238,6 +289,9 @@
+ pj_turn_session_shutdown(turn_sock->sess);
+ if (turn_sock->active_sock)
+ pj_activesock_close(turn_sock->active_sock);
++ for (int i=0; i < turn_sock->tcp_cnx_count; ++i) {
++ pj_activesock_close(turn_sock->tcp_cnx[i].active_tcp_sock);
++ }
+ pj_grp_lock_dec_ref(turn_sock->grp_lock);
+ pj_grp_lock_release(turn_sock->grp_lock);
+ }
+@@ -302,6 +356,34 @@
+ }
+ }
+
++static void peer_cnx_fail(pj_turn_tcp_data_connection *tcp_cnx,
++ const char *title, pj_status_t status)
++{
++ pj_turn_sock *turn_sock = tcp_cnx->turn_sock;
++
++ show_err(turn_sock, title, status);
++ if (tcp_cnx->stun_sess) {
++ pj_stun_session_destroy(tcp_cnx->stun_sess);
++ tcp_cnx->stun_sess = NULL;
++ }
++ if (tcp_cnx->active_tcp_sock) {
++ for (int i=0; i < turn_sock->tcp_cnx_count; ++i) {
++ if (&turn_sock->tcp_cnx[i] == tcp_cnx) {
++ turn_on_peer_connection(turn_sock->sess, tcp_cnx->id,
++ &tcp_cnx->peer_addr,
++ tcp_cnx->peer_addr_len,
++ PJ_EEOF);
++ pj_activesock_close(tcp_cnx->active_tcp_sock);
++ tcp_cnx->active_tcp_sock = NULL;
++ tcp_cnx->flags = 0;
++ if (i == turn_sock->tcp_cnx_count-1)
++ --turn_sock->tcp_cnx_count;
++ break;
++ }
++ }
++ }
++}
++
+ /*
+ * Set user data.
+ */
+@@ -411,6 +489,9 @@
+
+ /* Set credental */
+ if (cred) {
++ // save credentials for peer/TCP connections
++ if (param->peer_conn_type == PJ_TURN_TP_TCP)
++ pj_memcpy(&turn_sock->cred, cred, sizeof(turn_sock->cred));
+ status = pj_turn_session_set_credential(turn_sock->sess, cred);
+ if (status != PJ_SUCCESS) {
+ sess_fail(turn_sock, "Error setting credential", status);
+@@ -676,11 +757,35 @@
+ return PJ_EINVALIDOP;
+ }
+
+- PJ_UNUSED_ARG(dst_addr);
+- PJ_UNUSED_ARG(dst_addr_len);
++ /* With TCP peer connection filter by address
++ * if packet is for the server or the peer
++ */
++ pj_activesock_t *asock = NULL;
++ pj_turn_session_info info;
++ pj_turn_session_get_info(turn_sock->sess, &info);
++ if (pj_sockaddr_cmp(&info.server, dst_addr) &&
++ turn_sock->alloc_param.peer_conn_type == PJ_TURN_TP_TCP) {
++ for (int i=0; i < turn_sock->tcp_cnx_count; ++i) {
++ pj_turn_tcp_data_connection *tcp_cnx = &turn_sock->tcp_cnx[i];
++ if ((tcp_cnx->flags & CONNECTION_READY) == 0)
++ continue;
++ if (!pj_sockaddr_cmp(&tcp_cnx->peer_addr, dst_addr)) {
++ asock = tcp_cnx->active_tcp_sock;
++ break;
++ }
++ }
++ if (!asock) {
++ status = PJ_ENOTFOUND;
++ show_err(turn_sock, "socket send()", status);
++ return status;
++ }
++ } else {
++ asock = turn_sock->active_sock;
++ }
+
+- status = pj_activesock_send(turn_sock->active_sock, &turn_sock->send_key,
++ status = pj_activesock_send(asock, &turn_sock->send_key,
+ pkt, &len, 0);
++
+ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+ show_err(turn_sock, "socket send()", status);
+ }
+@@ -927,4 +1032,365 @@
+ }
+ }
+
++static void turn_on_peer_connection(pj_turn_session *sess,
++ pj_uint32_t conn_id,
++ const pj_sockaddr_t *peer_addr,
++ unsigned addr_len,
++ pj_status_t status)
++{
++ pj_turn_sock *turn_sock = (pj_turn_sock*) pj_turn_session_get_user_data(sess);
++ if (turn_sock == NULL || turn_sock->is_destroying) {
++ /* We've been destroyed */
++ return;
++ }
++
++ if (turn_sock->cb.on_peer_connection) {
++ (*turn_sock->cb.on_peer_connection)(turn_sock, conn_id,
++ peer_addr, addr_len,
++ status);
++ }
++}
++
++PJ_DECL(pj_status_t) pj_turn_connect_peer(pj_turn_sock *turn_sock,
++ pj_uint32_t conn_id,
++ const pj_sockaddr_t *peer_addr,
++ unsigned addr_len)
++{
++ pj_status_t status;
++ pj_turn_tcp_data_connection *new_tcp_cnx = NULL;
++
++ for (int i=0; i < turn_sock->tcp_cnx_count; ++i) {
++ pj_turn_tcp_data_connection *tcp_cnx = &turn_sock->tcp_cnx[i];
++ if ((tcp_cnx->flags & CONNECTION_USED) == 0) {
++ new_tcp_cnx = tcp_cnx;
++ continue;
++ }
++ if (tcp_cnx->id == conn_id)
++ // TODO: need log
++ return PJ_EINVAL; // TODO: need better error code
++ }
++
++ if (!new_tcp_cnx) {
++ if (turn_sock->tcp_cnx_count == PJ_TURN_MAX_TCP_CNX) {
++ // TODO: need log
++ return PJ_ETOOMANY;
++ }
++ new_tcp_cnx = &turn_sock->tcp_cnx[turn_sock->tcp_cnx_count++];
++ }
++
++ /* Initialize this TCP connection slot */
++ pj_bzero(new_tcp_cnx, sizeof(*new_tcp_cnx));
++ new_tcp_cnx->id = conn_id;
++ new_tcp_cnx->flags = CONNECTION_USED;
++ new_tcp_cnx->turn_sock = turn_sock;
++ pj_sockaddr_cp(&new_tcp_cnx->peer_addr, peer_addr);
++ new_tcp_cnx->peer_addr_len = addr_len;
++
++ pj_ioqueue_op_key_init(&new_tcp_cnx->send_key,
++ sizeof(new_tcp_cnx->send_key));
++
++ /* Initiate a new TCP connection on TURN server
++ * that will become the peer data connection */
++ pj_turn_session_info info;
++ int sock_type;
++ pj_sock_t sock;
++ pj_activesock_cfg asock_cfg;
++ pj_activesock_cb asock_cb;
++ pj_sockaddr bound_addr, *cfg_bind_addr;
++ pj_uint16_t max_bind_retry;
++
++ /* Get server address from session info */
++ pj_turn_session_get_info(turn_sock->sess, &info);
++
++ assert(turn_sock->conn_type == PJ_TURN_TP_TCP);
++ sock_type = pj_SOCK_STREAM();
++
++ /* Init socket */
++ status = pj_sock_socket(turn_sock->af, sock_type, 0, &sock);
++ if (status != PJ_SUCCESS) {
++ pj_turn_sock_destroy(turn_sock);
++ return status;
++ }
++
++ /* Bind socket */
++ cfg_bind_addr = &turn_sock->setting.bound_addr;
++ max_bind_retry = MAX_BIND_RETRY;
++ if (turn_sock->setting.port_range &&
++ turn_sock->setting.port_range < max_bind_retry)
++ {
++ max_bind_retry = turn_sock->setting.port_range;
++ }
++ pj_sockaddr_init(turn_sock->af, &bound_addr, NULL, 0);
++ if (cfg_bind_addr->addr.sa_family == pj_AF_INET() ||
++ cfg_bind_addr->addr.sa_family == pj_AF_INET6())
++ {
++ pj_sockaddr_cp(&bound_addr, cfg_bind_addr);
++ }
++ status = pj_sock_bind_random(sock, &bound_addr,
++ turn_sock->setting.port_range,
++ max_bind_retry);
++ if (status != PJ_SUCCESS) {
++ pj_turn_sock_destroy(turn_sock);
++ return status;
++ }
++
++ /* Apply socket buffer size */
++ if (turn_sock->setting.so_rcvbuf_size > 0) {
++ unsigned sobuf_size = turn_sock->setting.so_rcvbuf_size;
++ status = pj_sock_setsockopt_sobuf(sock, pj_SO_RCVBUF(),
++ PJ_TRUE, &sobuf_size);
++ if (status != PJ_SUCCESS) {
++ pj_perror(3, turn_sock->obj_name, status,
++ "Failed setting SO_RCVBUF");
++ } else {
++ if (sobuf_size < turn_sock->setting.so_rcvbuf_size) {
++ PJ_LOG(4, (turn_sock->obj_name,
++ "Warning! Cannot set SO_RCVBUF as configured,"
++ " now=%d, configured=%d", sobuf_size,
++ turn_sock->setting.so_rcvbuf_size));
++ } else {
++ PJ_LOG(5, (turn_sock->obj_name, "SO_RCVBUF set to %d",
++ sobuf_size));
++ }
++ }
++ }
++ if (turn_sock->setting.so_sndbuf_size > 0) {
++ unsigned sobuf_size = turn_sock->setting.so_sndbuf_size;
++ status = pj_sock_setsockopt_sobuf(sock, pj_SO_SNDBUF(),
++ PJ_TRUE, &sobuf_size);
++ if (status != PJ_SUCCESS) {
++ pj_perror(3, turn_sock->obj_name, status,
++ "Failed setting SO_SNDBUF");
++ } else {
++ if (sobuf_size < turn_sock->setting.so_sndbuf_size) {
++ PJ_LOG(4, (turn_sock->obj_name,
++ "Warning! Cannot set SO_SNDBUF as configured,"
++ " now=%d, configured=%d", sobuf_size,
++ turn_sock->setting.so_sndbuf_size));
++ } else {
++ PJ_LOG(5, (turn_sock->obj_name, "SO_SNDBUF set to %d",
++ sobuf_size));
++ }
++ }
++ }
++
++ /* Create active socket */
++ pj_activesock_cfg_default(&asock_cfg);
++ asock_cfg.grp_lock = turn_sock->grp_lock;
++
++ pj_bzero(&asock_cb, sizeof(asock_cb));
++ asock_cb.on_data_read = &on_peer_data_read;
++ asock_cb.on_connect_complete = &on_peer_connect_complete;
++ status = pj_activesock_create(turn_sock->pool, sock,
++ sock_type, &asock_cfg,
++ turn_sock->cfg.ioqueue, &asock_cb,
++ new_tcp_cnx,
++ &new_tcp_cnx->active_tcp_sock);
++ if (status != PJ_SUCCESS) {
++ pj_turn_sock_destroy(turn_sock);
++ return status;
++ }
++
++ char addrtxt[PJ_INET6_ADDRSTRLEN+8];
++ PJ_LOG(5,(turn_sock->pool->obj_name,
++ "Connecting to %s",
++ pj_sockaddr_print(&info.server, addrtxt,
++ sizeof(addrtxt), 3)));
++
++ status = pj_activesock_start_connect(new_tcp_cnx->active_tcp_sock,
++ turn_sock->pool,
++ &info.server,
++ pj_sockaddr_get_len(&info.server));
++ if (status == PJ_SUCCESS) {
++ on_peer_connect_complete(new_tcp_cnx->active_tcp_sock, PJ_SUCCESS);
++ } else if (status != PJ_EPENDING) {
++ pj_perror(3, turn_sock->pool->obj_name, status,
++ "Failed to connect to %s",
++ pj_sockaddr_print(&info.server, addrtxt,
++ sizeof(addrtxt), 3));
++ pj_turn_sock_destroy(turn_sock);
++ return status;
++ }
++
++ return PJ_SUCCESS;
++}
++
++static pj_bool_t on_peer_data_read(pj_activesock_t *asock,
++ void *data,
++ pj_size_t size,
++ pj_status_t status,
++ pj_size_t *remainder)
++{
++ pj_turn_tcp_data_connection *tcp_cnx;
++ pj_turn_sock *turn_sock;
++
++ tcp_cnx = (pj_turn_tcp_data_connection*) pj_activesock_get_user_data(asock);
++ pj_assert(tcp_cnx && tcp_cnx->turn_sock);
++ turn_sock = tcp_cnx->turn_sock;
++
++ pj_grp_lock_acquire(turn_sock->grp_lock);
++
++ if (status != PJ_SUCCESS) {
++ peer_cnx_fail(tcp_cnx, "Peer connection closed", status);
++ pj_grp_lock_release(turn_sock->grp_lock);
++ return PJ_FALSE;
++ }
++
++ *remainder = size;
++ pj_uint8_t* pkt = data;
++ while (*remainder > 0) {
++ if ((tcp_cnx->flags & CONNECTION_READY) != 0) {
++ if (turn_sock->cb.on_rx_data)
++ turn_sock->cb.on_rx_data(turn_sock, pkt, *remainder,
++ &tcp_cnx->peer_addr,
++ tcp_cnx->peer_addr_len);
++ pj_grp_lock_release(turn_sock->grp_lock);
++ *remainder = 0;
++ return PJ_TRUE;
++ }
+
++ /* STUN session waiting for ConnectBind response */
++ pj_size_t parsed_len;
++ unsigned options = PJ_STUN_CHECK_PACKET | PJ_STUN_NO_FINGERPRINT_CHECK;
++ status = pj_stun_session_on_rx_pkt(tcp_cnx->stun_sess, pkt, size,
++ options, NULL, &parsed_len,
++ &tcp_cnx->peer_addr,
++ tcp_cnx->peer_addr_len);
++
++ if (status != PJ_SUCCESS) {
++ peer_cnx_fail(tcp_cnx, "Peer connection closed", status);
++ pj_grp_lock_release(turn_sock->grp_lock);
++ return PJ_FALSE;
++ }
++
++ PJ_LOG(3, ("rfc6062",
++ "parsed STUN msg (read %zu byte(s) over %zu), status=%u",
++ parsed_len, size, status));
++
++ pkt += parsed_len;
++ *remainder -= parsed_len;
++ }
++
++ pj_grp_lock_release(turn_sock->grp_lock);
++
++ return PJ_TRUE;
++}
++
++static pj_bool_t on_peer_connect_complete(pj_activesock_t *asock,
++ pj_status_t status)
++{
++ pj_turn_tcp_data_connection *tcp_cnx;
++ pj_turn_sock *turn_sock;
++
++ PJ_LOG(1, ("rfc6062", "peer data connection %s", status == PJ_SUCCESS ? "ready" : "failed"));
++
++ tcp_cnx = (pj_turn_tcp_data_connection*) pj_activesock_get_user_data(asock);
++ if (!tcp_cnx)
++ return PJ_FALSE;
++
++ turn_sock = tcp_cnx->turn_sock;
++ pj_assert(turn_sock);
++
++ pj_grp_lock_acquire(turn_sock->grp_lock);
++
++ PJ_LOG(3, ("rfc6062", "peer data connection %s", status == PJ_SUCCESS ? "ready" : "failed"));
++
++ // TODO: handle failures
++ if (status != PJ_SUCCESS) {
++ pj_grp_lock_release(turn_sock->grp_lock);
++ return PJ_FALSE;
++ }
++
++ /* start pending read operation */
++ status = pj_activesock_start_read(asock, turn_sock->pool,
++ turn_sock->setting.max_pkt_size, 0);
++ if (status != PJ_SUCCESS) {
++ // TODO: error handling
++ pj_grp_lock_release(turn_sock->grp_lock);
++ return PJ_FALSE;
++ }
++
++ /* Create a temporary STUN session to send the ConnectBind request */
++ pj_stun_session_cb stun_cb;
++ pj_bzero(&stun_cb, sizeof(stun_cb));
++ stun_cb.on_send_msg = &on_tcp_stun_send_msg;
++ stun_cb.on_request_complete = &on_tcp_stun_request_complete;
++ status = pj_stun_session_create(&turn_sock->cfg, NULL,
++ &stun_cb, PJ_FALSE, NULL,
++ &tcp_cnx->stun_sess);
++ if (status != PJ_SUCCESS) {
++ pj_grp_lock_release(turn_sock->grp_lock);
++ return PJ_FALSE;
++ }
++
++ pj_stun_session_set_user_data(tcp_cnx->stun_sess, tcp_cnx);
++
++ /* Copy credentials from control connection */
++ pj_stun_session_set_credential(tcp_cnx->stun_sess, PJ_STUN_AUTH_LONG_TERM, &turn_sock->cred);
++ pj_str_t server_nonce, server_realm;
++ pj_turn_session_get_server_cred(turn_sock->sess, turn_sock->pool, &server_nonce, &server_realm);
++ pj_stun_session_set_server_cred(tcp_cnx->stun_sess, &server_nonce, &server_realm);
++
++ /* Send ConnectBind request */
++ pj_stun_tx_data *tdata;
++ status = pj_stun_session_create_req(tcp_cnx->stun_sess, PJ_STUN_CONNECTION_BIND_REQUEST,
++ PJ_STUN_MAGIC, NULL, &tdata);
++ if (status != PJ_SUCCESS) {
++ pj_stun_session_destroy(tcp_cnx->stun_sess);
++ pj_grp_lock_release(turn_sock->grp_lock);
++ return PJ_FALSE;
++ }
++
++ /* MUST include REQUESTED-TRANSPORT attribute */
++ pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_CONNECTION_ID, tcp_cnx->id);
++
++ PJ_LOG(3, ("rfc6062", "bind TCP connection id=%x", tcp_cnx->id));
++ status = pj_stun_session_send_msg(tcp_cnx->stun_sess, tcp_cnx, PJ_FALSE, PJ_FALSE,
++ &tcp_cnx->peer_addr, tcp_cnx->peer_addr_len,
++ tdata);
++ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
++ pj_stun_session_destroy(tcp_cnx->stun_sess);
++ pj_grp_lock_release(turn_sock->grp_lock);
++ return status;
++ }
++
++ pj_grp_lock_release(turn_sock->grp_lock);
++ return PJ_TRUE;
++}
++
++static pj_status_t on_tcp_stun_send_msg(pj_stun_session *sess,
++ void *token,
++ const void *pkt,
++ pj_size_t pkt_size,
++ const pj_sockaddr_t *dst_addr,
++ unsigned addr_len)
++{
++ pj_status_t status;
++ pj_turn_tcp_data_connection *tcp_cnx = (pj_turn_tcp_data_connection*) token;
++ pj_assert(tcp_cnx != NULL && tcp_cnx->turn_sock != NULL);
++
++ pj_grp_lock_acquire(tcp_cnx->turn_sock->grp_lock);
++ status = pj_activesock_send(tcp_cnx->active_tcp_sock, &tcp_cnx->send_key, pkt, &pkt_size, 0);
++ pj_grp_lock_release(tcp_cnx->turn_sock->grp_lock);
++
++ return status;
++}
++
++static void on_tcp_stun_request_complete(pj_stun_session *sess,
++ pj_status_t status,
++ void *token,
++ pj_stun_tx_data *tdata,
++ const pj_stun_msg *response,
++ const pj_sockaddr_t *src_addr,
++ unsigned src_addr_len)
++{
++ pj_turn_tcp_data_connection *tcp_cnx = (pj_turn_tcp_data_connection*) token;
++ pj_assert(tcp_cnx != NULL && tcp_cnx->turn_sock != NULL);
++
++ pj_grp_lock_acquire(tcp_cnx->turn_sock->grp_lock);
++ pj_stun_session_destroy(tcp_cnx->stun_sess);
++ tcp_cnx->stun_sess = NULL;
++ tcp_cnx->flags |= CONNECTION_READY;
++ PJ_LOG(3, ("rfc6062", "peer data connection bind %s", status == PJ_SUCCESS ? "succeed" : "failed"));
++ pj_grp_lock_release(tcp_cnx->turn_sock->grp_lock);
++}
+
More information about the arch-commits
mailing list