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

517 lines
16 KiB
C++

//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
#include "common.h"
// This header file contains all definitions used by both client and server services.
// Size of each file access when serializing the file into messages.
// A bigger number here results in fewer file reads while a smaller number reduces
// memory consumption. This is a sub-chunking of the chunk size set by the user
// when starting the services. If the chunk size set then is bigger than FILE_CHUNK
// a chunk is read in more than one file access.
#define FILE_CHUNK 131072
#define DISCOVERY_REQUEST -1
// Error Uris used to transmit errors from server service to client service.
namespace GlobalStrings
{
static const WCHAR noError[] = L"http://tempuri.org/FileRep/NoError";
static const WCHAR serializationFailed[] = L"http://tempuri.org/FileRep/SerializationFailed";
static const WCHAR invalidFileName[] = L"http://tempuri.org/FileRep/InvalidFileName";
static const WCHAR unableToDetermineFileLength[] = L"http://tempuri.org/FileRep/UnableToDetermineFileLength";
static const WCHAR invalidRequest[] = L"http://tempuri.org/FileRep/InvalidRequest";
static const WCHAR outOfRange[] = L"http://tempuri.org/FileRep/OutOfRange";
static const WCHAR unableToSetFilePointer[] = L"http://tempuri.org/FileRep/UnableToSetFilePointer";
}
class CChannelManager;
class CRequest;
typedef enum
{
REPORT_ERROR = 1,
REPORT_INFO = 2,
REPORT_VERBOSE = 3,
} REPORTING_LEVEL;
typedef enum
{
INVALID_REQUEST = 1,
FILE_DOES_NOT_EXIST = 2,
FAILED_TO_CREATE_FILE,
} FAULT_TYPE;
// This class is the base class for both the client and server service.
class CFileRep
{
public:
CFileRep(
__in REPORTING_LEVEL errorReporting,
__in long maxChannels,
__in TRANSPORT_MODE transport,
__in SECURITY_MODE security,
__in MESSAGE_ENCODING encoding);
~CFileRep();
HRESULT Start(
__in_ecount(uriLength) const LPWSTR uri,
__in DWORD uriLength);
// A stop resets all custom state and releases all resources associated with a running instance of the service.
HRESULT Stop();
inline bool IsRunning() { return started; }
WS_LISTENER* GetListener() { return listener; }
CChannelManager* GetChannelManager() { return channelManager; }
virtual HRESULT ProcessMessage(
__in CRequest* request,
__in const WS_XML_STRING* receivedAction) = 0;
// These have to be public since things like thread creation can fail outside of the class.
void PrintVerbose(
__in_z const WCHAR message[]);
void PrintInfo(
__in_z const WCHAR message[]);
void PrintError(
__in HRESULT hr,
__in_opt WS_ERROR* error,
__in bool displayAlways);
void PrintError(
__in_z const WCHAR message[],
__in bool displayAlways);
// The default maximum heap size is set very conservatively to protect against headers of excessive size.
// Since we are sending large chunks of data round that is not sufficient for us.
static WS_MESSAGE_PROPERTY CreateHeapProperty()
{
static SIZE_T heapSize = MAXMESSAGESIZE * 2;
static WS_HEAP_PROPERTY heapPropertyArray[] =
{
{ WS_HEAP_PROPERTY_MAX_SIZE, &heapSize, sizeof(heapSize) }
};
static WS_HEAP_PROPERTIES heapProperties =
{
heapPropertyArray, WsCountOf(heapPropertyArray)
};
WS_MESSAGE_PROPERTY ret;
ret.id = WS_MESSAGE_PROPERTY_HEAP_PROPERTIES;
ret.value = &heapProperties;
ret.valueSize = sizeof(heapProperties);
return ret;
}
void GetEncoding(
__out WS_ENCODING* encodingProperty,
__out ULONG* propertyCount);
protected:
HRESULT InitializeListener();
WS_STRING uri;
volatile bool started;
REPORTING_LEVEL errorReporting;
long maxChannels;
TRANSPORT_MODE transportMode;
MESSAGE_ENCODING encoding;
SECURITY_MODE securityMode;
WS_LISTENER* listener;
CChannelManager* channelManager;
};
// Manages the channels and related state to maximize reuse of structures and performance.
// If there are less channels that are ready to accept a request (called idle channels)
// than minIdleChannels then we create a new channel and make it listen for incoming requests.
// If there are more idle channels than maxIdleChannels then we destroy the next channel that
// becomes idle. There are never more than maxTotalChannels channels overall.
// The reason for having a minimum number of idle channels is that otherwise there would be a
// bottleneck when multiple requests come in simultaniously as channel creation takes some time.
// The reason for having a maximum number of idle channels is to limit resource consumption.
// Resource reuse is an important performance booster. Resetting a data structure is a lot cheaper
// than destroying and recreating it later, which is why there are so many reset APIs.
// However, never destroying resources can also be problematic as you can potentially hold on
// to significant resources much longer than neccessary. This class, using the algorithm described
// above, tries to find a middle ground.
// If the last issue is not a concern, a simpler and most likely superior implementation is to
// simply create as many channels and associated resources as needed and to reuse them perpetually
// in their own loop.
class CChannelManager
{
public:
CChannelManager(
__in CFileRep* server,
__in long minIdleChannels,
__in long maxIdleChannels,
__in long maxTotalChannels);
~CChannelManager();
HRESULT Initialize();
HRESULT CreateChannels();
static ULONG WINAPI CreateChannelWorkItem(
__in void* state);
void CreateChannel(
__in CRequest* request);
void ChannelCreated();
void ChannelInUse();
void ChannelFreed();
void ChannelIdle();
static void CALLBACK CleanupCallback(
__in HRESULT hr,
__in WS_CALLBACK_MODEL callbackModel,
__in void* state);
void Stop();
void WaitForCleanup();
inline long GetChannelCount() { return totalChannels; }
inline bool IsRunning() { return running; }
inline bool ShouldDestroyChannel() { return (!IsRunning() || idleChannels > maxIdleChannels); }
private:
inline void PrintVerbose(
__in_z const WCHAR message[])
{
server->PrintVerbose(message);
}
long minIdleChannels;
long maxIdleChannels;
long maxTotalChannels;
long idleChannels;
long activeChannels;
long totalChannels;
CFileRep* server;
HANDLE stopEvent;
volatile bool running;
#if (DBG || _DEBUG)
bool initialized;
#endif
};
// This class implements the part of the message processing loop that is common to the client and server service.
// It contains the per-channel state as well as the common processing methods.
class CRequest
{
public:
CRequest(
__in CFileRep* server);
~CRequest();
HRESULT Initialize();
// Static callback methods. They delegate to their non-static counterparts.
static HRESULT CALLBACK ResetChannelCallback(
__in HRESULT hr,
__in WS_CALLBACK_MODEL callbackModel,
__in void* callbackState,
__inout WS_ASYNC_OPERATION* next,
__in_opt const WS_ASYNC_CONTEXT* asyncContext,
__in_opt WS_ERROR* error);
static HRESULT CALLBACK AcceptChannelCallback(
__in HRESULT hr,
__in WS_CALLBACK_MODEL callbackModel,
__in void* callbackState,
__inout WS_ASYNC_OPERATION* next,
__in_opt const WS_ASYNC_CONTEXT* asyncContext,
__in_opt WS_ERROR* error);
static HRESULT CALLBACK ReceiveFirstMessageCallback(
__in HRESULT hr,
__in WS_CALLBACK_MODEL callbackModel,
__in void* callbackState,
__inout WS_ASYNC_OPERATION* next,
__in_opt const WS_ASYNC_CONTEXT* asyncContext,
__in_opt WS_ERROR* error);
static HRESULT CALLBACK ReceiveMessageCallback(
__in HRESULT hr,
__in WS_CALLBACK_MODEL callbackModel,
__in void* callbackState,
__inout WS_ASYNC_OPERATION* next,
__in_opt const WS_ASYNC_CONTEXT* asyncContext,
__in_opt WS_ERROR* error);
static HRESULT CALLBACK ReadHeaderCallback(
__in HRESULT hr,
__in WS_CALLBACK_MODEL callbackModel,
__in void* callbackState,
__inout WS_ASYNC_OPERATION* next,
__in_opt const WS_ASYNC_CONTEXT* asyncContext,
__in_opt WS_ERROR* error);
static HRESULT CALLBACK CloseChannelCallback(
__in HRESULT hr,
__in WS_CALLBACK_MODEL callbackModel,
__in void* callbackState,
__inout WS_ASYNC_OPERATION* next,
__in_opt const WS_ASYNC_CONTEXT* asyncContext,
__in_opt WS_ERROR* error);
static HRESULT CALLBACK RequestCompleteCallback(
__in HRESULT hr,
__in WS_CALLBACK_MODEL callbackModel,
__in void* callbackState,
__inout WS_ASYNC_OPERATION* next,
__in_opt const WS_ASYNC_CONTEXT* asyncContext,
__in_opt WS_ERROR* error);
static HRESULT CALLBACK HandleFailureCallback(
__in HRESULT hr,
__in WS_CALLBACK_MODEL callbackModel,
__in void* callbackState,
__inout WS_ASYNC_OPERATION* next,
__in_opt const WS_ASYNC_CONTEXT* asyncContext,
__in_opt WS_ERROR* error);
// The non-static counterparts that actually do the work.
HRESULT ResetChannel(
__in HRESULT hr,
__inout WS_ASYNC_OPERATION* next,
__in WS_CALLBACK_MODEL callbackModel,
__in_opt WS_ERROR* error);
HRESULT AcceptChannel(
__in HRESULT hr,
__inout WS_ASYNC_OPERATION* next,
__in WS_CALLBACK_MODEL callbackModel,
__in_opt const WS_ASYNC_CONTEXT* asyncContext,
__in_opt WS_ERROR* error);
HRESULT ReceiveFirstMessage(
__in HRESULT hr,
__inout WS_ASYNC_OPERATION* next,
__in WS_CALLBACK_MODEL callbackModel);
HRESULT ReceiveMessage(
__in HRESULT hr,
__inout WS_ASYNC_OPERATION* next,
__in WS_CALLBACK_MODEL callbackModel,
__in_opt const WS_ASYNC_CONTEXT* asyncContext,
__in_opt WS_ERROR* error);
HRESULT ReadHeader(
__in HRESULT hr,
__inout WS_ASYNC_OPERATION* next,
__in WS_CALLBACK_MODEL callbackModel,
__in_opt WS_ERROR* error);
HRESULT CloseChannel(
__in HRESULT hr,
__inout WS_ASYNC_OPERATION* next,
__in WS_CALLBACK_MODEL callbackModel,
__in_opt const WS_ASYNC_CONTEXT* asyncContext,
__in_opt WS_ERROR* error);
HRESULT RequestComplete(
__in HRESULT hr,
__inout WS_ASYNC_OPERATION* next);
HRESULT HandleFailure(
__in HRESULT hr,
__inout WS_ASYNC_OPERATION* next,
__in_opt WS_ERROR* error);
WS_CHANNEL* GetChannel() { return channel; }
WS_MESSAGE* GetRequestMessage() { return requestMessage; }
WS_MESSAGE* GetReplyMessage() { return replyMessage; }
WS_ERROR* GetError() { return error; }
CFileRep* GetServer() { return server; }
// We use SOAP faults to communicate to the tool errors that might be expected
// during execution, such as file not found. For critical errors we abort the channel.
// For internal communication between the services we use status messages that are part
// of the regular message exchange instead of faults. We could use faults there as well,
// but we do not want to fault internal communication because for example the user
// requested an invalid file.
HRESULT SendFault(
__in FAULT_TYPE faultType);
static inline CRequest* GetRequest(
__in void* callbackState);
WS_ASYNC_STATE asyncState;
private:
inline void PrintVerbose(
__in_z const WCHAR message[])
{
server->PrintVerbose(message);
}
// State associated with a request. Except the CFileRep pointer, this is not application specific.
WS_CHANNEL* channel;
WS_MESSAGE* requestMessage;
WS_MESSAGE* replyMessage;
WS_ERROR* error;
CFileRep* server;
bool channelInUse;
};
// Server service.
class CFileRepServer : public CFileRep
{
public:
CFileRepServer(
__in REPORTING_LEVEL errorReporting,
__in DWORD maxChannels,
__in TRANSPORT_MODE transport,
__in SECURITY_MODE security,
__in MESSAGE_ENCODING encoding,
__in DWORD chunkSize) : CFileRep(
errorReporting,
maxChannels,
transport,
security,
encoding)
{
this->chunkSize = chunkSize;
}
HRESULT ProcessMessage(
__in CRequest* request,
__in const WS_XML_STRING* receivedAction);
protected:
HRESULT SendError(
__in CRequest* request,
__in_z const WCHAR errorMessage[]);
HRESULT ReadAndSendFile(
__in CRequest* request,
__in const LPWSTR fileName,
__in LONGLONG chunkPosition,
__in_opt WS_ERROR* error);
HRESULT SendFileInfo(
__in CRequest* requeste,
__in_z const LPWSTR fileName,
__in LONGLONG llFileLength,
__in DWORD chunkSize);
HRESULT ReadAndSendChunk(
__in CRequest* request,
__in long chunkSize,
__in LONGLONG chunkPosition,
__in HANDLE file);
long chunkSize;
};
// Client service.
class CFileRepClient : public CFileRep
{
public:
CFileRepClient(
__in REPORTING_LEVEL errorReporting,
__in DWORD maxChannels,
__in TRANSPORT_MODE transport,
__in SECURITY_MODE security,
__in MESSAGE_ENCODING encoding) : CFileRep(
errorReporting,
maxChannels,
transport,
security,
encoding)
{
}
HRESULT ProcessMessage(
__in CRequest* request,
__in const WS_XML_STRING* receivedAction);
protected:
HRESULT SendUserResponse(
__in CRequest* request,
__in TRANSFER_RESULTS result);
HRESULT 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);
HRESULT CreateServerChannel(
__in MESSAGE_ENCODING serverEncoding,
__in TRANSPORT_MODE serverTransportMode,
__in SECURITY_MODE serverSecurityMode,
__in_opt WS_ERROR* error,
__deref_out WS_CHANNEL** channel);
HRESULT ExtendFile(
__in HANDLE file,
__in LONGLONG length);
HRESULT 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);
HRESULT DeserializeAndWriteMessage(
__in WS_MESSAGE* message,
__in long chunkSize,
__out LONGLONG* chunkPosition,
__out long* contentLength,
__in HANDLE file);
};
// Helper functions.
void PrintError(
__in HRESULT errorCode,
__in_opt WS_ERROR* error);
HRESULT ParseTransport(
__in_z const LPWSTR url,
__out TRANSPORT_MODE* transport,
__out SECURITY_MODE* securityMode);
void CleanupChannel(
__in_opt WS_CHANNEL* channel);