409 lines
10 KiB
C++
409 lines
10 KiB
C++
#include <Windows.h>
|
|
#include <Websocket.h>
|
|
#include <Assert.h>
|
|
#include <StdIo.h>
|
|
#include "Transport.h"
|
|
|
|
void DumpHeaders(
|
|
_In_reads_(headerCount) WEB_SOCKET_HTTP_HEADER* headers,
|
|
_In_ ULONG headerCount)
|
|
{
|
|
for (ULONG i = 0; i < headerCount; i++)
|
|
{
|
|
wprintf(L"%.*S: %.*S\n", headers[i].ulNameLength, headers[i].pcName, headers[i].ulValueLength, headers[i].pcValue);
|
|
}
|
|
}
|
|
|
|
void DumpData(
|
|
_In_reads_bytes_opt_(dataLength) BYTE* data,
|
|
_In_ ULONG dataLength)
|
|
{
|
|
if (data == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (ULONG i = 0; i < dataLength; i++)
|
|
{
|
|
wprintf(L"0x%x ", data[i]);
|
|
}
|
|
|
|
wprintf(L"\n");
|
|
}
|
|
|
|
HRESULT Initialize(
|
|
_Out_ WEB_SOCKET_HANDLE* outClientHandle,
|
|
_Out_ WEB_SOCKET_HANDLE* outServerHandle)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
WEB_SOCKET_HANDLE clientHandle = NULL;
|
|
WEB_SOCKET_HANDLE serverHandle = NULL;
|
|
|
|
// Create a client side websocket handle.
|
|
hr = WebSocketCreateClientHandle(NULL, 0, &clientHandle);
|
|
if (FAILED(hr))
|
|
{
|
|
goto quit;
|
|
}
|
|
|
|
// Create a server side websocket handle.
|
|
hr = WebSocketCreateServerHandle(NULL, 0, &serverHandle);
|
|
if (FAILED(hr))
|
|
{
|
|
goto quit;
|
|
}
|
|
|
|
*outClientHandle = clientHandle;
|
|
*outServerHandle = serverHandle;
|
|
clientHandle = NULL;
|
|
serverHandle = NULL;
|
|
|
|
quit:
|
|
|
|
if (clientHandle != NULL)
|
|
{
|
|
WebSocketDeleteHandle(clientHandle);
|
|
clientHandle = NULL;
|
|
}
|
|
|
|
if (serverHandle != NULL)
|
|
{
|
|
WebSocketDeleteHandle(serverHandle);
|
|
serverHandle = NULL;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT PerformHandshake(
|
|
_In_ WEB_SOCKET_HANDLE clientHandle,
|
|
_In_ WEB_SOCKET_HANDLE serverHandle)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
ULONG clientAdditionalHeaderCount = 0;
|
|
WEB_SOCKET_HTTP_HEADER* clientAdditionalHeaders = NULL;
|
|
ULONG serverAdditionalHeaderCount = 0;
|
|
WEB_SOCKET_HTTP_HEADER* serverAdditionalHeaders = NULL;
|
|
ULONG clientHeaderCount = 0;
|
|
WEB_SOCKET_HTTP_HEADER* clientHeaders = NULL;
|
|
|
|
// Static "Host" header.
|
|
const static WEB_SOCKET_HTTP_HEADER host =
|
|
{
|
|
"Host",
|
|
ARRAYSIZE("Host") - 1, // Length of "Host" string without the NULL-terminator.
|
|
"localhost",
|
|
ARRAYSIZE("localhost") - 1 // Length of "localhost" string without the NULL-terminator.
|
|
};
|
|
|
|
// Start a client side of the handshake - 'additionalHeaders' will hold an array of websocket specific headers.
|
|
// Production applications must add these headers to the outgoing HTTP request.
|
|
hr = WebSocketBeginClientHandshake(
|
|
clientHandle,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
0,
|
|
&clientAdditionalHeaders,
|
|
&clientAdditionalHeaderCount);
|
|
if (FAILED(hr))
|
|
{
|
|
goto quit;
|
|
}
|
|
|
|
// Concatenate list of headers that the HTTP stack must send (the Host header) with
|
|
// a list returned by WebSocketBeginClientHandshake.
|
|
clientHeaderCount = clientAdditionalHeaderCount + 1;
|
|
clientHeaders = new WEB_SOCKET_HTTP_HEADER[clientHeaderCount];
|
|
if (clientHeaders == NULL)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
goto quit;
|
|
}
|
|
|
|
CopyMemory(clientHeaders, clientAdditionalHeaders, clientAdditionalHeaderCount * sizeof(WEB_SOCKET_HTTP_HEADER));
|
|
clientHeaders[clientAdditionalHeaderCount] = host;
|
|
|
|
wprintf(L"-- Client side headers that need to be send with a request --\n");
|
|
DumpHeaders(clientHeaders, clientHeaderCount);
|
|
|
|
// Start a server side of the handshake. Production applications must parse the incoming
|
|
// HTTP request and pass all headers to the function. The function will return an array websocket
|
|
// specific headers that must be added to the outgoing HTTP response.
|
|
hr = WebSocketBeginServerHandshake(
|
|
serverHandle,
|
|
NULL,
|
|
NULL,
|
|
0,
|
|
clientHeaders,
|
|
clientHeaderCount,
|
|
&serverAdditionalHeaders,
|
|
&serverAdditionalHeaderCount);
|
|
if (FAILED(hr))
|
|
{
|
|
goto quit;
|
|
}
|
|
|
|
wprintf(L"\n-- Server side headers that need to be send with a response --\n");
|
|
DumpHeaders(serverAdditionalHeaders, serverAdditionalHeaderCount);
|
|
|
|
// Finish handshake. Once the client/server handshake is completed, memory allocated by
|
|
// the *Begin* functions is reclaimed and must not be used by the application.
|
|
hr = WebSocketEndClientHandshake(
|
|
clientHandle,
|
|
serverAdditionalHeaders,
|
|
serverAdditionalHeaderCount,
|
|
NULL,
|
|
0,
|
|
NULL);
|
|
if (FAILED(hr))
|
|
{
|
|
goto quit;
|
|
}
|
|
|
|
hr = WebSocketEndServerHandshake(serverHandle);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"4\n");
|
|
goto quit;
|
|
}
|
|
|
|
quit:
|
|
|
|
if (clientHeaders != NULL)
|
|
{
|
|
delete[] clientHeaders;
|
|
clientHeaders = NULL;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT RunLoop(
|
|
_In_ WEB_SOCKET_HANDLE handle,
|
|
_In_ Transport* transport)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
WEB_SOCKET_BUFFER buffers[2] = {0};
|
|
ULONG bufferCount;
|
|
ULONG bytesTransferred;
|
|
WEB_SOCKET_BUFFER_TYPE bufferType;
|
|
WEB_SOCKET_ACTION action;
|
|
PVOID actionContext;
|
|
|
|
do
|
|
{
|
|
// Initialize variables that change with every loop revolution.
|
|
bufferCount = ARRAYSIZE(buffers);
|
|
bytesTransferred = 0;
|
|
|
|
// Get an action to process.
|
|
hr = WebSocketGetAction(
|
|
handle,
|
|
WEB_SOCKET_ALL_ACTION_QUEUE,
|
|
buffers,
|
|
&bufferCount,
|
|
&action,
|
|
&bufferType,
|
|
NULL,
|
|
&actionContext);
|
|
if (FAILED(hr))
|
|
{
|
|
// If we cannot get an action, abort the handle but continue processing until all operations are completed.
|
|
WebSocketAbortHandle(handle);
|
|
}
|
|
|
|
switch (action)
|
|
{
|
|
case WEB_SOCKET_NO_ACTION:
|
|
|
|
// No action to perform - just exit the loop.
|
|
break;
|
|
|
|
case WEB_SOCKET_RECEIVE_FROM_NETWORK_ACTION:
|
|
|
|
wprintf(L"Receiving data from a network:\n");
|
|
|
|
assert(bufferCount >= 1);
|
|
for (ULONG i = 0; i < bufferCount; i++)
|
|
{
|
|
// Read data from a transport (in production application this may be a socket).
|
|
hr = transport->ReadData(buffers[i].Data.ulBufferLength, &bytesTransferred, buffers[i].Data.pbBuffer);
|
|
if (FAILED(hr))
|
|
{
|
|
break;
|
|
}
|
|
|
|
DumpData(buffers[i].Data.pbBuffer, bytesTransferred);
|
|
|
|
// Exit the loop if there were not enough data to fill this buffer.
|
|
if (buffers[i].Data.ulBufferLength > bytesTransferred)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case WEB_SOCKET_INDICATE_RECEIVE_COMPLETE_ACTION:
|
|
|
|
wprintf(L"Receive operation completed with a buffer:\n");
|
|
|
|
if (bufferCount != 1)
|
|
{
|
|
assert(!"This should never happen.");
|
|
hr = E_FAIL;
|
|
goto quit;
|
|
}
|
|
|
|
DumpData(buffers[0].Data.pbBuffer, buffers[0].Data.ulBufferLength);
|
|
|
|
break;
|
|
|
|
case WEB_SOCKET_SEND_TO_NETWORK_ACTION:
|
|
|
|
wprintf(L"Sending data to a network:\n");
|
|
|
|
for (ULONG i = 0; i < bufferCount; i++)
|
|
{
|
|
DumpData(buffers[i].Data.pbBuffer, buffers[i].Data.ulBufferLength);
|
|
|
|
// Write data to a transport (in production application this may be a socket).
|
|
hr = transport->WriteData(buffers[i].Data.pbBuffer, buffers[i].Data.ulBufferLength);
|
|
if (FAILED(hr))
|
|
{
|
|
break;
|
|
}
|
|
|
|
bytesTransferred += buffers[i].Data.ulBufferLength;
|
|
}
|
|
break;
|
|
|
|
|
|
case WEB_SOCKET_INDICATE_SEND_COMPLETE_ACTION:
|
|
|
|
wprintf(L"Send operation completed\n");
|
|
break;
|
|
|
|
default:
|
|
|
|
// This should never happen.
|
|
assert(!"Invalid switch");
|
|
hr = E_FAIL;
|
|
goto quit;
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
// If we failed at some point processing actions, abort the handle but continue processing
|
|
// until all operations are completed.
|
|
WebSocketAbortHandle(handle);
|
|
}
|
|
|
|
// Complete the action. If application performs asynchronous operation, the action has to be
|
|
// completed after the async operation has finished. The 'actionContext' then has to be preserved
|
|
// so the operation can complete properly.
|
|
WebSocketCompleteAction(handle, actionContext, bytesTransferred);
|
|
}
|
|
while (action != WEB_SOCKET_NO_ACTION);
|
|
|
|
quit:
|
|
return hr;
|
|
}
|
|
|
|
HRESULT PerformDataExchange(
|
|
_In_ WEB_SOCKET_HANDLE clientHandle,
|
|
_In_ WEB_SOCKET_HANDLE serverHandle,
|
|
_In_ Transport* transport)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
BYTE dataToSend[] = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd'};
|
|
WEB_SOCKET_BUFFER buffer;
|
|
|
|
buffer.Data.pbBuffer = dataToSend;
|
|
buffer.Data.ulBufferLength = ARRAYSIZE(dataToSend);
|
|
|
|
wprintf(L"\n-- Queueing a send with a buffer --\n");
|
|
DumpData(buffer.Data.pbBuffer, buffer.Data.ulBufferLength);
|
|
|
|
hr = WebSocketSend(clientHandle, WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE, &buffer, NULL);
|
|
if (FAILED(hr))
|
|
{
|
|
goto quit;
|
|
}
|
|
|
|
hr = RunLoop(clientHandle, transport);
|
|
if (FAILED(hr))
|
|
{
|
|
goto quit;
|
|
}
|
|
|
|
wprintf(L"\n-- Queueing a receive --\n");
|
|
|
|
hr = WebSocketReceive(serverHandle, NULL, NULL);
|
|
if (FAILED(hr))
|
|
{
|
|
goto quit;
|
|
}
|
|
|
|
hr = RunLoop(serverHandle, transport);
|
|
if (FAILED(hr))
|
|
{
|
|
goto quit;
|
|
}
|
|
|
|
quit:
|
|
|
|
return hr;
|
|
}
|
|
|
|
int __cdecl wmain()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
WEB_SOCKET_HANDLE clientHandle = NULL;
|
|
WEB_SOCKET_HANDLE serverHandle = NULL;
|
|
Transport transport;
|
|
|
|
hr = Initialize(&clientHandle, &serverHandle);
|
|
if (FAILED(hr))
|
|
{
|
|
goto quit;
|
|
}
|
|
|
|
hr = PerformHandshake(clientHandle, serverHandle);
|
|
if (FAILED(hr))
|
|
{
|
|
goto quit;
|
|
}
|
|
|
|
hr = PerformDataExchange(clientHandle, serverHandle, &transport);
|
|
if (FAILED(hr))
|
|
{
|
|
goto quit;
|
|
}
|
|
|
|
quit:
|
|
|
|
if (clientHandle != NULL)
|
|
{
|
|
WebSocketDeleteHandle(clientHandle);
|
|
clientHandle = NULL;
|
|
}
|
|
|
|
if (serverHandle != NULL)
|
|
{
|
|
WebSocketDeleteHandle(serverHandle);
|
|
serverHandle = NULL;
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"Websocket failed with error 0x%x\n", hr);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|