]> git.ipfire.org Git - thirdparty/freeswitch.git/commitdiff
add websocket transport to sofia
authorAnthony Minessale <anthm@freeswitch.org>
Thu, 24 Jan 2013 20:13:45 +0000 (14:13 -0600)
committerAnthony Minessale <anthm@freeswitch.org>
Mon, 1 Apr 2013 02:27:20 +0000 (21:27 -0500)
libs/sofia-sip/libsofia-sip-ua/nta/nta.c
libs/sofia-sip/libsofia-sip-ua/tport/Makefile.am
libs/sofia-sip/libsofia-sip-ua/tport/tport.c
libs/sofia-sip/libsofia-sip-ua/tport/tport_internal.h
libs/sofia-sip/libsofia-sip-ua/tport/tport_logging.c
libs/sofia-sip/libsofia-sip-ua/tport/tport_type_ws.c [new file with mode: 0644]
libs/sofia-sip/libsofia-sip-ua/tport/tport_ws.h [new file with mode: 0644]
libs/sofia-sip/libsofia-sip-ua/tport/ws.c [new file with mode: 0644]
libs/sofia-sip/libsofia-sip-ua/tport/ws.h [new file with mode: 0644]

index 3f6afc03f6d081c685fbd4053f7cd0c7555ff55b..41427dfc4a4151cc5d8e34f52b1a13af001cb657 100644 (file)
@@ -234,6 +234,8 @@ struct nta_agent_s
   unsigned sa_tport_tcp : 1;   /**< Transports support TCP. */
   unsigned sa_tport_sctp : 1;  /**< Transports support SCTP. */
   unsigned sa_tport_tls : 1;   /**< Transports support TLS. */
+  unsigned sa_tport_ws : 1;        /**< Transports support WS. */
+  unsigned sa_tport_wss : 1;       /**< Transports support WSS. */
 
   unsigned sa_use_naptr : 1;   /**< Use NAPTR lookup */
   unsigned sa_use_srv : 1;     /**< Use SRV lookup */
@@ -2045,22 +2047,24 @@ struct sipdns_tport {
   char prefix[14];             /**< Prefix for SRV domains */
   char service[10];            /**< NAPTR service */
 }
-#define SIPDNS_TRANSPORTS (4)
+#define SIPDNS_TRANSPORTS (6)
 const sipdns_tports[SIPDNS_TRANSPORTS] = {
   { "udp",  "5060", "_sip._udp.",  "SIP+D2U"  },
   { "tcp",  "5060", "_sip._tcp.",  "SIP+D2T"  },
-  { "sctp", "5060", "_sip._sctp.", "SIP+D2S" },
-  { "tls",  "5061", "_sips._tcp.", "SIPS+D2T"  },
+  { "sctp", "5060", "_sip._sctp.", "SIP+D2S"  },
+  { "tls",  "5061", "_sips._tcp.", "SIPS+D2T" },
+  { "ws",   "80",   "_sips._ws.",  "SIP+D2W"  },
+  { "wss",  "443",  "_sips._wss.", "SIPS+D2W" },
 };
 
 static char const * const tports_sip[] =
   {
-    "udp", "tcp", "sctp", NULL
+       "udp", "tcp", "sctp", "ws", NULL
   };
 
 static char const * const tports_sips[] =
   {
-    "tls", NULL
+    "tls", "ws", NULL
   };
 
 static tport_stack_class_t nta_agent_class[1] =
