Closed ticket #1107: iOS4 background feature

 * pjlib:
  * add support for activesock TCP to work in background mode.
  * add feature in ioqueue to recreate closed UDP sockets.
 * pjsip-apps:
  * ipjsua: add support for iPhone OS 4 background mode
  * ipjsystest: add support for iPhone OS 4 background mode



git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@3299 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjlib/src/pj/activesock.c b/pjlib/src/pj/activesock.c
index 6da4c7f..0ba1a79 100644
--- a/pjlib/src/pj/activesock.c
+++ b/pjlib/src/pj/activesock.c
@@ -21,10 +21,16 @@
 #include <pj/compat/socket.h>
 #include <pj/assert.h>
 #include <pj/errno.h>
+#include <pj/log.h>
 #include <pj/pool.h>
 #include <pj/sock.h>
 #include <pj/string.h>
 
+#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && \
+    PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT!=0
+#   include <CFNetwork/CFNetwork.h>
+#endif
+
 #define PJ_ACTIVESOCK_MAX_LOOP	    50
 
 
@@ -71,7 +77,13 @@
     unsigned		 async_count;
     unsigned		 max_loop;
     pj_activesock_cb	 cb;
-
+#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && \
+    PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT!=0
+    int			 bg_setting;
+    pj_sock_t		 sock;
+    CFReadStreamRef	 readStream;
+#endif
+    
     struct send_data	 send_data;
 
     struct read_op	*read_op;
@@ -105,6 +117,51 @@
     cfg->whole_data = PJ_TRUE;
 }
 
+#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && \
+    PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT!=0
+static void activesock_destroy_iphone_os_stream(pj_activesock_t *asock)
+{
+    if (asock->readStream) {
+	CFReadStreamClose(asock->readStream);
+	CFRelease(asock->readStream);
+	asock->readStream = NULL;
+    }
+}
+
+static void activesock_create_iphone_os_stream(pj_activesock_t *asock)
+{
+    if (asock->bg_setting && asock->stream_oriented) {
+	activesock_destroy_iphone_os_stream(asock);
+
+	CFStreamCreatePairWithSocket(kCFAllocatorDefault, asock->sock,
+				     &asock->readStream, NULL);
+
+	if (!asock->readStream ||
+	    CFReadStreamSetProperty(asock->readStream,
+				    kCFStreamNetworkServiceType,
+				    kCFStreamNetworkServiceTypeVoIP)
+	    != TRUE ||
+	    CFReadStreamOpen(asock->readStream) != TRUE)
+	{
+	    PJ_LOG(2,("", "Failed to configure TCP transport for VoIP "
+		      "usage. Background mode will not be supported."));
+	    
+	    activesock_destroy_iphone_os_stream(asock);
+	}
+    }
+}
+
+
+PJ_DEF(void) pj_activesock_set_iphone_os_bg(pj_activesock_t *asock,
+					    int val)
+{
+    asock->bg_setting = val;
+    if (asock->bg_setting)
+	activesock_create_iphone_os_stream(asock);
+    else
+	activesock_destroy_iphone_os_stream(asock);
+}
+#endif
 
 PJ_DEF(pj_status_t) pj_activesock_create( pj_pool_t *pool,
 					  pj_sock_t sock,
@@ -156,6 +213,13 @@
 	pj_ioqueue_set_concurrency(asock->key, opt->concurrency);
     }
 
+#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && \
+    PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT!=0
+    asock->sock = sock;
+    pj_activesock_set_iphone_os_bg(asock,
+				   PJ_ACTIVESOCK_TCP_IPHONE_OS_BG);
+#endif
+
     *p_asock = asock;
     return PJ_SUCCESS;
 }
@@ -215,6 +279,11 @@
 {
     PJ_ASSERT_RETURN(asock, PJ_EINVAL);
     if (asock->key) {
+#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && \
+    PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT!=0
+	activesock_destroy_iphone_os_stream(asock);
+#endif	
+	
 	pj_ioqueue_unregister(asock->key);
 	asock->key = NULL;
     }
@@ -733,6 +802,10 @@
 	    if (!ret)
 		return;
 
+#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && \
+    PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT!=0
+	    activesock_create_iphone_os_stream(asock);
+#endif
 	} else if (status==PJ_SUCCESS) {
 	    /* Application doesn't handle the new socket, we need to 
 	     * close it to avoid resource leak.
@@ -775,6 +848,12 @@
 	    /* We've been destroyed */
 	    return;
 	}
