diff --git a/libs/sofia-sip/libsofia-sip-ua/nta/nta.c b/libs/sofia-sip/libsofia-sip-ua/nta/nta.c index 3f6afc03f6..41427dfc4a 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nta/nta.c +++ b/libs/sofia-sip/libsofia-sip-ua/nta/nta.c @@ -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"; diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/Makefile.am b/libs/sofia-sip/libsofia-sip-ua/tport/Makefile.am index 171ad88269..e9aec1c997 100644 --- a/libs/sofia-sip/libsofia-sip-ua/tport/Makefile.am +++ b/libs/sofia-sip/libsofia-sip-ua/tport/Makefile.am @@ -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 diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/tport.c b/libs/sofia-sip/libsofia-sip-ua/tport/tport.c index eb79deba50..5c4fd188da 100644 --- a/libs/sofia-sip/libsofia-sip-ua/tport/tport.c +++ b/libs/sofia-sip/libsofia-sip-ua/tport/tport.c @@ -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); diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/tport_internal.h b/libs/sofia-sip/libsofia-sip-ua/tport/tport_internal.h index d4693ad167..5deac55268 100644 --- a/libs/sofia-sip/libsofia-sip-ua/tport/tport_internal.h +++ b/libs/sofia-sip/libsofia-sip-ua/tport/tport_internal.h @@ -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; diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/tport_logging.c b/libs/sofia-sip/libsofia-sip-ua/tport/tport_logging.c index 13eb63b2eb..655e49f487 100644 --- a/libs/sofia-sip/libsofia-sip-ua/tport/tport_logging.c +++ b/libs/sofia-sip/libsofia-sip-ua/tport/tport_logging.c @@ -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 index 0000000000..1f6206142a --- /dev/null +++ b/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_ws.c @@ -0,0 +1,413 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2006 Nokia Corporation. + * + * Contact: Pekka Pessi + * + * 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 + * @author Martti Mela + * + * @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 +#endif + +#ifndef SOL_TCP +#define SOL_TCP IPPROTO_TCP +#endif + +#include +#include +#include +#include +#include +#include + +#if HAVE_FUNC +#elif HAVE_FUNCTION +#define __func__ __FUNCTION__ +#else +static char const __func__[] = "tport_type_ws"; +#endif + +/* ---------------------------------------------------------------------- */ +/* WS */ + +#include +#include + +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 index 0000000000..274bde97ae --- /dev/null +++ b/libs/sofia-sip/libsofia-sip-ua/tport/tport_ws.h @@ -0,0 +1,88 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2005 Nokia Corporation. + * + * Contact: Pekka Pessi + * + * 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 has been included. */ +#define TPORT_WS_H +/**@internal + * @file tport_ws.h + * @brief Internal WS interface + * + * @author Mike Jerris + * + * Copyright 2013 Michael Jerris. All rights reserved. + * + */ + +#ifndef SU_TYPES_H +#include +#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 index 0000000000..4829de9a47 --- /dev/null +++ b/libs/sofia-sip/libsofia-sip-ua/tport/ws.c @@ -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 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= 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 index 0000000000..ce7e515efe --- /dev/null +++ b/libs/sofia-sip/libsofia-sip-ua/tport/ws.h @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include "sha1.h" +#include + + +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