@@ -2188,7 +2192,7 @@ int nta_agent_add_tport(nta_agent_t *self,
   if (url->url_params) {
     if (url_param(url->url_params, "transport", tp, sizeof(tp)) > 0) {
       if (strchr(tp, ',')) {
-       int i; char *t, *tps[9];
+                 int i; char *t, *tps[9] = {0};
 
        /* Split tp into transports */
        for (i = 0, t = tp; t && i < 8; i++) {
@@ -2311,6 +2315,8 @@ int agent_init_via(nta_agent_t *self, tport_t *primaries, int use_maddr)
   self->sa_tport_tcp = 0;
   self->sa_tport_sctp = 0;
   self->sa_tport_tls = 0;
+  self->sa_tport_ws = 0;
+  self->sa_tport_wss = 0;
 
   /* Set via fields for the tports */
   for (tp = primaries; tp; tp = tport_next(tp)) {
@@ -2343,6 +2349,10 @@ int agent_init_via(nta_agent_t *self, tport_t *primaries, int use_maddr)
       self->sa_tport_tcp = 1;
     else if (su_casematch(tpn->tpn_proto, "sctp"))
       self->sa_tport_sctp = 1;
+    else if (su_casematch(tpn->tpn_proto, "ws"))
+      self->sa_tport_ws = 1;
+    else if (su_casematch(tpn->tpn_proto, "wss"))
+      self->sa_tport_wss = 1;
 
     if (tport_has_tls(tp)) self->sa_tport_tls = 1;
 
@@ -2684,8 +2694,12 @@ nta_tpn_by_url(su_home_t *home,
 
   tpn->tpn_ident = NULL;
 
-  if (tpn->tpn_proto)
+  if (tpn->tpn_proto) {
+         if (su_casematch(url->url_scheme, "sips") && su_casematch(tpn->tpn_proto, "ws")) {
+                 tpn->tpn_proto = "wss";
+         }
     return 1;
+  }
 
   if (su_casematch(url->url_scheme, "sips"))
     tpn->tpn_proto = "tls";
index 171ad882691ce6dcfc5c4049019651895554a01f..e9aec1c99708eadc3f32c9bf8050adb972ebfc8d 100644 (file)
@@ -44,7 +44,7 @@ if HAVE_STUN
 USE_STUN_SRC =                 $(STUN_SRC)
 endif
 
-HTTP_SRC =             tport_type_connect.c
+HTTP_SRC =             tport_type_connect.c tport_type_ws.c ws.c
 if HAVE_NTH
 USE_HTTP_SRC =                 $(HTTP_SRC)
 endif
index eb79deba503b6a6f7b3a673ce1a6777c8e09cd7e..5c4fd188da9a6ed892ec7849d4dccd9568dbb1c8 100644 (file)
@@ -202,7 +202,7 @@ int tport_is_registered(tport_t const *self)
 /** Test if transport is stream. */
 int tport_is_stream(tport_t const *self)
 {
-  return self && self->tp_addrinfo->ai_socktype == SOCK_STREAM;
+       return self && !self->tp_pre_framed && self->tp_addrinfo->ai_socktype == SOCK_STREAM;
 }
 
 /** Test if transport is dgram. */
@@ -1345,10 +1345,12 @@ int tport_set_params(tport_t *self,
 extern tport_vtable_t const tport_udp_vtable;
 extern tport_vtable_t const tport_tcp_vtable;
 extern tport_vtable_t const tport_tls_vtable;
+extern tport_vtable_t const tport_ws_vtable;
 extern tport_vtable_t const tport_sctp_vtable;
 extern tport_vtable_t const tport_udp_client_vtable;
 extern tport_vtable_t const tport_tcp_client_vtable;
 extern tport_vtable_t const tport_sctp_client_vtable;
+extern tport_vtable_t const tport_ws_client_vtable;
 extern tport_vtable_t const tport_tls_client_vtable;
 extern tport_vtable_t const tport_http_connect_vtable;
 extern tport_vtable_t const tport_threadpool_vtable;
@@ -1359,6 +1361,8 @@ tport_vtable_t const *tport_vtables[TPORT_NUMBER_OF_TYPES + 1] =
 {
 #if HAVE_SOFIA_NTH
   &tport_http_connect_vtable,
+  &tport_ws_client_vtable,
+  &tport_ws_vtable,
 #endif
 #if HAVE_TLS
   &tport_tls_client_vtable,
@@ -2426,6 +2430,13 @@ int getprotohints(su_addrinfo_t *hints,
     proto = "tcp";
 #endif
 
+#if HAVE_SOFIA_NTH
+  if (su_casematch(proto, "ws"))
+    proto = "tcp";
+  if (su_casematch(proto, "wss"))
+    proto = "tcp";
+#endif
+
 #if HAVE_SCTP
   if (su_casematch(proto, "sctp")) {
     hints->ai_protocol = IPPROTO_SCTP;
@@ -2869,7 +2880,7 @@ void tport_recv_event(tport_t *self)
     }
 
     if (again >= 0)
-      tport_parse(self, !again, self->tp_rtime);
+      tport_parse(self, self->tp_pre_framed ? 1 : !again, self->tp_rtime);
   }
   while (again > 1);
 
index d4693ad167b1728466ec3c7c8d4f28b2bb297dd0..5deac55268dca914646ad3a37fdf346deaa8e82e 100644 (file)
@@ -161,6 +161,7 @@ struct tport_s {
   unsigned            tp_trunc:1;
   unsigned            tp_is_connected:1; /**< Connection is established */
   unsigned            tp_verified:1;     /**< Certificate Chain was verified */
+  unsigned            tp_pre_framed:1;   /** Data is pre-framed **/
   unsigned:0;
 
   tport_t *tp_left, *tp_right, *tp_dad; /**< Links in tport tree */
@@ -527,6 +528,10 @@ void tport_recv_timeout_timer(tport_t *self, su_time_t now);
 int tport_next_keepalive(tport_t *self, su_time_t *, char const **);
 void tport_keepalive_timer(tport_t *self, su_time_t now);
 
+extern tport_vtable_t const tport_ws_vtable;
+extern tport_vtable_t const tport_ws_client_vtable;
+extern tport_vtable_t const tport_wss_vtable;
+extern tport_vtable_t const tport_wss_client_vtable;
 extern tport_vtable_t const tport_sctp_vtable;
 extern tport_vtable_t const tport_sctp_client_vtable;
 extern tport_vtable_t const tport_tls_vtable;
index 13eb63b2eb416d993d2f2f90c6c61795f353f079..655e49f4872c372e45587669a75cccc2091a7e9c 100644 (file)
@@ -382,6 +382,8 @@ void tport_capt_msg(tport_t const *self, msg_t *msg, size_t n,
    if(strcmp(self->tp_name->tpn_proto, "tcp") == 0) hep_header.hp_p = IPPROTO_TCP;
    else if(strcmp(self->tp_name->tpn_proto, "tls") == 0) hep_header.hp_p = IPPROTO_IDP; /* FAKE*/
    else if(strcmp(self->tp_name->tpn_proto, "sctp") == 0) hep_header.hp_p = IPPROTO_SCTP;
+   else if(strcmp(self->tp_name->tpn_proto, "ws") == 0) hep_header.hp_p = IPPROTO_TCP;
+   else if(strcmp(self->tp_name->tpn_proto, "wss") == 0) hep_header.hp_p = IPPROTO_TCP;
    else hep_header.hp_p = IPPROTO_UDP; /* DEFAULT UDP */
 
    /* Check destination */         
diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_ws.c b/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_ws.c
new file mode 100644 (file)
index 0000000..1f62061
--- /dev/null
@@ -0,0 +1,413 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2006 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi@nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/**@CFILE tport_type_ws.c WS Transport
+ *
+ * See tport.docs for more detailed description of tport interface.
+ *
+ * @author Pekka Pessi <Pekka.Pessi@nokia.com>
+ * @author Martti Mela <Martti.Mela@nokia.com>
+ *
+ * @date Created: Fri Mar 24 08:45:49 EET 2006 ppessi
+ */
+
+#include "config.h"
+
+#include "tport_internal.h"
+#include "tport_ws.h"
+
+#if HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#endif
+
+#ifndef SOL_TCP
+#define SOL_TCP IPPROTO_TCP
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+
+#if HAVE_FUNC
+#elif HAVE_FUNCTION
+#define __func__ __FUNCTION__
+#else
+static char const __func__[] = "tport_type_ws";
+#endif
+
+/* ---------------------------------------------------------------------- */
+/* WS */
+
+#include <sofia-sip/http.h>
+#include <sofia-sip/http_header.h>
+
+tport_vtable_t const tport_ws_vtable =
+{
+  /* vtp_name               */ "ws",
+  /* vtp_public              */ tport_type_local,
+  /* vtp_pri_size            */ sizeof (tport_primary_t),
+  /* vtp_init_primary        */ tport_ws_init_primary,
+  /* vtp_deinit_primary      */ NULL,
+  /* vtp_wakeup_pri          */ tport_accept,
+  /* vtp_connect             */ NULL,
+  /* vtp_secondary_size      */ sizeof (tport_ws_t),
+  /* vtp_init_secondary      */ tport_ws_init_secondary,
+  /* vtp_deinit_secondary    */ NULL,
+  /* vtp_shutdown            */ NULL,
+  /* vtp_set_events          */ NULL,
+  /* vtp_wakeup              */ NULL,
+  /* vtp_recv                */ tport_recv_stream_ws,
+  /* vtp_send                */ tport_send_stream_ws,
+  /* vtp_deliver             */ NULL,
+  /* vtp_prepare             */ NULL,
+  /* vtp_keepalive           */ NULL,
+  /* vtp_stun_response       */ NULL,
+  /* vtp_next_secondary_timer*/ tport_ws_next_timer,
+  /* vtp_secondary_timer     */ tport_ws_timer,
+};
+
+tport_vtable_t const tport_ws_client_vtable =
+{
+  /* vtp_name               */ "ws",
+  /* vtp_public              */ tport_type_client,
+  /* vtp_pri_size            */ sizeof (tport_primary_t),
+  /* vtp_init_primary        */ tport_ws_init_client,
+  /* vtp_deinit_primary      */ NULL,
+  /* vtp_wakeup_pri          */ NULL,
+  /* vtp_connect             */ NULL,
+  /* vtp_secondary_size      */ sizeof (tport_ws_t),
+  /* vtp_init_secondary      */ tport_ws_init_secondary,
+  /* vtp_deinit_secondary    */ NULL,
+  /* vtp_shutdown            */ NULL,
+  /* vtp_set_events          */ NULL,
+  /* vtp_wakeup              */ NULL,
+  /* vtp_recv                */ tport_recv_stream_ws,
+  /* vtp_send                */ tport_send_stream_ws,
+  /* vtp_deliver             */ NULL,
+  /* vtp_prepare             */ NULL,
+  /* vtp_keepalive           */ NULL,
+  /* vtp_stun_response       */ NULL,
+  /* vtp_next_secondary_timer*/ tport_ws_next_timer,
+  /* vtp_secondary_timer     */ tport_ws_timer,
+};
+
+static int tport_ws_setsndbuf(int socket, int atleast);
+
+
+/** Receive from stream.
+ *
+ * @retval -1 error
+ * @retval 0  end-of-stream
+ * @retval 1  normal receive
+ * @retval 2  incomplete recv, recv again
+ *
+ */
+int tport_recv_stream_ws(tport_t *self)
+{
+  msg_t *msg;
+  ssize_t n, N, veclen, i, m;
+  int err;
+  msg_iovec_t iovec[msg_n_fragments] = {{ 0 }};
+  tport_ws_t *wstp = (tport_ws_t *)self;
+  wsh_t *ws = wstp->ws;
+  uint8_t *data;
+  ws_opcode_t oc;
+
+  if ( !wstp->ws_initialized ) {
+         ws_init(ws, self->tp_socket, 65336, wstp->ws_secure);
+         wstp->ws_initialized = 1;
+         self->tp_pre_framed = 1;
+         return 1;
+  }
+
+  N = ws_read_frame(ws, &oc, &data);
+
+  if (N == -1000) {
+    if (self->tp_msg)
+      msg_recv_commit(self->tp_msg, 0, 1);
+    return 0;    /* End of stream */
+  }
+  if (N < 0) {
+         err = su_errno();
+         SU_DEBUG_1(("%s(%p): su_getmsgsize(): %s (%d)\n", __func__, (void *)self,
+                                 su_strerror(err), err));
+         return -1;
+  }
+
+  veclen = tport_recv_iovec(self, &self->tp_msg, iovec, N, 0);
+  if (veclen < 0)
+    return -1;
+
+  msg = self->tp_msg;
+
+  msg_set_address(msg, self->tp_addr, self->tp_addrlen);
+
+  for (i = 0, n = 0; i < veclen; i++) {
+    m = iovec[i].mv_len; assert(N >= n + m);
+    memcpy(iovec[i].mv_base, data + n, m);
+    n += m;
+  }
+
+  assert(N == n);
+
+  /* Write the received data to the message dump file */
+  if (self->tp_master->mr_dump_file)
+         tport_dump_iovec(self, msg, n, iovec, veclen, "recv", "from");
+
+  /* Mark buffer as used */
+  msg_recv_commit(msg, N, 0);
+
+  return 1;
+}
+
+/** Send to stream */
+ssize_t tport_send_stream_ws(tport_t const *self, msg_t *msg,
+                         msg_iovec_t iov[],
+                         size_t iovlen)
+{
+  size_t i, j, n, m, size = 0;
+  ssize_t nerror;
+  tport_ws_t *wstp = (tport_ws_t *)self;
+  wsh_t *ws = wstp->ws;
+  char xbuf[65536] = "";
+  int blen = 0;
+
+
+  enum { WSBUFSIZE = 2048 };
+
+
+  for (i = 0; i < iovlen; i = j) {
+    char *buf = wstp->wstp_buffer;
+    unsigned wsbufsize = WSBUFSIZE;
+
+    if (i + 1 == iovlen) {
+               buf = NULL;             /* Don't bother copying single chunk */
+       }
+
+    if (buf &&
+               (char *)iov[i].siv_base - buf < WSBUFSIZE &&
+               (char *)iov[i].siv_base - buf >= 0) {
+               wsbufsize = buf + WSBUFSIZE - (char *)iov[i].siv_base;
+               assert(wsbufsize <= WSBUFSIZE);
+    }
+
+    for (j = i, m = 0; buf && j < iovlen; j++) {
+               if (m + iov[j].siv_len > wsbufsize) {
+                       break;
+               }
+               if (buf + m != iov[j].siv_base) {
+                       memcpy(buf + m, iov[j].siv_base, iov[j].siv_len);
+               }
+               m += iov[j].siv_len; iov[j].siv_len = 0;
+    }
+       
+    if (j == i) {
+      buf = iov[i].siv_base, m = iov[i].siv_len, j++;
+       } else {
+      iov[j].siv_base = buf, iov[j].siv_len = m;
+       }
+       
+       //* hacked to push to buffer
+       if (blen + m < sizeof(xbuf)) {
+               memcpy(xbuf+blen, buf, m);
+               nerror = m;
+               blen += m;
+       } else {
+               nerror = -1;
+       }
+       //*/
+       //nerror = ws_write_frame(ws, WSOC_TEXT, buf, m);
+
+    SU_DEBUG_9(("tport_ws_writevec: vec %p %p %lu ("MOD_ZD")\n",
+               (void *)ws, (void *)iov[i].siv_base, (LU)iov[i].siv_len,
+               nerror));
+
+    if (nerror == -1) {
+      int err = su_errno();
+      if (su_is_blocking(err))
+       break;
+      SU_DEBUG_3(("ws_write: %s\n", strerror(err)));
+      return -1;
+    }
+
+    n = (size_t)nerror;
+    size += n;
+
+    /* Return if the write buffer is full for now */
+    if (n != m)
+      break;
+  }
+
+  //* hacked .... 
+  if (size) {
+         size = ws_write_frame(ws, WSOC_TEXT, xbuf, blen);
+  }
+  //*/
+
+  return size;
+}
+
+
+int tport_ws_init_primary(tport_primary_t *pri,
+                          tp_name_t tpn[1],
+                          su_addrinfo_t *ai,
+                          tagi_t const *tags,
+                          char const **return_culprit)
+{
+  int socket;
+
+  socket = su_socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+
+  if (socket == INVALID_SOCKET)
+    return *return_culprit = "socket", -1;
+
+  tport_ws_setsndbuf(socket, 64 * 1024);
+
+  return tport_stream_init_primary(pri, socket, tpn, ai, tags, return_culprit);
+}
+
+int tport_ws_init_client(tport_primary_t *pri,
+                         tp_name_t tpn[1],
+                         su_addrinfo_t *ai,
+                         tagi_t const *tags,
+                         char const **return_culprit)
+{
+  pri->pri_primary->tp_conn_orient = 1;
+
+  return 0;
+}
+
+int tport_ws_init_secondary(tport_t *self, int socket, int accepted,
+                            char const **return_reason)
+{
+  int one = 1;
+
+  self->tp_has_connection = 1;
+
+  if (setsockopt(socket, SOL_TCP, TCP_NODELAY, (void *)&one, sizeof one) == -1)
+    return *return_reason = "TCP_NODELAY", -1;
+
+  if (!accepted)
+    tport_ws_setsndbuf(socket, 64 * 1024);
+
+  return 0;
+}
+
+static int tport_ws_setsndbuf(int socket, int atleast)
+{
+#if SU_HAVE_WINSOCK2
+  /* Set send buffer size to something reasonable on windows */
+  int size = 0;
+  socklen_t sizelen = sizeof size;
+
+  if (getsockopt(socket, SOL_SOCKET, SO_SNDBUF, (void *)&size, &sizelen) < 0)
+    return -1;
+
+  if (sizelen != sizeof size)
+    return su_seterrno(EINVAL);
+
+  if (size >= atleast)
+    return 0;                  /* OK */
+
+  return setsockopt(socket, SOL_SOCKET, SO_SNDBUF,
+                   (void *)&atleast, sizeof atleast);
+#else
+  return 0;
+#endif
+}
+
+
+/** Send PING */
+int tport_ws_ping(tport_t *self, su_time_t now)
+{
+  ssize_t n;
+  char *why = "";
+
+  if (tport_has_queued(self))
+    return 0;
+
+  n = send(self->tp_socket, "\r\n\r\n", 4, 0);
+
+  if (n > 0)
+    self->tp_ktime = now;
+
+  if (n == 4) {
+    if (self->tp_ptime.tv_sec == 0)
+      self->tp_ptime = now;
+  }
+  else if (n == -1) {
+    int error = su_errno();
+
+    why = " failed";
+
+    if (!su_is_blocking(error))
+      tport_error_report(self, error, NULL);
+    else
+      why = " blocking";
+
+    return -1;
+  }
+
+  SU_DEBUG_7(("%s(%p): %s to " TPN_FORMAT "%s\n",
+             __func__, (void *)self,
+             "sending PING", TPN_ARGS(self->tp_name), why));
+
+  return n == -1 ? -1 : 0;
+}
+
+/** Send pong */
+int tport_ws_pong(tport_t *self)
+{
+  self->tp_ping = 0;
+
+  if (tport_has_queued(self) || !self->tp_params->tpp_pong2ping)
+    return 0;
+
+  SU_DEBUG_7(("%s(%p): %s to " TPN_FORMAT "%s\n",
+             __func__, (void *)self,
+             "sending PONG", TPN_ARGS(self->tp_name), ""));
+
+  return send(self->tp_socket, "\r\n", 2, 0);
+}
+
+/** Calculate next timer for WS. */
+int tport_ws_next_timer(tport_t *self,
+                        su_time_t *return_target,
+                        char const **return_why)
+{
+  return
+    tport_next_recv_timeout(self, return_target, return_why) |
+    tport_next_keepalive(self, return_target, return_why);
+}
+
+/** WS timer. */
+void tport_ws_timer(tport_t *self, su_time_t now)
+{
+  tport_recv_timeout_timer(self, now);
+  tport_keepalive_timer(self, now);
+  tport_base_timer(self, now);
+}
diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/tport_ws.h b/libs/sofia-sip/libsofia-sip-ua/tport/tport_ws.h
new file mode 100644 (file)
index 0000000..274bde9
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2005 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi@nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef TPORT_WS_H
+/** Defined when <tport_ws.h> has been included. */
+#define TPORT_WS_H
+/**@internal
+ * @file tport_ws.h
+ * @brief Internal WS interface
+ *
+ * @author Mike Jerris <mike@jerris.com>
+ *
+ * Copyright 2013 Michael Jerris.  All rights reserved.
+ *
+ */
+
+#ifndef SU_TYPES_H
+#include <sofia-sip/su_types.h>
+#endif
+
+#include "tport_internal.h"
+#include "ws.h"
+
+SOFIA_BEGIN_DECLS
+
+typedef enum {
+    TPORT_WS_OPCODE_CONTINUATION = 0x0,
+    TPORT_WS_OPCODE_TEXT = 0x1,
+    TPORT_WS_OPCODE_BINARY = 0x2,
+    TPORT_WS_OPCODE_CLOSE = 0x8,
+    TPORT_WS_OPCODE_PING = 0x9,
+    TPORT_WS_OPCODE_PONG = 0xA
+} tport_ws_opcode_t;
+
+typedef struct tport_ws_s {
+  tport_t  wstp_tp[1];
+  wsh_t    ws[1];
+  char    *wstp_buffer;
+  unsigned ws_initialized:1;
+  unsigned ws_secure:1;
+  unsigned:0;
+} tport_ws_t;
+
+int tport_recv_stream_ws(tport_t *self);
+ssize_t tport_send_stream_ws(tport_t const *self, msg_t *msg,
+                         msg_iovec_t iov[], size_t iovused);
+
+int tport_ws_ping(tport_t *self, su_time_t now);
+int tport_ws_pong(tport_t *self);
+
+int tport_ws_init_primary(tport_primary_t *,
+                         tp_name_t  tpn[1],
+                         su_addrinfo_t *, tagi_t const *,
+                         char const **return_culprit);
+int tport_ws_init_client(tport_primary_t *,
+                        tp_name_t tpn[1],
+                        su_addrinfo_t *, tagi_t const *,
+                        char const **return_culprit);
+int tport_ws_init_secondary(tport_t *self, int socket, int accepted,
+                            char const **return_reason);
+
+int tport_ws_next_timer(tport_t *self, su_time_t *, char const **);
+void tport_ws_timer(tport_t *self, su_time_t);
+
+SOFIA_END_DECLS
+
+#endif
diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/ws.c b/libs/sofia-sip/libsofia-sip-ua/tport/ws.c
new file mode 100644 (file)
index 0000000..4829de9
--- /dev/null
@@ -0,0 +1,585 @@
+#include "ws.h"
+
+#define SHA1_HASH_SIZE 20
+struct globals_s globals;
+
+#ifndef PTHREAD
+void ssl_init() {}
+#else
+static unsigned long pthreads_thread_id(void);
+static void pthreads_locking_callback(int mode, int type, const char *file, int line);
+
+static pthread_mutex_t *lock_cs;
+static long *lock_count;
+
+
+
+static void thread_setup(void)
+{
+       int i;
+
+       lock_cs = OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));
+       lock_count = OPENSSL_malloc(CRYPTO_num_locks() * sizeof(long));
+
+       for (i = 0; i < CRYPTO_num_locks(); i++) {
+               lock_count[i] = 0;
+               pthread_mutex_init(&(lock_cs[i]), NULL);
+       }
+
+       CRYPTO_set_id_callback(pthreads_thread_id);
+       CRYPTO_set_locking_callback(pthreads_locking_callback);
+}
+
+static void thread_cleanup(void)
+{
+       int i;
+
+       CRYPTO_set_locking_callback(NULL);
+
+       for (i=0; i<CRYPTO_num_locks(); i++) {
+               pthread_mutex_destroy(&(lock_cs[i]));
+       }
+       OPENSSL_free(lock_cs);
+       OPENSSL_free(lock_count);
+
+}
+
+static void pthreads_locking_callback(int mode, int type, const char *file, int line)
+{
+
+       if (mode & CRYPTO_LOCK) {
+               pthread_mutex_lock(&(lock_cs[type]));
+               lock_count[type]++;
+       } else {
+               pthread_mutex_unlock(&(lock_cs[type]));
+       }
+}
+
+
+
+static unsigned long pthreads_thread_id(void)
+{
+       return (unsigned long) pthread_self();
+}
+
+
+void init_ssl(void) {
+       SSL_library_init();
+       //      OpenSSL_add_all_algorithms();   /* load & register cryptos */
+       //      SSL_load_error_strings();     /* load all error messages */
+       globals.ssl_method = SSLv23_server_method();   /* create server instance */
+       globals.ssl_ctx = SSL_CTX_new(globals.ssl_method);         /* create context */
+       assert(globals.ssl_ctx);
+
+       /* set the local certificate from CertFile */
+       SSL_CTX_use_certificate_file(globals.ssl_ctx, globals.cert, SSL_FILETYPE_PEM);
+       /* set the private key from KeyFile */
+       SSL_CTX_use_PrivateKey_file(globals.ssl_ctx, globals.key, SSL_FILETYPE_PEM);
+       /* verify private key */
+       if ( !SSL_CTX_check_private_key(globals.ssl_ctx) ) {
+               abort();
+       }
+
+       thread_setup();
+
+}
+
+
+void deinit_ssl(void) {
+       thread_cleanup();
+}
+
+#endif
+
+static const char c64[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+
+static int cheezy_get_var(char *data, char *name, char *buf, size_t buflen)
+{
+  char *p=data;
+
+  /* the old way didnt make sure that variable values were used for the name hunt
+   * and didnt ensure that only a full match of the variable name was used
+   */
+
+  do {
+    if(!strncmp(p,name,strlen(name)) && *(p+strlen(name))==':') break;
+  } while((p = (strstr(p,"\n")+1))!=(char *)1);
+
+
+  if (p != (char *)1 && *p!='\0') {
+    char *v, *e;
+
+    if ((v = strchr(p, ':'))) {
+      v++;
+      while(v && *v == ' ') {
+       v++;
+      }
+      if (v)  {
+       if (!(e = strchr(v, '\r'))) {
+         e = strchr(v, '\n');
+       }
+      }
+                       
+      if (v && e) {
+       int cplen;
+       int len = e - v;
+       
+       if (len > buflen - 1) {
+         cplen = buflen -1;
+       } else {
+         cplen = len;
+       }
+       
+       strncpy(buf, v, cplen);
+       *(buf+cplen) = '\0';
+       return 1;
+      }
+      
+    }
+  }
+  return 0;
+}
+
+static int b64encode(unsigned char *in, size_t ilen, unsigned char *out, size_t olen) 
+{
+       int y=0,bytes=0;
+       size_t x=0;
+       unsigned int b=0,l=0;
+
+       for(x=0;x<ilen;x++) {
+               b = (b<<8) + in[x];
+               l += 8;
+               while (l >= 6) {
+                       out[bytes++] = c64[(b>>(l-=6))%64];
+                       if(++y!=72) {
+                               continue;
+                       }
+                       //out[bytes++] = '\n';
+                       y=0;
+               }
+       }
+
+       if (l > 0) {
+               out[bytes++] = c64[((b%16)<<(6-l))%64];
+       }
+       if (l != 0) while (l < 6) {
+               out[bytes++] = '=', l += 2;
+       }
+
+       return 0;
+}
+
+#ifdef NO_OPENSSL
+static void sha1_digest(char *digest, unsigned char *in)
+{
+       SHA1Context sha;
+       char *p;
+       int x;
+
+
+       SHA1Init(&sha);
+       SHA1Update(&sha, in, strlen(in));
+       SHA1Final(&sha, digest);
+}
+#else 
+
+static void sha1_digest(unsigned char *digest, char *in)
+{
+       SHA_CTX sha;
+
+       SHA1_Init(&sha);
+       SHA1_Update(&sha, in, strlen(in));
+       SHA1_Final(digest, &sha);
+
+}
+
+#endif
+
+int ws_handshake(wsh_t *wsh)
+{
+       char key[256] = "";
+       char version[5] = "";
+       char proto[256] = "";
+       char uri[256] = "";
+       char input[256] = "";
+       unsigned char output[SHA1_HASH_SIZE] = "";
+       char b64[256] = "";
+       char respond[512] = "";
+       ssize_t bytes;
+       char *p, *e;
+
+       if (wsh->sock == ws_sock_invalid) {
+               return -3;
+       }
+
+       if (wsh->secure) {
+               wsh->ssl = SSL_new(globals.ssl_ctx);
+               SSL_set_fd(wsh->ssl, wsh->sock);
+               SSL_accept(wsh->ssl);   
+       }
+       
+       while((bytes = ws_raw_read(wsh, wsh->buffer + wsh->datalen, wsh->buflen - wsh->datalen)) > 0) {
+               wsh->datalen += bytes;
+               if (strstr(wsh->buffer, "\r\n\r\n") || strstr(wsh->buffer, "\n\n")) {
+                       break;
+               }
+       }
+
+       *(wsh->buffer+bytes) = '\0';
+       
+       if (strncasecmp(wsh->buffer, "GET ", 4)) {
+               goto err;
+       }
+       
+       p = wsh->buffer + 4;
+       
+       if (!(e = strchr(p, ' '))) {
+               goto err;
+       }
+       
+       strncpy(uri, p, e-p);
+       
+       cheezy_get_var(wsh->buffer, "Sec-WebSocket-Key", key, sizeof(key));
+       cheezy_get_var(wsh->buffer, "Sec-WebSocket-Version", version, sizeof(version));
+       cheezy_get_var(wsh->buffer, "Sec-WebSocket-Protocol", proto, sizeof(proto));
+       
+       if (!*key) {
+               goto err;
+       }
+               
+       snprintf(input, sizeof(input), "%s%s", key, WEBSOCKET_GUID);
+       sha1_digest(output, input);
+       b64encode((unsigned char *)output, SHA1_HASH_SIZE, (unsigned char *)b64, sizeof(b64));
+
+       snprintf(respond, sizeof(respond), 
+                        "HTTP/1.1 101 Switching Protocols\r\n"
+                        "Upgrade: websocket\r\n"
+                        "Connection: Upgrade\r\n"
+                        "Sec-WebSocket-Accept: %s\r\n"
+                        "Sec-WebSocket-Protocol: %s\r\n\r\n",
+                        b64,
+                        proto);
+
+
+       ws_raw_write(wsh, respond, strlen(respond));
+       wsh->handshake = 1;
+       
+       return 0;
+
+ err:
+
+       snprintf(respond, sizeof(respond), "HTTP/1.1 400 Bad Request\r\n"
+                        "Sec-WebSocket-Version: 13\r\n\r\n");
+
+       //printf("ERR:\n%s\n", respond);
+
+
+       send(wsh->sock, respond, strlen(respond), 0);
+
+       ws_close(wsh, WS_NONE);
+
+       return -1;
+
+}
+
+ssize_t ws_raw_read(wsh_t *wsh, void *data, size_t bytes)
+{
+       ssize_t r;
+
+       if (wsh->ssl) {
+               return SSL_read(wsh->ssl, data, bytes);
+       }
+
+       do {
+               r = recv(wsh->sock, data, bytes, 0);
+       } while (r == -1 && (errno == EAGAIN || errno == EINTR));
+
+       if (r<0) {
+               //printf("READ FAIL: %s\n", strerror(errno));
+       }
+
+       return r;
+}
+
+ssize_t ws_raw_write(wsh_t *wsh, void *data, size_t bytes)
+{
+       size_t r;
+
+       if (wsh->ssl) {
+               return SSL_write(wsh->ssl, data, bytes);
+       }
+
+       do {
+               r = send(wsh->sock, data, bytes, 0);
+       } while (r == -1 && (errno == EAGAIN || errno == EINTR));
+
+       if (r<0) {
+               //printf("wRITE FAIL: %s\n", strerror(errno));
+       }
+
+       return r;
+}
+
+int ws_init(wsh_t *wsh, ws_socket_t sock, size_t buflen, int secure)
+{
+       memset(wsh, 0, sizeof(*wsh));
+       wsh->sock = sock;
+
+       if (buflen > MAXLEN) {
+               buflen = MAXLEN;
+       }
+
+       wsh->buflen = buflen;
+       wsh->secure = secure;
+
+       if (!wsh->buffer) {
+               wsh->buffer = malloc(wsh->buflen);
+               assert(wsh->buffer);
+       }
+
+       while (!wsh->down && !wsh->handshake) {
+               ws_handshake(wsh);
+       }
+
+       if (wsh->down) {
+               return -1;
+       }
+
+       return 0;
+}
+
+ssize_t ws_close(wsh_t *wsh, int16_t reason) 
+{
+       
+       if (wsh->down) {
+               return -1;
+       }
+       wsh->down++;
+
+       if (reason) {
+               uint16_t *u16;
+               uint8_t fr[4] = {WSOC_CLOSE | 0x80, 2, 0};
+
+               u16 = (uint16_t *) &fr[2];
+               *u16 = htons((int16_t)reason);
+               ws_raw_write(wsh, fr, 4);
+       }
+
+
+       if (wsh->ssl) {
+               SSL_free(wsh->ssl);
+               wsh->ssl = NULL;
+       }
+
+       //close(wsh->sock);
+       wsh->sock = ws_sock_invalid;
+
+       if (wsh->buffer) {
+               free(wsh->buffer);
+               wsh->buffer = NULL;
+       }
+
+
+       return reason * -1;
+       
+}
+
+ssize_t ws_read_frame(wsh_t *wsh, ws_opcode_t *oc, uint8_t **data)
+{
+       
+       ssize_t need = 2;
+       char *maskp;
+
+ again:
+       need = 2;
+       maskp = NULL;
+       *data = NULL;
+
+       if (wsh->down) {
+               return -1;
+       }
+
+       if (!wsh->handshake) {
+               return ws_close(wsh, WS_PROTO_ERR);
+       }
+
+       if ((wsh->datalen = ws_raw_read(wsh, wsh->buffer, 14)) < need) {
+               /* too small - protocol err */
+               return ws_close(wsh, WS_PROTO_ERR);
+       }
+
+       *oc = *wsh->buffer & 0xf;
+
+       switch(*oc) {
+       case WSOC_CLOSE:
+               {
+                       wsh->plen = wsh->buffer[1] & 0x7f;
+                       *data = (uint8_t *) &wsh->buffer[2];
+                       return ws_close(wsh, 1000);
+               }
+               break;
+       case WSOC_CONTINUATION:
+       case WSOC_TEXT:
+       case WSOC_BINARY:
+       case WSOC_PING:
+       case WSOC_PONG:
+               {
+                       //int fin = (wsh->buffer[0] >> 7) & 1;
+                       int mask = (wsh->buffer[1] >> 7) & 1;
+
+                       if (mask) {
+                               need += 4;
+                               
+                               if (need > wsh->datalen) {
+                                       /* too small - protocol err */
+                                       *oc = WSOC_CLOSE;
+                                       return ws_close(wsh, WS_PROTO_ERR);
+                               }
+                       }
+
+                       wsh->plen = wsh->buffer[1] & 0x7f;
+                       wsh->payload = &wsh->buffer[2];
+                       
+                       if (wsh->plen == 127) {
+                               uint64_t *u64;
+
+                               need += 8;
+
+                               if (need > wsh->datalen) {
+                                       /* too small - protocol err */
+                                       *oc = WSOC_CLOSE;
+                                       return ws_close(wsh, WS_PROTO_ERR);
+                               }
+
+                               u64 = (uint64_t *) wsh->payload;
+                               wsh->payload += 8;
+
+                               wsh->plen = ntohl(*u64);
+
+                       } else if (wsh->plen == 126) {
+                               uint16_t *u16;
+
+                               need += 2;
+
+                               if (need > wsh->datalen) {
+                                       /* too small - protocol err */
+                                       *oc = WSOC_CLOSE;
+                                       return ws_close(wsh, WS_PROTO_ERR);
+                               }
+
+                               u16 = (uint16_t *) wsh->payload;
+                               wsh->payload += 2;
+                               wsh->plen = ntohs(*u16);
+                       }
+
+                       if (mask) {
+                               maskp = (char *)wsh->payload;
+                               wsh->payload += 4;
+                       }
+
+                       need = (wsh->plen - (wsh->datalen - need));
+
+                       if ((need + wsh->datalen) > wsh->buflen) {
+                               /* too big - Ain't nobody got time fo' dat */
+                               *oc = WSOC_CLOSE;
+                               return ws_close(wsh, WS_DATA_TOO_BIG);                          
+                       }
+
+                       wsh->rplen = wsh->plen - need;
+
+                       while(need) {
+                               ssize_t r = ws_raw_read(wsh, wsh->payload + wsh->rplen, need);
+
+                               if (r < 1) {
+                                       /* invalid read - protocol err .. */
+                                       *oc = WSOC_CLOSE;
+                                       return ws_close(wsh, WS_PROTO_ERR);
+                               }
+
+                               wsh->datalen += r;
+                               wsh->rplen += r;
+                               need -= r;
+                       }
+                       
+                       if (mask && maskp) {
+                               uint32_t i;
+
+                               for (i = 0; i < wsh->datalen; i++) {
+                                       wsh->payload[i] ^= maskp[i % 4];
+                               }
+                       }
+                       
+
+                       if (*oc == WSOC_PING) {
+                               ws_write_frame(wsh, WSOC_PONG, wsh->payload, wsh->rplen);
+                               goto again;
+                       }
+                       
+
+                       *(wsh->payload+wsh->rplen) = '\0';
+                       *data = (uint8_t *)wsh->payload;
+
+                       //printf("READ[%ld][%d]-----------------------------:\n[%s]\n-------------------------------\n", wsh->rplen, *oc, (char *)*data);
+
+
+                       return wsh->rplen;
+               }
+               break;
+       default:
+               {
+                       /* invalid op code - protocol err .. */
+                       *oc = WSOC_CLOSE;
+                       return ws_close(wsh, WS_PROTO_ERR);
+               }
+               break;
+       }
+}
+
+
+ssize_t ws_write_frame(wsh_t *wsh, ws_opcode_t oc, void *data, size_t bytes)
+{
+       uint8_t hdr[14] = { 0 };
+       size_t hlen = 2;
+
+       if (wsh->down) {
+               return -1;
+       }
+
+       //printf("WRITE[%ld]-----------------------------:\n[%s]\n-----------------------------------\n", bytes, (char *) data);
+
+       hdr[0] = oc | 0x80;
+
+       if (bytes < 126) {
+               hdr[1] = bytes;
+       } else if (bytes < 0x10000) {
+               uint16_t *u16;
+
+               hdr[1] = 126;
+               hlen += 2;
+
+               u16 = (uint16_t *) &hdr[2];
+               *u16 = htons((uint16_t) bytes);
+
+       } else {
+               uint64_t *u64;
+
+               hdr[1] = 127;
+               hlen += 8;
+               
+               u64 = (uint64_t *) &hdr[2];
+               *u64 = htonl(bytes);
+       }
+
+       if (ws_raw_write(wsh, (void *) &hdr[0], hlen) != hlen) {
+               return -1;
+       }
+
+       if (ws_raw_write(wsh, data, bytes) != bytes) {
+               return -2;
+       }
+       
+       return bytes;
+}
+
+
diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/ws.h b/libs/sofia-sip/libsofia-sip-ua/tport/ws.h
new file mode 100644 (file)
index 0000000..ce7e515
--- /dev/null
@@ -0,0 +1,84 @@
+#ifndef _WS_H
+#define _WS_H
+
+#define MAXLEN 0x10000
+#define WEBSOCKET_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+#define B64BUFFLEN 1024
+
+#include <sys/types.h>
+#include <arpa/inet.h>
+#include <sys/wait.h> 
+#include <string.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <assert.h>
+#include <errno.h>
+//#include "sha1.h"
+#include <openssl/ssl.h>
+
+
+struct globals_s {
+       const SSL_METHOD *ssl_method;
+       SSL_CTX *ssl_ctx;
+       char cert[512];
+       char key[512];
+};
+
+extern struct globals_s globals;
+
+typedef int ws_socket_t;
+#define ws_sock_invalid -1
+
+
+typedef enum {
+       WS_NONE = 0,
+       WS_NORMAL = 1000,
+       WS_PROTO_ERR = 1002,
+       WS_DATA_TOO_BIG = 1009
+} ws_cause_t;
+
+typedef enum {
+       WSOC_CONTINUATION = 0x0,
+       WSOC_TEXT = 0x1,
+       WSOC_BINARY = 0x2,
+       WSOC_CLOSE = 0x8,
+       WSOC_PING = 0x9,
+       WSOC_PONG = 0xA
+} ws_opcode_t;
+
+typedef struct wsh_s {
+       ws_socket_t sock;
+       char *buffer;
+       size_t buflen;
+       ssize_t datalen;
+       char *payload;
+       ssize_t plen;
+       ssize_t rplen;
+       SSL *ssl;
+       int handshake;
+       uint8_t down;
+       int secure;
+} wsh_t;
+
+ssize_t ws_raw_read(wsh_t *wsh, void *data, size_t bytes);
+ssize_t ws_raw_write(wsh_t *wsh, void *data, size_t bytes);
+ssize_t ws_read_frame(wsh_t *wsh, ws_opcode_t *oc, uint8_t **data);
+ssize_t ws_write_frame(wsh_t *wsh, ws_opcode_t oc, void *data, size_t bytes);
+int ws_init(wsh_t *wsh, ws_socket_t sock, size_t buflen, int secure);
+ssize_t ws_close(wsh_t *wsh, int16_t reason);
+void init_ssl(void);
+void deinit_ssl(void);
+
+
+static inline uint64_t get_unaligned_uint64(const void *p)
+{   
+    const struct { uint64_t d; } __attribute__((packed)) *pp = p;
+    return pp->d;
+}
+
+
+#endif