+	
+#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && \
+    PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT!=0
+	activesock_create_iphone_os_stream(asock);
+#endif
+	
     }
 }
 #endif	/* PJ_HAS_TCP */
diff --git a/pjlib/src/pj/ioqueue_common_abs.c b/pjlib/src/pj/ioqueue_common_abs.c
index bcd4f6a..07ed838 100644
--- a/pjlib/src/pj/ioqueue_common_abs.c
+++ b/pjlib/src/pj/ioqueue_common_abs.c
@@ -529,6 +529,16 @@
 
             /* In any case we would report this to caller. */
             bytes_read = -rc;
+
+#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && \
+    PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT!=0
+	    /* Special treatment for dead UDP sockets here, see ticket #1107 */
+	    if (rc == PJ_STATUS_FROM_OS(ENOTCONN) && !IS_CLOSING(h) &&
+		h->fd_type==pj_SOCK_DGRAM())
+	    {
+		replace_udp_sock(h);
+	    }
+#endif
 	}
 
 	/* Unlock; from this point we don't need to hold key's mutex
diff --git a/pjlib/src/pj/ioqueue_select.c b/pjlib/src/pj/ioqueue_select.c
index d4f9372..3a7c14e 100644
--- a/pjlib/src/pj/ioqueue_select.c
+++ b/pjlib/src/pj/ioqueue_select.c
@@ -37,6 +37,7 @@
 #include <pj/sock.h>
 #include <pj/compat/socket.h>
 #include <pj/sock_select.h>
+#include <pj/sock_qos.h>
 #include <pj/errno.h>
 
 /* Now that we have access to OS'es <sys/select>, lets check again that
@@ -123,6 +124,9 @@
 #endif
 };
 
+/* Proto */
+static pj_status_t replace_udp_sock(pj_ioqueue_key_t *h);
+
 /* Include implementation for common abstraction after we declare
  * pj_ioqueue_key_t and pj_ioqueue_t.
  */
@@ -620,6 +624,139 @@
 }
 #endif
 
