2025-11-28 00:35:46 +09:00

693 lines
22 KiB
C++

//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
#include "Service.h"
#include "process.h"
#include "string.h"
#include "strsafe.h"
#include "stdlib.h"
#include "intsafe.h"
#include "assert.h"
#include "wtypes.h"
// This file contains the client-service specific code.
// The client version of ProcessMessage. This is the entry point for the application-specific code.
HRESULT CFileRepClient::ProcessMessage(
__in CRequest* request,
__in const WS_XML_STRING* receivedAction)
{
PrintVerbose(L"Entering CFileRepClient::ProcessMessage");
HRESULT hr = S_OK;
WS_CHANNEL* channel = request->GetChannel();
WS_MESSAGE* requestMessage = request->GetRequestMessage();
WS_ERROR* error = request->GetError();
// Make sure action is what we expect
if (WsXmlStringEquals(receivedAction, &faultAction, error) == S_OK)
{
PrintInfo(L"Received fault message. Aborting.");
hr = E_FAIL;
EXIT_FUNCTION
}
// Make sure action is what we expect
if (WsXmlStringEquals(receivedAction, &userRequestAction, error) != S_OK)
{
PrintInfo(L"Received unexpected message");
hr = WS_E_ENDPOINT_ACTION_NOT_SUPPORTED;
EXIT_FUNCTION
}
// Get the heap of the message
WS_HEAP* heap;
IfFailedExit(WsGetMessageProperty(requestMessage, WS_MESSAGE_PROPERTY_HEAP, &heap, sizeof(heap), error));
// Read user request
UserRequest* userRequest = NULL;
IfFailedExit(WsReadBody(requestMessage, &userRequestElement, WS_READ_REQUIRED_POINTER, heap, &userRequest, sizeof(userRequest), error));
// Read end of message
IfFailedExit(WsReadMessageEnd(channel, requestMessage, NULL, error));
// Sanity check
if (::wcslen(userRequest->sourcePath) >= MAX_PATH ||
::wcslen(userRequest->destinationPath) >= MAX_PATH ||
::wcslen(userRequest->serverUri) >= MAX_PATH)
{
PrintInfo(L"Invalid request");
hr = request->SendFault(INVALID_REQUEST);
}
else
{
hr = ProcessUserRequest(request, userRequest->sourcePath,
userRequest->destinationPath, userRequest->serverUri, userRequest->serverProtocol,
userRequest->securityMode, userRequest->messageEncoding, userRequest->requestType);
}
EXIT
// The caller handles the failures. So just return the error;
PrintVerbose(L"Leaving CCFileRepClient::ProcessMessage");
return hr;
}
// The message exchange pattern when transfering a file is as follows:
// - We get a request message from the command line tool
// - If the request is asynchronous send back a confirmation immediately
// - We send a request for file information to the server service. A discovery request is denoted by a chunk position of -1.
// - We get the file information
// - We request the individual chunks sequentially one by one from the server.
// Chunks are identified by their position within the file. This could be optimized by asynchronously requesting
// multiple chunks. However, that is beyond the scope of this version of the sample.
// - Repeat until the file transfer is completed or a failure occured
// - If the request is synchronous send success or failure message to the command line tool.
// For the individual data structures associated with each message, see common.h.
HRESULT CFileRepClient::ProcessUserRequest(
__in CRequest* request,
__in_z const LPWSTR sourcePath,
__in_z const LPWSTR destinationPath,
__in_z const LPWSTR serverUri,
__in TRANSPORT_MODE transportMode,
__in SECURITY_MODE securityMode,
__in MESSAGE_ENCODING encoding,
__in REQUEST_TYPE requestType)
{
PrintVerbose(L"Entering CFileRepClient::ProcessUserRequest");
HRESULT hr = S_OK;
WS_ERROR* error = request->GetError();
WS_CHANNEL* serverChannel = NULL;
WS_MESSAGE* serverRequestMessage = NULL;
WS_MESSAGE* serverReplyMessage = NULL;
WS_HEAP* heap = NULL;
HANDLE file = INVALID_HANDLE_VALUE;
LPWSTR statusMessage = NULL;
LONGLONG fileLength = 0;
long chunkSize = -1;
DWORD transferTime = 0;
LARGE_INTEGER size;
size.QuadPart = 0;
WS_MESSAGE_PROPERTY heapProperty;
WS_ENDPOINT_ADDRESS address = {};
if (ASYNC_REQUEST == requestType)
{
// In case of an async request, acknowlege request before doing any actual work.
IfFailedExit(SendUserResponse(request, TRANSFER_ASYNC));
PrintInfo(L"Asynchronous request. Sending asynchronous acknowledgement.");
}
heapProperty = CFileRepClient::CreateHeapProperty();
IfFailedExit(CreateServerChannel(encoding, transportMode, securityMode, error, &serverChannel));
IfFailedExit(WsCreateMessageForChannel(serverChannel, NULL, 0, &serverRequestMessage, error));
IfFailedExit(WsCreateMessageForChannel(serverChannel, &heapProperty, 1, &serverReplyMessage, error));
// Initialize address of service
address.url.chars = serverUri;
IfFailedExit(SizeTToULong(::wcslen(address.url.chars),&address.url.length));
// Open channel to address
IfFailedExit(WsOpenChannel(serverChannel, &address, NULL, error));
// Initialize file request
FileRequest fileRequest;
fileRequest.filePosition = DISCOVERY_REQUEST;
fileRequest.fileName = sourcePath;
// We ensured that those are not too long earlier
SIZE_T strLen = ::wcslen(fileRequest.fileName) + address.url.length + 100;
statusMessage = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, strLen * sizeof (WCHAR));
// We do not care about the failure value since in the worst case it truncates, which we
// still would want to display since its better than nothing.
StringCchPrintfW(statusMessage, strLen, L"Requesting file %s from server %s.", fileRequest.fileName, address.url.chars);
statusMessage[strLen-1] = L'\0'; // Terminate string in case StringCchPrintfW fails.
PrintInfo(statusMessage);
IfFailedExit(WsCreateHeap(65536, 0, NULL, 0, &heap, NULL));
WS_MESSAGE_DESCRIPTION fileRequestMessageDescription;
fileRequestMessageDescription.action = &fileRequestAction;
fileRequestMessageDescription.bodyElementDescription = &fileRequestElement;
WS_MESSAGE_DESCRIPTION fileInfoMessageDescription;
fileInfoMessageDescription.action = &fileInfoAction;
fileInfoMessageDescription.bodyElementDescription = &fileInfoElement;
// Send discovery request and get file info
FileInfo* fileInfo;
IfFailedExit(WsRequestReply(
serverChannel,
serverRequestMessage,
&fileRequestMessageDescription,
WS_WRITE_REQUIRED_VALUE,
&fileRequest,
sizeof(fileRequest),
serverReplyMessage,
&fileInfoMessageDescription,
WS_READ_REQUIRED_POINTER,
heap,
&fileInfo,
sizeof(fileInfo),
NULL,
error));
fileLength = fileInfo->fileLength;
chunkSize = fileInfo->chunkSize;
size.QuadPart = fileLength;
if (-1 == fileLength || 0 == chunkSize)
{
PrintInfo(L"File does not exist on server.");
if (SYNC_REQUEST == requestType)
{
hr = request->SendFault(FILE_DOES_NOT_EXIST);
}
EXIT_FUNCTION
}
// For simplicity reasons we do not read alternate data streams.
file = CreateFileW(destinationPath, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE == file)
{
PrintInfo(L"Failed to create file");
if (SYNC_REQUEST == requestType)
{
hr = request->SendFault(FAILED_TO_CREATE_FILE);
}
EXIT_FUNCTION
}
IfFailedExit(ExtendFile(file, fileLength));
transferTime = GetTickCount();
// This loop could be further optimized by asychronously requesting the individual chunks in
// parallel and then assembling them later. We chose to draw the line regarding perf optimizations
// for this version of the code here to avoid the complexity of doing asynchronous file assembly.
fileRequest.filePosition = 0;
while (fileRequest.filePosition < fileLength)
{
IfFailedExit(ProcessChunk(chunkSize , file, fileLength, serverRequestMessage,
serverReplyMessage, serverChannel, error, &fileRequest));
}
transferTime = GetTickCount() - transferTime;
if (SYNC_REQUEST == requestType)
{
hr = SendUserResponse(request, TRANSFER_SUCCESS);
}
WCHAR perf[255];
// This assumes that we did not use more than 4 billion chunks, a pretty reasonable assumption.
DWORD totalChunks = (DWORD)(fileLength/chunkSize) + 1;
if (size.HighPart != 0) // Big file
{
// Again failures are ignored since it is just a status message.
StringCchPrintfW(perf, CountOf(perf), L"Transferred %d%d bytes via %d chunks in %d milliseconds.",
size.HighPart, size.LowPart, totalChunks, transferTime);
}
else
{
StringCchPrintfW(perf, CountOf(perf), L"Transferred %d bytes via %d chunks in %d milliseconds.",
size.LowPart, totalChunks, transferTime);
}
perf[CountOf(perf)-1] = L'\0'; // Ensures that the buffer is terminated even if StringCChPrintfW fails.
PrintInfo(perf);
// We fall through here even in the success case due to the cleanup that has to be performed in both cases.
EXIT
//Has to come first so file gets properly deleted on error.
if (INVALID_HANDLE_VALUE != file)
{
if (!CloseHandle(file))
{
PrintInfo(L"CFileRepClient::ProcessUserRequest - CloseHandle failed. Potential handle leak.");
}
}
if (NULL != serverRequestMessage)
{
WsFreeMessage(serverRequestMessage);
}
if (NULL != serverReplyMessage)
{
WsFreeMessage(serverReplyMessage);
}
CleanupChannel(serverChannel);
if (NULL != statusMessage)
{
HeapFree(GetProcessHeap(), 0, statusMessage);
}
if (NULL != heap)
{
WsFreeHeap(heap);
}
if (FAILED(hr))
{
DeleteFileW(destinationPath);
PrintError(L"CFileRepClient::ProcessUserRequest", true);
PrintError(hr, error, true);
}
PrintVerbose(L"Leaving CFileRepClient::ProcessUserRequest");
return hr;
}
HRESULT CFileRepClient::CreateServerChannel(
__in MESSAGE_ENCODING serverEncoding,
__in TRANSPORT_MODE serverTransportMode,
__in SECURITY_MODE serverSecurityMode,
__in_opt WS_ERROR* error,
__deref_out WS_CHANNEL** channel)
{
PrintVerbose(L"Entering CFileRepClient::CreateServerChannel");
WS_SSL_TRANSPORT_SECURITY_BINDING transportSecurityBinding = {};
WS_SECURITY_DESCRIPTION securityDescription = {};
WS_SECURITY_DESCRIPTION* pSecurityDescription = NULL;
WS_SECURITY_BINDING* securityBindings[1];
HRESULT hr = S_OK;
WS_CHANNEL_PROPERTY channelProperty[2];
// The default maximum message size is 64k. This is not enough as the server may chose to send
// very large messages to maximize throughput.
ULONG maxMessageSize = MAXMESSAGESIZE;
channelProperty[0].id = WS_CHANNEL_PROPERTY_MAX_BUFFERED_MESSAGE_SIZE;
channelProperty[0].value = &maxMessageSize;
channelProperty[0].valueSize = sizeof(maxMessageSize);
WS_ENCODING messageEncoding;
if (TEXT_ENCODING == serverEncoding)
{
messageEncoding = WS_ENCODING_XML_UTF8;
}
else if (BINARY_ENCODING == serverEncoding)
{
messageEncoding = WS_ENCODING_XML_BINARY_SESSION_1;
}
else
{
messageEncoding = WS_ENCODING_XML_MTOM_UTF8;
}
channelProperty[1].id = WS_CHANNEL_PROPERTY_ENCODING;
channelProperty[1].value = &messageEncoding;
channelProperty[1].valueSize = sizeof(messageEncoding);
ULONG propCount = 1;
if (DEFAULT_ENCODING != serverEncoding)
{
propCount++;
}
if (SSL_SECURITY == serverSecurityMode)
{
// Initialize a security description for SSL
transportSecurityBinding.binding.bindingType = WS_SSL_TRANSPORT_SECURITY_BINDING_TYPE;
securityBindings[0] = &transportSecurityBinding.binding;
securityDescription.securityBindings = securityBindings;
securityDescription.securityBindingCount = WsCountOf(securityBindings);
pSecurityDescription = &securityDescription;
}
// Create a channel
if (TCP_TRANSPORT == serverTransportMode)
{
hr = WsCreateChannel(WS_CHANNEL_TYPE_DUPLEX_SESSION, WS_TCP_CHANNEL_BINDING, channelProperty,
propCount, pSecurityDescription, channel, error);
}
else // HTTP
{
hr = WsCreateChannel(WS_CHANNEL_TYPE_REQUEST, WS_HTTP_CHANNEL_BINDING,
channelProperty, propCount, pSecurityDescription, channel, error);
}
PrintVerbose(L"Leaving CFileRepClient::CreateServerChannel");
return hr;
}
// Extend the file to the total size needed for performance reasons.
// For a synchronous write such as this this is not a big deal, but if this code
// would be made async then it would be. And even now this is more performant.
HRESULT CFileRepClient::ExtendFile(
__in HANDLE file,
__in LONGLONG length)
{
LARGE_INTEGER size;
size.QuadPart = length;
if (!SetFilePointerEx(file, size, NULL, FILE_BEGIN))
{
PrintError(L"Error extending file.", true);
return HRESULT_FROM_WIN32(GetLastError());
}
if (!SetEndOfFile(file))
{
PrintError(L"Error extending file.", true);
return HRESULT_FROM_WIN32(GetLastError());
}
size.QuadPart = 0;
if (!SetFilePointerEx(file, size, NULL, FILE_BEGIN))
{
PrintError(L"Error resetting file.", true);
return HRESULT_FROM_WIN32(GetLastError());
}
return S_OK;
}
HRESULT CFileRepClient::ProcessChunk(
__in long chunkSize,
__in HANDLE file,
__in LONGLONG fileLength,
__in WS_MESSAGE* requestMessage,
__in WS_MESSAGE* replyMessage,
__in WS_CHANNEL* channel,
__in_opt WS_ERROR* error,
__in FileRequest* request)
{
PrintVerbose(L"Entering CFileRepClient::ProcessChunk");
LONGLONG pos = request->filePosition;
LONGLONG chunkPosition = 0;
long contentLength = 0;
HRESULT hr = S_OK;
IfFailedExit(WsResetMessage(requestMessage, error));
IfFailedExit(WsResetMessage(replyMessage, error));
WS_MESSAGE_DESCRIPTION fileRequestMessageDescription;
fileRequestMessageDescription.action = &fileRequestAction;
fileRequestMessageDescription.bodyElementDescription = &fileRequestElement;
IfFailedExit(WsSendMessage(
channel,
requestMessage,
&fileRequestMessageDescription,
WS_WRITE_REQUIRED_VALUE,
request,
sizeof(*request),
NULL,
error));
// Receive start of message (headers).
IfFailedExit(WsReadMessageStart(channel, replyMessage, NULL, error));
// Get action value.
WS_XML_STRING* receivedAction = NULL;
IfFailedExit(WsGetHeader(
replyMessage,
WS_ACTION_HEADER,
WS_XML_STRING_TYPE,
WS_READ_REQUIRED_POINTER,
NULL,
&receivedAction,
sizeof(receivedAction),
error));
// Make sure action is what we expect.
if (WsXmlStringEquals(receivedAction, &fileReplyAction, error) != S_OK)
{
hr = WS_E_ENDPOINT_ACTION_NOT_SUPPORTED;
PrintInfo(L"Received unexpected message.\n");
EXIT_FUNCTION
}
IfFailedExit(DeserializeAndWriteMessage(replyMessage, chunkSize, &chunkPosition, &contentLength, file));
// Read end of message.
IfFailedExit(WsReadMessageEnd(channel, replyMessage, NULL, error));
if (contentLength != chunkSize && contentLength + pos != fileLength || pos != chunkPosition)
{
PrintError(L"File message was corrupted. Aborting transfer\n", true);
hr = E_FAIL;
}
else
{
request->filePosition = pos + contentLength;
}
EXIT
if (WS_E_INVALID_FORMAT == hr)
{
PrintInfo(L"Deserialization of the message failed.");
}
PrintVerbose(L"Leaving CFileRepClient::ProcessChunk");
return hr;
}
// It is more efficient to manually read this message instead of using the serializer since otherwise the
// byte array would have to be copied around memory multiple times while here we can stream it directly into the file.
// Since the message is simple this is relatively easy to do and makes the perf gain worth the extra effort. In general,
// one should only go down to this level if the performance gain is significant. For most cases the serialization APIs
// are the better choice and they also make future changes easier to implement.
HRESULT CFileRepClient::DeserializeAndWriteMessage(
__in WS_MESSAGE* message,
__in long chunkSize,
__out LONGLONG* chunkPosition,
__out long* contentLength,
__in HANDLE file)
{
PrintVerbose(L"Entering CFileServer::DeserializeAndWriteMessage");
WS_XML_READER* reader = NULL;
HRESULT hr = S_OK;
LPWSTR errorString = NULL;
WS_HEAP* heap = NULL;
// Create a description for the error text field that we read later.
WS_ELEMENT_DESCRIPTION errorDescription = {&errorLocalName, &fileChunkNamespace, WS_WSZ_TYPE, NULL};
// To avoid using too much memory we read and write the message in chunks.
long bytesToRead = FILE_CHUNK;
if (bytesToRead > chunkSize)
{
bytesToRead = chunkSize;
}
BYTE* buf = NULL;
ULONG length = 0;
// We do not use WS_ERROR here since this function does not print extended error information.
// The reason for that is that a failure here is likely due to a malformed message coming in, which
// is probably a client issue and not an error condition for us. So the caller will print a simple status
// message if this fails and call it good.
IfFailedExit(WsGetMessageProperty(message, WS_MESSAGE_PROPERTY_BODY_READER, &reader, sizeof(reader), NULL));
// Read to FileChunk element
IfFailedExit(WsReadToStartElement(reader, &fileChunkLocalName, &fileChunkNamespace, NULL, NULL));
// Read FileChunk start element
IfFailedExit(WsReadStartElement(reader, NULL));
// The next four APIs read the chunk position element. There are helper APIs that can do this for you,
// but they are more complex to use. For simple types such as integers, doing this manually is easiest.
// For more complex type the serialization APIs should be used.
// Read to chunkPosition element
IfFailedExit(WsReadToStartElement(reader, &chunkPositionLocalName, &fileChunkNamespace, NULL, NULL));
// Read chunk position start element
IfFailedExit(WsReadStartElement(reader, NULL));
// Read chunk position
IfFailedExit(WsReadValue(reader, WS_INT64_VALUE_TYPE, chunkPosition, sizeof(*chunkPosition), NULL));
// Read chunk position end element
IfFailedExit(WsReadEndElement(reader, NULL));
// Read to file content element
IfFailedExit(WsReadToStartElement(reader, &fileContentLocalName, &fileChunkNamespace, NULL, NULL));
// Read file content start element
IfFailedExit(WsReadStartElement(reader, NULL));
buf = (BYTE*)HeapAlloc(GetProcessHeap(), 0, bytesToRead);
IfNullExit(buf);
// Read file content into buffer
// We are reading a chunk of the byte array in the message, writing it to disk and then read
// the next chunk. That way we only need mimimal amounts of memory compared to the total amount
// of data transferred. The exact way this is done is subject to perf tweaking.
for (;;)
{
// Read next block of bytes.
ULONG bytesRead = 0;
IfFailedExit(WsReadBytes(reader, buf, bytesToRead, &bytesRead, NULL));
if (bytesRead == 0)
{
// We read it all.
break;
}
length+=bytesRead;
ULONG count = 0;
if (!WriteFile(file, buf, bytesRead, &count, NULL))
{
PrintError(L"File write error.", true);
hr = HRESULT_FROM_WIN32(GetLastError());
EXIT_FUNCTION
}
if (count != bytesRead)
{
PrintError(L"File write error.", true);
hr = E_FAIL;
EXIT_FUNCTION
}
}
// Read file content end element
IfFailedExit(WsReadEndElement(reader, NULL));
// Read the error string and write it to a heap.
IfFailedExit(WsCreateHeap(/*maxSize*/ 1024, /*trimSize*/ 1024, NULL, 0, &heap, NULL));
// Here it pays to use WsReadElementType instead of manually parsing the element since we require heap memory anyway.
// This can fail if the error message does not fit on the relatively small heap created for it. The server is not
// expected to use error messages that long. If we get back such a long message we talk to a buggy or rogue server
// and failing is the right thing to do.
IfFailedExit(WsReadElement(reader, &errorDescription, WS_READ_REQUIRED_POINTER, heap,
&errorString, sizeof(errorString), NULL));
// Read file data end element
IfFailedExit(WsReadEndElement(reader, NULL));
*contentLength = length;
if (lstrcmpW(errorString, &GlobalStrings::noError[0]))
{
PrintInfo(L"Chunk transfer failed");
if (errorString)
{
PrintInfo(errorString);
}
hr = E_FAIL;
}
EXIT
if (NULL != buf)
{
HeapFree(GetProcessHeap(), 0, buf);
}
if (heap)
{
// Clean up errorString.
WsResetHeap(heap, NULL);
WsFreeHeap(heap);
}
if (FAILED(hr))
{
hr = WS_E_INVALID_FORMAT;
}
PrintVerbose(L"Leaving CFileRepClient::DeserializeAndWriteMessage");
return hr;
}
// Tell the command line tool what happened to the request.
HRESULT CFileRepClient::SendUserResponse(
__in CRequest* request,
__in TRANSFER_RESULTS result)
{
PrintVerbose(L"Entering CFileRepClient::SendUserResponse");
HRESULT hr = S_OK;
WS_ERROR* error = request->GetError();
WS_MESSAGE* replyMessage = request->GetReplyMessage();
WS_MESSAGE* requestMessage = request->GetRequestMessage();
WS_CHANNEL* channel = request->GetChannel();
UserResponse userResponse;
userResponse.returnValue = result;
WS_MESSAGE_DESCRIPTION userResponseMessageDescription;
userResponseMessageDescription.action = &userResponseAction;
userResponseMessageDescription.bodyElementDescription = &userResponseElement;
hr = WsSendReplyMessage(
channel,
replyMessage,
&userResponseMessageDescription,
WS_WRITE_REQUIRED_VALUE,
&userResponse,
sizeof(userResponse),
requestMessage,
NULL, error);
if (FAILED(hr))
{
PrintError(L"CFileRepClient::SendUserResponse\n", true);
PrintError(hr, error, true);
}
WsResetMessage(replyMessage, NULL);
PrintVerbose(L"Leaving CFileRepClient::SendUserResponse");
return hr;
}