/* $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 */ #pragma once #include #include #include #include enum { READ_TIMEOUT = 60 * 1000, WRITE_TIMEOUT = 60 * 1000, SEND_BUFFER_SIZE = 128 * 1024, }; enum PjUwpSocketType { SOCKTYPE_UNKNOWN, SOCKTYPE_LISTENER, SOCKTYPE_STREAM, SOCKTYPE_DATAGRAM }; enum PjUwpSocketState { SOCKSTATE_NULL, SOCKSTATE_INITIALIZED, SOCKSTATE_CONNECTING, SOCKSTATE_CONNECTED, SOCKSTATE_DISCONNECTED, SOCKSTATE_ERROR }; ref class PjUwpSocketDatagramRecvHelper; ref class PjUwpSocketListenerHelper; class PjUwpSocket; typedef struct PjUwpSocketCallback { void (*on_read)(PjUwpSocket *s, int bytes_read); void (*on_write)(PjUwpSocket *s, int bytes_sent); void (*on_accept)(PjUwpSocket *s); void (*on_connect)(PjUwpSocket *s, pj_status_t status); } PjUwpSocketCallback; /* * UWP Socket Wrapper. */ class PjUwpSocket { public: PjUwpSocket(int af_, int type_, int proto_); virtual ~PjUwpSocket(); pj_status_t InitSocket(enum PjUwpSocketType sock_type_); void DeinitSocket(); void* GetUserData() { return user_data; } void SetNonBlocking(const PjUwpSocketCallback *cb_, void *user_data_) { is_blocking = PJ_FALSE; cb=*cb_; user_data = user_data_; } enum PjUwpSocketType GetType() { return sock_type; } enum PjUwpSocketState GetState() { return sock_state; } pj_sockaddr* GetLocalAddr() { return &local_addr; } pj_sockaddr* GetRemoteAddr() { return &remote_addr; } pj_status_t Bind(const pj_sockaddr_t *addr = NULL); pj_status_t Send(const void *buf, pj_ssize_t *len); pj_status_t SendTo(const void *buf, pj_ssize_t *len, const pj_sockaddr_t *to); pj_status_t Recv(void *buf, pj_ssize_t *len); pj_status_t RecvFrom(void *buf, pj_ssize_t *len, pj_sockaddr_t *from); pj_status_t Connect(const pj_sockaddr_t *addr); pj_status_t Listen(); pj_status_t Accept(PjUwpSocket **new_sock); void (*on_read)(PjUwpSocket *s, int bytes_read); void (*on_write)(PjUwpSocket *s, int bytes_sent); void (*on_accept)(PjUwpSocket *s, pj_status_t status); void (*on_connect)(PjUwpSocket *s, pj_status_t status); private: PjUwpSocket* CreateAcceptSocket(Windows::Networking::Sockets::StreamSocket^ stream_sock_); pj_status_t SendImp(const void *buf, pj_ssize_t *len); int ConsumeReadBuffer(void *buf, int max_len); int af; int type; int proto; pj_sockaddr local_addr; pj_sockaddr remote_addr; pj_bool_t is_blocking; pj_bool_t has_pending_bind; pj_bool_t has_pending_send; pj_bool_t has_pending_recv; void *user_data; PjUwpSocketCallback cb; enum PjUwpSocketType sock_type; enum PjUwpSocketState sock_state; Windows::Networking::Sockets::DatagramSocket^ datagram_sock; Windows::Networking::Sockets::StreamSocket^ stream_sock; Windows::Networking::Sockets::StreamSocketListener^ listener_sock; /* Helper objects */ PjUwpSocketDatagramRecvHelper^ dgram_recv_helper; PjUwpSocketListenerHelper^ listener_helper; Windows::Storage::Streams::DataReader^ socket_reader; Windows::Storage::Streams::DataWriter^ socket_writer; Windows::Storage::Streams::IBuffer^ send_buffer; friend PjUwpSocketDatagramRecvHelper; friend PjUwpSocketListenerHelper; }; ////////////////////////////////// // Misc inline pj_status_t wstr_addr_to_sockaddr(const wchar_t *waddr, const wchar_t *wport, pj_sockaddr_t *sockaddr) { #if 0 char tmp_str_buf[PJ_INET6_ADDRSTRLEN+1]; pj_assert(wcslen(waddr) < sizeof(tmp_str_buf)); pj_unicode_to_ansi(waddr, wcslen(waddr), tmp_str_buf, sizeof(tmp_str_buf)); pj_str_t remote_host; pj_strset(&remote_host, tmp_str_buf, pj_ansi_strlen(tmp_str_buf)); pj_sockaddr_parse(pj_AF_UNSPEC(), 0, &remote_host, (pj_sockaddr*)sockaddr); pj_sockaddr_set_port((pj_sockaddr*)sockaddr, (pj_uint16_t)_wtoi(wport)); return PJ_SUCCESS; #endif char tmp_str_buf[PJ_INET6_ADDRSTRLEN+1]; pj_assert(wcslen(waddr) < sizeof(tmp_str_buf)); pj_unicode_to_ansi(waddr, wcslen(waddr), tmp_str_buf, sizeof(tmp_str_buf)); pj_str_t remote_host; pj_strset(&remote_host, tmp_str_buf, pj_ansi_strlen(tmp_str_buf)); pj_sockaddr *addr = (pj_sockaddr*)sockaddr; pj_bool_t got_addr = PJ_FALSE; if (pj_inet_pton(PJ_AF_INET, &remote_host, &addr->ipv4.sin_addr) == PJ_SUCCESS) { addr->addr.sa_family = PJ_AF_INET; got_addr = PJ_TRUE; } else if (pj_inet_pton(PJ_AF_INET6, &remote_host, &addr->ipv6.sin6_addr) == PJ_SUCCESS) { addr->addr.sa_family = PJ_AF_INET6; got_addr = PJ_TRUE; } if (!got_addr) return PJ_EINVAL; pj_sockaddr_set_port(addr, (pj_uint16_t)_wtoi(wport)); return PJ_SUCCESS; } inline pj_status_t sockaddr_to_hostname_port(const pj_sockaddr_t *sockaddr, Windows::Networking::HostName ^&hostname, int *port) { char tmp[PJ_INET6_ADDRSTRLEN]; wchar_t wtmp[PJ_INET6_ADDRSTRLEN]; pj_sockaddr_print(sockaddr, tmp, PJ_INET6_ADDRSTRLEN, 0); pj_ansi_to_unicode(tmp, pj_ansi_strlen(tmp), wtmp, PJ_INET6_ADDRSTRLEN); hostname = ref new Windows::Networking::HostName(ref new Platform::String(wtmp)); *port = pj_sockaddr_get_port(sockaddr); return PJ_SUCCESS; } /* Buffer helper */ #include #include inline Microsoft::WRL::ComPtr GetBufferByteAccess(Windows::Storage::Streams::IBuffer^ buffer) { auto pUnk = reinterpret_cast(buffer); Microsoft::WRL::ComPtr comBuff; pUnk->QueryInterface(__uuidof(Windows::Storage::Streams::IBufferByteAccess), (void**)comBuff.ReleaseAndGetAddressOf()); return comBuff; } inline void GetRawBufferFromIBuffer(Windows::Storage::Streams::IBuffer^ buffer, unsigned char** pbuffer) { Platform::Object^ obj = buffer; Microsoft::WRL::ComPtr insp(reinterpret_cast(obj)); Microsoft::WRL::ComPtr bufferByteAccess; insp.As(&bufferByteAccess); bufferByteAccess->Buffer(pbuffer); } inline void CopyToIBuffer(unsigned char* buffSource, unsigned int copyByteCount, Windows::Storage::Streams::IBuffer^ buffer, unsigned int writeStartPos = 0) { auto bufferLen = buffer->Capacity; assert(copyByteCount <= bufferLen); unsigned char* pBuffer; GetRawBufferFromIBuffer(buffer, &pBuffer); memcpy(pBuffer + writeStartPos, buffSource, copyByteCount); } inline void CopyFromIBuffer(unsigned char* buffDestination, unsigned int copyByteCount, Windows::Storage::Streams::IBuffer^ buffer, unsigned int readStartPos = 0) { assert(copyByteCount <= buffer->Capacity); unsigned char* pBuffer; GetRawBufferFromIBuffer(buffer, &pBuffer); memcpy(buffDestination, pBuffer + readStartPos, copyByteCount); } /* PPL helper */ #include #include // Creates a task that completes after the specified delay, in ms. inline concurrency::task complete_after(unsigned int timeout) { // A task completion event that is set when a timer fires. concurrency::task_completion_event tce; // Create a non-repeating timer. auto fire_once = new concurrency::timer(timeout, 0, nullptr, false); // Create a call object that sets the completion event after the timer fires. auto callback = new concurrency::call([tce](int) { tce.set(); }); // Connect the timer to the callback and start the timer. fire_once->link_target(callback); fire_once->start(); // Create a task that completes after the completion event is set. concurrency::task event_set(tce); // Create a continuation task that cleans up resources and // and return that continuation task. return event_set.then([callback, fire_once]() { delete callback; delete fire_once; }); } // Cancels the provided task after the specifed delay, if the task // did not complete. template inline concurrency::task cancel_after_timeout(concurrency::task t, concurrency::cancellation_token_source cts, unsigned int timeout) { // Create a task that returns true after the specified task completes. concurrency::task success_task = t.then([](T) { return true; }); // Create a task that returns false after the specified timeout. concurrency::task failure_task = complete_after(timeout).then([] { return false; }); // Create a continuation task that cancels the overall task // if the timeout task finishes first. return (failure_task || success_task).then([t, cts](bool success) { if (!success) { // Set the cancellation token. The task that is passed as the // t parameter should respond to the cancellation and stop // as soon as it can. cts.cancel(); } // Return the original task. return t; }); }