+#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && \
+    PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT!=0
+static pj_status_t replace_udp_sock(pj_ioqueue_key_t *h)
+{
+    enum flags {
+	HAS_PEER_ADDR = 1,
+	HAS_QOS = 2
+    };
+    pj_sock_t old_sock, new_sock = PJ_INVALID_SOCKET;
+    pj_sockaddr local_addr, rem_addr;
+    int val, addr_len;
+    pj_fd_set_t *fds[3];
+    unsigned i, fds_cnt, flags=0;
+    pj_qos_params qos_params;
+    unsigned msec;
+    pj_status_t status;
+
+    pj_lock_acquire(h->ioqueue->lock);
+
+    old_sock = h->fd;
+
+    /* Can only replace UDP socket */
+    pj_assert(h->fd_type == pj_SOCK_DGRAM());
+
+    PJ_LOG(4,(THIS_FILE, "Attempting to replace UDP socket %d", old_sock));
+
+    /* Investigate the old socket */
+    addr_len = sizeof(local_addr);
+    status = pj_sock_getsockname(old_sock, &local_addr, &addr_len);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+    
+    addr_len = sizeof(rem_addr);
+    status = pj_sock_getpeername(old_sock, &rem_addr, &addr_len);
+    if (status == PJ_SUCCESS)
+	flags |= HAS_PEER_ADDR;
+
+    status = pj_sock_get_qos_params(old_sock, &qos_params);
+    if (status == PJ_SUCCESS)
+	flags |= HAS_QOS;
+
+    /* We're done with the old socket, close it otherwise we'll get
+     * error in bind()
+     */
+    pj_sock_close(old_sock);
+
+    /* Prepare the new socket */
+    status = pj_sock_socket(local_addr.addr.sa_family, PJ_SOCK_DGRAM, 0,
+			    &new_sock);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    /* Even after the socket is closed, we'll still get "Address in use"
+     * errors, so force it with SO_REUSEADDR
+     */
+    val = 1;
+    status = pj_sock_setsockopt(new_sock, SOL_SOCKET, SO_REUSEADDR,
+				&val, sizeof(val));
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    /* The loop is silly, but what else can we do? */
+    addr_len = pj_sockaddr_get_len(&local_addr);
+    for (msec=20; ; msec<1000? msec=msec*2 : 1000) {
+	status = pj_sock_bind(new_sock, &local_addr, addr_len);
+	if (status != PJ_STATUS_FROM_OS(EADDRINUSE))
+	    break;
+	PJ_LOG(4,(THIS_FILE, "Address is still in use, retrying.."));
+	pj_thread_sleep(msec);
+    }
+
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    if (flags & HAS_QOS) {
+	status = pj_sock_set_qos_params(new_sock, &qos_params);
+	if (status != PJ_SUCCESS)
+	    goto on_error;
+    }
+
+    if (flags & HAS_PEER_ADDR) {
+	status = pj_sock_connect(new_sock, &rem_addr, addr_len);
+	if (status != PJ_SUCCESS)
+	    goto on_error;
+    }
+
+    /* Set socket to nonblocking. */
+    val = 1;
+#if defined(PJ_WIN32) && PJ_WIN32!=0 || \
+    defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE!=0
+    if (ioctlsocket(new_sock, FIONBIO, &val)) {
+#else
+    if (ioctl(new_sock, FIONBIO, &val)) {
+#endif
+        status = pj_get_netos_error();
+	goto on_error;
+    }
+
+    /* Replace the occurrence of old socket with new socket in the
+     * fd sets.
+     */
+    fds_cnt = 0;
+    fds[fds_cnt++] = &h->ioqueue->rfdset;
+    fds[fds_cnt++] = &h->ioqueue->wfdset;
+#if PJ_HAS_TCP
+    fds[fds_cnt++] = &h->ioqueue->xfdset;
+#endif
+
+    for (i=0; i<fds_cnt; ++i) {
+	if (PJ_FD_ISSET(old_sock, fds[i])) {
+	    PJ_FD_CLR(old_sock, fds[i]);
+	    PJ_FD_SET(new_sock, fds[i]);
+	}
+    }
+
+    /* And finally replace the fd in the key */
+    h->fd = new_sock;
+
+    PJ_LOG(4,(THIS_FILE, "UDP has been replaced successfully!"));
+
+    pj_lock_release(h->ioqueue->lock);
+
+    return PJ_SUCCESS;
+
+on_error:
+    if (new_sock != PJ_INVALID_SOCKET)
+	pj_sock_close(new_sock);
+    PJ_PERROR(1,(THIS_FILE, status, "Error replacing socket"));
+    pj_lock_release(h->ioqueue->lock);
+    return status;
+}
+#endif
+
 
 /*
  * pj_ioqueue_poll()
diff --git a/pjlib/src/pj/sock_bsd.c b/pjlib/src/pj/sock_bsd.c
index 2551e64..aef2ff8 100644
--- a/pjlib/src/pj/sock_bsd.c
+++ b/pjlib/src/pj/sock_bsd.c
@@ -513,12 +513,21 @@
     PJ_ASSERT_RETURN(sock!=NULL, PJ_EINVAL);
     PJ_ASSERT_RETURN(PJ_INVALID_SOCKET==-1, 
                      (*sock=PJ_INVALID_SOCKET, PJ_EINVAL));
-
+    
     *sock = socket(af, type, proto);
     if (*sock == PJ_INVALID_SOCKET)
 	return PJ_RETURN_OS_ERROR(pj_get_native_netos_error());
-    else 
+    else {
+#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && \
+    PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT!=0
+	pj_int32_t val = 1;
+	if (type == pj_SOCK_DGRAM()) {
+	    pj_sock_setsockopt(*sock, pj_SOL_SOCKET(), SO_NOSIGPIPE, 
+			       &val, sizeof(val));
+	}
+#endif
 	return PJ_SUCCESS;
+    }
 }
 #endif