diff options
Diffstat (limited to 'pjlib/src/pj/sock_uwp.cpp')
-rw-r--r-- | pjlib/src/pj/sock_uwp.cpp | 1502 |
1 files changed, 1502 insertions, 0 deletions
diff --git a/pjlib/src/pj/sock_uwp.cpp b/pjlib/src/pj/sock_uwp.cpp new file mode 100644 index 00000000..876c3287 --- /dev/null +++ b/pjlib/src/pj/sock_uwp.cpp @@ -0,0 +1,1502 @@ +/* $Id$ */ +/* + * Copyright (C) 2016 Teluu Inc. (http://www.teluu.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/assert.h> +#include <pj/errno.h> +#include <pj/math.h> +#include <pj/os.h> +#include <pj/compat/socket.h> + +#include <ppltasks.h> +#include <string> + +#define THIS_FILE "sock_uwp.cpp" + +#include "sock_uwp.h" + + /* + * Address families conversion. + * The values here are indexed based on pj_addr_family. + */ +const pj_uint16_t PJ_AF_UNSPEC = AF_UNSPEC; +const pj_uint16_t PJ_AF_UNIX = AF_UNIX; +const pj_uint16_t PJ_AF_INET = AF_INET; +const pj_uint16_t PJ_AF_INET6 = AF_INET6; +#ifdef AF_PACKET +const pj_uint16_t PJ_AF_PACKET = AF_PACKET; +#else +const pj_uint16_t PJ_AF_PACKET = 0xFFFF; +#endif +#ifdef AF_IRDA +const pj_uint16_t PJ_AF_IRDA = AF_IRDA; +#else +const pj_uint16_t PJ_AF_IRDA = 0xFFFF; +#endif + +/* +* Socket types conversion. +* The values here are indexed based on pj_sock_type +*/ +const pj_uint16_t PJ_SOCK_STREAM= SOCK_STREAM; +const pj_uint16_t PJ_SOCK_DGRAM = SOCK_DGRAM; +const pj_uint16_t PJ_SOCK_RAW = SOCK_RAW; +const pj_uint16_t PJ_SOCK_RDM = SOCK_RDM; + +/* +* Socket level values. +*/ +const pj_uint16_t PJ_SOL_SOCKET = SOL_SOCKET; +#ifdef SOL_IP +const pj_uint16_t PJ_SOL_IP = SOL_IP; +#elif (defined(PJ_WIN32) && PJ_WIN32) || (defined(PJ_WIN64) && PJ_WIN64) +const pj_uint16_t PJ_SOL_IP = IPPROTO_IP; +#else +const pj_uint16_t PJ_SOL_IP = 0; +#endif /* SOL_IP */ + +#if defined(SOL_TCP) +const pj_uint16_t PJ_SOL_TCP = SOL_TCP; +#elif defined(IPPROTO_TCP) +const pj_uint16_t PJ_SOL_TCP = IPPROTO_TCP; +#elif (defined(PJ_WIN32) && PJ_WIN32) || (defined(PJ_WIN64) && PJ_WIN64) +const pj_uint16_t PJ_SOL_TCP = IPPROTO_TCP; +#else +const pj_uint16_t PJ_SOL_TCP = 6; +#endif /* SOL_TCP */ + +#ifdef SOL_UDP +const pj_uint16_t PJ_SOL_UDP = SOL_UDP; +#elif defined(IPPROTO_UDP) +const pj_uint16_t PJ_SOL_UDP = IPPROTO_UDP; +#elif (defined(PJ_WIN32) && PJ_WIN32) || (defined(PJ_WIN64) && PJ_WIN64) +const pj_uint16_t PJ_SOL_UDP = IPPROTO_UDP; +#else +const pj_uint16_t PJ_SOL_UDP = 17; +#endif /* SOL_UDP */ + +#ifdef SOL_IPV6 +const pj_uint16_t PJ_SOL_IPV6 = SOL_IPV6; +#elif (defined(PJ_WIN32) && PJ_WIN32) || (defined(PJ_WIN64) && PJ_WIN64) +# if defined(IPPROTO_IPV6) || (_WIN32_WINNT >= 0x0501) +const pj_uint16_t PJ_SOL_IPV6 = IPPROTO_IPV6; +# else +const pj_uint16_t PJ_SOL_IPV6 = 41; +# endif +#else +const pj_uint16_t PJ_SOL_IPV6 = 41; +#endif /* SOL_IPV6 */ + +/* IP_TOS */ +#ifdef IP_TOS +const pj_uint16_t PJ_IP_TOS = IP_TOS; +#else +const pj_uint16_t PJ_IP_TOS = 1; +#endif + + +/* TOS settings (declared in netinet/ip.h) */ +#ifdef IPTOS_LOWDELAY +const pj_uint16_t PJ_IPTOS_LOWDELAY = IPTOS_LOWDELAY; +#else +const pj_uint16_t PJ_IPTOS_LOWDELAY = 0x10; +#endif +#ifdef IPTOS_THROUGHPUT +const pj_uint16_t PJ_IPTOS_THROUGHPUT = IPTOS_THROUGHPUT; +#else +const pj_uint16_t PJ_IPTOS_THROUGHPUT = 0x08; +#endif +#ifdef IPTOS_RELIABILITY +const pj_uint16_t PJ_IPTOS_RELIABILITY = IPTOS_RELIABILITY; +#else +const pj_uint16_t PJ_IPTOS_RELIABILITY = 0x04; +#endif +#ifdef IPTOS_MINCOST +const pj_uint16_t PJ_IPTOS_MINCOST = IPTOS_MINCOST; +#else +const pj_uint16_t PJ_IPTOS_MINCOST = 0x02; +#endif + + +/* optname values. */ +const pj_uint16_t PJ_SO_TYPE = SO_TYPE; +const pj_uint16_t PJ_SO_RCVBUF = SO_RCVBUF; +const pj_uint16_t PJ_SO_SNDBUF = SO_SNDBUF; +const pj_uint16_t PJ_TCP_NODELAY= TCP_NODELAY; +const pj_uint16_t PJ_SO_REUSEADDR= SO_REUSEADDR; +#ifdef SO_NOSIGPIPE +const pj_uint16_t PJ_SO_NOSIGPIPE = SO_NOSIGPIPE; +#else +const pj_uint16_t PJ_SO_NOSIGPIPE = 0xFFFF; +#endif +#if defined(SO_PRIORITY) +const pj_uint16_t PJ_SO_PRIORITY = SO_PRIORITY; +#else +/* This is from Linux, YMMV */ +const pj_uint16_t PJ_SO_PRIORITY = 12; +#endif + +/* Multicasting is not supported e.g. in PocketPC 2003 SDK */ +#ifdef IP_MULTICAST_IF +const pj_uint16_t PJ_IP_MULTICAST_IF = IP_MULTICAST_IF; +const pj_uint16_t PJ_IP_MULTICAST_TTL = IP_MULTICAST_TTL; +const pj_uint16_t PJ_IP_MULTICAST_LOOP = IP_MULTICAST_LOOP; +const pj_uint16_t PJ_IP_ADD_MEMBERSHIP = IP_ADD_MEMBERSHIP; +const pj_uint16_t PJ_IP_DROP_MEMBERSHIP = IP_DROP_MEMBERSHIP; +#else +const pj_uint16_t PJ_IP_MULTICAST_IF = 0xFFFF; +const pj_uint16_t PJ_IP_MULTICAST_TTL = 0xFFFF; +const pj_uint16_t PJ_IP_MULTICAST_LOOP = 0xFFFF; +const pj_uint16_t PJ_IP_ADD_MEMBERSHIP = 0xFFFF; +const pj_uint16_t PJ_IP_DROP_MEMBERSHIP = 0xFFFF; +#endif + +/* recv() and send() flags */ +const int PJ_MSG_OOB = MSG_OOB; +const int PJ_MSG_PEEK = MSG_PEEK; +const int PJ_MSG_DONTROUTE = MSG_DONTROUTE; + + + +using namespace Platform; +using namespace Windows::Foundation; +using namespace Windows::Networking; +using namespace Windows::Networking::Sockets; +using namespace Windows::Storage::Streams; + + +ref class PjUwpSocketDatagramRecvHelper sealed +{ +internal: + PjUwpSocketDatagramRecvHelper(PjUwpSocket* uwp_sock_) : + uwp_sock(uwp_sock_), avail_data_len(0), recv_args(nullptr) + { + recv_wait = CreateEventEx(nullptr, nullptr, 0, EVENT_ALL_ACCESS); + event_token = uwp_sock->datagram_sock->MessageReceived += + ref new TypedEventHandler<DatagramSocket^, + DatagramSocketMessageReceivedEventArgs^> + (this, &PjUwpSocketDatagramRecvHelper::OnMessageReceived); + } + + void OnMessageReceived(DatagramSocket ^sender, + DatagramSocketMessageReceivedEventArgs ^args) + { + try { + if (uwp_sock->sock_state >= SOCKSTATE_DISCONNECTED) + return; + + recv_args = args; + avail_data_len = args->GetDataReader()->UnconsumedBufferLength; + + if (uwp_sock->cb.on_read) { + (*uwp_sock->cb.on_read)(uwp_sock, avail_data_len); + } + + WaitForSingleObjectEx(recv_wait, INFINITE, false); + } catch (...) {} + } + + pj_status_t ReadDataIfAvailable(void *buf, pj_ssize_t *len, + pj_sockaddr_t *from) + { + if (avail_data_len <= 0) + return PJ_ENOTFOUND; + + if (*len < avail_data_len) + return PJ_ETOOSMALL; + + // Read data + auto reader = recv_args->GetDataReader(); + auto buffer = reader->ReadBuffer(avail_data_len); + unsigned char *p; + GetRawBufferFromIBuffer(buffer, &p); + pj_memcpy((void*) buf, p, avail_data_len); + *len = avail_data_len; + + // Get source address + wstr_addr_to_sockaddr(recv_args->RemoteAddress->CanonicalName->Data(), + recv_args->RemotePort->Data(), from); + + // finally + avail_data_len = 0; + SetEvent(recv_wait); + + return PJ_SUCCESS; + } + +private: + + ~PjUwpSocketDatagramRecvHelper() + { + if (uwp_sock->datagram_sock) + uwp_sock->datagram_sock->MessageReceived -= event_token; + + SetEvent(recv_wait); + CloseHandle(recv_wait); + } + + PjUwpSocket* uwp_sock; + DatagramSocketMessageReceivedEventArgs^ recv_args; + EventRegistrationToken event_token; + HANDLE recv_wait; + int avail_data_len; +}; + + +ref class PjUwpSocketListenerHelper sealed +{ +internal: + PjUwpSocketListenerHelper(PjUwpSocket* uwp_sock_) : + uwp_sock(uwp_sock_), conn_args(nullptr) + { + conn_wait = CreateEventEx(nullptr, nullptr, 0, EVENT_ALL_ACCESS); + event_token = uwp_sock->listener_sock->ConnectionReceived += + ref new TypedEventHandler<StreamSocketListener^, StreamSocketListenerConnectionReceivedEventArgs^> + (this, &PjUwpSocketListenerHelper::OnConnectionReceived); + } + + void OnConnectionReceived(StreamSocketListener ^sender, + StreamSocketListenerConnectionReceivedEventArgs ^args) + { + try { + conn_args = args; + + if (uwp_sock->cb.on_accept) { + (*uwp_sock->cb.on_accept)(uwp_sock); + } + + WaitForSingleObjectEx(conn_wait, INFINITE, false); + } catch (Exception^ e) {} + } + + pj_status_t GetAcceptedSocket(StreamSocket^& stream_sock) + { + if (conn_args == nullptr) + return PJ_ENOTFOUND; + + stream_sock = conn_args->Socket; + + // finally + conn_args = nullptr; + SetEvent(conn_wait); + + return PJ_SUCCESS; + } + +private: + + ~PjUwpSocketListenerHelper() + { + if (uwp_sock->listener_sock) + uwp_sock->listener_sock->ConnectionReceived -= event_token; + + SetEvent(conn_wait); + CloseHandle(conn_wait); + } + + PjUwpSocket* uwp_sock; + StreamSocketListenerConnectionReceivedEventArgs^ conn_args; + EventRegistrationToken event_token; + HANDLE conn_wait; +}; + + +PjUwpSocket::PjUwpSocket(int af_, int type_, int proto_) : + af(af_), type(type_), proto(proto_), + sock_type(SOCKTYPE_UNKNOWN), + sock_state(SOCKSTATE_NULL), + is_blocking(PJ_TRUE), + has_pending_bind(PJ_FALSE), + has_pending_send(PJ_FALSE), + has_pending_recv(PJ_FALSE) +{ + pj_sockaddr_init(pj_AF_INET(), &local_addr, NULL, 0); + pj_sockaddr_init(pj_AF_INET(), &remote_addr, NULL, 0); +} + +PjUwpSocket::~PjUwpSocket() +{ + DeinitSocket(); +} + +PjUwpSocket* PjUwpSocket::CreateAcceptSocket(Windows::Networking::Sockets::StreamSocket^ stream_sock_) +{ + PjUwpSocket *new_sock = new PjUwpSocket(pj_AF_INET(), pj_SOCK_STREAM(), 0); + new_sock->stream_sock = stream_sock_; + new_sock->sock_type = SOCKTYPE_STREAM; + new_sock->sock_state = SOCKSTATE_CONNECTED; + new_sock->socket_reader = ref new DataReader(new_sock->stream_sock->InputStream); + new_sock->socket_writer = ref new DataWriter(new_sock->stream_sock->OutputStream); + new_sock->socket_reader->InputStreamOptions = InputStreamOptions::Partial; + new_sock->send_buffer = ref new Buffer(SEND_BUFFER_SIZE); + new_sock->is_blocking = is_blocking; + + // Update local & remote address + wstr_addr_to_sockaddr(stream_sock_->Information->RemoteAddress->CanonicalName->Data(), + stream_sock_->Information->RemotePort->Data(), + &new_sock->remote_addr); + wstr_addr_to_sockaddr(stream_sock_->Information->LocalAddress->CanonicalName->Data(), + stream_sock_->Information->LocalPort->Data(), + &new_sock->local_addr); + + return new_sock; +} + + +pj_status_t PjUwpSocket::InitSocket(enum PjUwpSocketType sock_type_) +{ + PJ_ASSERT_RETURN(sock_type_ > SOCKTYPE_UNKNOWN, PJ_EINVAL); + + sock_type = sock_type_; + if (sock_type == SOCKTYPE_LISTENER) { + listener_sock = ref new StreamSocketListener(); + } else if (sock_type == SOCKTYPE_STREAM) { + stream_sock = ref new StreamSocket(); + } else if (sock_type == SOCKTYPE_DATAGRAM) { + datagram_sock = ref new DatagramSocket(); + } else { + pj_assert(!"Invalid socket type"); + return PJ_EINVAL; + } + + if (sock_type == SOCKTYPE_DATAGRAM || sock_type == SOCKTYPE_STREAM) { + send_buffer = ref new Buffer(SEND_BUFFER_SIZE); + } + + sock_state = SOCKSTATE_INITIALIZED; + + return PJ_SUCCESS; +} + + +void PjUwpSocket::DeinitSocket() +{ + if (stream_sock) { + concurrency::create_task(stream_sock->CancelIOAsync()).wait(); + } + if (datagram_sock && has_pending_send) { + concurrency::create_task(datagram_sock->CancelIOAsync()).wait(); + } + if (listener_sock) { + concurrency::create_task(listener_sock->CancelIOAsync()).wait(); + } + while (has_pending_recv) pj_thread_sleep(10); + + stream_sock = nullptr; + datagram_sock = nullptr; + dgram_recv_helper = nullptr; + listener_sock = nullptr; + listener_helper = nullptr; + socket_writer = nullptr; + socket_reader = nullptr; + sock_state = SOCKSTATE_NULL; +} + +pj_status_t PjUwpSocket::Bind(const pj_sockaddr_t *addr) +{ + /* Not initialized yet, socket type is perhaps TCP, just not decided yet + * whether it is a stream or a listener. + */ + if (sock_state < SOCKSTATE_INITIALIZED) { + pj_sockaddr_cp(&local_addr, addr); + has_pending_bind = PJ_TRUE; + return PJ_SUCCESS; + } + + PJ_ASSERT_RETURN(sock_state == SOCKSTATE_INITIALIZED, PJ_EINVALIDOP); + if (sock_type != SOCKTYPE_DATAGRAM && sock_type != SOCKTYPE_LISTENER) + return PJ_EINVALIDOP; + + if (has_pending_bind) { + has_pending_bind = PJ_FALSE; + if (!addr) + addr = &local_addr; + } + + /* If no bound address is set, just return */ + if (!pj_sockaddr_has_addr(addr) && !pj_sockaddr_get_port(addr)) + return PJ_SUCCESS; + + if (addr != &local_addr) + pj_sockaddr_cp(&local_addr, addr); + + HRESULT err = 0; + concurrency::create_task([this, addr, &err]() { + HostName ^host; + int port; + + sockaddr_to_hostname_port(addr, host, &port); + if (pj_sockaddr_has_addr(addr)) { + if (sock_type == SOCKTYPE_DATAGRAM) + return datagram_sock->BindEndpointAsync(host, port.ToString()); + else + return listener_sock->BindEndpointAsync(host, port.ToString()); + } else /* if (pj_sockaddr_get_port(addr) != 0) */ { + if (sock_type == SOCKTYPE_DATAGRAM) + return datagram_sock->BindServiceNameAsync(port.ToString()); + else + return listener_sock->BindServiceNameAsync(port.ToString()); + } + }).then([this, &err](concurrency::task<void> t) + { + try { + if (!err) + t.get(); + } catch (Exception^ e) { + err = e->HResult; + } + }).get(); + + return (err? PJ_RETURN_OS_ERROR(err) : PJ_SUCCESS); +} + + +pj_status_t PjUwpSocket::SendImp(const void *buf, pj_ssize_t *len) +{ + if (has_pending_send) + return PJ_RETURN_OS_ERROR(PJ_BLOCKING_ERROR_VAL); + + if (*len > (pj_ssize_t)send_buffer->Capacity) + return PJ_ETOOBIG; + + CopyToIBuffer((unsigned char*)buf, *len, send_buffer); + send_buffer->Length = *len; + socket_writer->WriteBuffer(send_buffer); + + /* Blocking version */ + if (is_blocking) { + pj_status_t status = PJ_SUCCESS; + concurrency::cancellation_token_source cts; + auto cts_token = cts.get_token(); + auto t = concurrency::create_task(socket_writer->StoreAsync(), + cts_token); + *len = cancel_after_timeout(t, cts, (unsigned int)WRITE_TIMEOUT). + then([cts_token, &status](concurrency::task<unsigned int> t_) + { + int sent = 0; + try { + if (cts_token.is_canceled()) + status = PJ_ETIMEDOUT; + else + sent = t_.get(); + } catch (Exception^ e) { + status = PJ_RETURN_OS_ERROR(e->HResult); + } + return sent; + }).get(); + + return status; + } + + /* Non-blocking version */ + has_pending_send = PJ_TRUE; + concurrency::create_task(socket_writer->StoreAsync()). + then([this](concurrency::task<unsigned int> t_) + { + try { + unsigned int l = t_.get(); + has_pending_send = PJ_FALSE; + + // invoke callback + if (cb.on_write) { + (*cb.on_write)(this, l); + } + } catch (...) { + has_pending_send = PJ_FALSE; + sock_state = SOCKSTATE_ERROR; + DeinitSocket(); + + // invoke callback + if (cb.on_write) { + (*cb.on_write)(this, -PJ_EUNKNOWN); + } + } + }); + + return PJ_SUCCESS; +} + + +pj_status_t PjUwpSocket::Send(const void *buf, pj_ssize_t *len) +{ + if ((sock_type!=SOCKTYPE_STREAM && sock_type!=SOCKTYPE_DATAGRAM) || + (sock_state!=SOCKSTATE_CONNECTED)) + { + return PJ_EINVALIDOP; + } + + /* Sending for SOCKTYPE_DATAGRAM is implemented in pj_sock_sendto() */ + if (sock_type == SOCKTYPE_DATAGRAM) { + return SendTo(buf, len, &remote_addr); + } + + return SendImp(buf, len); +} + + +pj_status_t PjUwpSocket::SendTo(const void *buf, pj_ssize_t *len, + const pj_sockaddr_t *to) +{ + if (sock_type != SOCKTYPE_DATAGRAM || sock_state < SOCKSTATE_INITIALIZED + || sock_state >= SOCKSTATE_DISCONNECTED) + { + return PJ_EINVALIDOP; + } + + if (has_pending_send) + return PJ_RETURN_OS_ERROR(PJ_BLOCKING_ERROR_VAL); + + if (*len > (pj_ssize_t)send_buffer->Capacity) + return PJ_ETOOBIG; + + HostName ^hostname; + int port; + sockaddr_to_hostname_port(to, hostname, &port); + + concurrency::cancellation_token_source cts; + auto cts_token = cts.get_token(); + auto t = concurrency::create_task(datagram_sock->GetOutputStreamAsync( + hostname, port.ToString()), cts_token); + pj_status_t status = PJ_SUCCESS; + + cancel_after_timeout(t, cts, (unsigned int)WRITE_TIMEOUT). + then([this, cts_token, &status](concurrency::task<IOutputStream^> t_) + { + try { + if (cts_token.is_canceled()) { + status = PJ_ETIMEDOUT; + } else { + IOutputStream^ outstream = t_.get(); + socket_writer = ref new DataWriter(outstream); + } + } catch (Exception^ e) { + status = PJ_RETURN_OS_ERROR(e->HResult); + } + }).get(); + + if (status != PJ_SUCCESS) + return status; + + status = SendImp(buf, len); + if ((status == PJ_SUCCESS || status == PJ_EPENDING) && + sock_state < SOCKSTATE_CONNECTED) + { + sock_state = SOCKSTATE_CONNECTED; + } + + return status; +} + + +int PjUwpSocket::ConsumeReadBuffer(void *buf, int max_len) +{ + if (socket_reader->UnconsumedBufferLength == 0) + return 0; + + int read_len = PJ_MIN((int)socket_reader->UnconsumedBufferLength,max_len); + IBuffer^ buffer = socket_reader->ReadBuffer(read_len); + read_len = buffer->Length; + CopyFromIBuffer((unsigned char*)buf, read_len, buffer); + return read_len; +} + + +pj_status_t PjUwpSocket::Recv(void *buf, pj_ssize_t *len) +{ + /* Only for TCP, at least for now! */ + if (sock_type == SOCKTYPE_DATAGRAM) + return PJ_ENOTSUP; + + if (sock_type != SOCKTYPE_STREAM || sock_state != SOCKSTATE_CONNECTED) + return PJ_EINVALIDOP; + + if (has_pending_recv) + return PJ_RETURN_OS_ERROR(PJ_BLOCKING_ERROR_VAL); + + /* First check if there is already some data in the read buffer */ + if (buf) { + int avail_len = ConsumeReadBuffer(buf, *len); + if (avail_len > 0) { + *len = avail_len; + return PJ_SUCCESS; + } + } + + /* Blocking version */ + if (is_blocking) { + pj_status_t status = PJ_SUCCESS; + concurrency::cancellation_token_source cts; + auto cts_token = cts.get_token(); + auto t = concurrency::create_task(socket_reader->LoadAsync(*len), + cts_token); + *len = cancel_after_timeout(t, cts, READ_TIMEOUT) + .then([this, len, buf, cts_token, &status] + (concurrency::task<unsigned int> t_) + { + try { + if (cts_token.is_canceled()) { + status = PJ_ETIMEDOUT; + return 0; + } + t_.get(); + } catch (Exception^) { + status = PJ_ETIMEDOUT; + return 0; + } + + *len = ConsumeReadBuffer(buf, *len); + return (int)*len; + }).get(); + + return status; + } + + /* Non-blocking version */ + + concurrency::cancellation_token_source cts; + auto cts_token = cts.get_token(); + + has_pending_recv = PJ_TRUE; + concurrency::create_task(socket_reader->LoadAsync(*len), cts_token) + .then([this, cts_token](concurrency::task<unsigned int> t_) + { + try { + if (cts_token.is_canceled()) { + has_pending_recv = PJ_FALSE; + + // invoke callback + if (cb.on_read) { + (*cb.on_read)(this, -PJ_EUNKNOWN); + } + return; + } + + t_.get(); + has_pending_recv = PJ_FALSE; + + // invoke callback + int read_len = socket_reader->UnconsumedBufferLength; + if (read_len > 0 && cb.on_read) { + (*cb.on_read)(this, read_len); + } + } catch (...) { + has_pending_recv = PJ_FALSE; + + // invoke callback + if (cb.on_read) { + (*cb.on_read)(this, -PJ_EUNKNOWN); + } + } + }); + + return PJ_RETURN_OS_ERROR(PJ_BLOCKING_ERROR_VAL); +} + + +pj_status_t PjUwpSocket::RecvFrom(void *buf, pj_ssize_t *len, + pj_sockaddr_t *from) +{ + if (sock_type != SOCKTYPE_DATAGRAM || sock_state < SOCKSTATE_INITIALIZED + || sock_state >= SOCKSTATE_DISCONNECTED) + { + return PJ_EINVALIDOP; + } + + /* Start receive, if not yet */ + if (dgram_recv_helper == nullptr) { + dgram_recv_helper = ref new PjUwpSocketDatagramRecvHelper(this); + } + + /* Try to read any available data first */ + if (buf || is_blocking) { + pj_status_t status; + status = dgram_recv_helper->ReadDataIfAvailable(buf, len, from); + if (status != PJ_ENOTFOUND) + return status; + } + + /* Blocking version */ + if (is_blocking) { + int max_loop = 0; + pj_status_t status = PJ_ENOTFOUND; + while (status == PJ_ENOTFOUND && sock_state <= SOCKSTATE_CONNECTED) + { + status = dgram_recv_helper->ReadDataIfAvailable(buf, len, from); + if (status != PJ_SUCCESS) + pj_thread_sleep(100); + + if (++max_loop > 10) + return PJ_ETIMEDOUT; + } + return status; + } + + /* For non-blocking version, just return PJ_EPENDING */ + return PJ_RETURN_OS_ERROR(PJ_BLOCKING_ERROR_VAL); +} + + +pj_status_t PjUwpSocket::Connect(const pj_sockaddr_t *addr) +{ + pj_status_t status; + + PJ_ASSERT_RETURN((sock_type == SOCKTYPE_UNKNOWN && sock_state == SOCKSTATE_NULL) || + (sock_type == SOCKTYPE_DATAGRAM && sock_state == SOCKSTATE_INITIALIZED), + PJ_EINVALIDOP); + + if (sock_type == SOCKTYPE_UNKNOWN) { + InitSocket(SOCKTYPE_STREAM); + // No need to check pending bind, no bind for TCP client socket + } + + pj_sockaddr_cp(&remote_addr, addr); + + auto t = concurrency::create_task([this, addr]() + { + HostName ^hostname; + int port; + sockaddr_to_hostname_port(&remote_addr, hostname, &port); + if (sock_type == SOCKTYPE_STREAM) + return stream_sock->ConnectAsync(hostname, port.ToString(), + SocketProtectionLevel::PlainSocket); + else + return datagram_sock->ConnectAsync(hostname, port.ToString()); + }).then([=](concurrency::task<void> t_) + { + try { + t_.get(); + + sock_state = SOCKSTATE_CONNECTED; + + // Update local & remote address + HostName^ local_address; + String^ local_port; + + if (sock_type == SOCKTYPE_STREAM) { + local_address = stream_sock->Information->LocalAddress; + local_port = stream_sock->Information->LocalPort; + + socket_reader = ref new DataReader(stream_sock->InputStream); + socket_writer = ref new DataWriter(stream_sock->OutputStream); + socket_reader->InputStreamOptions = InputStreamOptions::Partial; + } else { + local_address = datagram_sock->Information->LocalAddress; + local_port = datagram_sock->Information->LocalPort; + } + if (local_address && local_port) { + wstr_addr_to_sockaddr(local_address->CanonicalName->Data(), + local_port->Data(), + &local_addr); + } + + if (!is_blocking && cb.on_connect) { + (*cb.on_connect)(this, PJ_SUCCESS); + } + return (pj_status_t)PJ_SUCCESS; + + } catch (Exception^ ex) { + + SocketErrorStatus status = SocketError::GetStatus(ex->HResult); + + switch (status) + { + case SocketErrorStatus::UnreachableHost: + break; + case SocketErrorStatus::ConnectionTimedOut: + break; + case SocketErrorStatus::ConnectionRefused: + break; + default: + break; + } + + if (!is_blocking && cb.on_connect) { + (*cb.on_connect)(this, PJ_EUNKNOWN); + } + + return (pj_status_t)PJ_EUNKNOWN; + } + }); + + if (!is_blocking) + return PJ_RETURN_OS_ERROR(PJ_BLOCKING_CONNECT_ERROR_VAL); + + try { + status = t.get(); + } catch (Exception^) { + return PJ_EUNKNOWN; + } + return status; +} + +pj_status_t PjUwpSocket::Listen() +{ + PJ_ASSERT_RETURN((sock_type == SOCKTYPE_UNKNOWN) || + (sock_type == SOCKTYPE_LISTENER && + sock_state == SOCKSTATE_INITIALIZED), + PJ_EINVALIDOP); + + if (sock_type == SOCKTYPE_UNKNOWN) + InitSocket(SOCKTYPE_LISTENER); + + if (has_pending_bind) + Bind(); + + /* Start listen */ + if (listener_helper == nullptr) { + listener_helper = ref new PjUwpSocketListenerHelper(this); + } + + return PJ_SUCCESS; +} + +pj_status_t PjUwpSocket::Accept(PjUwpSocket **new_sock) +{ + if (sock_type != SOCKTYPE_LISTENER || sock_state != SOCKSTATE_INITIALIZED) + return PJ_EINVALIDOP; + + StreamSocket^ accepted_sock; + pj_status_t status = listener_helper->GetAcceptedSocket(accepted_sock); + if (status == PJ_ENOTFOUND) + return PJ_RETURN_OS_ERROR(PJ_BLOCKING_ERROR_VAL); + + if (status != PJ_SUCCESS) + return status; + + *new_sock = CreateAcceptSocket(accepted_sock); + return PJ_SUCCESS; +} + + +///////////////////////////////////////////////////////////////////////////// +// +// PJLIB's sock.h implementation +// + +/* + * Convert 16-bit value from network byte order to host byte order. + */ +PJ_DEF(pj_uint16_t) pj_ntohs(pj_uint16_t netshort) +{ + return ntohs(netshort); +} + +/* + * Convert 16-bit value from host byte order to network byte order. + */ +PJ_DEF(pj_uint16_t) pj_htons(pj_uint16_t hostshort) +{ + return htons(hostshort); +} + +/* + * Convert 32-bit value from network byte order to host byte order. + */ +PJ_DEF(pj_uint32_t) pj_ntohl(pj_uint32_t netlong) +{ + return ntohl(netlong); +} + +/* + * Convert 32-bit value from host byte order to network byte order. + */ +PJ_DEF(pj_uint32_t) pj_htonl(pj_uint32_t hostlong) +{ + return htonl(hostlong); +} + +/* + * Convert an Internet host address given in network byte order + * to string in standard numbers and dots notation. + */ +PJ_DEF(char*) pj_inet_ntoa(pj_in_addr inaddr) +{ + return inet_ntoa(*(struct in_addr*)&inaddr); +} + +/* + * This function converts the Internet host address cp from the standard + * numbers-and-dots notation into binary data and stores it in the structure + * that inp points to. + */ +PJ_DEF(int) pj_inet_aton(const pj_str_t *cp, struct pj_in_addr *inp) +{ + char tempaddr[PJ_INET_ADDRSTRLEN]; + + /* Initialize output with PJ_INADDR_NONE. + * Some apps relies on this instead of the return value + * (and anyway the return value is quite confusing!) + */ + inp->s_addr = PJ_INADDR_NONE; + + /* Caution: + * this function might be called with cp->slen >= 16 + * (i.e. when called with hostname to check if it's an IP addr). + */ + PJ_ASSERT_RETURN(cp && cp->slen && inp, 0); + if (cp->slen >= PJ_INET_ADDRSTRLEN) { + return 0; + } + + pj_memcpy(tempaddr, cp->ptr, cp->slen); + tempaddr[cp->slen] = '\0'; + +#if defined(PJ_SOCK_HAS_INET_ATON) && PJ_SOCK_HAS_INET_ATON != 0 + return inet_aton(tempaddr, (struct in_addr*)inp); +#else + inp->s_addr = inet_addr(tempaddr); + return inp->s_addr == PJ_INADDR_NONE ? 0 : 1; +#endif +} + +/* + * Convert text to IPv4/IPv6 address. + */ +PJ_DEF(pj_status_t) pj_inet_pton(int af, const pj_str_t *src, void *dst) +{ + char tempaddr[PJ_INET6_ADDRSTRLEN]; + + PJ_ASSERT_RETURN(af == PJ_AF_INET || af == PJ_AF_INET6, PJ_EAFNOTSUP); + PJ_ASSERT_RETURN(src && src->slen && dst, PJ_EINVAL); + + /* Initialize output with PJ_IN_ADDR_NONE for IPv4 (to be + * compatible with pj_inet_aton() + */ + if (af == PJ_AF_INET) { + ((pj_in_addr*)dst)->s_addr = PJ_INADDR_NONE; + } + + /* Caution: + * this function might be called with cp->slen >= 46 + * (i.e. when called with hostname to check if it's an IP addr). + */ + if (src->slen >= PJ_INET6_ADDRSTRLEN) { + return PJ_ENAMETOOLONG; + } + + pj_memcpy(tempaddr, src->ptr, src->slen); + tempaddr[src->slen] = '\0'; + +#if defined(PJ_SOCK_HAS_INET_PTON) && PJ_SOCK_HAS_INET_PTON != 0 + /* + * Implementation using inet_pton() + */ + if (inet_pton(af, tempaddr, dst) != 1) { + pj_status_t status = pj_get_netos_error(); + if (status == PJ_SUCCESS) + status = PJ_EUNKNOWN; + + return status; + } + + return PJ_SUCCESS; + +#elif defined(PJ_WIN32) || defined(PJ_WIN64) || defined(PJ_WIN32_WINCE) + /* + * Implementation on Windows, using WSAStringToAddress(). + * Should also work on Unicode systems. + */ + { + PJ_DECL_UNICODE_TEMP_BUF(wtempaddr, PJ_INET6_ADDRSTRLEN) + pj_sockaddr sock_addr; + int addr_len = sizeof(sock_addr); + int rc; + + sock_addr.addr.sa_family = (pj_uint16_t)af; + rc = WSAStringToAddress( + PJ_STRING_TO_NATIVE(tempaddr, wtempaddr, sizeof(wtempaddr)), + af, NULL, (LPSOCKADDR)&sock_addr, &addr_len); + if (rc != 0) { + /* If you get rc 130022 Invalid argument (WSAEINVAL) with IPv6, + * check that you have IPv6 enabled (install it in the network + * adapter). + */ + pj_status_t status = pj_get_netos_error(); + if (status == PJ_SUCCESS) + status = PJ_EUNKNOWN; + + return status; + } + + if (sock_addr.addr.sa_family == PJ_AF_INET) { + pj_memcpy(dst, &sock_addr.ipv4.sin_addr, 4); + return PJ_SUCCESS; + } + else if (sock_addr.addr.sa_family == PJ_AF_INET6) { + pj_memcpy(dst, &sock_addr.ipv6.sin6_addr, 16); + return PJ_SUCCESS; + } + else { + pj_assert(!"Shouldn't happen"); + return PJ_EBUG; + } + } +#elif !defined(PJ_HAS_IPV6) || PJ_HAS_IPV6==0 + /* IPv6 support is disabled, just return error without raising assertion */ + return PJ_EIPV6NOTSUP; +#else + pj_assert(!"Not supported"); + return PJ_EIPV6NOTSUP; +#endif +} + +/* + * Convert IPv4/IPv6 address to text. + */ +PJ_DEF(pj_status_t) pj_inet_ntop(int af, const void *src, + char *dst, int size) + +{ + PJ_ASSERT_RETURN(src && dst && size, PJ_EINVAL); + PJ_ASSERT_RETURN(af == PJ_AF_INET || af == PJ_AF_INET6, PJ_EAFNOTSUP); + + *dst = '\0'; + +#if defined(PJ_SOCK_HAS_INET_NTOP) && PJ_SOCK_HAS_INET_NTOP != 0 + /* + * Implementation using inet_ntop() + */ + if (inet_ntop(af, src, dst, size) == NULL) { + pj_status_t status = pj_get_netos_error(); + if (status == PJ_SUCCESS) + status = PJ_EUNKNOWN; + + return status; + } + + return PJ_SUCCESS; + +#elif defined(PJ_WIN32) || defined(PJ_WIN64) || defined(PJ_WIN32_WINCE) + /* + * Implementation on Windows, using WSAAddressToString(). + * Should also work on Unicode systems. + */ + { + PJ_DECL_UNICODE_TEMP_BUF(wtempaddr, PJ_INET6_ADDRSTRLEN) + pj_sockaddr sock_addr; + DWORD addr_len, addr_str_len; + int rc; + + pj_bzero(&sock_addr, sizeof(sock_addr)); + sock_addr.addr.sa_family = (pj_uint16_t)af; + if (af == PJ_AF_INET) { + if (size < PJ_INET_ADDRSTRLEN) + return PJ_ETOOSMALL; + pj_memcpy(&sock_addr.ipv4.sin_addr, src, 4); + addr_len = sizeof(pj_sockaddr_in); + addr_str_len = PJ_INET_ADDRSTRLEN; + } + else if (af == PJ_AF_INET6) { + if (size < PJ_INET6_ADDRSTRLEN) + return PJ_ETOOSMALL; + pj_memcpy(&sock_addr.ipv6.sin6_addr, src, 16); + addr_len = sizeof(pj_sockaddr_in6); + addr_str_len = PJ_INET6_ADDRSTRLEN; + } + else { + pj_assert(!"Unsupported address family"); + return PJ_EAFNOTSUP; + } + +#if PJ_NATIVE_STRING_IS_UNICODE + rc = WSAAddressToString((LPSOCKADDR)&sock_addr, addr_len, + NULL, wtempaddr, &addr_str_len); + if (rc == 0) { + pj_unicode_to_ansi(wtempaddr, wcslen(wtempaddr), dst, size); + } +#else + rc = WSAAddressToString((LPSOCKADDR)&sock_addr, addr_len, + NULL, dst, &addr_str_len); +#endif + + if (rc != 0) { + pj_status_t status = pj_get_netos_error(); + if (status == PJ_SUCCESS) + status = PJ_EUNKNOWN; + + return status; + } + + return PJ_SUCCESS; + } + +#elif !defined(PJ_HAS_IPV6) || PJ_HAS_IPV6==0 + /* IPv6 support is disabled, just return error without raising assertion */ + return PJ_EIPV6NOTSUP; +#else + pj_assert(!"Not supported"); + return PJ_EIPV6NOTSUP; +#endif +} + +/* + * Get hostname. + */ +PJ_DEF(const pj_str_t*) pj_gethostname(void) +{ + static char buf[PJ_MAX_HOSTNAME]; + static pj_str_t hostname; + + PJ_CHECK_STACK(); + + if (hostname.ptr == NULL) { + hostname.ptr = buf; + if (gethostname(buf, sizeof(buf)) != 0) { + hostname.ptr[0] = '\0'; + hostname.slen = 0; + } + else { + hostname.slen = strlen(buf); + } + } + return &hostname; +} + +/* + * Create new socket/endpoint for communication and returns a descriptor. + */ +PJ_DEF(pj_status_t) pj_sock_socket(int af, + int type, + int proto, + pj_sock_t *p_sock) +{ + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(p_sock!=NULL, PJ_EINVAL); + + PjUwpSocket *s = new PjUwpSocket(af, type, proto); + + /* Init UDP socket here */ + if (type == pj_SOCK_DGRAM()) { + s->InitSocket(SOCKTYPE_DATAGRAM); + } + + *p_sock = (pj_sock_t)s; + return PJ_SUCCESS; +} + + +/* + * Bind socket. + */ +PJ_DEF(pj_status_t) pj_sock_bind( pj_sock_t sock, + const pj_sockaddr_t *addr, + int len) +{ + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(sock, PJ_EINVAL); + PJ_ASSERT_RETURN(addr && len>=(int)sizeof(pj_sockaddr_in), PJ_EINVAL); + PjUwpSocket *s = (PjUwpSocket*)sock; + return s->Bind(addr); +} + + +/* + * Bind socket. + */ +PJ_DEF(pj_status_t) pj_sock_bind_in( pj_sock_t sock, + pj_uint32_t addr32, + pj_uint16_t port) +{ + pj_sockaddr_in addr; + + PJ_CHECK_STACK(); + + pj_bzero(&addr, sizeof(addr)); + addr.sin_family = PJ_AF_INET; + addr.sin_addr.s_addr = pj_htonl(addr32); + addr.sin_port = pj_htons(port); + + return pj_sock_bind(sock, &addr, sizeof(pj_sockaddr_in)); +} + + +/* + * Close socket. + */ +PJ_DEF(pj_status_t) pj_sock_close(pj_sock_t sock) +{ + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(sock, PJ_EINVAL); + + if (sock == PJ_INVALID_SOCKET) + return PJ_SUCCESS; + + PjUwpSocket *s = (PjUwpSocket*)sock; + delete s; + + return PJ_SUCCESS; +} + +/* + * Get remote's name. + */ +PJ_DEF(pj_status_t) pj_sock_getpeername( pj_sock_t sock, + pj_sockaddr_t *addr, + int *namelen) +{ + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(sock && addr && namelen && + *namelen>=(int)sizeof(pj_sockaddr_in), PJ_EINVAL); + + PjUwpSocket *s = (PjUwpSocket*)sock; + pj_sockaddr_cp(addr, s->GetRemoteAddr()); + *namelen = pj_sockaddr_get_len(addr); + + return PJ_SUCCESS; +} + +/* + * Get socket name. + */ +PJ_DEF(pj_status_t) pj_sock_getsockname( pj_sock_t sock, + pj_sockaddr_t *addr, + int *namelen) +{ + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(sock && addr && namelen && + *namelen>=(int)sizeof(pj_sockaddr_in), PJ_EINVAL); + + PjUwpSocket *s = (PjUwpSocket*)sock; + pj_sockaddr_cp(addr, s->GetLocalAddr()); + *namelen = pj_sockaddr_get_len(addr); + + return PJ_SUCCESS; +} + + +/* + * Send data + */ +PJ_DEF(pj_status_t) pj_sock_send(pj_sock_t sock, + const void *buf, + pj_ssize_t *len, + unsigned flags) +{ + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(sock && buf && len, PJ_EINVAL); + PJ_UNUSED_ARG(flags); + + PjUwpSocket *s = (PjUwpSocket*)sock; + return s->Send(buf, len); +} + + +/* + * Send data. + */ +PJ_DEF(pj_status_t) pj_sock_sendto(pj_sock_t sock, + const void *buf, + pj_ssize_t *len, + unsigned flags, + const pj_sockaddr_t *to, + int tolen) +{ + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(sock && buf && len, PJ_EINVAL); + PJ_UNUSED_ARG(flags); + PJ_UNUSED_ARG(tolen); + + PjUwpSocket *s = (PjUwpSocket*)sock; + return s->SendTo(buf, len, to); +} + + +/* + * Receive data. + */ +PJ_DEF(pj_status_t) pj_sock_recv(pj_sock_t sock, + void *buf, + pj_ssize_t *len, + unsigned flags) +{ + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(sock && len && *len > 0, PJ_EINVAL); + + PJ_UNUSED_ARG(flags); + + PjUwpSocket *s = (PjUwpSocket*)sock; + return s->Recv(buf, len); +} + +/* + * Receive data. + */ +PJ_DEF(pj_status_t) pj_sock_recvfrom(pj_sock_t sock, + void *buf, + pj_ssize_t *len, + unsigned flags, + pj_sockaddr_t *from, + int *fromlen) +{ + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(sock && buf && len && from && fromlen, PJ_EINVAL); + PJ_ASSERT_RETURN(*len > 0, PJ_EINVAL); + PJ_ASSERT_RETURN(*fromlen >= (int)sizeof(pj_sockaddr_in), PJ_EINVAL); + + PJ_UNUSED_ARG(flags); + + PjUwpSocket *s = (PjUwpSocket*)sock; + pj_status_t status = s->RecvFrom(buf, len, from); + if (status == PJ_SUCCESS) + *fromlen = pj_sockaddr_get_len(from); + return status; +} + +/* + * Get socket option. + */ +PJ_DEF(pj_status_t) pj_sock_getsockopt( pj_sock_t sock, + pj_uint16_t level, + pj_uint16_t optname, + void *optval, + int *optlen) +{ + // Not supported for now. + PJ_UNUSED_ARG(sock); + PJ_UNUSED_ARG(level); + PJ_UNUSED_ARG(optname); + PJ_UNUSED_ARG(optval); + PJ_UNUSED_ARG(optlen); + return PJ_ENOTSUP; +} + + +/* + * Set socket option. + */ +PJ_DEF(pj_status_t) pj_sock_setsockopt( pj_sock_t sock, + pj_uint16_t level, + pj_uint16_t optname, + const void *optval, + int optlen) +{ + // Not supported for now. + PJ_UNUSED_ARG(sock); + PJ_UNUSED_ARG(level); + PJ_UNUSED_ARG(optname); + PJ_UNUSED_ARG(optval); + PJ_UNUSED_ARG(optlen); + return PJ_ENOTSUP; +} + + +/* +* Set socket option. +*/ +PJ_DEF(pj_status_t) pj_sock_setsockopt_params( pj_sock_t sockfd, + const pj_sockopt_params *params) +{ + unsigned int i = 0; + pj_status_t retval = PJ_SUCCESS; + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(params, PJ_EINVAL); + + for (;i<params->cnt && i<PJ_MAX_SOCKOPT_PARAMS;++i) { + pj_status_t status = pj_sock_setsockopt(sockfd, + (pj_uint16_t)params->options[i].level, + (pj_uint16_t)params->options[i].optname, + params->options[i].optval, + params->options[i].optlen); + if (status != PJ_SUCCESS) { + retval = status; + PJ_PERROR(4,(THIS_FILE, status, + "Warning: error applying sock opt %d", + params->options[i].optname)); + } + } + + return retval; +} + + +/* + * Connect socket. + */ +PJ_DEF(pj_status_t) pj_sock_connect( pj_sock_t sock, + const pj_sockaddr_t *addr, + int namelen) +{ + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(sock && addr, PJ_EINVAL); + PJ_UNUSED_ARG(namelen); + + PjUwpSocket *s = (PjUwpSocket*)sock; + return s->Connect(addr); +} + + +/* + * Shutdown socket. + */ +#if PJ_HAS_TCP +PJ_DEF(pj_status_t) pj_sock_shutdown( pj_sock_t sock, + int how) +{ + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(sock, PJ_EINVAL); + PJ_UNUSED_ARG(how); + + return pj_sock_close(sock); +} + +/* + * Start listening to incoming connections. + */ +PJ_DEF(pj_status_t) pj_sock_listen( pj_sock_t sock, + int backlog) +{ + PJ_CHECK_STACK(); + PJ_UNUSED_ARG(backlog); + PJ_ASSERT_RETURN(sock, PJ_EINVAL); + + PjUwpSocket *s = (PjUwpSocket*)sock; + return s->Listen(); +} + +/* + * Accept incoming connections + */ +PJ_DEF(pj_status_t) pj_sock_accept( pj_sock_t serverfd, + pj_sock_t *newsock, + pj_sockaddr_t *addr, + int *addrlen) +{ + pj_status_t status; + + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(serverfd && newsock, PJ_EINVAL); + + PjUwpSocket *s = (PjUwpSocket*)serverfd; + PjUwpSocket *new_uwp_sock; + + status = s->Accept(&new_uwp_sock); + if (status != PJ_SUCCESS) + return status; + if (newsock == NULL) + return PJ_ENOTFOUND; + + *newsock = (pj_sock_t)new_uwp_sock; + + if (addr) + pj_sockaddr_cp(addr, new_uwp_sock->GetRemoteAddr()); + + if (addrlen) + *addrlen = pj_sockaddr_get_len(addr); + + return PJ_SUCCESS; +} +#endif /* PJ_HAS_TCP */ |