From: Paul Gofman Subject: [PATCH 2/5] winhttp: Attemp sync websocket send even if data doesn't fit SSL buffer. Message-Id: <20220126230309.1530051-2-pgofman@codeweavers.com> Date: Thu, 27 Jan 2022 02:03:06 +0300 In-Reply-To: <20220126230309.1530051-1-pgofman@codeweavers.com> References: <20220126230309.1530051-1-pgofman@codeweavers.com> Signed-off-by: Paul Gofman --- dlls/winhttp/net.c | 15 ++++-- dlls/winhttp/request.c | 35 ++++++++++--- dlls/winhttp/tests/notification.c | 86 ++++++++++++++++++++++--------- dlls/winhttp/winhttp_private.h | 1 + 4 files changed, 101 insertions(+), 36 deletions(-) diff --git a/dlls/winhttp/net.c b/dlls/winhttp/net.c index cdb67f74481..446dbc996e7 100644 --- a/dlls/winhttp/net.c +++ b/dlls/winhttp/net.c @@ -436,21 +436,23 @@ static DWORD send_ssl_chunk( struct netconn *conn, const void *msg, size_t size, DWORD netconn_send( struct netconn *conn, const void *msg, size_t len, int *sent, WSAOVERLAPPED *ovr ) { + DWORD err; + if (conn->secure) { const BYTE *ptr = msg; 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, ovr ))) + { + if (res == WSA_IO_PENDING) *sent += chunk_size; return res; - + } *sent += chunk_size; ptr += chunk_size; len -= chunk_size; @@ -459,7 +461,12 @@ 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, ovr )) < 0) return WSAGetLastError(); + if ((*sent = sock_send( conn->socket, msg, len, ovr )) < 0) + { + err = WSAGetLastError(); + *sent = (err == WSA_IO_PENDING) ? len : 0; + return err; + } return ERROR_SUCCESS; } diff --git a/dlls/winhttp/request.c b/dlls/winhttp/request.c index 0a5362d3c16..e8576256fe7 100644 --- a/dlls/winhttp/request.c +++ b/dlls/winhttp/request.c @@ -3127,12 +3127,14 @@ HINTERNET WINAPI WinHttpWebSocketCompleteUpgrade( HINTERNET hrequest, DWORD_PTR return hsocket; } -static DWORD send_bytes( struct socket *socket, char *bytes, int len, WSAOVERLAPPED *ovr ) +static DWORD send_bytes( struct socket *socket, char *bytes, int len, int *sent, WSAOVERLAPPED *ovr ) { int count; DWORD err; - if ((err = netconn_send( socket->request->netconn, bytes, len, &count, ovr ))) return err; - return (count == len) ? ERROR_SUCCESS : ERROR_INTERNAL_ERROR; + err = netconn_send( socket->request->netconn, bytes, len, &count, ovr ); + if (sent) *sent = count; + if (err) return err; + return (count == len || (ovr && count)) ? ERROR_SUCCESS : ERROR_INTERNAL_ERROR; } #define FIN_BIT (1 << 7) @@ -3146,6 +3148,7 @@ static DWORD send_frame( struct socket *socket, enum socket_opcode opcode, USHOR DWORD i = 0, j, offset = 2, len = buflen; DWORD buffer_size, ret = 0, send_size; char hdr[14], *mask = NULL; + int sent_size; char *ptr; TRACE( "sending %02x frame, len %u.\n", opcode, len ); @@ -3215,8 +3218,17 @@ 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, ovr ))) return ret; - + sent_size = 0; + ret = send_bytes( socket, socket->send_frame_buffer, offset, &sent_size, ovr ); + if (ret) + { + if (ovr && ret == WSA_IO_PENDING) + { + memmove( socket->send_frame_buffer, socket->send_frame_buffer + sent_size, offset - sent_size ); + socket->bytes_in_send_frame_buffer = offset - sent_size; + } + return ret; + } if (!(send_size -= offset)) break; offset = 0; buf += j; @@ -3225,13 +3237,18 @@ 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 ) +static DWORD complete_send_frame( struct socket *socket, WSAOVERLAPPED *ovr, const char *buf ) { - DWORD retflags, len; + DWORD ret, retflags, len; if (!WSAGetOverlappedResult( socket->request->netconn->socket, ovr, &len, TRUE, &retflags )) return WSAGetLastError(); + if (socket->bytes_in_send_frame_buffer) + { + ret = send_bytes( socket, socket->send_frame_buffer, socket->bytes_in_send_frame_buffer, NULL, NULL ); + if (ret) return ret; + } return ERROR_SUCCESS; } @@ -3298,7 +3315,7 @@ static void CALLBACK task_socket_send( TP_CALLBACK_INSTANCE *instance, void *ctx TRACE("running %p\n", work); - if (s->complete_async) ret = complete_send_frame( s->socket, &s->ovr ); + if (s->complete_async) ret = complete_send_frame( s->socket, &s->ovr, s->buf ); else ret = socket_send( s->socket, s->type, s->buf, s->len, NULL ); send_io_complete( &s->socket->hdr ); @@ -3360,6 +3377,7 @@ DWORD WINAPI WinHttpWebSocketSend( HINTERNET hsocket, WINHTTP_WEB_SOCKET_BUFFER_ if (async_send) { s->complete_async = complete_async; + TRACE("queueing, complete_async %#x.\n", complete_async); s->socket = socket; s->type = type; s->buf = buf; @@ -3375,6 +3393,7 @@ DWORD WINAPI WinHttpWebSocketSend( HINTERNET hsocket, WINHTTP_WEB_SOCKET_BUFFER_ } else { + TRACE("sent sync.\n"); InterlockedDecrement( &socket->hdr.pending_sends ); free( s ); socket_send_complete( socket, ret, type, len ); diff --git a/dlls/winhttp/tests/notification.c b/dlls/winhttp/tests/notification.c index cab87df339d..5ec8740f66e 100644 --- a/dlls/winhttp/tests/notification.c +++ b/dlls/winhttp/tests/notification.c @@ -678,8 +678,8 @@ static const struct notification websocket_test[] = { 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 }, + { winhttp_websocket_receive, WINHTTP_CALLBACK_STATUS_READ_COMPLETE, NF_SAVE_BUFFER | NF_SIGNAL }, + { winhttp_websocket_receive, WINHTTP_CALLBACK_STATUS_READ_COMPLETE, NF_SAVE_BUFFER | NF_SIGNAL }, { winhttp_websocket_close, WINHTTP_CALLBACK_STATUS_CLOSE_COMPLETE, NF_SIGNAL }, { winhttp_close_handle, WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING }, { winhttp_close_handle, WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION, NF_WINE_ALLOW }, @@ -712,18 +712,22 @@ static const struct notification websocket_test2[] = { winhttp_close_handle, WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING, NF_SIGNAL } }; +#define BIG_BUFFER_SIZE (32 * 1024) + static void test_websocket(BOOL secure) { HANDLE session, connection, request, socket, event; WINHTTP_WEB_SOCKET_ASYNC_RESULT *result; + WINHTTP_WEB_SOCKET_STATUS *ws_status; WINHTTP_WEB_SOCKET_BUFFER_TYPE type; DWORD size, status, err; BOOL ret, unload = TRUE; struct info info, *context = &info; + unsigned char *big_buffer; char buffer[1024]; USHORT close_status; DWORD protocols, flags; - unsigned int i; + unsigned int i, test_index, offset; if (!pWinHttpWebSocketCompleteUpgrade) { @@ -831,17 +835,24 @@ static void test_websocket(BOOL secure) ok( err == ERROR_SUCCESS, "got %u\n", err ); WaitForSingleObject( info.wait, INFINITE ); - 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") ); - ok( err == ERROR_SUCCESS, "got %u\n", err ); - WaitForSingleObject( info.wait, INFINITE ); - } + /* 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. */ + big_buffer = malloc( BIG_BUFFER_SIZE ); + for (i = 0; i < BIG_BUFFER_SIZE; ++i) + big_buffer[i] = (i & 0xff) ^ 0xcc; + + setup_test( &info, winhttp_websocket_send, __LINE__ ); + err = pWinHttpWebSocketSend( socket, WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE, + big_buffer, BIG_BUFFER_SIZE ); + ok( err == ERROR_SUCCESS, "got %u\n", err ); + WaitForSingleObject( info.wait, INFINITE ); + + setup_test( &info, winhttp_websocket_send, __LINE__ ); + err = pWinHttpWebSocketSend( socket, WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE, + (void*)"hello", sizeof("hello") ); + ok( err == ERROR_SUCCESS, "got %u\n", err ); + WaitForSingleObject( info.wait, INFINITE ); setup_test( &info, winhttp_websocket_shutdown, __LINE__ ); err = pWinHttpWebSocketShutdown( socket, 1000, (void *)"success", sizeof("success") ); @@ -866,20 +877,47 @@ static void test_websocket(BOOL secure) err = pWinHttpWebSocketReceive( socket, buffer, sizeof(buffer), &size, &type ); ok( err == ERROR_SUCCESS, "got %u\n", err ); WaitForSingleObject( info.wait, INFINITE ); + ok( info.buflen == sizeof(*ws_status), "got unexpected buflen %u.\n", info.buflen ); + ws_status = (WINHTTP_WEB_SOCKET_STATUS *)info.buffer; + ok( ws_status->eBufferType == WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE, + "Got unexpected eBufferType %u.\n", ws_status->eBufferType ); ok( size == 0xdeadbeef, "got %u\n", size ); ok( type == 0xdeadbeef, "got %u\n", type ); ok( buffer[0] == 'R', "unexpected data\n" ); - setup_test( &info, winhttp_websocket_receive, __LINE__ ); - buffer[0] = 0; - size = 0xdeadbeef; - type = 0xdeadbeef; - err = pWinHttpWebSocketReceive( socket, buffer, sizeof(buffer), &size, &type ); - ok( err == ERROR_SUCCESS, "got %u\n", err ); - WaitForSingleObject( info.wait, INFINITE ); - ok( size == 0xdeadbeef, "got %u\n", size ); - ok( type == 0xdeadbeef, "got %u\n", type ); - ok( buffer[0] == 'h', "unexpected data\n" ); + memset( big_buffer, 0, BIG_BUFFER_SIZE ); + offset = 0; + test_index = info.index; + do + { + info.index = test_index; + setup_test( &info, winhttp_websocket_receive, __LINE__ ); + size = 0xdeadbeef; + type = 0xdeadbeef; + ws_status = (WINHTTP_WEB_SOCKET_STATUS *)info.buffer; + ws_status->eBufferType = ~0u; + err = pWinHttpWebSocketReceive( socket, big_buffer + offset, BIG_BUFFER_SIZE - offset, &size, &type ); + ok( err == ERROR_SUCCESS, "got %u\n", err ); + WaitForSingleObject( info.wait, INFINITE ); + ok( info.buflen == sizeof(*ws_status), "got unexpected buflen %u.\n", info.buflen ); + ok( ws_status->eBufferType == WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE + || ws_status->eBufferType == WINHTTP_WEB_SOCKET_BINARY_FRAGMENT_BUFFER_TYPE, + "Got unexpected eBufferType %u.\n", ws_status->eBufferType ); + offset += ws_status->dwBytesTransferred; + ok( offset <= BIG_BUFFER_SIZE, "Got unexpected dwBytesTransferred %u.\n", + ws_status->dwBytesTransferred ); + ok( size == 0xdeadbeef, "got %u\n", size ); + ok( type == 0xdeadbeef, "got %u\n", type ); + } + while (ws_status->eBufferType == WINHTTP_WEB_SOCKET_BINARY_FRAGMENT_BUFFER_TYPE); + + ok( offset == BIG_BUFFER_SIZE, "Got unexpected offset %u.\n", offset ); + + for (i = 0; i < BIG_BUFFER_SIZE; ++i) + if (big_buffer[i] != ((i & 0xff) ^ 0xcc)) break; + ok( i == BIG_BUFFER_SIZE, "unexpected data %#x at %u\n", (unsigned char)big_buffer[i], i ); + + free( big_buffer ); close_status = 0xdead; size = sizeof(buffer) + 1; diff --git a/dlls/winhttp/winhttp_private.h b/dlls/winhttp/winhttp_private.h index a6638aba30f..639c97b126e 100644 --- a/dlls/winhttp/winhttp_private.h +++ b/dlls/winhttp/winhttp_private.h @@ -254,6 +254,7 @@ struct socket DWORD reason_len; char *send_frame_buffer; unsigned int send_frame_buffer_size; + unsigned int bytes_in_send_frame_buffer; }; struct send_request -- 2.34.1