winhttp: Allow synchronous nonblocking send in WinHttpWebSocketSend() when possible.
Signed-off-by: Paul Gofman <pgofman@codeweavers.com> Signed-off-by: Hans Leidekker <hans@codeweavers.com> Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
parent
ad5d011f58
commit
9ef1e44914
|
@ -32,15 +32,23 @@
|
|||
|
||||
WINE_DEFAULT_DEBUG_CHANNEL(winhttp);
|
||||
|
||||
static int sock_send(int fd, const void *msg, size_t len, int flags)
|
||||
static int sock_send(int fd, const void *msg, size_t len, WSAOVERLAPPED *ovr)
|
||||
{
|
||||
int ret;
|
||||
do
|
||||
WSABUF wsabuf;
|
||||
DWORD size;
|
||||
DWORD err;
|
||||
|
||||
wsabuf.len = len;
|
||||
wsabuf.buf = (void *)msg;
|
||||
|
||||
if (!WSASend( (SOCKET)fd, &wsabuf, 1, &size, 0, ovr, NULL ))
|
||||
{
|
||||
if ((ret = send(fd, msg, len, flags)) == -1) WARN("send error %u\n", WSAGetLastError());
|
||||
assert( size == len );
|
||||
return size;
|
||||
}
|
||||
while(ret == -1 && WSAGetLastError() == WSAEINTR);
|
||||
return ret;
|
||||
err = WSAGetLastError();
|
||||
if (!(ovr && err == WSA_IO_PENDING)) WARN( "send error %u\n", err );
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int sock_recv(int fd, void *msg, size_t len, int flags)
|
||||
|
@ -190,7 +198,7 @@ DWORD netconn_create( struct hostdata *host, const struct sockaddr_storage *sock
|
|||
if (!(conn = calloc( 1, sizeof(*conn) ))) return ERROR_OUTOFMEMORY;
|
||||
conn->host = host;
|
||||
conn->sockaddr = *sockaddr;
|
||||
if ((conn->socket = socket( sockaddr->ss_family, SOCK_STREAM, 0 )) == -1)
|
||||
if ((conn->socket = WSASocketW( sockaddr->ss_family, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED )) == -1)
|
||||
{
|
||||
ret = WSAGetLastError();
|
||||
WARN("unable to create socket (%u)\n", ret);
|
||||
|
@ -290,7 +298,7 @@ DWORD netconn_secure_connect( struct netconn *conn, WCHAR *hostname, DWORD secur
|
|||
|
||||
TRACE("sending %u bytes\n", out_buf.cbBuffer);
|
||||
|
||||
size = sock_send(conn->socket, out_buf.pvBuffer, out_buf.cbBuffer, 0);
|
||||
size = sock_send(conn->socket, out_buf.pvBuffer, out_buf.cbBuffer, NULL);
|
||||
if(size != out_buf.cbBuffer) {
|
||||
ERR("send failed\n");
|
||||
res = ERROR_WINHTTP_SECURE_CHANNEL_ERROR;
|
||||
|
@ -398,7 +406,7 @@ DWORD netconn_secure_connect( struct netconn *conn, WCHAR *hostname, DWORD secur
|
|||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
static DWORD send_ssl_chunk( struct netconn *conn, const void *msg, size_t size )
|
||||
static DWORD send_ssl_chunk( struct netconn *conn, const void *msg, size_t size, WSAOVERLAPPED *ovr )
|
||||
{
|
||||
SecBuffer bufs[4] = {
|
||||
{conn->ssl_sizes.cbHeader, SECBUFFER_STREAM_HEADER, conn->ssl_write_buf},
|
||||
|
@ -416,7 +424,8 @@ static DWORD send_ssl_chunk( struct netconn *conn, const void *msg, size_t size
|
|||
return res;
|
||||
}
|
||||
|
||||
if (sock_send( conn->socket, conn->ssl_write_buf, bufs[0].cbBuffer + bufs[1].cbBuffer + bufs[2].cbBuffer, 0 ) < 1)
|
||||
if (sock_send( conn->socket, conn->ssl_write_buf,
|
||||
bufs[0].cbBuffer + bufs[1].cbBuffer + bufs[2].cbBuffer, ovr ) < 1)
|
||||
{
|
||||
WARN("send failed\n");
|
||||
return WSAGetLastError();
|
||||
|
@ -425,7 +434,7 @@ static DWORD send_ssl_chunk( struct netconn *conn, const void *msg, size_t size
|
|||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
DWORD netconn_send( struct netconn *conn, const void *msg, size_t len, int *sent )
|
||||
DWORD netconn_send( struct netconn *conn, const void *msg, size_t len, int *sent, WSAOVERLAPPED *ovr )
|
||||
{
|
||||
if (conn->secure)
|
||||
{
|
||||
|
@ -433,11 +442,13 @@ DWORD netconn_send( struct netconn *conn, const void *msg, size_t len, int *sent
|
|||
size_t chunk_size;
|
||||
DWORD res;
|
||||
|
||||
if (ovr && len > conn->ssl_sizes.cbMaximumMessage) return WSAEWOULDBLOCK;
|
||||
|
||||
*sent = 0;
|
||||
while (len)
|
||||
{
|
||||
chunk_size = min( len, conn->ssl_sizes.cbMaximumMessage );
|
||||
if ((res = send_ssl_chunk( conn, ptr, chunk_size )))
|
||||
if ((res = send_ssl_chunk( conn, ptr, chunk_size, ovr )))
|
||||
return res;
|
||||
|
||||
*sent += chunk_size;
|
||||
|
@ -448,7 +459,7 @@ DWORD netconn_send( struct netconn *conn, const void *msg, size_t len, int *sent
|
|||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
if ((*sent = sock_send( conn->socket, msg, len, 0 )) < 0) return WSAGetLastError();
|
||||
if ((*sent = sock_send( conn->socket, msg, len, ovr )) < 0) return WSAGetLastError();
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
|
|
|
@ -1286,7 +1286,7 @@ static DWORD secure_proxy_connect( struct request *request )
|
|||
if (!strA) return ERROR_OUTOFMEMORY;
|
||||
|
||||
len = strlen( strA );
|
||||
ret = netconn_send( request->netconn, strA, len, &bytes_sent );
|
||||
ret = netconn_send( request->netconn, strA, len, &bytes_sent, NULL );
|
||||
free( strA );
|
||||
if (!ret) ret = read_reply( request );
|
||||
|
||||
|
@ -2138,13 +2138,13 @@ static DWORD send_request( struct request *request, const WCHAR *headers, DWORD
|
|||
|
||||
send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_SENDING_REQUEST, NULL, 0 );
|
||||
|
||||
ret = netconn_send( request->netconn, wire_req, len, &bytes_sent );
|
||||
ret = netconn_send( request->netconn, wire_req, len, &bytes_sent, NULL );
|
||||
free( wire_req );
|
||||
if (ret) goto end;
|
||||
|
||||
if (optional_len)
|
||||
{
|
||||
if ((ret = netconn_send( request->netconn, optional, optional_len, &bytes_sent ))) goto end;
|
||||
if ((ret = netconn_send( request->netconn, optional, optional_len, &bytes_sent, NULL ))) goto end;
|
||||
request->optional = optional;
|
||||
request->optional_len = optional_len;
|
||||
len += optional_len;
|
||||
|
@ -2972,7 +2972,7 @@ static DWORD write_data( struct request *request, const void *buffer, DWORD to_w
|
|||
DWORD ret;
|
||||
int num_bytes;
|
||||
|
||||
ret = netconn_send( request->netconn, buffer, to_write, &num_bytes );
|
||||
ret = netconn_send( request->netconn, buffer, to_write, &num_bytes, NULL );
|
||||
|
||||
if (async)
|
||||
{
|
||||
|
@ -3127,11 +3127,11 @@ HINTERNET WINAPI WinHttpWebSocketCompleteUpgrade( HINTERNET hrequest, DWORD_PTR
|
|||
return hsocket;
|
||||
}
|
||||
|
||||
static DWORD send_bytes( struct socket *socket, char *bytes, int len )
|
||||
static DWORD send_bytes( struct socket *socket, char *bytes, int len, WSAOVERLAPPED *ovr )
|
||||
{
|
||||
int count;
|
||||
DWORD err;
|
||||
if ((err = netconn_send( socket->request->netconn, bytes, len, &count ))) return err;
|
||||
if ((err = netconn_send( socket->request->netconn, bytes, len, &count, ovr ))) return err;
|
||||
return (count == len) ? ERROR_SUCCESS : ERROR_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
|
@ -3141,7 +3141,7 @@ static DWORD send_bytes( struct socket *socket, char *bytes, int len )
|
|||
#define CONTROL_BIT (1 << 3)
|
||||
|
||||
static DWORD send_frame( struct socket *socket, enum socket_opcode opcode, USHORT status, const char *buf,
|
||||
DWORD buflen, BOOL final )
|
||||
DWORD buflen, BOOL final, WSAOVERLAPPED *ovr )
|
||||
{
|
||||
DWORD i = 0, j, offset = 2, len = buflen;
|
||||
DWORD buffer_size, ret = 0, send_size;
|
||||
|
@ -3177,6 +3177,7 @@ static DWORD send_frame( struct socket *socket, enum socket_opcode opcode, USHOR
|
|||
buffer_size = len + offset;
|
||||
if (len) buffer_size += 4;
|
||||
assert( buffer_size - len < MAX_FRAME_BUFFER_SIZE );
|
||||
if (ovr && buffer_size > MAX_FRAME_BUFFER_SIZE) return WSAEWOULDBLOCK;
|
||||
if (buffer_size > socket->send_frame_buffer_size && socket->send_frame_buffer_size < MAX_FRAME_BUFFER_SIZE)
|
||||
{
|
||||
DWORD new_size;
|
||||
|
@ -3217,7 +3218,7 @@ static DWORD send_frame( struct socket *socket, enum socket_opcode opcode, USHOR
|
|||
while (j < buflen && offset < MAX_FRAME_BUFFER_SIZE)
|
||||
socket->send_frame_buffer[offset++] = buf[j++] ^ mask[i++ % 4];
|
||||
|
||||
if ((ret = send_bytes( socket, socket->send_frame_buffer, offset ))) return ret;
|
||||
if ((ret = send_bytes( socket, socket->send_frame_buffer, offset, ovr ))) return ret;
|
||||
|
||||
if (!(send_size -= offset)) break;
|
||||
offset = 0;
|
||||
|
@ -3227,6 +3228,16 @@ static DWORD send_frame( struct socket *socket, enum socket_opcode opcode, USHOR
|
|||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
static DWORD complete_send_frame( struct socket *socket, WSAOVERLAPPED *ovr )
|
||||
{
|
||||
DWORD retflags, len;
|
||||
|
||||
if (!WSAGetOverlappedResult( socket->request->netconn->socket, ovr, &len, TRUE, &retflags ))
|
||||
return WSAGetLastError();
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
static void send_io_complete( struct object_header *hdr )
|
||||
{
|
||||
LONG count = InterlockedDecrement( &hdr->pending_sends );
|
||||
|
@ -3265,11 +3276,12 @@ static void socket_send_complete( struct socket *socket, DWORD ret, WINHTTP_WEB_
|
|||
}
|
||||
}
|
||||
|
||||
static DWORD socket_send( struct socket *socket, WINHTTP_WEB_SOCKET_BUFFER_TYPE type, const void *buf, DWORD len )
|
||||
static DWORD socket_send( struct socket *socket, WINHTTP_WEB_SOCKET_BUFFER_TYPE type, const void *buf, DWORD len,
|
||||
WSAOVERLAPPED *ovr )
|
||||
{
|
||||
enum socket_opcode opcode = map_buffer_type( type );
|
||||
|
||||
return send_frame( socket, opcode, 0, buf, len, TRUE );
|
||||
return send_frame( socket, opcode, 0, buf, len, TRUE, ovr );
|
||||
}
|
||||
|
||||
static void CALLBACK task_socket_send( TP_CALLBACK_INSTANCE *instance, void *ctx, TP_WORK *work )
|
||||
|
@ -3278,7 +3290,10 @@ static void CALLBACK task_socket_send( TP_CALLBACK_INSTANCE *instance, void *ctx
|
|||
DWORD ret;
|
||||
|
||||
TRACE("running %p\n", work);
|
||||
ret = socket_send( s->socket, s->type, s->buf, s->len );
|
||||
|
||||
if (s->complete_async) ret = complete_send_frame( s->socket, &s->ovr );
|
||||
else ret = socket_send( s->socket, s->type, s->buf, s->len, NULL );
|
||||
|
||||
send_io_complete( &s->socket->hdr );
|
||||
socket_send_complete( s->socket, ret, s->type, s->len );
|
||||
|
||||
|
@ -3289,7 +3304,7 @@ static void CALLBACK task_socket_send( TP_CALLBACK_INSTANCE *instance, void *ctx
|
|||
DWORD WINAPI WinHttpWebSocketSend( HINTERNET hsocket, WINHTTP_WEB_SOCKET_BUFFER_TYPE type, void *buf, DWORD len )
|
||||
{
|
||||
struct socket *socket;
|
||||
DWORD ret;
|
||||
DWORD ret = 0;
|
||||
|
||||
TRACE("%p, %u, %p, %u\n", hsocket, type, buf, len);
|
||||
|
||||
|
@ -3314,24 +3329,53 @@ DWORD WINAPI WinHttpWebSocketSend( HINTERNET hsocket, WINHTTP_WEB_SOCKET_BUFFER_
|
|||
|
||||
if (socket->request->connect->hdr.flags & WINHTTP_FLAG_ASYNC)
|
||||
{
|
||||
BOOL async_send, complete_async = FALSE;
|
||||
struct socket_send *s;
|
||||
|
||||
if (!(s = malloc( sizeof(*s) ))) return FALSE;
|
||||
s->socket = socket;
|
||||
s->type = type;
|
||||
s->buf = buf;
|
||||
s->len = len;
|
||||
if (!(s = malloc( sizeof(*s) )))
|
||||
{
|
||||
release_object( &socket->hdr );
|
||||
return ERROR_OUTOFMEMORY;
|
||||
}
|
||||
|
||||
addref_object( &socket->hdr );
|
||||
InterlockedIncrement( &socket->hdr.pending_sends );
|
||||
if ((ret = queue_task( &socket->send_q, task_socket_send, s )))
|
||||
async_send = InterlockedIncrement( &socket->hdr.pending_sends ) > 1 || socket->hdr.recursion_count >= 3;
|
||||
if (!async_send)
|
||||
{
|
||||
memset( &s->ovr, 0, sizeof(s->ovr) );
|
||||
if ((ret = socket_send( socket, type, buf, len, &s->ovr )) == WSA_IO_PENDING)
|
||||
{
|
||||
async_send = TRUE;
|
||||
complete_async = TRUE;
|
||||
}
|
||||
else if (ret == WSAEWOULDBLOCK) async_send = TRUE;
|
||||
}
|
||||
|
||||
if (async_send)
|
||||
{
|
||||
s->complete_async = complete_async;
|
||||
s->socket = socket;
|
||||
s->type = type;
|
||||
s->buf = buf;
|
||||
s->len = len;
|
||||
|
||||
addref_object( &socket->hdr );
|
||||
if ((ret = queue_task( &socket->send_q, task_socket_send, s )))
|
||||
{
|
||||
InterlockedDecrement( &socket->hdr.pending_sends );
|
||||
release_object( &socket->hdr );
|
||||
free( s );
|
||||
}
|
||||
else ++socket->hdr.pending_sends;
|
||||
}
|
||||
else
|
||||
{
|
||||
InterlockedDecrement( &socket->hdr.pending_sends );
|
||||
release_object( &socket->hdr );
|
||||
free( s );
|
||||
socket_send_complete( socket, ret, type, len );
|
||||
ret = ERROR_SUCCESS;
|
||||
}
|
||||
}
|
||||
else ret = socket_send( socket, type, buf, len );
|
||||
else ret = socket_send( socket, type, buf, len, NULL );
|
||||
|
||||
release_object( &socket->hdr );
|
||||
return ret;
|
||||
|
@ -3418,7 +3462,7 @@ static void CALLBACK task_socket_send_pong( TP_CALLBACK_INSTANCE *instance, void
|
|||
struct socket_send *s = ctx;
|
||||
|
||||
TRACE("running %p\n", work);
|
||||
send_frame( s->socket, SOCKET_OPCODE_PONG, 0, NULL, 0, TRUE );
|
||||
send_frame( s->socket, SOCKET_OPCODE_PONG, 0, NULL, 0, TRUE, NULL );
|
||||
send_io_complete( &s->socket->hdr );
|
||||
|
||||
release_object( &s->socket->hdr );
|
||||
|
@ -3445,7 +3489,7 @@ static DWORD socket_send_pong( struct socket *socket )
|
|||
}
|
||||
return ret;
|
||||
}
|
||||
return send_frame( socket, SOCKET_OPCODE_PONG, 0, NULL, 0, TRUE );
|
||||
return send_frame( socket, SOCKET_OPCODE_PONG, 0, NULL, 0, TRUE, NULL );
|
||||
}
|
||||
|
||||
static DWORD socket_drain( struct socket *socket )
|
||||
|
@ -3611,7 +3655,7 @@ static DWORD socket_shutdown( struct socket *socket, USHORT status, const void *
|
|||
DWORD ret;
|
||||
|
||||
stop_queue( &socket->send_q );
|
||||
if (!(ret = send_frame( socket, SOCKET_OPCODE_CLOSE, status, reason, len, TRUE )))
|
||||
if (!(ret = send_frame( socket, SOCKET_OPCODE_CLOSE, status, reason, len, TRUE, NULL )))
|
||||
{
|
||||
socket->state = SOCKET_STATE_SHUTDOWN;
|
||||
}
|
||||
|
@ -3697,7 +3741,7 @@ static DWORD socket_close( struct socket *socket, USHORT status, const void *rea
|
|||
if (socket->state < SOCKET_STATE_SHUTDOWN)
|
||||
{
|
||||
stop_queue( &socket->send_q );
|
||||
if ((ret = send_frame( socket, SOCKET_OPCODE_CLOSE, status, reason, len, TRUE ))) goto done;
|
||||
if ((ret = send_frame( socket, SOCKET_OPCODE_CLOSE, status, reason, len, TRUE, NULL ))) goto done;
|
||||
socket->state = SOCKET_STATE_SHUTDOWN;
|
||||
}
|
||||
|
||||
|
|
|
@ -62,6 +62,7 @@ struct notification
|
|||
#define NF_ALLOW 0x0001 /* notification may or may not happen */
|
||||
#define NF_WINE_ALLOW 0x0002 /* wine sends notification when it should not */
|
||||
#define NF_SIGNAL 0x0004 /* signal wait handle when notified */
|
||||
#define NF_MAIN_THREAD 0x0008 /* the operation completes synchronously and callback is called from the main thread */
|
||||
|
||||
struct info
|
||||
{
|
||||
|
@ -71,6 +72,7 @@ struct info
|
|||
unsigned int index;
|
||||
HANDLE wait;
|
||||
unsigned int line;
|
||||
DWORD main_thread_id;
|
||||
DWORD last_thread_id;
|
||||
DWORD last_status;
|
||||
};
|
||||
|
@ -111,6 +113,12 @@ static void CALLBACK check_notification( HINTERNET handle, DWORD_PTR context, DW
|
|||
ok(status_ok, "%u: expected status 0x%08x got 0x%08x\n", info->line, info->test[info->index].status, status);
|
||||
ok(function_ok, "%u: expected function %u got %u\n", info->line, info->test[info->index].function, info->function);
|
||||
|
||||
if (info->test[info->index].flags & NF_MAIN_THREAD)
|
||||
{
|
||||
ok(GetCurrentThreadId() == info->main_thread_id, "%u: expected callback to be called from the same thread\n",
|
||||
info->line);
|
||||
}
|
||||
|
||||
if (status_ok && function_ok && info->test[info->index++].flags & NF_SIGNAL)
|
||||
{
|
||||
SetEvent( info->wait );
|
||||
|
@ -184,6 +192,7 @@ static void setup_test( struct info *info, enum api function, unsigned int line
|
|||
info->test[info->index].function, function);
|
||||
info->last_thread_id = 0xdeadbeef;
|
||||
info->last_status = 0xdeadbeef;
|
||||
info->main_thread_id = GetCurrentThreadId();
|
||||
}
|
||||
|
||||
static void end_test( struct info *info, unsigned int line )
|
||||
|
@ -658,8 +667,8 @@ static const struct notification websocket_test[] =
|
|||
{ winhttp_receive_response, WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED },
|
||||
{ winhttp_receive_response, WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE, NF_SIGNAL },
|
||||
{ winhttp_websocket_complete_upgrade, WINHTTP_CALLBACK_STATUS_HANDLE_CREATED, NF_SIGNAL },
|
||||
{ winhttp_websocket_send, WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE, NF_SIGNAL },
|
||||
{ winhttp_websocket_send, WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE, NF_SIGNAL },
|
||||
{ winhttp_websocket_send, WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE, NF_MAIN_THREAD | NF_SIGNAL },
|
||||
{ winhttp_websocket_send, WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE, NF_MAIN_THREAD | NF_SIGNAL },
|
||||
{ winhttp_websocket_shutdown, WINHTTP_CALLBACK_STATUS_SHUTDOWN_COMPLETE, NF_SIGNAL },
|
||||
{ winhttp_websocket_receive, WINHTTP_CALLBACK_STATUS_READ_COMPLETE, NF_SIGNAL },
|
||||
{ winhttp_websocket_receive, WINHTTP_CALLBACK_STATUS_READ_COMPLETE, NF_SIGNAL },
|
||||
|
@ -792,6 +801,9 @@ static void test_websocket(BOOL secure)
|
|||
|
||||
for (i = 0; i < 2; ++i)
|
||||
{
|
||||
/* The send is executed synchronously (even if sending a reasonably big buffer exceeding SSL buffer size).
|
||||
* It is possible to trigger queueing the send into another thread but that involves sending a considerable
|
||||
* amount of big enough buffers. */
|
||||
setup_test( &info, winhttp_websocket_send, __LINE__ );
|
||||
err = pWinHttpWebSocketSend( socket, WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE,
|
||||
(void*)"hello", sizeof("hello") );
|
||||
|
|
|
@ -297,6 +297,8 @@ struct socket_send
|
|||
WINHTTP_WEB_SOCKET_BUFFER_TYPE type;
|
||||
const void *buf;
|
||||
DWORD len;
|
||||
WSAOVERLAPPED ovr;
|
||||
BOOL complete_async;
|
||||
};
|
||||
|
||||
struct socket_receive
|
||||
|
@ -331,7 +333,7 @@ ULONG netconn_query_data_available( struct netconn * ) DECLSPEC_HIDDEN;
|
|||
DWORD netconn_recv( struct netconn *, void *, size_t, int, int * ) DECLSPEC_HIDDEN;
|
||||
DWORD netconn_resolve( WCHAR *, INTERNET_PORT, struct sockaddr_storage *, int ) DECLSPEC_HIDDEN;
|
||||
DWORD netconn_secure_connect( struct netconn *, WCHAR *, DWORD, CredHandle *, BOOL ) DECLSPEC_HIDDEN;
|
||||
DWORD netconn_send( struct netconn *, const void *, size_t, int * ) DECLSPEC_HIDDEN;
|
||||
DWORD netconn_send( struct netconn *, const void *, size_t, int *, WSAOVERLAPPED * ) DECLSPEC_HIDDEN;
|
||||
DWORD netconn_set_timeout( struct netconn *, BOOL, int ) DECLSPEC_HIDDEN;
|
||||
BOOL netconn_is_alive( struct netconn * ) DECLSPEC_HIDDEN;
|
||||
const void *netconn_get_certificate( struct netconn * ) DECLSPEC_HIDDEN;
|
||||
|
|
Loading…
Reference in New Issue