You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
27291 lines
813 KiB
27291 lines
813 KiB
/*++
|
|
|
|
Copyright (C) Microsoft Corporation, 2001
|
|
|
|
Module Name:
|
|
|
|
HTTP2.cxx
|
|
|
|
Abstract:
|
|
|
|
HTTP2 transport-specific functions.
|
|
|
|
Author:
|
|
|
|
KamenM 08-30-01 Created
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include <precomp.hxx>
|
|
#include <rpcssl.h>
|
|
#include <CharConv.hxx>
|
|
#include <HttpRTS.hxx>
|
|
#include <sdict.hxx>
|
|
#include <binding.hxx>
|
|
#include <Cookie.hxx>
|
|
#include <Http2Log.hxx>
|
|
#include <WHttpImp.hxx>
|
|
|
|
// external definition
|
|
TRANS_INFO *
|
|
GetLoadedClientTransportInfoFromId (
|
|
IN unsigned short TransportId
|
|
);
|
|
|
|
BOOL DefaultChannelLifetimeStringRead = FALSE;
|
|
#if DBG
|
|
ULONG DefaultChannelLifetime = 128 * 1024; // 128K for now
|
|
char *DefaultChannelLifetimeString = "131072";
|
|
ULONG DefaultChannelLifetimeStringLength = 6; // does not include null terminator
|
|
#else
|
|
ULONG DefaultChannelLifetime = 1024 * 1024 * 1024; // 1GB for now
|
|
char *DefaultChannelLifetimeString = "1073741824";
|
|
ULONG DefaultChannelLifetimeStringLength = 11; // does not include null terminator
|
|
#endif
|
|
|
|
ULONG DefaultReceiveWindowSize = 64 * 1024; // 64K
|
|
|
|
const ULONG ClientReservedChannelLifetime = 4 * 1024; // 4K
|
|
const ULONG ServerReservedChannelLifetime = 8 * 1024; // 8K
|
|
|
|
const ULONG DefaultReplacementChannelCallTimeout = 3 * 60 * 1000; // 3 minutes in milliseconds
|
|
|
|
const ULONG MinimumConnectionTimeout = 30 * 1000; // 30 seconds in milliseconds
|
|
|
|
BOOL ActAsSeparateMachinesOnWebFarm = FALSE;
|
|
|
|
BOOL AlwaysUseWinHttp = FALSE;
|
|
|
|
long ChannelIdCounter = 0;
|
|
|
|
ULONG HTTP2ClientReceiveWindow = HTTP2DefaultClientReceiveWindow;
|
|
ULONG HTTP2InProxyReceiveWindow = HTTP2DefaultInProxyReceiveWindow;
|
|
ULONG HTTP2OutProxyReceiveWindow = HTTP2DefaultOutProxyReceiveWindow;
|
|
ULONG HTTP2ServerReceiveWindow = HTTP2DefaultServerReceiveWindow;
|
|
|
|
ULONG OverrideMinimumConnectionTimeout = 0;
|
|
|
|
RPC_CHAR *InChannelTargetTestOverride = NULL;
|
|
RPC_CHAR *OutChannelTargetTestOverride = NULL;
|
|
|
|
/*********************************************************************
|
|
Global Functions and utility classes
|
|
*********************************************************************/
|
|
|
|
static const RPC_CHAR *HTTP_DEF_CHANNEL_LIFE_KEY = RPC_CONST_STRING("Software\\Microsoft\\Rpc");
|
|
|
|
RPC_STATUS InitializeDefaultChannelLifetime (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Read the default channel lifetime from the registry and
|
|
initialize the default global variable
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
HKEY h = 0;
|
|
DWORD DwordSize;
|
|
DWORD Type;
|
|
DWORD Result;
|
|
char Buffer[20];
|
|
|
|
if (DefaultChannelLifetimeStringRead)
|
|
return RPC_S_OK;
|
|
|
|
DWORD Status = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
|
|
(PWSTR)HTTP_DEF_CHANNEL_LIFE_KEY,
|
|
0,
|
|
KEY_READ,
|
|
&h);
|
|
|
|
if (Status == ERROR_FILE_NOT_FOUND)
|
|
{
|
|
// no key, proceed with static defaults
|
|
return RPC_S_OK;
|
|
}
|
|
else if (Status != ERROR_SUCCESS)
|
|
{
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
DwordSize = sizeof(DWORD);
|
|
|
|
Status = RegQueryValueExW(
|
|
h,
|
|
L"DefaultChannelLifetime",
|
|
0,
|
|
&Type,
|
|
(LPBYTE) &Result,
|
|
&DwordSize
|
|
);
|
|
|
|
if (Status == ERROR_FILE_NOT_FOUND)
|
|
{
|
|
if (h)
|
|
{
|
|
RegCloseKey(h);
|
|
}
|
|
// no key, proceed with static defaults
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
if (Status == ERROR_SUCCESS
|
|
&& Type == REG_DWORD)
|
|
{
|
|
DefaultChannelLifetime = Result;
|
|
RpcpItoa(Result, Buffer, 10);
|
|
DefaultChannelLifetimeStringLength = RpcpStringLengthA(Buffer);
|
|
|
|
DefaultChannelLifetimeString = new char [DefaultChannelLifetimeStringLength + 1];
|
|
if (DefaultChannelLifetimeString == NULL)
|
|
Status = RPC_S_OUT_OF_MEMORY;
|
|
else
|
|
{
|
|
RpcpMemoryCopy(DefaultChannelLifetimeString, Buffer, DefaultChannelLifetimeStringLength + 1);
|
|
DefaultChannelLifetimeStringRead = TRUE;
|
|
}
|
|
}
|
|
|
|
// if the type was not REG_DWORD, probably registry is corrupted
|
|
// in this case, simply return success, since we don't want a corrupted
|
|
// registry to hose the whole machine
|
|
|
|
if (h)
|
|
{
|
|
RegCloseKey(h);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
static const RPC_CHAR *HTTP_ACT_AS_WEB_FARM_KEY = RPC_CONST_STRING("Software\\Microsoft\\Rpc");
|
|
|
|
RPC_STATUS InitializeActAsWebFarm (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Read the act as web farm variable. Used as a test hook to emulate
|
|
web farm on single machine
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
HKEY h = 0;
|
|
DWORD DwordSize;
|
|
DWORD Type;
|
|
DWORD Result;
|
|
char Buffer[20];
|
|
|
|
DWORD Status = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
|
|
(PWSTR)HTTP_ACT_AS_WEB_FARM_KEY,
|
|
0,
|
|
KEY_READ,
|
|
&h);
|
|
|
|
if (Status == ERROR_FILE_NOT_FOUND)
|
|
{
|
|
// no key, proceed with static defaults
|
|
return RPC_S_OK;
|
|
}
|
|
else if (Status != ERROR_SUCCESS)
|
|
{
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
DwordSize = sizeof(DWORD);
|
|
|
|
Status = RegQueryValueExW(
|
|
h,
|
|
L"ActAsWebFarm",
|
|
0,
|
|
&Type,
|
|
(LPBYTE) &Result,
|
|
&DwordSize
|
|
);
|
|
|
|
if (Status == ERROR_FILE_NOT_FOUND)
|
|
{
|
|
if (h)
|
|
{
|
|
RegCloseKey(h);
|
|
}
|
|
// no key, proceed with static defaults
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
if (Status == ERROR_SUCCESS
|
|
&& Type == REG_DWORD)
|
|
{
|
|
ActAsSeparateMachinesOnWebFarm = Result;
|
|
}
|
|
|
|
// if the type was not REG_DWORD, probably registry is corrupted
|
|
// in this case, simply return success, since we don't want a corrupted
|
|
// registry to hose the whole machine
|
|
|
|
if (h)
|
|
{
|
|
RegCloseKey(h);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
static const RPC_CHAR *HTTP_USE_HTTP_KEY = RPC_CONST_STRING("Software\\Microsoft\\Rpc");
|
|
|
|
RPC_STATUS InitializeUseWinHttp (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Read the use WinHttp variable. Used as a test hook to force
|
|
WinHttp usage regardless of security
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
#if 1
|
|
// always use WinHttp for now
|
|
return RPC_S_OK;
|
|
#else
|
|
HKEY h = 0;
|
|
DWORD DwordSize;
|
|
DWORD Type;
|
|
DWORD Result;
|
|
char Buffer[20];
|
|
|
|
DWORD Status = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
|
|
(PWSTR)HTTP_ACT_AS_WEB_FARM_KEY,
|
|
0,
|
|
KEY_READ,
|
|
&h);
|
|
|
|
if (Status == ERROR_FILE_NOT_FOUND)
|
|
{
|
|
// no key, proceed with static defaults
|
|
return RPC_S_OK;
|
|
}
|
|
else if (Status != ERROR_SUCCESS)
|
|
{
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
DwordSize = sizeof(DWORD);
|
|
|
|
Status = RegQueryValueExW(
|
|
h,
|
|
L"UseWinHttp",
|
|
0,
|
|
&Type,
|
|
(LPBYTE) &Result,
|
|
&DwordSize
|
|
);
|
|
|
|
if (Status == ERROR_FILE_NOT_FOUND)
|
|
{
|
|
if (h)
|
|
{
|
|
RegCloseKey(h);
|
|
}
|
|
// no key, proceed with static defaults
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
if (Status == ERROR_SUCCESS
|
|
&& Type == REG_DWORD)
|
|
{
|
|
AlwaysUseWinHttp = Result;
|
|
}
|
|
|
|
// if the type was not REG_DWORD, probably registry is corrupted
|
|
// in this case, simply return success, since we don't want a corrupted
|
|
// registry to hose the whole machine
|
|
|
|
if (h)
|
|
{
|
|
RegCloseKey(h);
|
|
}
|
|
|
|
return Status;
|
|
#endif
|
|
}
|
|
|
|
static const RPC_CHAR *HTTP_RECEIVE_WINDOWS_KEY = RPC_CONST_STRING("Software\\Microsoft\\Rpc");
|
|
|
|
RPC_STATUS InitializeReceiveWindows (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Read the receive windows.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
HKEY h = 0;
|
|
DWORD DwordSize;
|
|
DWORD Type;
|
|
DWORD Result;
|
|
char Buffer[20];
|
|
RPC_CHAR *Keys[4];
|
|
ULONG *Values[4];
|
|
int i;
|
|
|
|
DWORD Status = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
|
|
(PWSTR)HTTP_RECEIVE_WINDOWS_KEY,
|
|
0,
|
|
KEY_READ,
|
|
&h);
|
|
|
|
if (Status == ERROR_FILE_NOT_FOUND)
|
|
{
|
|
// no key, proceed with static defaults
|
|
return RPC_S_OK;
|
|
}
|
|
else if (Status != ERROR_SUCCESS)
|
|
{
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
Keys[0] = L"ClientReceiveWindow";
|
|
Keys[1] = L"InProxyReceiveWindow";
|
|
Keys[2] = L"OutProxyReceiveWindow";
|
|
Keys[3] = L"ServerReceiveWindow";
|
|
|
|
Values[0] = &HTTP2ClientReceiveWindow;
|
|
Values[1] = &HTTP2InProxyReceiveWindow;
|
|
Values[2] = &HTTP2OutProxyReceiveWindow;
|
|
Values[3] = &HTTP2ServerReceiveWindow;
|
|
|
|
for (i = 0; i < 4; i ++)
|
|
{
|
|
DwordSize = sizeof(DWORD);
|
|
|
|
Status = RegQueryValueExW(
|
|
h,
|
|
Keys[i],
|
|
0,
|
|
&Type,
|
|
(LPBYTE) &Result,
|
|
&DwordSize
|
|
);
|
|
|
|
if (Status == ERROR_SUCCESS
|
|
&& Type == REG_DWORD)
|
|
{
|
|
*(Values[i]) = Result;
|
|
}
|
|
}
|
|
|
|
// if the type was not REG_DWORD, probably registry is corrupted
|
|
// in this case, simply return success, since we don't want a corrupted
|
|
// registry to hose the whole machine
|
|
|
|
if (h)
|
|
{
|
|
RegCloseKey(h);
|
|
}
|
|
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
// N.B. This value must agree with the key specified in the system.adm file
|
|
static const RPC_CHAR *HTTP_MIN_CONN_TIMEOUT_KEY =
|
|
L"Software\\Policies\\Microsoft\\Windows NT\\Rpc\\MinimumConnectionTimeout";
|
|
|
|
RPC_STATUS InitializeMinConnectionTimeout (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Read the minimum connection timeout from the registry/policy.
|
|
An admin may set a lower timeout than the IIS timeout.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
HKEY h = 0;
|
|
DWORD DwordSize;
|
|
DWORD Type;
|
|
DWORD Result;
|
|
char Buffer[20];
|
|
|
|
DWORD Status = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
|
|
HTTP_MIN_CONN_TIMEOUT_KEY,
|
|
0,
|
|
KEY_READ,
|
|
&h);
|
|
|
|
if (Status == ERROR_FILE_NOT_FOUND)
|
|
{
|
|
// no key, proceed with static defaults
|
|
return RPC_S_OK;
|
|
}
|
|
else if (Status != ERROR_SUCCESS)
|
|
{
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
DwordSize = sizeof(DWORD);
|
|
|
|
Status = RegQueryValueExW(
|
|
h,
|
|
L"MinimumConnectionTimeout",
|
|
0,
|
|
&Type,
|
|
(LPBYTE) &Result,
|
|
&DwordSize
|
|
);
|
|
|
|
if (Status == ERROR_FILE_NOT_FOUND)
|
|
{
|
|
if (h)
|
|
{
|
|
RegCloseKey(h);
|
|
}
|
|
// no key, proceed with static defaults
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
if (Status == ERROR_SUCCESS
|
|
&& Type == REG_DWORD
|
|
&& Result >= 90
|
|
&& Result <= 14400)
|
|
{
|
|
OverrideMinimumConnectionTimeout = Result * 1000;
|
|
}
|
|
|
|
// if the type was not REG_DWORD or out of range, probably registry is corrupted
|
|
// in this case, simply return success, since we don't want a corrupted
|
|
// registry to hose the whole machine
|
|
|
|
if (h)
|
|
{
|
|
RegCloseKey(h);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
// N.B. This value must agree with the key specified in the system.adm file
|
|
static const RPC_CHAR *LM_COMPATIBILITY_LEVEL_KEY =
|
|
L"System\\Currentcontrolset\\Control\\Lsa";
|
|
|
|
RPC_STATUS IsLanManHashDisabled (
|
|
OUT BOOL *Disabled
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Check in the registry whether the lan man hash was disabled.
|
|
|
|
Arguments:
|
|
|
|
Disabled - on successful output, if true, the lan man hash was disabled.
|
|
Undefined on failure.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
HKEY h = 0;
|
|
DWORD DwordSize;
|
|
DWORD Type;
|
|
DWORD Result;
|
|
char Buffer[20];
|
|
|
|
*Disabled = FALSE;
|
|
|
|
DWORD Status = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
|
|
LM_COMPATIBILITY_LEVEL_KEY,
|
|
0,
|
|
KEY_READ,
|
|
&h);
|
|
|
|
if (Status == ERROR_FILE_NOT_FOUND)
|
|
{
|
|
// no key, proceed as if the hash is enabled
|
|
return RPC_S_OK;
|
|
}
|
|
else if (Status != ERROR_SUCCESS)
|
|
{
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
DwordSize = sizeof(DWORD);
|
|
|
|
Status = RegQueryValueExW(
|
|
h,
|
|
L"lmcompatibilitylevel",
|
|
0,
|
|
&Type,
|
|
(LPBYTE) &Result,
|
|
&DwordSize
|
|
);
|
|
|
|
if (Status == ERROR_FILE_NOT_FOUND)
|
|
{
|
|
if (h)
|
|
{
|
|
RegCloseKey(h);
|
|
}
|
|
// no key, proceed as if hash is enabled
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
if (Status == ERROR_SUCCESS
|
|
&& Type == REG_DWORD
|
|
&& Result >= 2)
|
|
{
|
|
*Disabled = TRUE;
|
|
}
|
|
|
|
// if the type was not REG_DWORD or out of range, probably registry is corrupted
|
|
// in this case, assume hash is enabled
|
|
|
|
if (h)
|
|
{
|
|
RegCloseKey(h);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
BOOL g_fHttpClientInitialized = FALSE;
|
|
|
|
BOOL g_fHttpServerInitialized = FALSE;
|
|
|
|
TRANS_INFO *HTTPTransInfo = NULL;
|
|
|
|
RPC_STATUS InitializeHttpCommon (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialize the common (b/n client & server) Http transport
|
|
if not done already
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
GlobalMutexVerifyOwned();
|
|
|
|
if (HTTPTransInfo == NULL)
|
|
{
|
|
HTTPTransInfo = GetLoadedClientTransportInfoFromId(HTTP_TOWER_ID);
|
|
// the TCP transport should have been initialized by now
|
|
ASSERT(HTTPTransInfo);
|
|
}
|
|
|
|
return InitializeReceiveWindows();
|
|
}
|
|
|
|
RPC_STATUS InitializeHttpClient (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialize the Http client if not done already
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
GlobalMutexRequest();
|
|
|
|
if (g_fHttpClientInitialized == FALSE)
|
|
{
|
|
RpcStatus = InitializeHttpCommon();
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
RpcStatus = InitializeDefaultChannelLifetime();
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
RpcStatus = InitializeUseWinHttp();
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
RpcStatus = InitializeMinConnectionTimeout();
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
g_fHttpClientInitialized = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RpcStatus = RPC_S_OK;
|
|
}
|
|
GlobalMutexClear();
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS InitializeHttpServer (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialize the Http server if not done already
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
GlobalMutexRequest();
|
|
|
|
if (g_fHttpServerInitialized == FALSE)
|
|
{
|
|
RpcStatus = InitializeHttpCommon();
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
RpcStatus = CookieCollection::InitializeServerCookieCollection();
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
g_fHttpServerInitialized = TRUE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RpcStatus = RPC_S_OK;
|
|
}
|
|
GlobalMutexClear();
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
#if 1
|
|
|
|
#define ShouldUseWinHttp(x) (TRUE)
|
|
|
|
#else
|
|
BOOL ShouldUseWinHttp (
|
|
IN RPC_HTTP_TRANSPORT_CREDENTIALS_W *HttpCredentials
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Based on http credentials determines whether WinHttp
|
|
should be used or raw sockets.
|
|
|
|
Arguments:
|
|
|
|
HttpCredentials - transport credentials
|
|
|
|
Return Value:
|
|
|
|
non-zero - WinHttp should be used. 0 means it is not
|
|
necessary to use WinHttp.
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
if (AlwaysUseWinHttp)
|
|
return 1;
|
|
|
|
if (HttpCredentials == NULL)
|
|
return 0;
|
|
|
|
if (HttpCredentials->Flags & RPC_C_HTTP_FLAG_USE_SSL)
|
|
return 1;
|
|
|
|
if (HttpCredentials->Flags & RPC_C_HTTP_FLAG_USE_FIRST_AUTH_SCHEME)
|
|
return 1;
|
|
|
|
if (HttpCredentials->TransportCredentials)
|
|
return 1;
|
|
|
|
if (HttpCredentials->NumberOfAuthnSchemes)
|
|
return 1;
|
|
|
|
if (HttpCredentials->AuthnSchemes)
|
|
return 1;
|
|
|
|
if (HttpCredentials->ServerCertificateSubject)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
void CALLBACK HTTP2PingTimerCallback(
|
|
PVOID lpParameter, // thread data
|
|
BOOLEAN TimerOrWaitFired // reason
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
A periodic timer fired. Reference it, and dispatch to
|
|
the appropriate object.
|
|
|
|
Arguments:
|
|
|
|
lpParameter - the parameter supplied when the timer was
|
|
registered. In our case the HTTP2PingOriginator object
|
|
|
|
TimerOrWaitFired - the reason for the callback. Must be timer
|
|
in our case.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
HTTP2PingOriginator *PingOriginator;
|
|
RPC_STATUS RpcStatus;
|
|
THREAD *Thread;
|
|
|
|
ASSERT(TimerOrWaitFired);
|
|
|
|
Thread = ThreadSelf();
|
|
// if we can't initialize the thread object, we just return. Worst case
|
|
// the connection will fall apart due to the timeout. That's ok.
|
|
if (Thread == NULL)
|
|
return;
|
|
|
|
PingOriginator = (HTTP2PingOriginator *)lpParameter;
|
|
RpcStatus = PingOriginator->ReferenceFromCallback();
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
PingOriginator->TimerCallback();
|
|
}
|
|
|
|
void CALLBACK HTTP2TimeoutTimerCallback(
|
|
PVOID lpParameter, // thread data
|
|
BOOLEAN TimerOrWaitFired // reason
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
A one time timer fired. Dispatch to
|
|
the appropriate object.
|
|
|
|
Arguments:
|
|
|
|
lpParameter - the parameter supplied when the timer was
|
|
registered. In our case the address of a TimerContext object.
|
|
|
|
TimerOrWaitFired - the reason for the callback. Must be timer
|
|
in our case.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
TimerContext *pTimer;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
ASSERT(TimerOrWaitFired);
|
|
|
|
// REVIEW - if this fails, offload to an RPC worker thread?
|
|
ThreadSelf();
|
|
|
|
pTimer = (TimerContext *)lpParameter;
|
|
pTimer->Parent->TimeoutExpired(pTimer);
|
|
}
|
|
|
|
void CALLBACK WinHttpCallback (
|
|
IN HINTERNET hInternet,
|
|
IN DWORD_PTR dwContext,
|
|
IN DWORD dwInternetStatus,
|
|
IN LPVOID lpvStatusInformation OPTIONAL,
|
|
IN DWORD dwStatusInformationLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
The WinHttp callback routine.
|
|
|
|
Arguments:
|
|
|
|
hInternet - The hSession handle specified in a call to WinHttpSetStatusCallback.
|
|
|
|
dwContext - Depends on the callback. May be the Context argument to WinHttpSendRequest
|
|
|
|
dwInternetStatus - Status with which an async IO completed.
|
|
|
|
lpvStatusInformation - Additional info depending on the callback.
|
|
|
|
dwStatusInformationLength - Additional info depending on the callback.
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
HTTP2WinHttpTransportChannel *TransportChannel = (HTTP2WinHttpTransportChannel *) dwContext;
|
|
WINHTTP_ASYNC_RESULT *AsyncResult;
|
|
void *SendContext;
|
|
BYTE *Buffer;
|
|
RPC_STATUS RpcStatus;
|
|
ULONG Api;
|
|
BOOL HttpResult;
|
|
ULONG StatusCode;
|
|
ULONG StatusCodeLength;
|
|
|
|
// WinHttp bug #541722 - bogus handles can be signalled if we do SSL through proxy. Ignore them
|
|
if (dwContext == NULL)
|
|
return;
|
|
|
|
LOG_FN_OPERATION_ENTRY2(HTTP2LOG_OPERATION_WINHTTP_CALLBACK, HTTP2LOG_OT_WINHTTP_CALLBACK, TransportChannel, dwInternetStatus);
|
|
|
|
// N.B. After setting the event or posting a runtime event, do not touch
|
|
// the TransportChannel - it could be gone
|
|
switch (dwInternetStatus)
|
|
{
|
|
case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST:
|
|
if (TransportChannel->State == whtcsSendingRequest)
|
|
TransportChannel->VerifyServerCredentials();
|
|
break;
|
|
|
|
case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
|
|
ASSERT(TransportChannel->State == whtcsSendingRequest);
|
|
|
|
// This signals the async completion of WinHttpSendRequest.
|
|
TransportChannel->State = whtcsSentRequest;
|
|
TransportChannel->AsyncError = RPC_S_OK;
|
|
ASSERT(TransportChannel->SyncEvent);
|
|
SetEvent(TransportChannel->SyncEvent);
|
|
break;
|
|
|
|
case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
|
|
ASSERT(TransportChannel->State == whtcsReceivingResponse);
|
|
|
|
// This signals the async completion of WinHttpReceiveResponse.
|
|
TransportChannel->State = whtcsReceivedResponse;
|
|
TransportChannel->AsyncError = RPC_S_OK;
|
|
(void) COMMON_PostRuntimeEvent(HTTP2_WINHTTP_DELAYED_RECV,
|
|
TransportChannel
|
|
);
|
|
break;
|
|
|
|
case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
|
|
ASSERT(TransportChannel->State == whtcsReceivedResponse);
|
|
|
|
TransportChannel->AsyncError = RPC_S_OK;
|
|
|
|
// Get the bytes read.
|
|
// Since we query for the data available before a receive, we should
|
|
// receive at least the amount the query has promissed.
|
|
ASSERT(TransportChannel->NumberOfBytesTransferred <= dwStatusInformationLength);
|
|
TransportChannel->NumberOfBytesTransferred = dwStatusInformationLength;
|
|
|
|
// This signals the async completion of WinHttpRead.
|
|
ASSERT(TransportChannel->SyncEvent);
|
|
SetEvent(TransportChannel->SyncEvent);
|
|
break;
|
|
|
|
case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE:
|
|
if (TransportChannel->State == whtcsDraining)
|
|
{
|
|
// harvest the bytes available
|
|
TransportChannel->AsyncError = RPC_S_OK;
|
|
TransportChannel->NumberOfBytesTransferred = *(ULONG *)lpvStatusInformation;
|
|
TransportChannel->ContinueDrainChannel();
|
|
}
|
|
else
|
|
{
|
|
ASSERT(TransportChannel->State == whtcsReading);
|
|
|
|
TransportChannel->State = whtcsReceivedResponse;
|
|
|
|
// A read has completed asyncronously - issue a callback.
|
|
TransportChannel->AsyncError = RPC_S_OK;
|
|
// harvest the bytes available
|
|
TransportChannel->NumberOfBytesTransferred = *(ULONG *)lpvStatusInformation;
|
|
(void) COMMON_PostRuntimeEvent(HTTP2_WINHTTP_DIRECT_RECV,
|
|
TransportChannel
|
|
);
|
|
}
|
|
break;
|
|
|
|
case WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE:
|
|
ASSERT(TransportChannel->State == whtcsWriting);
|
|
|
|
TransportChannel->AsyncError = RPC_S_OK;
|
|
|
|
// get the bytes written
|
|
TransportChannel->NumberOfBytesTransferred = *(ULONG *)lpvStatusInformation;
|
|
// A write has completed asyncronously - issue a callback.
|
|
(void) COMMON_PostRuntimeEvent(HTTP2_WINHTTP_DIRECT_SEND,
|
|
TransportChannel
|
|
);
|
|
break;
|
|
|
|
case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
|
|
// An async IO has failed. The async IO that can be outstanding is
|
|
// from the following APIs:
|
|
//
|
|
// WinHttpSendRequest, WinHttpReceiveResponse, WinHttpReadData,
|
|
// WinHttpWriteData.
|
|
//
|
|
// Conditions when async IO is outstanding for these APIs correpsond to the
|
|
// states of: whtcsSendingRequest, whtcsReceivingResponse, whtcsReading,
|
|
// whtcsWriting respectively.
|
|
//
|
|
// We are also notified of the failed API via dwResult field of WINHTTP_ASYNC_RESULT.
|
|
//
|
|
// WinHttpSendRequest and WinHttpReceiveResponse will wait for SyncEvent
|
|
// to be raised. We will notify them of the failure by setting AsyncError.
|
|
// WinHttpReadData and WinHttpWriteData failures will be propagated to the upper layers
|
|
// via an async callback.
|
|
//
|
|
AsyncResult = (WINHTTP_ASYNC_RESULT *) lpvStatusInformation;
|
|
Api = AsyncResult->dwResult;
|
|
|
|
LOG_FN_OPERATION_ENTRY2(HTTP2LOG_OPERATION_WHTTP_ERROR, HTTP2LOG_OT_WINHTTP_CALLBACK, UlongToPtr(Api), AsyncResult->dwError);
|
|
|
|
switch (Api)
|
|
{
|
|
case API_SEND_REQUEST:
|
|
ASSERT(TransportChannel->State == whtcsSendingRequest);
|
|
// the only two values allowed here are ok or access denied
|
|
// (which means we didn't like the server certificate name).
|
|
ASSERT((TransportChannel->AsyncError == RPC_S_OK)
|
|
|| (TransportChannel->AsyncError == RPC_S_INTERNAL_ERROR)
|
|
|| (TransportChannel->AsyncError == RPC_S_ACCESS_DENIED) );
|
|
TransportChannel->State = whtcsSentRequest;
|
|
VALIDATE(AsyncResult->dwError)
|
|
{
|
|
ERROR_WINHTTP_CANNOT_CONNECT,
|
|
ERROR_WINHTTP_CONNECTION_ERROR,
|
|
ERROR_WINHTTP_INVALID_SERVER_RESPONSE,
|
|
ERROR_WINHTTP_OUT_OF_HANDLES,
|
|
ERROR_WINHTTP_REDIRECT_FAILED,
|
|
ERROR_WINHTTP_RESEND_REQUEST,
|
|
ERROR_WINHTTP_SECURE_FAILURE,
|
|
ERROR_WINHTTP_SHUTDOWN,
|
|
ERROR_WINHTTP_TIMEOUT,
|
|
ERROR_WINHTTP_NAME_NOT_RESOLVED,
|
|
ERROR_NOT_ENOUGH_MEMORY,
|
|
ERROR_COMMITMENT_LIMIT,
|
|
ERROR_WINHTTP_OPERATION_CANCELLED,
|
|
ERROR_WINHTTP_INTERNAL_ERROR
|
|
} END_VALIDATE;
|
|
|
|
ASSERT (AsyncResult->dwError != ERROR_WINHTTP_RESEND_REQUEST);
|
|
|
|
// if not access denied, make it send failed
|
|
if (TransportChannel->AsyncError != RPC_S_ACCESS_DENIED)
|
|
TransportChannel->AsyncError = RPC_P_SEND_FAILED;
|
|
ASSERT(TransportChannel->SyncEvent);
|
|
SetEvent(TransportChannel->SyncEvent);
|
|
break;
|
|
|
|
case API_READ_DATA:
|
|
ASSERT(TransportChannel->State == whtcsReceivedResponse);
|
|
|
|
VALIDATE(AsyncResult->dwError)
|
|
{
|
|
ERROR_WINHTTP_CONNECTION_ERROR
|
|
} END_VALIDATE;
|
|
|
|
// This signals the async completion of WinHttpRead.
|
|
TransportChannel->AsyncError = RPC_P_RECEIVE_FAILED;
|
|
ASSERT(TransportChannel->SyncEvent);
|
|
SetEvent(TransportChannel->SyncEvent);
|
|
break;
|
|
|
|
case API_RECEIVE_RESPONSE:
|
|
ASSERT(TransportChannel->State == whtcsReceivingResponse);
|
|
TransportChannel->State = whtcsReceivedResponse;
|
|
VALIDATE(AsyncResult->dwError)
|
|
{
|
|
ERROR_WINHTTP_CANNOT_CONNECT,
|
|
ERROR_WINHTTP_CONNECTION_ERROR,
|
|
ERROR_WINHTTP_INVALID_SERVER_RESPONSE,
|
|
ERROR_WINHTTP_OUT_OF_HANDLES,
|
|
ERROR_WINHTTP_REDIRECT_FAILED,
|
|
ERROR_WINHTTP_RESEND_REQUEST,
|
|
ERROR_WINHTTP_SECURE_FAILURE,
|
|
ERROR_WINHTTP_SHUTDOWN,
|
|
ERROR_WINHTTP_TIMEOUT,
|
|
ERROR_WINHTTP_OPERATION_CANCELLED,
|
|
ERROR_NOT_ENOUGH_MEMORY,
|
|
ERROR_COMMITMENT_LIMIT,
|
|
ERROR_WINHTTP_LOGIN_FAILURE
|
|
} END_VALIDATE;
|
|
|
|
if (AsyncResult->dwError == ERROR_WINHTTP_RESEND_REQUEST)
|
|
TransportChannel->AsyncError = RPC_P_AUTH_NEEDED;
|
|
else
|
|
TransportChannel->AsyncError = RPC_P_RECEIVE_FAILED;
|
|
|
|
(void) COMMON_PostRuntimeEvent(HTTP2_WINHTTP_DELAYED_RECV,
|
|
TransportChannel
|
|
);
|
|
break;
|
|
|
|
case API_QUERY_DATA_AVAILABLE:
|
|
// if we get closed while receiving data, we can be
|
|
// in draining state
|
|
ASSERT((TransportChannel->State == whtcsReading)
|
|
|| (TransportChannel->State == whtcsDraining));
|
|
TransportChannel->State = whtcsReceivedResponse;
|
|
VALIDATE(AsyncResult->dwError)
|
|
{
|
|
ERROR_WINHTTP_CANNOT_CONNECT,
|
|
ERROR_WINHTTP_CONNECTION_ERROR,
|
|
ERROR_WINHTTP_INVALID_SERVER_RESPONSE,
|
|
ERROR_WINHTTP_OUT_OF_HANDLES,
|
|
ERROR_WINHTTP_REDIRECT_FAILED,
|
|
ERROR_WINHTTP_RESEND_REQUEST,
|
|
ERROR_WINHTTP_SECURE_FAILURE,
|
|
ERROR_WINHTTP_SHUTDOWN,
|
|
ERROR_WINHTTP_TIMEOUT,
|
|
ERROR_WINHTTP_OPERATION_CANCELLED
|
|
} END_VALIDATE;
|
|
|
|
if (AsyncResult->dwError == ERROR_WINHTTP_RESEND_REQUEST)
|
|
{
|
|
ASSERT(0);
|
|
}
|
|
|
|
TransportChannel->AsyncError = RPC_P_RECEIVE_FAILED;
|
|
(void) COMMON_PostRuntimeEvent(HTTP2_WINHTTP_DIRECT_RECV,
|
|
TransportChannel
|
|
);
|
|
break;
|
|
|
|
case API_WRITE_DATA:
|
|
ASSERT(TransportChannel->State == whtcsWriting);
|
|
VALIDATE(AsyncResult->dwError)
|
|
{
|
|
ERROR_WINHTTP_CANNOT_CONNECT,
|
|
ERROR_WINHTTP_CONNECTION_ERROR,
|
|
ERROR_WINHTTP_INVALID_SERVER_RESPONSE,
|
|
ERROR_WINHTTP_OUT_OF_HANDLES,
|
|
ERROR_WINHTTP_REDIRECT_FAILED,
|
|
ERROR_WINHTTP_RESEND_REQUEST,
|
|
ERROR_WINHTTP_SECURE_FAILURE,
|
|
ERROR_WINHTTP_SHUTDOWN,
|
|
ERROR_WINHTTP_TIMEOUT,
|
|
ERROR_WINHTTP_OPERATION_CANCELLED
|
|
} END_VALIDATE;
|
|
|
|
if (AsyncResult->dwError == ERROR_WINHTTP_RESEND_REQUEST)
|
|
{
|
|
ASSERT(0);
|
|
}
|
|
|
|
TransportChannel->AsyncError = RPC_P_SEND_FAILED;
|
|
(void) COMMON_PostRuntimeEvent(HTTP2_WINHTTP_DIRECT_SEND,
|
|
TransportChannel
|
|
);
|
|
break;
|
|
|
|
default:
|
|
ASSERT(0);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
// don't care about the other notifications
|
|
break;
|
|
}
|
|
|
|
LOG_FN_OPERATION_EXIT2(HTTP2LOG_OPERATION_WINHTTP_CALLBACK, HTTP2LOG_OT_WINHTTP_CALLBACK, TransportChannel, dwInternetStatus);
|
|
};
|
|
|
|
RPC_STATUS WaitForSyncSend (
|
|
IN BASE_ASYNC_OBJECT *Connection,
|
|
IN HTTP2SendContext *SendContext,
|
|
IN HTTP2VirtualConnection *Parent,
|
|
IN BOOL fDisableCancelCheck,
|
|
IN ULONG Timeout
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Waits for a synchronous send to complete.
|
|
|
|
Arguments:
|
|
|
|
Connection - run time view of the transport connection
|
|
|
|
SendContext - the send context
|
|
|
|
Parent - the parent virtual connection (used to abort)
|
|
|
|
fDisableCancelCheck - don't do checks for cancels. Can be
|
|
used as optimization
|
|
|
|
Timeout - the call timeout
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
HTTP2Channel *ThisChannel;
|
|
|
|
// the IO was submitted. Wait for it.
|
|
// If fDisableCancelCheck, make the thread wait non-alertably,
|
|
// otherwise, make it wait alertably.
|
|
RpcStatus = UTIL_GetOverlappedHTTP2ResultEx(Connection,
|
|
&SendContext->Write.ol,
|
|
SendContext->u.SyncEvent,
|
|
!fDisableCancelCheck, // bAlertable
|
|
Timeout);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
Parent->AbortChannels(RpcStatus);
|
|
|
|
if ((RpcStatus == RPC_S_CALL_CANCELLED) || (RpcStatus == RPC_P_TIMEOUT))
|
|
{
|
|
// Wait for the write to finish. Since we closed the
|
|
// connection this won't take very long.
|
|
UTIL_WaitForSyncHTTP2IO(&SendContext->Write.ol,
|
|
SendContext->u.SyncEvent,
|
|
FALSE, // fAlertable
|
|
INFINITE // Timeout
|
|
);
|
|
}
|
|
}
|
|
|
|
return(RpcStatus);
|
|
}
|
|
|
|
void
|
|
AddBufferQueueToChannel (
|
|
IN LIST_ENTRY *NewBufferHead,
|
|
IN HTTP2Channel *Channel
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Adds all the send contexts from the queue to the front of given channel.
|
|
Presumably the channel has a plug channel down somewhere which does
|
|
the actual ordering work
|
|
|
|
Arguments:
|
|
|
|
NewBufferHead - the list head of the buffer queue. They are assumed to
|
|
be in order.
|
|
|
|
Channel - the channel to make the sends on
|
|
|
|
Return Value:
|
|
|
|
Notes:
|
|
|
|
The new channel must still be plugged.
|
|
|
|
--*/
|
|
{
|
|
HTTP2SendContext *QueuedSendContext;
|
|
LIST_ENTRY *CurrentListEntry;
|
|
LIST_ENTRY *PrevListEntry;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
// Queue the sends to the front of the new channel
|
|
// walk the queue in reverse order and add it to the plug channel
|
|
CurrentListEntry = NewBufferHead->Blink;
|
|
while (CurrentListEntry != NewBufferHead)
|
|
{
|
|
QueuedSendContext
|
|
= CONTAINING_RECORD(CurrentListEntry, HTTP2SendContext, ListEntry);
|
|
PrevListEntry = CurrentListEntry->Blink;
|
|
|
|
QueuedSendContext->Flags |= SendContextFlagPutInFront;
|
|
|
|
// Setting this flag ensures that the send will not fail even for
|
|
// a channel with a pending abort. This is necessary to force a send
|
|
// and get the send context queued inside the plug channel.
|
|
QueuedSendContext->Flags |= SendContextFlagPluggedChannel;
|
|
|
|
RpcStatus = Channel->Send(QueuedSendContext);
|
|
|
|
// since we know the channel is plugged yet, this cannot fail
|
|
ASSERT(RpcStatus == RPC_S_OK);
|
|
|
|
CurrentListEntry = PrevListEntry;
|
|
}
|
|
}
|
|
|
|
void
|
|
RPC_CLIENT_PROCESS_IDENTIFIER::SetHTTP2ClientIdentifier (
|
|
IN void *Buffer,
|
|
IN size_t BufferSize,
|
|
IN BOOL fLocal
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
sets an HTTP2 client identifier
|
|
|
|
Arguments:
|
|
|
|
Buffer - the buffer with the client identifier.
|
|
|
|
BufferSize - the number of bytes containg valid
|
|
info in the buffer.
|
|
|
|
fLocal - non-zero if client is local. 0 otherwise.
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
BYTE *CurrentPosition;
|
|
|
|
this->fLocal = fLocal;
|
|
this->ZeroPadding = 0;
|
|
ASSERT(sizeof(u.ULongClientId) >= BufferSize);
|
|
RpcpMemorySet(u.ULongClientId, 0, sizeof(u.ULongClientId) - BufferSize);
|
|
CurrentPosition = ((BYTE *)u.ULongClientId) + sizeof(u.ULongClientId) - BufferSize;
|
|
RpcpMemoryCopy(CurrentPosition, Buffer, BufferSize);
|
|
}
|
|
|
|
|
|
RPC_STATUS
|
|
HttpSendIdentifyResponse(
|
|
IN SOCKET Socket
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
<TBS>
|
|
|
|
Arguments:
|
|
|
|
Socket -
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS Status = RPC_S_OK;
|
|
int iBytes;
|
|
char *pszId = HTTP_SERVER_ID_STR;
|
|
DWORD dwSize;
|
|
|
|
iBytes = send(
|
|
Socket,
|
|
pszId,
|
|
HTTP_SERVER_ID_STR_LEN,
|
|
0
|
|
);
|
|
|
|
if (iBytes == SOCKET_ERROR)
|
|
{
|
|
VALIDATE(GetLastError())
|
|
{
|
|
WSAENETDOWN,
|
|
WSAECONNREFUSED,
|
|
WSAECONNRESET,
|
|
WSAENETRESET,
|
|
WSAETIMEDOUT,
|
|
WSAECONNABORTED,
|
|
WSASYSCALLFAILURE
|
|
} END_VALIDATE;
|
|
|
|
Status = RPC_S_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
RPC_STATUS
|
|
HTTP_TryConnect( SOCKET Socket,
|
|
char *pszProxyMachine,
|
|
USHORT iPort )
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Used by HTTP_Open() to actually call the connect(). HTTP_Open() will try
|
|
first to reach the RPC Proxy directly, if it can't then it will call this
|
|
routine again to try to reach an HTTP proxy (i.e. MSProxy for example).
|
|
|
|
Arguments:
|
|
|
|
Socket - The socket to use in the connect().
|
|
pszProxyMachine - The name of the machine to try to connect() to.
|
|
iPort - The port to connect() on.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK
|
|
|
|
RPC_S_OUT_OF_MEMORY
|
|
RPC_S_OUT_OF_RESOURCES
|
|
RPC_S_SERVER_UNAVAILABLE
|
|
RPC_S_INVALID_NET_ADDR
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS Status = RPC_S_OK;
|
|
WS_SOCKADDR ProxyServer;
|
|
RPC_CHAR pwszBuffer[MAX_HTTP_COMPUTERNAME_SIZE+1];
|
|
|
|
//
|
|
// Check for empty proxy machine name:
|
|
//
|
|
if ( (!pszProxyMachine) || (*pszProxyMachine == 0))
|
|
{
|
|
return RPC_S_SERVER_UNAVAILABLE;
|
|
}
|
|
|
|
memset((char *)&ProxyServer, 0, sizeof(ProxyServer));
|
|
|
|
//
|
|
// Resolve the machine name (or dot-notation address) into
|
|
// a network address. If that works then try to connect
|
|
// using the supplied port.
|
|
//
|
|
SimpleAnsiToPlatform(pszProxyMachine,pwszBuffer);
|
|
IP_ADDRESS_RESOLVER resolver(pwszBuffer,
|
|
cosClient,
|
|
ipvtuIPv4 // IP version to use
|
|
);
|
|
|
|
Status = resolver.NextAddress(&ProxyServer.inetaddr);
|
|
if (Status == RPC_S_OK)
|
|
{
|
|
ProxyServer.inetaddr.sin_family = AF_INET;
|
|
ProxyServer.inetaddr.sin_port = htons(iPort);
|
|
|
|
//
|
|
// Try to connect...
|
|
//
|
|
if (SOCKET_ERROR == connect(Socket,
|
|
(struct sockaddr *)&ProxyServer.inetaddr,
|
|
sizeof(ProxyServer.inetaddr)))
|
|
{
|
|
#if DBG_ERROR
|
|
TransDbgPrint((DPFLTR_RPCPROXY_ID,
|
|
DPFLTR_WARNING_LEVEL,
|
|
"HTTP_Open(): connect() failed: %d\n",
|
|
WSAGetLastError()));
|
|
|
|
#endif // DBG_ERROR
|
|
|
|
VALIDATE(GetLastError())
|
|
{
|
|
WSAENETDOWN,
|
|
WSAEADDRNOTAVAIL,
|
|
WSAECONNREFUSED,
|
|
WSAECONNABORTED,
|
|
WSAENETUNREACH,
|
|
WSAEHOSTUNREACH,
|
|
WSAENOBUFS,
|
|
WSAETIMEDOUT
|
|
} END_VALIDATE;
|
|
|
|
Status = RPC_S_SERVER_UNAVAILABLE;
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
RPC_STATUS HTTP2Cookie::Create (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Create a cryptographically strong HTTP2 cookie
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* failure
|
|
|
|
--*/
|
|
{
|
|
return GenerateRandomNumber(Cookie, sizeof(Cookie));
|
|
}
|
|
|
|
void HTTPResolverHint::VerifyInitialized (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Verify that the resolver hint is properly initialized and consistent
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
ASSERT(RpcServer);
|
|
ASSERT(ServerPort != 0);
|
|
ASSERT(RpcProxy);
|
|
ASSERT(RpcProxyPort != 0);
|
|
if (HTTPProxy)
|
|
{
|
|
ASSERT(HTTPProxyPort != 0);
|
|
}
|
|
}
|
|
|
|
|
|
RPC_STATUS
|
|
RPC_ENTRY
|
|
HTTP_Initialize (
|
|
IN RPC_TRANSPORT_CONNECTION ThisConnection,
|
|
IN RPC_CHAR * /* NetworkAddress */,
|
|
IN RPC_CHAR * /* NetworkOptions */,
|
|
IN BOOL /* fAsync */
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Called by the runtime to do initial initialization of the transport
|
|
object. The purpose of this initialization is to allow the transport
|
|
to do some minimal initialization sufficient to ensure ordely
|
|
destruction in case of failure.
|
|
|
|
Arguments:
|
|
|
|
ThisConnection - an uninitialized connection allocated by the runtime
|
|
NetworkAddress - ignored
|
|
NetworkOptions - ignored
|
|
fAsync - ignored
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success of RPC_S_* / Win32 error for error.
|
|
|
|
--*/
|
|
{
|
|
BASE_ASYNC_OBJECT *BaseObject = (BASE_ASYNC_OBJECT *) ThisConnection;
|
|
|
|
BaseObject->id = INVALID_PROTOCOL_ID;
|
|
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
RPC_STATUS
|
|
RPC_ENTRY
|
|
HTTP_CheckIPAddressForDirectConnection (
|
|
IN HTTPResolverHint *Hint
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Checks if the rpc proxy server address given in the hint should be used
|
|
for direct connection based on registry settings
|
|
|
|
Arguments:
|
|
|
|
Hint - the resolver hint
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* / win32 error code
|
|
|
|
--*/
|
|
{
|
|
HKEY RpcOptionsKey;
|
|
DWORD Status;
|
|
DWORD KeyType;
|
|
const RPC_CHAR *UseProxyForIPAddrIfRDNSFailsRegKey = RPC_CONST_STRING("UseProxyForIPAddrIfRDNSFails");
|
|
const RPC_CHAR *RpcRegistryOptions =
|
|
RPC_CONST_STRING("Software\\Microsoft\\Rpc");
|
|
DWORD UseProxyForIPAddrIfRDNSFails;
|
|
DWORD RegKeySize;
|
|
int err;
|
|
ADDRINFO AddrHint;
|
|
ADDRINFO *AddrInfo;
|
|
|
|
RpcpMemorySet(&AddrHint, 0, sizeof(ADDRINFO));
|
|
AddrHint.ai_flags = AI_NUMERICHOST;
|
|
|
|
err = getaddrinfo(Hint->RpcProxy,
|
|
NULL,
|
|
&AddrHint,
|
|
&AddrInfo
|
|
);
|
|
|
|
ASSERT((err != EAI_BADFLAGS)
|
|
&& (err != EAI_SOCKTYPE));
|
|
|
|
if (err)
|
|
{
|
|
// the address was not numeric. It's not up to us to tell
|
|
// where this should go
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
// assume direct connection for now
|
|
Hint->AccessType = rpcpatDirect;
|
|
|
|
Status = RegOpenKey( HKEY_LOCAL_MACHINE,
|
|
(const RPC_SCHAR *)RpcRegistryOptions,
|
|
&RpcOptionsKey );
|
|
|
|
if (Status != ERROR_SUCCESS)
|
|
{
|
|
// direct connection is already set
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
RegKeySize = sizeof(DWORD);
|
|
Status = RegQueryValueEx(RpcOptionsKey,
|
|
(const RPC_SCHAR *)UseProxyForIPAddrIfRDNSFailsRegKey,
|
|
NULL,
|
|
&KeyType,
|
|
(LPBYTE)&UseProxyForIPAddrIfRDNSFails,
|
|
&RegKeySize);
|
|
|
|
RegCloseKey(RpcOptionsKey);
|
|
|
|
if ( (Status != ERROR_SUCCESS) ||
|
|
(KeyType != REG_DWORD) )
|
|
{
|
|
// direct connection is already set
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
if (UseProxyForIPAddrIfRDNSFails != 1)
|
|
{
|
|
// direct connection is already set
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
err = getnameinfo(AddrInfo->ai_addr,
|
|
AddrInfo->ai_addrlen,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
0,
|
|
NI_NAMEREQD
|
|
);
|
|
|
|
if (err)
|
|
{
|
|
Hint->AccessType = rpcpatHTTPProxy;
|
|
}
|
|
// else
|
|
// direct connection is already set
|
|
|
|
CleanupAndExit:
|
|
freeaddrinfo(AddrInfo);
|
|
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
void
|
|
RPC_ENTRY HTTP_FreeResolverHint (
|
|
IN void *ResolverHint
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Called by the runtime to free the resolver hint.
|
|
|
|
Arguments:
|
|
|
|
ResolverHint - the resolver hint created by the transport.
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
HTTPResolverHint *Hint = (HTTPResolverHint *)ResolverHint;
|
|
|
|
Hint->FreeHTTPProxy();
|
|
Hint->FreeRpcProxy();
|
|
Hint->FreeRpcServer();
|
|
}
|
|
|
|
RPC_STATUS
|
|
RPC_ENTRY HTTP_CopyResolverHint (
|
|
IN void *TargetResolverHint,
|
|
IN void *SourceResolverHint,
|
|
IN BOOL SourceWillBeAbandoned
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Tells the transport to copy the resolver hint from Source to Target
|
|
|
|
Arguments:
|
|
|
|
TargetResolverHint - pointer to the target resolver hint
|
|
|
|
SourceResolverHint - pointer to the source resolver hint
|
|
|
|
SourceWillBeAbandoned - non-zero if the source hint was in temporary
|
|
location and will be abandoned. Zero otherwise.
|
|
|
|
Return Value:
|
|
|
|
if SourceWillBeAbandoned is specified, this function is guaranteed
|
|
to return RPC_S_OK. Otherwise, it may return RPC_S_OUT_OF_MEMORY as well.
|
|
|
|
--*/
|
|
{
|
|
HTTPResolverHint *TargetHint = (HTTPResolverHint *)TargetResolverHint;
|
|
HTTPResolverHint *SourceHint = (HTTPResolverHint *)SourceResolverHint;
|
|
ULONG HTTPProxyNameLength;
|
|
|
|
ASSERT(TargetHint != SourceHint);
|
|
|
|
// bulk copy most of the stuff, and then hand copy few items
|
|
RpcpMemoryCopy(TargetHint, SourceHint, sizeof(HTTPResolverHint));
|
|
|
|
if (SourceWillBeAbandoned)
|
|
{
|
|
// the source hint will be abandoned - just hijack all
|
|
// embedded pointers
|
|
if (SourceHint->RpcServer == SourceHint->RpcServerName)
|
|
TargetHint->RpcServer = TargetHint->RpcServerName;
|
|
SourceHint->HTTPProxy = NULL;
|
|
SourceHint->RpcProxy = NULL;
|
|
SourceHint->RpcServer = NULL;
|
|
}
|
|
else
|
|
{
|
|
TargetHint->HTTPProxy = NULL;
|
|
TargetHint->RpcProxy = NULL;
|
|
TargetHint->RpcServer = NULL;
|
|
if (SourceHint->HTTPProxy)
|
|
{
|
|
HTTPProxyNameLength = RpcpStringLengthA(SourceHint->HTTPProxy) + 1;
|
|
TargetHint->HTTPProxy = new char [HTTPProxyNameLength];
|
|
if (TargetHint->HTTPProxy == NULL)
|
|
goto FreeTargetHintAndExit;
|
|
RpcpMemoryCopy(TargetHint->HTTPProxy, SourceHint->HTTPProxy, HTTPProxyNameLength);
|
|
}
|
|
|
|
TargetHint->RpcProxy = new char [SourceHint->ProxyNameLength + 1];
|
|
if (TargetHint->RpcProxy == NULL)
|
|
goto FreeTargetHintAndExit;
|
|
RpcpMemoryCopy(TargetHint->RpcProxy, SourceHint->RpcProxy, SourceHint->ProxyNameLength + 1);
|
|
|
|
if (SourceHint->RpcServer == SourceHint->RpcServerName)
|
|
TargetHint->RpcServer = TargetHint->RpcServerName;
|
|
else
|
|
{
|
|
TargetHint->RpcServer = new char [SourceHint->ServerNameLength + 1];
|
|
if (TargetHint->RpcServer == NULL)
|
|
goto FreeTargetHintAndExit;
|
|
RpcpMemoryCopy(TargetHint->RpcServer, SourceHint->RpcServer, SourceHint->ServerNameLength + 1);
|
|
}
|
|
}
|
|
|
|
return RPC_S_OK;
|
|
|
|
FreeTargetHintAndExit:
|
|
TargetHint->FreeHTTPProxy();
|
|
TargetHint->FreeRpcProxy();
|
|
TargetHint->FreeRpcServer();
|
|
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
int
|
|
RPC_ENTRY HTTP_CompareResolverHint (
|
|
IN void *ResolverHint1,
|
|
IN void *ResolverHint2
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Tells the transport to compare the given 2 resolver hints
|
|
|
|
Arguments:
|
|
|
|
ResolverHint1 - pointer to the first resolver hint
|
|
|
|
ResolverHint2 - pointer to the second resolver hint
|
|
|
|
Return Value:
|
|
|
|
(same semantics as memcmp)
|
|
0 - the resolver hints are equal
|
|
non-zero - the resolver hints are not equal
|
|
|
|
--*/
|
|
{
|
|
HTTPResolverHint *Hint1 = (HTTPResolverHint *)ResolverHint1;
|
|
HTTPResolverHint *Hint2 = (HTTPResolverHint *)ResolverHint2;
|
|
|
|
if (Hint1->Version != Hint2->Version)
|
|
return 1;
|
|
|
|
if (Hint1->ServerPort != Hint2->ServerPort)
|
|
return 1;
|
|
|
|
if (Hint1->ServerNameLength != Hint2->ServerNameLength)
|
|
return 1;
|
|
|
|
return RpcpMemoryCompare(Hint1->RpcServer, Hint2->RpcServer, Hint1->ServerNameLength);
|
|
}
|
|
|
|
RPC_STATUS RPC_ENTRY
|
|
HTTP_SetLastBufferToFree (
|
|
IN RPC_TRANSPORT_CONNECTION ThisConnection,
|
|
IN void *Buffer
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Tells the transport what buffer to free when it is done with the last send
|
|
|
|
Arguments:
|
|
|
|
ThisConnection - connection to act on.
|
|
|
|
Buffer - pointer of the buffer to free. Must be freed using
|
|
RpcFreeBuffer/I_RpcTransConnectionFreePacket
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK - the last buffer to free was accepted by the transport
|
|
|
|
RPC_S_CANNOT_SUPPORT - the transport does not support this functionality
|
|
|
|
--*/
|
|
{
|
|
BASE_ASYNC_OBJECT *BaseObject = (BASE_ASYNC_OBJECT *) ThisConnection;
|
|
HTTP2ServerVirtualConnection *VirtualConnection;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
if (BaseObject->id == HTTPv2)
|
|
{
|
|
// this must be called on server connections only
|
|
ASSERT(BaseObject->type == (COMPLEX_T | CONNECTION | SERVER));
|
|
VirtualConnection = (HTTP2ServerVirtualConnection *) ThisConnection;
|
|
|
|
VirtualConnection->SetLastBufferToFree(Buffer);
|
|
|
|
return RPC_S_OK;
|
|
}
|
|
else
|
|
{
|
|
return RPC_S_CANNOT_SUPPORT;
|
|
}
|
|
}
|
|
|
|
|
|
RPC_STATUS
|
|
RPC_ENTRY
|
|
HTTP_Open (
|
|
IN RPC_TRANSPORT_CONNECTION ThisConnection,
|
|
IN RPC_CHAR * ProtocolSequence,
|
|
IN RPC_CHAR * NetworkAddress,
|
|
IN RPC_CHAR * Endpoint,
|
|
IN RPC_CHAR * NetworkOptions,
|
|
IN UINT ConnTimeout,
|
|
IN UINT SendBufferSize,
|
|
IN UINT RecvBufferSize,
|
|
IN PVOID ResolverHint,
|
|
IN BOOL fHintInitialized,
|
|
IN ULONG CallTimeout,
|
|
IN ULONG AdditionalTransportCredentialsType, OPTIONAL
|
|
IN void *AdditionalCredentials OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Opens a connection to a server.
|
|
|
|
Arguments:
|
|
|
|
ThisConnection - A place to store the connection
|
|
|
|
ProtocolSeqeunce - "ncacn_http"
|
|
|
|
NetworkAddress - The name of the server, either a dot address or DNS name
|
|
|
|
NetworkOptions - the http binding handle options (e.g. HttpProxy/RpcProxy)
|
|
|
|
ConnTimeout - See RpcMgmtSetComTimeout
|
|
0 - Min
|
|
5 - Default
|
|
9 - Max
|
|
10 - Infinite
|
|
|
|
SendBufferSize - ignored
|
|
|
|
RecvBufferSize - ignored
|
|
|
|
ResolverHint - pointer to the resolver hint object
|
|
|
|
fHintInitialized - non-zero if the ResolverHint points to previously
|
|
initialized memory. 0 otheriwse.
|
|
|
|
CallTimeout - call timeout in milliseconds
|
|
|
|
AdditionalTransportCredentialsType - the type of additional credentials that we were
|
|
given
|
|
|
|
AdditionalCredentials - additional credentials that we were given.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK
|
|
|
|
RPC_S_OUT_OF_MEMORY
|
|
RPC_S_OUT_OF_RESOURCES
|
|
RPC_S_SERVER_UNAVAILABLE
|
|
RPC_S_INVALID_ENDPOINT_FORMAT
|
|
RPC_S_INVALID_NET_ADDR
|
|
|
|
--*/
|
|
{
|
|
HTTPResolverHint *Hint = (HTTPResolverHint *)ResolverHint;
|
|
char *RpcProxyPort = NULL;
|
|
char *HttpProxyPort = NULL;
|
|
BOOL NetworkAddressAllocated;
|
|
BOOL Result;
|
|
char PortString[20];
|
|
ULONG StringLength;
|
|
RPC_STATUS Status;
|
|
RPC_STATUS RetValue;
|
|
PWS_CCONNECTION p = (PWS_CCONNECTION) ThisConnection;
|
|
HTTP2ClientVirtualConnection *VirtualConnection = (HTTP2ClientVirtualConnection *) ThisConnection;
|
|
BOOL Retry;
|
|
BOOL fUserModeConnection;
|
|
BOOL HintNeedsCleanup;
|
|
RPC_HTTP_TRANSPORT_CREDENTIALS_W *HttpCredentials;
|
|
BOOL UseSSLPort;
|
|
ULONG HostAddr;
|
|
BOOL LocalDirect;
|
|
|
|
ASSERT(NetworkAddress);
|
|
ASSERT(Endpoint);
|
|
|
|
ASSERT(RpcpStringCompare(ProtocolSequence, L"ncacn_http") == 0);
|
|
|
|
if (AdditionalTransportCredentialsType != 0)
|
|
{
|
|
if (AdditionalTransportCredentialsType != RPC_C_AUTHN_INFO_TYPE_HTTP)
|
|
{
|
|
ASSERT(0);
|
|
return RPC_S_CANNOT_SUPPORT;
|
|
}
|
|
ASSERT(AdditionalCredentials != NULL);
|
|
}
|
|
|
|
HttpCredentials = (RPC_HTTP_TRANSPORT_CREDENTIALS_W *) AdditionalCredentials;
|
|
|
|
Status = InitializeHttpClientIfNecessary();
|
|
if (Status != RPC_S_OK)
|
|
return Status;
|
|
|
|
HintNeedsCleanup = FALSE;
|
|
|
|
// Check the resolver hint. If not initialized, initialize all
|
|
// fields in the hint
|
|
if (fHintInitialized == FALSE)
|
|
{
|
|
RpcProxyPort = NULL;
|
|
HttpProxyPort = NULL;
|
|
Hint->HTTPProxy = NULL;
|
|
Hint->RpcProxy = NULL;
|
|
Hint->RpcServer = NULL;
|
|
|
|
Status = Hint->AssociationGroupId.Create();
|
|
if (Status != RPC_S_OK)
|
|
{
|
|
RetValue = Status;
|
|
goto AbortAndCleanup;
|
|
}
|
|
|
|
// the TCP transport should have been initialized by now
|
|
ASSERT(HTTPTransInfo);
|
|
|
|
Status = HTTPTransInfo->StartServerIfNecessary();
|
|
if (Status != RPC_S_OK)
|
|
{
|
|
RetValue = Status;
|
|
goto AbortAndCleanup;
|
|
}
|
|
|
|
StringLength = RpcpStringLength(NetworkAddress);
|
|
|
|
//
|
|
// RPC Server Name
|
|
//
|
|
if (StringLength == 0)
|
|
{
|
|
// no server name was specified. Use local machine name
|
|
NetworkAddress = AllocateAndGetComputerName(cnaNew,
|
|
ComputerNamePhysicalDnsFullyQualified,
|
|
0, // ExtraBytes
|
|
0, // Starting offset
|
|
&StringLength
|
|
);
|
|
|
|
if (NetworkAddress == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
NetworkAddressAllocated = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// make space for terminating NULL
|
|
StringLength += 1;
|
|
NetworkAddressAllocated = FALSE;
|
|
}
|
|
|
|
// StringLength is in characters and includes terminating null
|
|
if (StringLength <= sizeof(Hint->RpcServerName))
|
|
{
|
|
Hint->RpcServer = Hint->RpcServerName;
|
|
}
|
|
else
|
|
{
|
|
Hint->RpcServer = new char [StringLength];
|
|
if (Hint->RpcServer == NULL)
|
|
{
|
|
if (NetworkAddressAllocated)
|
|
delete NetworkAddress;
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
|
|
SimplePlatformToAnsi(NetworkAddress, Hint->RpcServer);
|
|
|
|
// subtract 1 to eliminate terminating NULL
|
|
Hint->ServerNameLength = StringLength - 1;
|
|
|
|
if (NetworkAddressAllocated)
|
|
{
|
|
delete NetworkAddress;
|
|
NetworkAddress = NULL;
|
|
}
|
|
|
|
// by now Hint->RpcServer points to the ascii name for the server
|
|
ASSERT(Hint->RpcServer);
|
|
|
|
//
|
|
// At this point, we know the destination server/port, but don't yet know
|
|
// if we need to go through an HTTP proxy, and what the IIS RPC proxy
|
|
// machine is. We'll get these, if specified from the network options
|
|
// and the registry.
|
|
//
|
|
if (HttpCredentials && (HttpCredentials->Flags & RPC_C_HTTP_FLAG_USE_SSL))
|
|
UseSSLPort = TRUE;
|
|
else
|
|
UseSSLPort = FALSE;
|
|
|
|
Result = HttpParseNetworkOptions(
|
|
NetworkOptions,
|
|
Hint->RpcServer,
|
|
&(Hint->RpcProxy),
|
|
&RpcProxyPort,
|
|
UseSSLPort,
|
|
&(Hint->HTTPProxy),
|
|
&HttpProxyPort,
|
|
&(Hint->AccessType),
|
|
(unsigned long *) &Status
|
|
);
|
|
|
|
if (Result == FALSE)
|
|
{
|
|
ASSERT(Status != RPC_S_OK);
|
|
Hint->FreeRpcServer();
|
|
return Status;
|
|
}
|
|
else
|
|
{
|
|
if (Hint->AccessType != rpcpatDirect)
|
|
{
|
|
ASSERT(Hint->HTTPProxy);
|
|
// if the proxy name is empty, set the method to direct
|
|
if (Hint->HTTPProxy[0] == 0)
|
|
Hint->AccessType = rpcpatDirect;
|
|
}
|
|
ASSERT(Status == RPC_S_OK);
|
|
HintNeedsCleanup = TRUE;
|
|
}
|
|
|
|
Status = EndpointToPortNumber(Endpoint, Hint->ServerPort);
|
|
if (Status != RPC_S_OK)
|
|
{
|
|
RetValue = Status;
|
|
goto AbortAndCleanup;
|
|
}
|
|
|
|
Status = EndpointToPortNumberA(RpcProxyPort, Hint->RpcProxyPort);
|
|
if (Status != RPC_S_OK)
|
|
{
|
|
RetValue = Status;
|
|
goto AbortAndCleanup;
|
|
}
|
|
|
|
Hint->ProxyNameLength = RpcpStringLengthA(Hint->RpcProxy);
|
|
|
|
if (Hint->HTTPProxy)
|
|
{
|
|
Status = EndpointToPortNumberA(HttpProxyPort, Hint->HTTPProxyPort);
|
|
if (Status != RPC_S_OK)
|
|
{
|
|
RetValue = Status;
|
|
goto AbortAndCleanup;
|
|
}
|
|
}
|
|
|
|
// we will optimistically presume that we can talk HTTP2
|
|
// until proven wrong
|
|
Hint->Version = httpvHTTP2;
|
|
|
|
// by now the resolver hint is fully initialized. fall through
|
|
// the case that has initialized resolver hint
|
|
}
|
|
|
|
Hint->VerifyInitialized();
|
|
|
|
// disable retries by default. If we have to loop around, we will set it to TRUE
|
|
Retry = FALSE;
|
|
|
|
do
|
|
{
|
|
// we have it all now.
|
|
if (Hint->Version == httpvHTTP2)
|
|
{
|
|
// use explicit placement
|
|
VirtualConnection = new (ThisConnection) HTTP2ClientVirtualConnection (
|
|
(RPC_HTTP_TRANSPORT_CREDENTIALS *)AdditionalCredentials,
|
|
&Status);
|
|
if (Status != RPC_S_OK)
|
|
{
|
|
VirtualConnection->HTTP2ClientVirtualConnection::~HTTP2ClientVirtualConnection();
|
|
RetValue = Status;
|
|
goto AbortAndCleanup;
|
|
}
|
|
|
|
Status = VirtualConnection->ClientOpen(Hint,
|
|
fHintInitialized,
|
|
ConnTimeout,
|
|
CallTimeout
|
|
);
|
|
|
|
// if we got a protocol error or a receive failed, and
|
|
// we don't have credentials, fall back to old protocol.
|
|
if (
|
|
(
|
|
(Status == RPC_S_PROTOCOL_ERROR)
|
|
||
|
|
(Status == RPC_P_RECEIVE_FAILED)
|
|
)
|
|
&&
|
|
(HttpCredentials == NULL)
|
|
)
|
|
{
|
|
// cause the loop to start over.
|
|
// make sure next iteration it tries old http
|
|
Hint->Version = httpvHTTP;
|
|
VirtualConnection->HTTP2ClientVirtualConnection::~HTTP2ClientVirtualConnection();
|
|
Retry = TRUE;
|
|
}
|
|
else
|
|
{
|
|
if ((Status == RPC_S_PROTOCOL_ERROR)
|
|
||
|
|
(Status == RPC_P_RECEIVE_FAILED))
|
|
{
|
|
Status = RPC_S_SERVER_UNAVAILABLE;
|
|
}
|
|
|
|
ASSERT(Status != RPC_P_PACKET_CONSUMED);
|
|
RetValue = Status;
|
|
VirtualConnection->id = HTTPv2;
|
|
if (Status == RPC_S_OK)
|
|
goto CleanupAndExit;
|
|
else
|
|
{
|
|
VirtualConnection->HTTP2ClientVirtualConnection::~HTTP2ClientVirtualConnection();
|
|
goto AbortAndCleanup;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ASSERT(Hint->Version == httpvHTTP);
|
|
|
|
// HTTP1 doesn't support proxy discovery. If we don't know
|
|
// just assume local and hope it works.
|
|
if (Hint->AccessType == rpcpatUnknown)
|
|
Hint->AccessType = rpcpatDirect;
|
|
|
|
// we need to re-initialize the connection object with old
|
|
// format connection
|
|
WS_Initialize(p, 0, 0, 0);
|
|
|
|
// use explicit placement to initialize the vtable. We need this to
|
|
// be able to use the virtual functions
|
|
p = new (p) WS_CLIENT_CONNECTION;
|
|
|
|
p->id = HTTP;
|
|
|
|
// Call common open function. Note that for http connection
|
|
// WS_Open will just open a socket. That's all we need right now.
|
|
Status = WS_Open(p,
|
|
NULL,
|
|
ConnTimeout,
|
|
SendBufferSize,
|
|
RecvBufferSize,
|
|
CallTimeout,
|
|
FALSE // fHTTP2Open
|
|
);
|
|
if (Status != RPC_S_OK)
|
|
{
|
|
RetValue = Status;
|
|
goto AbortAndCleanup;
|
|
}
|
|
|
|
//
|
|
// WS_Open has been successfully called. Do connect() work here...
|
|
//
|
|
|
|
// If AccessType is direct, then we are going to try to directly
|
|
// connect to the IIS that is the RPC Proxy first. If that suceeds,
|
|
// then we will just tunnel to the RPC server from there. If it fails,
|
|
// then we will try to go through an HTTP proxy (i.e. MSProxy server)
|
|
// if one is available...
|
|
if (Hint->AccessType != rpcpatHTTPProxy)
|
|
{
|
|
Status = HTTP_CheckIPAddressForDirectConnection(Hint);
|
|
if (Status != RPC_S_OK)
|
|
{
|
|
RetValue = RPC_S_OUT_OF_MEMORY;
|
|
goto AbortAndCleanup;
|
|
}
|
|
|
|
if (Hint->AccessType == rpcpatDirect)
|
|
{
|
|
Status = HTTP_TryConnect( p->Conn.Socket, Hint->RpcProxy, Hint->RpcProxyPort );
|
|
}
|
|
}
|
|
|
|
if ((Status != RPC_S_OK) || (Hint->AccessType != rpcpatDirect))
|
|
{
|
|
//
|
|
// If we get here, then we are going to try to use an HTTP proxy first...
|
|
//
|
|
Status = HTTP_TryConnect( p->Conn.Socket, Hint->HTTPProxy, Hint->HTTPProxyPort );
|
|
|
|
//
|
|
// If we successfully connected to the HTTP proxy, then let's go on and
|
|
// tunnel through to the RPC proxy:
|
|
//
|
|
if (Status != RPC_S_OK)
|
|
{
|
|
RetValue = RPC_S_SERVER_UNAVAILABLE;
|
|
goto Abort;
|
|
}
|
|
|
|
PortNumberToEndpointA(Hint->RpcProxyPort, PortString);
|
|
|
|
if (!HttpTunnelToRpcProxy(p->Conn.Socket,
|
|
Hint->RpcProxy,
|
|
PortString))
|
|
{
|
|
RetValue = RPC_S_SERVER_UNAVAILABLE;
|
|
goto Abort;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Finally, negotiate with the RPC proxy to get the connection through to
|
|
// the RPC server.
|
|
//
|
|
|
|
PortNumberToEndpointA(Hint->ServerPort, PortString);
|
|
|
|
if (!HttpTunnelToRpcServer( p->Conn.Socket,
|
|
Hint->RpcServer,
|
|
PortString ))
|
|
{
|
|
RetValue = RPC_S_SERVER_UNAVAILABLE;
|
|
goto Abort;
|
|
}
|
|
|
|
fUserModeConnection = IsUserModeSocket(p->Conn.Socket, &RetValue);
|
|
if (RetValue != RPC_S_OK)
|
|
goto Abort;
|
|
|
|
// if this is SAN or loadable transport not using true handles, go through Winsock
|
|
if (fUserModeConnection)
|
|
p = new (p) WS_SAN_CLIENT_CONNECTION;
|
|
|
|
Retry = FALSE;
|
|
}
|
|
}
|
|
while (Retry);
|
|
|
|
RetValue = RPC_S_OK;
|
|
goto CleanupAndExit;
|
|
|
|
Abort:
|
|
p->WS_CONNECTION::Abort();
|
|
|
|
AbortAndCleanup:
|
|
if (HintNeedsCleanup)
|
|
{
|
|
ASSERT(RetValue != RPC_S_OK);
|
|
Hint->FreeRpcServer();
|
|
Hint->FreeRpcProxy();
|
|
Hint->FreeHTTPProxy();
|
|
}
|
|
|
|
CleanupAndExit:
|
|
|
|
// Creating a thread can fail with RPC_S_OUT_OF_THREADS
|
|
// and the caller may not be prepared to deal with this error.
|
|
if (RetValue == RPC_S_OUT_OF_THREADS)
|
|
{
|
|
RetValue = RPC_S_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
VALIDATE (RetValue)
|
|
{
|
|
RPC_S_OK,
|
|
RPC_S_PROTSEQ_NOT_SUPPORTED,
|
|
RPC_S_SERVER_UNAVAILABLE,
|
|
RPC_S_OUT_OF_MEMORY,
|
|
RPC_S_OUT_OF_RESOURCES,
|
|
RPC_S_SERVER_TOO_BUSY,
|
|
RPC_S_INVALID_NETWORK_OPTIONS,
|
|
RPC_S_INVALID_ENDPOINT_FORMAT,
|
|
RPC_S_INVALID_NET_ADDR,
|
|
RPC_S_ACCESS_DENIED,
|
|
RPC_S_INTERNAL_ERROR,
|
|
RPC_S_SERVER_OUT_OF_MEMORY,
|
|
RPC_S_CALL_CANCELLED
|
|
} END_VALIDATE;
|
|
|
|
if (RpcProxyPort != NULL)
|
|
delete RpcProxyPort;
|
|
|
|
if (HttpProxyPort != NULL)
|
|
delete HttpProxyPort;
|
|
|
|
if (Hint->ServerNameLength <= sizeof(Hint->RpcServerName))
|
|
{
|
|
ASSERT(Hint->RpcServer == Hint->RpcServerName);
|
|
}
|
|
|
|
return (RetValue);
|
|
}
|
|
|
|
|
|
|
|
|
|
RPC_STATUS
|
|
HTTP_ServerListen(
|
|
IN RPC_TRANSPORT_ADDRESS ThisAddress,
|
|
IN RPC_CHAR *NetworkAddress,
|
|
IN OUT RPC_CHAR * *pEndpoint,
|
|
IN UINT PendingQueueSize,
|
|
IN PSECURITY_DESCRIPTOR SecurityDescriptor,
|
|
IN ULONG EndpointFlags,
|
|
IN ULONG NICFlags
|
|
)
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
RpcStatus = InitializeHttpServerIfNecessary();
|
|
if (RpcStatus != RPC_S_OK)
|
|
return RpcStatus;
|
|
|
|
return (TCP_ServerListenEx(
|
|
ThisAddress,
|
|
NetworkAddress,
|
|
pEndpoint,
|
|
PendingQueueSize,
|
|
SecurityDescriptor,
|
|
EndpointFlags,
|
|
NICFlags,
|
|
TRUE // HTTP!
|
|
));
|
|
}
|
|
|
|
void WINAPI ProxyIoCompletionCallback (
|
|
IN LPEXTENSION_CONTROL_BLOCK lpECB,
|
|
IN PVOID pContext,
|
|
IN DWORD cbIO,
|
|
IN DWORD dwError
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
IIS io completion callback function
|
|
|
|
Arguments:
|
|
|
|
lpECB - extension control block
|
|
|
|
pContext - the IISChannel pointer.
|
|
|
|
cbIO - Bytes transferred in the last operation
|
|
|
|
dwError - status of the operation
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
HTTP2IISTransportChannel *IISChannel;
|
|
THREAD *Thread;
|
|
|
|
Thread = ThreadSelf();
|
|
if (Thread == NULL)
|
|
return; // abandon the completion if worse comes to worse.
|
|
|
|
IISChannel = (HTTP2IISTransportChannel *)pContext;
|
|
|
|
IISChannel->IOCompleted(cbIO, dwError);
|
|
}
|
|
|
|
BOOL RPCTransInitialized = FALSE;
|
|
|
|
RPCRTAPI
|
|
RPC_STATUS
|
|
RPC_ENTRY
|
|
I_RpcProxyNewConnection (
|
|
IN ULONG ConnectionType,
|
|
IN USHORT *ServerAddress,
|
|
IN USHORT *ServerPort,
|
|
IN void *ConnectionParameter,
|
|
IN I_RpcProxyCallbackInterface *ProxyCallbackInterface
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Entry point from the ISAPI extension. Called when a new
|
|
connection request arrives at an in or out proxy.
|
|
|
|
Arguments:
|
|
|
|
ConnectionType - currently RPC_PROXY_CONNECTION_TYPE_IN_PROXY or
|
|
RPC_PROXY_CONNECTION_TYPE_OUT_PROXY to indicate the type of
|
|
connection establishment request we have received
|
|
ServerAddress - unicode network address of the server
|
|
ServerPort - unicode port of the server
|
|
ConnectionParameter - the Extension Control Block in this case.
|
|
ProxyCallbackInterface - a callback interface to the proxy to perform
|
|
various proxy specific functions
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
Note:
|
|
|
|
This function and all its callees must ensure that if this function
|
|
returns error, it doesn't call HSE_REQ_DONE_WITH_SESSION. If it
|
|
return RPC_S_OK, it must call HSE_REQ_DONE_WITH_SESSION. If these 2
|
|
rules are violated, IIS will AV.
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus = RPC_S_OK;
|
|
EXTENSION_CONTROL_BLOCK *ECB;
|
|
void *IISContext;
|
|
BOOL Result;
|
|
RPC_TRANSPORT_INTERFACE TransInterface;
|
|
TRANS_INFO *TransInfo;
|
|
THREAD *ThisThread;
|
|
HTTP2ProxyVirtualConnection *ProxyVirtualConnection;
|
|
|
|
if (RPCTransInitialized == FALSE)
|
|
{
|
|
InitializeIfNecessary();
|
|
|
|
GlobalMutexRequest();
|
|
|
|
// all of the initialization here is idempotent.
|
|
// If we fail midway, we don't have to un-initialize -
|
|
// next initialization attempt will pick up where we left.
|
|
|
|
RpcStatus = OsfMapRpcProtocolSequence(FALSE,
|
|
L"ncacn_http",
|
|
&TransInfo);
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
ASSERT(TransInfo);
|
|
|
|
if (HTTPTransInfo == NULL)
|
|
HTTPTransInfo = TransInfo;
|
|
|
|
RpcStatus = TransInfo->StartServerIfNecessary();
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
RpcStatus = InitializeDefaultChannelLifetime();
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
RpcStatus = InitializeActAsWebFarm();
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
RpcStatus = InitializeMinConnectionTimeout();
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
RpcStatus = CookieCollection::InitializeInProxyCookieCollection();
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
RpcStatus = CookieCollection::InitializeOutProxyCookieCollection();
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
RpcStatus = InitializeReceiveWindows();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
RPCTransInitialized = (RpcStatus == RPC_S_OK);
|
|
GlobalMutexClear();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
return RPC_S_OK;
|
|
}
|
|
}
|
|
|
|
ThisThread = ThreadSelf();
|
|
if (ThisThread == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
|
|
if (ConnectionType == RPC_PROXY_CONNECTION_TYPE_IN_PROXY)
|
|
{
|
|
ProxyVirtualConnection = new HTTP2InProxyVirtualConnection(&RpcStatus);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(ConnectionType == RPC_PROXY_CONNECTION_TYPE_OUT_PROXY);
|
|
ProxyVirtualConnection = new HTTP2OutProxyVirtualConnection(&RpcStatus);
|
|
}
|
|
|
|
if (ProxyVirtualConnection == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
delete ProxyVirtualConnection;
|
|
return RpcStatus;
|
|
}
|
|
|
|
RpcStatus = ProxyVirtualConnection->InitializeProxyFirstLeg(ServerAddress,
|
|
ServerPort,
|
|
ConnectionParameter,
|
|
ProxyCallbackInterface,
|
|
&IISContext
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
delete ProxyVirtualConnection;
|
|
return RpcStatus;
|
|
}
|
|
|
|
// we have initialized far enough. Associate callback
|
|
// with this connection
|
|
ECB = (EXTENSION_CONTROL_BLOCK *) ConnectionParameter;
|
|
|
|
Result = ECB->ServerSupportFunction (ECB->ConnID,
|
|
HSE_REQ_IO_COMPLETION,
|
|
ProxyIoCompletionCallback,
|
|
NULL,
|
|
(LPDWORD)IISContext
|
|
);
|
|
|
|
if (Result == FALSE)
|
|
{
|
|
ProxyVirtualConnection->Abort();
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
// after we call StartProxy, it takes off on success and
|
|
// we don't know whether we have a vconnection anymore. Thus
|
|
// we must block rundowns until we are done with everything.
|
|
ProxyVirtualConnection->BlockConnectionFromRundown();
|
|
|
|
RpcStatus = ProxyVirtualConnection->StartProxy();
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
ProxyVirtualConnection->UnblockConnectionFromRundown();
|
|
ProxyVirtualConnection->Abort();
|
|
return RpcStatus;
|
|
}
|
|
|
|
ProxyVirtualConnection->EnableIISSessionClose();
|
|
|
|
ProxyVirtualConnection->UnblockConnectionFromRundown();
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPCRTAPI
|
|
RPC_STATUS
|
|
RPC_ENTRY
|
|
HTTP2IISDirectReceive (
|
|
IN void *Context
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Direct notification from the thread pool to an IIS channel
|
|
for a receive. The proxy stacks use that to post receives
|
|
to themselves.
|
|
|
|
Arguments:
|
|
|
|
Context - the HTTP2IISTransportChannel
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
((HTTP2IISTransportChannel *)Context)->DirectReceive();
|
|
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
RPCRTAPI
|
|
RPC_STATUS
|
|
RPC_ENTRY
|
|
HTTP2DirectReceive (
|
|
IN void *Context,
|
|
OUT BYTE **ReceivedBuffer,
|
|
OUT ULONG *ReceivedBufferLength,
|
|
OUT void **RuntimeConnection,
|
|
OUT BOOL *IsServer
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Direct notification from the thread pool to a receiver
|
|
for a receive. The stacks use that to post receives
|
|
to themselves.
|
|
|
|
Arguments:
|
|
|
|
Context - an instance of the HTTP2EndpointReceiver
|
|
|
|
ReceivedBuffer - the buffer that we received.
|
|
|
|
ReceivedBufferLength - the length of the received
|
|
buffer
|
|
|
|
RuntimeConnection - the connection to return to the runtime
|
|
if the packet is not consumed.
|
|
|
|
IsServer - true if this is the server
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
LOG_FN_OPERATION_ENTRY(HTTP2LOG_OPERATION_DIRECT_RECV_COMPLETE, HTTP2LOG_OT_CALLBACK, (ULONG_PTR)Context);
|
|
|
|
RpcStatus = ((HTTP2EndpointReceiver *)Context)->DirectReceiveComplete(
|
|
ReceivedBuffer,
|
|
ReceivedBufferLength,
|
|
RuntimeConnection,
|
|
IsServer
|
|
);
|
|
|
|
LOG_FN_OPERATION_EXIT(HTTP2LOG_OPERATION_DIRECT_RECV_COMPLETE, HTTP2LOG_OT_CALLBACK, RpcStatus);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPCRTAPI
|
|
RPC_STATUS
|
|
RPC_ENTRY
|
|
HTTP2WinHttpDirectReceive (
|
|
IN void *Context,
|
|
OUT BYTE **ReceivedBuffer,
|
|
OUT ULONG *ReceivedBufferLength,
|
|
OUT void **RuntimeConnection
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Direct notification from the thread pool to a receiver
|
|
for a receive. The stacks use that to post receives
|
|
to themselves.
|
|
|
|
Arguments:
|
|
|
|
Context - an instance of HTTP2WinHttpTransportChannel
|
|
|
|
ReceivedBuffer - the buffer that we received.
|
|
|
|
ReceivedBufferLength - the length of the received
|
|
buffer
|
|
|
|
RuntimeConnection - the connection to return to the runtime
|
|
if the packet is not consumed.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
LOG_FN_OPERATION_ENTRY(HTTP2LOG_OPERATION_WHTTP_DRECV_COMPLETE, HTTP2LOG_OT_CALLBACK, (ULONG_PTR)Context);
|
|
|
|
RpcStatus = ((HTTP2WinHttpTransportChannel *)Context)->DirectReceiveComplete(
|
|
ReceivedBuffer,
|
|
ReceivedBufferLength,
|
|
RuntimeConnection
|
|
);
|
|
|
|
LOG_FN_OPERATION_EXIT(HTTP2LOG_OPERATION_WHTTP_DRECV_COMPLETE, HTTP2LOG_OT_CALLBACK, RpcStatus);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPCRTAPI
|
|
RPC_STATUS
|
|
RPC_ENTRY
|
|
HTTP2WinHttpDirectSend (
|
|
IN void *Context,
|
|
OUT BYTE **SentBuffer,
|
|
OUT void **SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Direct notification from the thread pool to a sender
|
|
for a send. The stacks use that to post sends
|
|
to themselves.
|
|
|
|
Arguments:
|
|
|
|
Context - an instance of HTTP2WinHttpTransportChannel
|
|
|
|
SentBuffer - the buffer that we sent.
|
|
|
|
SendContext - the send context to return to the runtime
|
|
if the packet is not consumed.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
LOG_FN_OPERATION_ENTRY(HTTP2LOG_OPERATION_WHTTP_DSEND_COMPLETE, HTTP2LOG_OT_CALLBACK, (ULONG_PTR)Context);
|
|
|
|
RpcStatus = ((HTTP2WinHttpTransportChannel *)Context)->DirectSendComplete(
|
|
SentBuffer,
|
|
SendContext
|
|
);
|
|
|
|
LOG_FN_OPERATION_EXIT(HTTP2LOG_OPERATION_WHTTP_DSEND_COMPLETE, HTTP2LOG_OT_CALLBACK, RpcStatus);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPCRTAPI
|
|
void
|
|
RPC_ENTRY
|
|
HTTP2WinHttpDelayedReceive (
|
|
IN void *Context
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Direct notification from the thread pool to
|
|
post a DelayedReceive.
|
|
|
|
Arguments:
|
|
|
|
Context - an instance of HTTP2WinHttpTransportChannel
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
{
|
|
((HTTP2WinHttpTransportChannel *)Context)->DelayedReceive();
|
|
}
|
|
|
|
RPCRTAPI
|
|
RPC_STATUS
|
|
RPC_ENTRY
|
|
HTTP2PlugChannelDirectSend (
|
|
IN void *Context
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Direct notification from the thread pool to the plug channel
|
|
for a send. The plug channel uses that to post sends
|
|
to itself. Usable only on proxies (i.e. doesn't return to runtime)
|
|
|
|
Arguments:
|
|
|
|
Context - an instance of HTTP2PlugChannel
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK
|
|
|
|
--*/
|
|
{
|
|
return ((HTTP2PlugChannel *)Context)->DirectSendComplete();
|
|
}
|
|
|
|
RPCRTAPI
|
|
RPC_STATUS
|
|
RPC_ENTRY
|
|
HTTP2FlowControlChannelDirectSend (
|
|
IN void *Context,
|
|
OUT BOOL *IsServer,
|
|
OUT BOOL *SendToRuntime,
|
|
OUT void **SendContext,
|
|
OUT BUFFER *Buffer,
|
|
OUT UINT *BufferLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Direct notification from the thread pool to the flow control channel
|
|
for a send. The flow control channel uses that to post sends
|
|
to itself.
|
|
|
|
Arguments:
|
|
|
|
Context - an instance of HTTP2FlowControlSender
|
|
|
|
IsServer - on both success and failure MUST be set by this function.
|
|
|
|
SendToRuntime - on both success and failure MUST be set by this function.
|
|
|
|
SendContext - the send context as needs to be seen by the runtime
|
|
|
|
Buffer - on output the buffer that we tried to send
|
|
|
|
BufferLength - on output the length of the buffer we tried to send
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
#if DBG
|
|
*IsServer = 0xBAADBAAD;
|
|
*SendToRuntime = 0xBAADBAAD;
|
|
#endif // DBG
|
|
|
|
RpcStatus = ((HTTP2FlowControlSender *)Context)->DirectSendComplete(IsServer,
|
|
SendToRuntime,
|
|
SendContext,
|
|
Buffer,
|
|
BufferLength);
|
|
|
|
// make sure DirectSendComplete didn't forget to set it
|
|
ASSERT(*IsServer != 0xBAADBAAD);
|
|
ASSERT(*SendToRuntime != 0xBAADBAAD);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPCRTAPI
|
|
RPC_STATUS
|
|
RPC_ENTRY
|
|
HTTP2ChannelDataOriginatorDirectSend (
|
|
IN void *Context,
|
|
OUT BOOL *IsServer,
|
|
OUT void **SendContext,
|
|
OUT BUFFER *Buffer,
|
|
OUT UINT *BufferLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Direct notification from the thread pool to the channel data originator
|
|
for a send complete. The channel data originator uses that to post send
|
|
compeltes to itself. Usable only on endpoints (i.e. does return to runtime)
|
|
|
|
Arguments:
|
|
|
|
Context - an instance of HTTP2ChannelDataOriginator
|
|
|
|
IsServer - on both success and failure MUST be set by this function.
|
|
|
|
SendContext - the send context as needs to be seen by the runtime
|
|
|
|
Buffer - on output the buffer that we tried to send
|
|
|
|
BufferLength - on output the length of the buffer we tried to send
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
#if DBG
|
|
*IsServer = 0xBAADBAAD;
|
|
#endif // DBG
|
|
|
|
RpcStatus = ((HTTP2ChannelDataOriginator *)Context)->DirectSendComplete(IsServer,
|
|
SendContext,
|
|
Buffer,
|
|
BufferLength);
|
|
|
|
// make sure DirectSendComplete didn't forget to set it
|
|
ASSERT(*IsServer != 0xBAADBAAD);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPCRTAPI
|
|
void
|
|
RPC_ENTRY
|
|
HTTP2TimerReschedule (
|
|
IN void *Context
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
A timer reschedule notification came in.
|
|
|
|
Arguments:
|
|
|
|
Context - actually a ping channel pointer for the channel
|
|
that asked for rescheduling
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
HTTP2PingOriginator *PingChannel;
|
|
|
|
PingChannel = (HTTP2PingOriginator *)Context;
|
|
|
|
PingChannel->RescheduleTimer();
|
|
}
|
|
|
|
RPCRTAPI
|
|
void
|
|
RPC_ENTRY
|
|
HTTP2AbortConnection (
|
|
IN void *Context
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
A request to abort the connection was posted on a worker thread.
|
|
|
|
Arguments:
|
|
|
|
Context - actually a top channel pointer for the connection to abort.
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
HTTP2Channel *TopChannel;
|
|
|
|
TopChannel = (HTTP2Channel *)Context;
|
|
|
|
TopChannel->AbortConnection(RPC_P_CONNECTION_SHUTDOWN);
|
|
TopChannel->RemoveReference();
|
|
}
|
|
|
|
RPCRTAPI
|
|
void
|
|
RPC_ENTRY
|
|
HTTP2RecycleChannel (
|
|
IN void *Context
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
A request to recycle the channel was posted on a worker thread.
|
|
|
|
Arguments:
|
|
|
|
Context - actually a top channel pointer for the channel to
|
|
recycle.
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
HTTP2Channel *TopChannel;
|
|
|
|
TopChannel = (HTTP2Channel *)Context;
|
|
|
|
// don't care about return code. See rule 29.
|
|
(void) TopChannel->HandleSendResultFromNeutralContext(RPC_P_CHANNEL_NEEDS_RECYCLING);
|
|
TopChannel->RemoveReference();
|
|
}
|
|
|
|
RPC_STATUS
|
|
HTTP2ProcessComplexTReceive (
|
|
IN OUT void **Connection,
|
|
IN RPC_STATUS EventStatus,
|
|
IN ULONG Bytes,
|
|
OUT BUFFER *Buffer,
|
|
OUT UINT *BufferLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
A receive notification came from the completion port.
|
|
|
|
Arguments:
|
|
|
|
Connection - a pointer to a pointer to a connection.
|
|
On input it will be the raw connection. On output
|
|
it needs to be the virtual connection so that
|
|
runtime can find its object off there. This out
|
|
parameter must be set on both success and failure.
|
|
|
|
EventStatus - status of the operation
|
|
|
|
Bytes - bytes received
|
|
|
|
Buffer - on output (success only), the received buffer
|
|
|
|
BufferLength - on output (success only), the length of the
|
|
received buffer.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
RPC_P_PARTIAL_RECEIVE allowed for partial receives.
|
|
RPC_P_PACKET_CONSUMED must be returned for all transport
|
|
traffic (success or failure). Anything else will AV the
|
|
runtime.
|
|
|
|
--*/
|
|
{
|
|
WS_HTTP2_CONNECTION *RawConnection = (WS_HTTP2_CONNECTION *)*Connection;
|
|
BYTE *Packet;
|
|
ULONG PacketLength;
|
|
WS_HTTP2_INITIAL_CONNECTION *ThisConnection;
|
|
HTTP2ServerVirtualConnection *ServerVirtualConnection;
|
|
BOOL VirtualConnectionCreated;
|
|
|
|
LOG_FN_OPERATION_ENTRY(HTTP2LOG_COMPLEX_T_RECV, HTTP2LOG_OT_CALLBACK, EventStatus);
|
|
|
|
ASSERT (EventStatus != RPC_P_PACKET_CONSUMED);
|
|
|
|
// Detect whether this is an initial connection.
|
|
// If this is an initial connection, process receive and initialize the connection.
|
|
|
|
// EventStatus will be RPC_P_INITIALIZE_HTTP2_CONNECTION in the case of connection establishment.
|
|
if (EventStatus == RPC_P_INITIALIZE_HTTP2_CONNECTION)
|
|
{
|
|
Packet = (BYTE *) *Buffer;
|
|
PacketLength = *BufferLength;
|
|
ThisConnection = (WS_HTTP2_INITIAL_CONNECTION *) *Connection;
|
|
|
|
// The packet received must have been an RTS packet.
|
|
ASSERT(IsRTSPacket(Packet));
|
|
|
|
// unlink this connection from the PnP list before it is migrated
|
|
TransportProtocol::RemoveObjectFromProtocolList((BASE_ASYNC_OBJECT *) ThisConnection);
|
|
|
|
EventStatus = HTTP2ServerVirtualConnection::InitializeServerConnection (
|
|
Packet,
|
|
PacketLength,
|
|
ThisConnection,
|
|
&ServerVirtualConnection,
|
|
&VirtualConnectionCreated
|
|
);
|
|
|
|
// Note that if the above call succeeds, ThisConnection may have been deleted on another thread
|
|
// after this point. This is possible when the data receive posted has completed on another thread,
|
|
// causing a connection close.
|
|
|
|
if (EventStatus == RPC_S_OK)
|
|
{
|
|
// We are done with connection initialization and may return.
|
|
|
|
// N.B. Do not use the this pointer as WS_HTTP2_INITIAL_CONNECTION
|
|
// pointer after here. It has been migrated to a new location
|
|
// and this actually points to HTTP2ServerVirtualConnection
|
|
|
|
*Buffer = NULL;
|
|
*BufferLength = 0;
|
|
RpcFreeBuffer(Packet);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
else
|
|
{
|
|
if (VirtualConnectionCreated == FALSE)
|
|
{
|
|
// failed to create a virtual connection. Link the connection
|
|
// back to its protocol list to ensure orderly destruction
|
|
TransportProtocol::AddObjectToProtocolList((BASE_ASYNC_OBJECT *) ThisConnection);
|
|
|
|
// Send failure to the runtime to have the OSF_SCONNECTION cleaned up.
|
|
return RPC_P_RECEIVE_FAILED;
|
|
}
|
|
else
|
|
{
|
|
// nothing to do. The virtual connection was created but it failed to
|
|
// initialize. We will process the failure and let the runtime destroy
|
|
// the connection.
|
|
EventStatus = RPC_P_RECEIVE_FAILED;
|
|
}
|
|
}
|
|
|
|
// The only status expected beyond this point is a failure.
|
|
ASSERT(EventStatus == RPC_P_RECEIVE_FAILED);
|
|
}
|
|
|
|
// stick the runtime idea of the transport connection
|
|
*Connection = RawConnection->RuntimeConnectionPtr;
|
|
|
|
if (Bytes && (EventStatus == RPC_S_OK))
|
|
{
|
|
EventStatus = RawConnection->ProcessReceiveComplete(Bytes,
|
|
Buffer,
|
|
BufferLength);
|
|
|
|
if (EventStatus == RPC_P_PARTIAL_RECEIVE)
|
|
{
|
|
// Message is not complete, submit the next read and continue.
|
|
EventStatus = CO_SubmitRead(RawConnection);
|
|
|
|
if (EventStatus != RPC_S_OK)
|
|
{
|
|
EventStatus = RawConnection->ProcessReceiveFailed(RPC_P_CONNECTION_SHUTDOWN);
|
|
if (EventStatus != RPC_P_PACKET_CONSUMED)
|
|
{
|
|
ASSERT(EventStatus == RPC_P_RECEIVE_FAILED);
|
|
}
|
|
}
|
|
else
|
|
EventStatus = RPC_P_PARTIAL_RECEIVE;
|
|
}
|
|
else
|
|
{
|
|
ASSERT( (EventStatus == RPC_P_RECEIVE_FAILED)
|
|
|| (EventStatus == RPC_S_OK)
|
|
|| (EventStatus == RPC_P_PACKET_CONSUMED)
|
|
|| (EventStatus == RPC_P_CONNECTION_CLOSED));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// in other rare case (again server connection establishment), the connection
|
|
// can be the virtual connection, not the transport connection. In such cases,
|
|
// let the error fall through back to the runtime. Since this happens only during
|
|
// connection establishment, and the receive did not go through the channels,
|
|
// we should not complete it through the channels.
|
|
if ((RawConnection->id == HTTPv2)
|
|
&& (RawConnection->type == (COMPLEX_T | CONNECTION | SERVER)))
|
|
{
|
|
// this is a server virtual connecton. Read the connection from there
|
|
*Connection = RawConnection;
|
|
}
|
|
else
|
|
{
|
|
if (EventStatus != RPC_S_OK)
|
|
EventStatus = RawConnection->ProcessReceiveFailed(EventStatus);
|
|
else
|
|
EventStatus = RawConnection->ProcessReceiveFailed(RPC_P_RECEIVE_FAILED);
|
|
|
|
if (EventStatus == RPC_P_CONNECTION_SHUTDOWN)
|
|
EventStatus = RPC_P_RECEIVE_FAILED;
|
|
}
|
|
|
|
ASSERT( (EventStatus == RPC_P_RECEIVE_FAILED)
|
|
|| (EventStatus == RPC_S_OK)
|
|
|| (EventStatus == RPC_P_PACKET_CONSUMED));
|
|
}
|
|
|
|
LOG_FN_OPERATION_EXIT(HTTP2LOG_COMPLEX_T_RECV, HTTP2LOG_OT_CALLBACK, EventStatus);
|
|
|
|
return EventStatus;
|
|
}
|
|
|
|
RPC_STATUS
|
|
HTTP2ProcessComplexTSend (
|
|
IN void *SendContext,
|
|
IN RPC_STATUS EventStatus,
|
|
OUT BUFFER *Buffer
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
A send notification came from the completion port.
|
|
|
|
Arguments:
|
|
|
|
SendContext - the send context
|
|
|
|
EventStatus - status of the operation
|
|
|
|
Buffer - if the packet is not consumed, must be the sent
|
|
buffer.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
RPC_P_PACKET_CONSUMED must be returned for all transport
|
|
traffic (success or failure). Anything else will AV the
|
|
runtime.
|
|
|
|
--*/
|
|
{
|
|
HTTP2SendContext *HttpSendContext = (HTTP2SendContext *)SendContext;
|
|
WS_HTTP2_CONNECTION *RawConnection;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
LOG_FN_OPERATION_ENTRY(HTTP2LOG_COMPLEX_T_SEND, HTTP2LOG_OT_CALLBACK, (ULONG_PTR)HttpSendContext);
|
|
|
|
*Buffer = HttpSendContext->pWriteBuffer;
|
|
RawConnection = (WS_HTTP2_CONNECTION *)HttpSendContext->Write.pAsyncObject;
|
|
|
|
RpcStatus = RawConnection->ProcessSendComplete(EventStatus, HttpSendContext);
|
|
|
|
LOG_FN_OPERATION_EXIT(HTTP2LOG_COMPLEX_T_SEND, HTTP2LOG_OT_CALLBACK, RpcStatus);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS ProxyAsyncCompleteHelper (
|
|
IN HTTP2Channel *TopChannel,
|
|
IN RPC_STATUS CurrentStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
A helper function that completes an async io.
|
|
|
|
Arguments:
|
|
|
|
TopChannel - the top channel for the stack
|
|
|
|
CurrentStatus - the status with which the complete
|
|
notification completed.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK.
|
|
|
|
--*/
|
|
{
|
|
ASSERT(CurrentStatus != RPC_S_CANNOT_SUPPORT);
|
|
ASSERT(CurrentStatus != RPC_S_INTERNAL_ERROR);
|
|
|
|
if ((CurrentStatus != RPC_S_OK)
|
|
&&
|
|
(CurrentStatus != RPC_P_PACKET_CONSUMED))
|
|
{
|
|
// if this failed, abort the whole connection
|
|
TopChannel->AbortAndDestroyConnection(CurrentStatus);
|
|
}
|
|
|
|
TopChannel->RemoveReference();
|
|
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
RPCRTAPI
|
|
RPC_STATUS
|
|
RPC_ENTRY
|
|
HTTP2TestHook (
|
|
IN SystemFunction001Commands FunctionCode,
|
|
IN void *InData,
|
|
OUT void *OutData
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Test hook for the http functions
|
|
|
|
Arguments:
|
|
|
|
FunctionCode - which test function to perform
|
|
|
|
InData - input data from the test function
|
|
|
|
OutData - output data from the test function
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
RPC_CHAR *NewTarget;
|
|
|
|
switch (FunctionCode)
|
|
{
|
|
case sf001cHttpSetInChannelTarget:
|
|
NewTarget = (RPC_CHAR *)InData;
|
|
|
|
if (InChannelTargetTestOverride)
|
|
{
|
|
delete [] InChannelTargetTestOverride;
|
|
InChannelTargetTestOverride = NULL;
|
|
}
|
|
|
|
if (NewTarget)
|
|
{
|
|
InChannelTargetTestOverride = DuplicateString(NewTarget);
|
|
if (InChannelTargetTestOverride == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
break;
|
|
|
|
case sf001cHttpSetOutChannelTarget:
|
|
NewTarget = (RPC_CHAR *)InData;
|
|
|
|
if (OutChannelTargetTestOverride)
|
|
{
|
|
delete [] OutChannelTargetTestOverride;
|
|
OutChannelTargetTestOverride = NULL;
|
|
}
|
|
|
|
if (NewTarget)
|
|
{
|
|
OutChannelTargetTestOverride = DuplicateString(NewTarget);
|
|
if (OutChannelTargetTestOverride == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// we should never be called with a value we can't handle
|
|
ASSERT(0);
|
|
return RPC_S_INTERNAL_ERROR;
|
|
}
|
|
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2TransportChannel
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS HTTP2TransportChannel::Send (
|
|
IN OUT HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send request
|
|
|
|
Arguments:
|
|
|
|
SendContext - the send context
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
VerifyValidSendContext(SendContext);
|
|
|
|
return LowerLayer->Send(SendContext);
|
|
}
|
|
|
|
RPC_STATUS HTTP2TransportChannel::Receive (
|
|
IN HTTP2TrafficType TrafficType
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Receive request
|
|
|
|
Arguments:
|
|
|
|
TrafficType - the type of traffic we want to receive
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
return LowerLayer->Receive(TrafficType);
|
|
}
|
|
|
|
RPC_STATUS HTTP2TransportChannel::SendComplete (
|
|
IN RPC_STATUS EventStatus,
|
|
IN OUT HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send complete notification
|
|
|
|
Arguments:
|
|
|
|
EventStatus - the status of the send
|
|
SendContext - send context
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
return UpperLayer->SendComplete(EventStatus,
|
|
SendContext
|
|
);
|
|
}
|
|
|
|
RPC_STATUS HTTP2TransportChannel::ReceiveComplete (
|
|
IN RPC_STATUS EventStatus,
|
|
IN HTTP2TrafficType TrafficType,
|
|
IN BYTE *Buffer,
|
|
IN UINT BufferLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Receive complete notification.
|
|
|
|
Arguments:
|
|
|
|
EventStatus - status of the operation
|
|
|
|
TrafficType - the type of traffic we have received
|
|
|
|
Buffer - the received buffer (success only)
|
|
|
|
BufferLength - the length of the received buffer (success only)
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
return UpperLayer->ReceiveComplete(EventStatus,
|
|
TrafficType,
|
|
Buffer,
|
|
BufferLength
|
|
);
|
|
}
|
|
|
|
void HTTP2TransportChannel::Abort (
|
|
IN RPC_STATUS RpcStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Abort the channel
|
|
|
|
Arguments:
|
|
|
|
RpcStatus - the error code with which we abort
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
LowerLayer->Abort(RpcStatus);
|
|
}
|
|
|
|
void HTTP2TransportChannel::SendCancelled (
|
|
IN HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
A lower channel cancelled a send already passed through this channel.
|
|
Most channels don't care as they don't account for or hang on to sends.
|
|
Called only in submission context.
|
|
|
|
Arguments:
|
|
|
|
SendContext - the send context of the send that was cancelled
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
UpperLayer->SendCancelled(SendContext);
|
|
}
|
|
|
|
void HTTP2TransportChannel::Reset (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Reset the channel for next open/send/receive. This is
|
|
used in submission context only and implies there are no
|
|
pending operations on the channel. It is used on the client
|
|
during opening the connection to do quick negotiation on the
|
|
same connection instead of opening a new connection every time.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
if (LowerLayer)
|
|
LowerLayer->Reset();
|
|
}
|
|
|
|
RPC_STATUS HTTP2TransportChannel::AsyncCompleteHelper (
|
|
IN RPC_STATUS CurrentStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Helper routine that helps complete an async operation
|
|
|
|
Arguments:
|
|
|
|
CurrentStatus - the current status of the operation
|
|
|
|
Return Value:
|
|
|
|
The status to return to the runtime.
|
|
|
|
--*/
|
|
{
|
|
return TopChannel->AsyncCompleteHelper(CurrentStatus);
|
|
}
|
|
|
|
RPC_STATUS HTTP2TransportChannel::HandleSendResultFromNeutralContext (
|
|
IN RPC_STATUS CurrentStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Handles the result code from send from a neutral context.
|
|
This includes checking for channel recycling and intiating
|
|
one if necessary. This routine simply delegates to the top channel
|
|
|
|
Arguments:
|
|
|
|
CurrentStatus - the status from the send operation
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_*. Callers may ignore it since all cleanup was
|
|
done.
|
|
|
|
Notes:
|
|
|
|
This must be called in upcall or neutral context only
|
|
|
|
--*/
|
|
{
|
|
return TopChannel->HandleSendResultFromNeutralContext(CurrentStatus);
|
|
}
|
|
|
|
/*********************************************************************
|
|
WS_HTTP2_CONNECTION
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS WS_HTTP2_CONNECTION::Send(HANDLE hFile, LPCVOID lpBuffer,
|
|
DWORD nNumberOfBytesToWrite,
|
|
LPDWORD lpNumberOfBytesWritten,
|
|
LPOVERLAPPED lpOverlapped)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Does an asynchronous send on the connection.
|
|
|
|
Arguments:
|
|
|
|
hFile - file to send on
|
|
|
|
lpBuffer - buffer to send
|
|
|
|
nNumberOfBytesToWrite - number of bytes to send
|
|
|
|
lpNumberOfBytesWritten - number of bytes written. Will never get filled
|
|
in this code path because it is async.
|
|
|
|
lpOverlapped - overlapped to use for the operation
|
|
|
|
Return Value:
|
|
|
|
WSA Error Code
|
|
|
|
--*/
|
|
{
|
|
// See Rule 32.
|
|
return UTIL_WriteFile2(hFile, lpBuffer, nNumberOfBytesToWrite, lpOverlapped);
|
|
}
|
|
|
|
RPC_STATUS WS_HTTP2_CONNECTION::Receive(HANDLE hFile,
|
|
LPVOID lpBuffer,
|
|
DWORD nNumberOfBytesToRead,
|
|
LPDWORD lpNumberOfBytesRead,
|
|
LPOVERLAPPED lpOverlapped
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Does an asynchronous receive on the connection.
|
|
|
|
Arguments:
|
|
|
|
hFile - file to receive on
|
|
|
|
lpBuffer - buffer to receive into
|
|
|
|
nNumberOfBytesToRead - number of bytes to receive
|
|
|
|
lpNumberOfBytesRead - number of bytes read. Will never get filled
|
|
in this code path because it is async.
|
|
|
|
lpOverlapped - overlapped to use for the operation
|
|
|
|
Return Value:
|
|
|
|
WSA Error Code
|
|
|
|
--*/
|
|
{
|
|
return SANReceive(hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, lpOverlapped);
|
|
}
|
|
|
|
RPC_STATUS WS_HTTP2_CONNECTION::ProcessReceiveFailed (
|
|
IN RPC_STATUS EventStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Notifies a raw connection of receive failure.
|
|
|
|
Arguments:
|
|
|
|
EventStatus - error with which the receive failed
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK to return packet to runtime
|
|
or RPC_P_PACKET_CONSUMED to hide it.
|
|
|
|
--*/
|
|
{
|
|
// if we failed before we parsed the header, chances are the problem
|
|
// was with the header format. Treat it as protocol error.
|
|
if ((HeaderRead == FALSE) && (EventStatus == RPC_P_CONNECTION_SHUTDOWN))
|
|
EventStatus = RPC_S_PROTOCOL_ERROR;
|
|
|
|
return Channel->ReceiveComplete(EventStatus, http2ttRaw, pReadBuffer, 0);
|
|
}
|
|
|
|
RPC_STATUS WS_HTTP2_CONNECTION::ProcessSendComplete (
|
|
IN RPC_STATUS EventStatus,
|
|
IN CO_SEND_CONTEXT *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Notifies a raw connection of send completion (fail or succeed).
|
|
|
|
Arguments:
|
|
|
|
EventStatus - error with which the send failed
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK to return packet to runtime
|
|
or RPC_P_PACKET_CONSUMED to hide it.
|
|
|
|
--*/
|
|
{
|
|
HTTP2SendContext *HttpSendContext = (HTTP2SendContext *)SendContext;
|
|
VerifyValidSendContext(HttpSendContext);
|
|
|
|
return Channel->SendComplete(EventStatus, HttpSendContext);
|
|
}
|
|
|
|
RPC_STATUS WS_HTTP2_CONNECTION::ProcessRead(
|
|
IN DWORD bytes,
|
|
OUT BUFFER *pBuffer,
|
|
OUT PUINT pBufferLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Processes a connection oriented receive
|
|
complete. But in HTTP2 we no-op this and do all read
|
|
processing through the COMPLEX_T mechanism.
|
|
|
|
Arguments:
|
|
|
|
bytes - the number of read (not including those in iLastRead).
|
|
|
|
pBuffer - when returning RPC_S_OK will contain the message.
|
|
|
|
pBufferLength - when return RPC_S_OK will contain the message length.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK
|
|
|
|
--*/
|
|
{
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
RPC_STATUS WS_HTTP2_CONNECTION::ProcessReceiveComplete(
|
|
IN DWORD bytes,
|
|
OUT BUFFER *pBuffer,
|
|
OUT PUINT pBufferLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Processes a connection oriented receive
|
|
complete. It takes care of fragmentation.
|
|
|
|
Arguments:
|
|
|
|
bytes - the number of read (not including those in iLastRead).
|
|
|
|
pBuffer - when returning RPC_S_OK will contain the message.
|
|
|
|
pBufferLength - when return RPC_S_OK will contain the message length.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK to return packet to runtime
|
|
or RPC_P_PACKET_CONSUMED to hide it.
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
if (HeaderRead)
|
|
{
|
|
RpcStatus = BASE_CONNECTION::ProcessRead(bytes, pBuffer, pBufferLength);
|
|
|
|
if (RpcStatus == RPC_P_PARTIAL_RECEIVE)
|
|
return RpcStatus;
|
|
}
|
|
else if (bytes != 0)
|
|
{
|
|
ASSERT(ReadHeaderFn);
|
|
|
|
RpcStatus = ReadHeaderFn(this,
|
|
bytes,
|
|
(ULONG *)pBufferLength
|
|
);
|
|
|
|
if (RpcStatus == RPC_P_PARTIAL_RECEIVE)
|
|
return RpcStatus;
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
RpcStatus = BASE_CONNECTION::ProcessRead(*pBufferLength, pBuffer, pBufferLength);
|
|
}
|
|
|
|
if (RpcStatus == RPC_P_PARTIAL_RECEIVE)
|
|
return RpcStatus;
|
|
}
|
|
else
|
|
{
|
|
RpcStatus = RPC_P_CONNECTION_CLOSED;
|
|
}
|
|
|
|
// we corrupt the RTS packet if necessary
|
|
if ( gfRPCVerifierEnabled && (pRpcVerifierSettings->fCorruptionInjectServerReceives) && (RpcStatus == RPC_S_OK) )
|
|
{
|
|
if (IsRTSPacket(*pBuffer))
|
|
{
|
|
CorruptionInject(ServerReceive,
|
|
(UINT *)pBufferLength,
|
|
(void **)pBuffer);
|
|
|
|
LogEvent(SU_CORRUPT, EV_NOTIFY, *pBuffer, this, *pBufferLength, 0, 0);
|
|
}
|
|
}
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
RpcStatus = Channel->ReceiveComplete(RpcStatus,
|
|
http2ttRaw,
|
|
(BYTE *)*pBuffer,
|
|
*pBufferLength);
|
|
}
|
|
else
|
|
{
|
|
RpcStatus = Channel->ReceiveComplete(RpcStatus,
|
|
http2ttRaw,
|
|
NULL,
|
|
0);
|
|
}
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS WS_HTTP2_CONNECTION::Abort (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
No-op. This is called from common
|
|
transport code. We don't abort HTTP2
|
|
connections from common transport code. Ignore
|
|
this call.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK
|
|
|
|
--*/
|
|
{
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
void WS_HTTP2_CONNECTION::Free (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Acts like destructor. All memory needs to
|
|
be freed.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK
|
|
|
|
--*/
|
|
{
|
|
if (pReadBuffer)
|
|
{
|
|
RpcFreeBuffer(pReadBuffer);
|
|
pReadBuffer = NULL;
|
|
}
|
|
|
|
// This may be called on a partially initialized connection
|
|
// during its migration. Ignore such calls.
|
|
if (fIgnoreFree)
|
|
return;
|
|
|
|
// Unlink the object from the PnP list.
|
|
TransportProtocol::RemoveObjectFromProtocolList(this);
|
|
|
|
// Make sure we don't free the connection without closing the socket.
|
|
// When we close the socket, we set it to NULL.
|
|
ASSERT(Conn.Socket == NULL);
|
|
}
|
|
|
|
void WS_HTTP2_CONNECTION::RealAbort (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Aborts an HTTP2 connection.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_ABORT, HTTP2LOG_OT_RAW_CONNECTION, 0);
|
|
|
|
(void)WS_CONNECTION::Abort();
|
|
}
|
|
|
|
void WS_HTTP2_CONNECTION::Initialize (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initializes a raw connection
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
BASE_CONNECTION::Initialize();
|
|
#if DBG
|
|
// client and server virtual connections must initialize this. In debug
|
|
// builds toast anybody who forgets. Proxies don't care
|
|
type = 0xC0C0C0C0;
|
|
#endif
|
|
pAddress = NULL;
|
|
RpcpInitializeListHead(&ObjectList);
|
|
|
|
fIgnoreFree = FALSE;
|
|
|
|
// use explicit placement to initialize the vtable. We need this to
|
|
// be able to use the virtual functions
|
|
(void) new (this) WS_HTTP2_CONNECTION;
|
|
}
|
|
|
|
/*********************************************************************
|
|
WS_HTTP2_INITIAL_CONNECTION
|
|
*********************************************************************/
|
|
|
|
C_ASSERT(sizeof(rpcconn_common) == sizeof(CONN_RPC_HEADER));
|
|
|
|
RPC_STATUS WS_HTTP2_INITIAL_CONNECTION::ProcessRead(
|
|
IN DWORD BytesRead,
|
|
OUT BUFFER *pBuffer,
|
|
OUT PUINT pBufferLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Processes a connection oriented receive
|
|
complete. It determines whether HTTP2 or HTTP will be
|
|
used. For HTTP2, the actual work is done in
|
|
HTTP2ProcessComplexTReceive.
|
|
|
|
Arguments:
|
|
|
|
BytesRead - the number of read (not including those in iLastRead).
|
|
|
|
pBuffer - when returning RPC_S_OK will contain the message.
|
|
|
|
pBufferLength - when return RPC_S_OK will contain the message length.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for successful processing
|
|
RPC_PARTIAL_RECEIVE - not enough was received to tell
|
|
RPC_P_RECEIVE_FAILED - error occurred.
|
|
RPC_P_INITIALIZE_HTTP2_CONNECTION - offload the connection establishment to HTTP2ProcessComplexTReceive.
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
BYTE *Packet;
|
|
|
|
RpcStatus = BASE_CONNECTION::ProcessRead(BytesRead,
|
|
pBuffer,
|
|
pBufferLength
|
|
);
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
// ProcessRead guarantees that on return value of RPC_S_OK
|
|
// we have at least rpcconn_common bytes read successfully
|
|
Packet = (BYTE *)*pBuffer;
|
|
if (IsRTSPacket(Packet))
|
|
{
|
|
// The final initialization will take place in HTTP2ProcessComplexTReceive.
|
|
this->type |= COMPLEX_T;
|
|
|
|
// Signal to HTTP2ProcessComplexTReceive that we are in the process of
|
|
// connection establishment by returning RPC_P_INITIALIZE_HTTP2_CONNECTION.
|
|
RpcStatus = RPC_P_INITIALIZE_HTTP2_CONNECTION;
|
|
}
|
|
else
|
|
{
|
|
// morph the connection into WS_CONNECTION to serve
|
|
// HTTP requests. The only thing we need to change is the
|
|
// vtable. We have a little bit of extra goo at the end, but
|
|
// that's ok.
|
|
(void) new (this) WS_CONNECTION;
|
|
}
|
|
}
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
|
|
RPC_STATUS WS_HTTP2_INITIAL_CONNECTION::Abort(
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Aborts an WS_HTTP2_INITIAL_CONNECTION connection.
|
|
Very rare to be called.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK
|
|
|
|
--*/
|
|
{
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_ABORT, HTTP2LOG_OT_INITIAL_RAW_CONNECTION, 0);
|
|
|
|
WS_HTTP2_CONNECTION::RealAbort();
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2BottomChannel
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS HTTP2BottomChannel::SendComplete (
|
|
IN RPC_STATUS EventStatus,
|
|
IN OUT HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send complete notification
|
|
|
|
Arguments:
|
|
|
|
EventStatus - status of the operation
|
|
|
|
SendContext - the send context
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_SEND_COMPLETE, HTTP2LOG_OT_BOTTOM_CHANNEL, EventStatus);
|
|
|
|
RpcStatus = HTTP2TransportChannel::SendComplete(EventStatus,
|
|
SendContext
|
|
);
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_SEND_COMPLETE, HTTP2LOG_OT_BOTTOM_CHANNEL, RpcStatus);
|
|
|
|
return AsyncCompleteHelper(RpcStatus);
|
|
}
|
|
|
|
RPC_STATUS HTTP2BottomChannel::ReceiveComplete (
|
|
IN RPC_STATUS EventStatus,
|
|
IN HTTP2TrafficType TrafficType,
|
|
IN BYTE *Buffer,
|
|
IN UINT BufferLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Receive complete notification
|
|
|
|
Arguments:
|
|
|
|
EventStatus - status of the operation
|
|
|
|
TrafficType - the type of traffic we have received
|
|
|
|
Buffer - the received buffer (success only)
|
|
|
|
BufferLength - the length of the received buffer (success only)
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_RECV_COMPLETE, HTTP2LOG_OT_BOTTOM_CHANNEL, EventStatus);
|
|
|
|
RpcStatus = HTTP2TransportChannel::ReceiveComplete(EventStatus,
|
|
TrafficType,
|
|
Buffer,
|
|
BufferLength
|
|
);
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_RECV_COMPLETE, HTTP2LOG_OT_BOTTOM_CHANNEL, RpcStatus);
|
|
|
|
return AsyncCompleteHelper(RpcStatus);
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2SocketTransportChannel
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS HTTP2SocketTransportChannel::Send (
|
|
IN OUT HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send request. Forward the send to the raw connection
|
|
|
|
Arguments:
|
|
|
|
SendContext - the send context
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
DWORD Ignored;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_SEND, HTTP2LOG_OT_SOCKET_CHANNEL, (ULONG_PTR)SendContext);
|
|
|
|
// route this through the completion port
|
|
SendContext->Write.ol.hEvent = NULL;
|
|
SendContext->Write.pAsyncObject = RawConnection;
|
|
|
|
// N.B. The Winsock provider will touch the overlapped on the return path. We need
|
|
// to make sure that either the overlapped is around (in which case we can use WSASend),
|
|
// or otherwise use UTIL_WriteFile2 which does not touch the overlapped on return.
|
|
// We know the overlapped may not be around when this is a proxy data send, any type
|
|
// of RTS send, or abandoned send. All non-proxy, not-abandoned data sends will have
|
|
// the overlapped around.
|
|
if ((SendContext->TrafficType == http2ttData)
|
|
&& (((SendContext->Flags & (SendContextFlagAbandonedSend | SendContextFlagProxySend)) == 0)))
|
|
{
|
|
RpcStatus = RawConnection->WS_HTTP2_CONNECTION::SANSend(
|
|
RawConnection->Conn.Handle,
|
|
SendContext->pWriteBuffer,
|
|
SendContext->maxWriteBuffer,
|
|
&Ignored,
|
|
&SendContext->Write.ol
|
|
);
|
|
}
|
|
else
|
|
{
|
|
RpcStatus = RawConnection->WS_HTTP2_CONNECTION::Send(
|
|
RawConnection->Conn.Handle,
|
|
SendContext->pWriteBuffer,
|
|
SendContext->maxWriteBuffer,
|
|
&Ignored,
|
|
&SendContext->Write.ol
|
|
);
|
|
}
|
|
|
|
if ( (RpcStatus != RPC_S_OK)
|
|
&& (RpcStatus != ERROR_IO_PENDING) )
|
|
{
|
|
VALIDATE(RpcStatus)
|
|
{
|
|
ERROR_NETNAME_DELETED,
|
|
ERROR_GRACEFUL_DISCONNECT,
|
|
ERROR_NO_DATA,
|
|
ERROR_NO_SYSTEM_RESOURCES,
|
|
ERROR_WORKING_SET_QUOTA,
|
|
ERROR_BAD_COMMAND,
|
|
ERROR_OPERATION_ABORTED,
|
|
ERROR_WORKING_SET_QUOTA,
|
|
WSAECONNABORTED,
|
|
WSAECONNRESET
|
|
} END_VALIDATE;
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_SEND, HTTP2LOG_OT_SOCKET_CHANNEL, RPC_P_SEND_FAILED);
|
|
|
|
return(RPC_P_SEND_FAILED);
|
|
}
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_SEND, HTTP2LOG_OT_SOCKET_CHANNEL, RPC_S_OK);
|
|
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
RPC_STATUS HTTP2SocketTransportChannel::Receive (
|
|
IN HTTP2TrafficType TrafficType
|
|
)
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_RECV, HTTP2LOG_OT_SOCKET_CHANNEL, TrafficType);
|
|
|
|
ASSERT(TrafficType == http2ttRaw);
|
|
|
|
RpcStatus = CO_Recv(RawConnection);
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_RECV, HTTP2LOG_OT_SOCKET_CHANNEL, RpcStatus);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
void HTTP2SocketTransportChannel::Abort (
|
|
IN RPC_STATUS RpcStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Abort the channel
|
|
|
|
Arguments:
|
|
|
|
RpcStatus - the error code with which we abort
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_ABORT, HTTP2LOG_OT_SOCKET_CHANNEL, RpcStatus);
|
|
|
|
RawConnection->RealAbort();
|
|
}
|
|
|
|
void HTTP2SocketTransportChannel::FreeObject (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Frees the object. Acts like a destructor for the
|
|
channel.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_FREE_OBJECT, HTTP2LOG_OT_SOCKET_CHANNEL, 0);
|
|
|
|
RawConnection->Free();
|
|
|
|
HTTP2SocketTransportChannel::~HTTP2SocketTransportChannel();
|
|
}
|
|
|
|
void HTTP2SocketTransportChannel::Reset (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Reset the channel for next open/send/receive. This is
|
|
used in submission context only and implies there are no
|
|
pending operations on the channel. It is used on the client
|
|
during opening the connection to do quick negotiation on the
|
|
same connection instead of opening a new connection every time.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
RawConnection->HeaderRead = FALSE;
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2FragmentReceiver
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS HTTP2FragmentReceiver::Receive (
|
|
IN HTTP2TrafficType TrafficType
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Receive request
|
|
|
|
Arguments:
|
|
|
|
TrafficType - the type of traffic we want to receive
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
if (iLastRead && iLastRead == MaxReadBuffer)
|
|
{
|
|
ASSERT(pReadBuffer);
|
|
|
|
// This means we received a coalesced read of a complete
|
|
// message. (Or that we received a coalesced read < header size)
|
|
// We should complete that as it's own IO in neutral context.
|
|
// This is very rare.
|
|
(void) COMMON_PostRuntimeEvent(GetPostRuntimeEvent(),
|
|
this
|
|
);
|
|
|
|
return(RPC_S_OK);
|
|
}
|
|
|
|
ASSERT(iLastRead == 0 || (iLastRead < MaxReadBuffer));
|
|
|
|
return(PostReceive());
|
|
};
|
|
|
|
RPC_STATUS HTTP2FragmentReceiver::ReceiveComplete (
|
|
IN RPC_STATUS EventStatus,
|
|
IN HTTP2TrafficType TrafficType,
|
|
IN OUT BYTE **Buffer,
|
|
IN OUT UINT *BufferLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Processes a receive complete notification.
|
|
|
|
Arguments:
|
|
|
|
EventStatus - the status code of the operation.
|
|
|
|
TrafficType - the type of traffic we have received
|
|
|
|
Buffer - the buffer. Must be NULL at this level on input. On
|
|
output contains the buffer for the current receive. If NULL
|
|
on output, we did not have a full packet. Undefined on failure.
|
|
|
|
BufferLength - the actual number of bytes received. On output the
|
|
number of bytes for the current packet. If 0 on output,
|
|
we did not have a complete packet. Undefined on failure.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error. Note that unlike BASE_CONNECTION::ProcessRead,
|
|
this function does not return RPC_P_PARTIAL_RECEIVE. See note section for
|
|
more information.
|
|
|
|
Note:
|
|
|
|
NULL returned Buffer and RPC_S_OK means a partial receive.
|
|
|
|
--*/
|
|
{
|
|
BYTE *LocalReadBuffer;
|
|
ULONG MessageSize;
|
|
ULONG ExtraSize;
|
|
ULONG AllocSize;
|
|
BYTE *NewBuffer;
|
|
BOOL DoNotComplete;
|
|
ULONG LocalBufferLength = *BufferLength;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_RECV_COMPLETE, HTTP2LOG_OT_FRAGMENT_RECEIVER, LocalBufferLength);
|
|
|
|
DoNotComplete = FALSE;
|
|
if (EventStatus == RPC_S_OK)
|
|
{
|
|
ASSERT(pReadBuffer);
|
|
|
|
LocalBufferLength += iLastRead;
|
|
|
|
ASSERT(LocalBufferLength <= MaxReadBuffer);
|
|
|
|
if (LocalBufferLength < sizeof(CONN_RPC_HEADER))
|
|
{
|
|
// Not a whole header, resubmit the read and continue.
|
|
|
|
iLastRead = LocalBufferLength;
|
|
|
|
EventStatus = PostReceive();
|
|
|
|
if (EventStatus == RPC_S_OK)
|
|
DoNotComplete = TRUE;
|
|
else
|
|
LocalReadBuffer = NULL;
|
|
}
|
|
else
|
|
{
|
|
MessageSize = MessageLength((PCONN_RPC_HEADER)pReadBuffer);
|
|
|
|
if (MessageSize < sizeof(CONN_RPC_HEADER))
|
|
{
|
|
ASSERT(MessageSize >= sizeof(CONN_RPC_HEADER));
|
|
EventStatus = RPC_P_RECEIVE_FAILED;
|
|
LocalReadBuffer = NULL;
|
|
}
|
|
else if (LocalBufferLength == MessageSize)
|
|
{
|
|
// All set, have a complete request.
|
|
LocalReadBuffer = pReadBuffer;
|
|
LocalBufferLength = MessageSize;
|
|
|
|
iLastRead = 0;
|
|
pReadBuffer = 0;
|
|
}
|
|
else if (MessageSize > LocalBufferLength)
|
|
{
|
|
// Don't have a complete message, realloc if needed and
|
|
// resubmit a read for the remaining bytes.
|
|
|
|
if (MaxReadBuffer < MessageSize)
|
|
{
|
|
// Buffer too small for the message.
|
|
EventStatus = TransConnectionReallocPacket(NULL,
|
|
&pReadBuffer,
|
|
LocalBufferLength,
|
|
MessageSize);
|
|
|
|
if (EventStatus == RPC_S_OK)
|
|
{
|
|
// increase the post size, but not if we are in direct
|
|
// buffer mode.
|
|
if (gBCacheMode == BCacheModeCached)
|
|
iPostSize = MessageSize;
|
|
}
|
|
}
|
|
|
|
if (EventStatus == RPC_S_OK)
|
|
{
|
|
// Setup to receive exactly the remaining bytes of the message.
|
|
iLastRead = LocalBufferLength;
|
|
MaxReadBuffer = MessageSize;
|
|
|
|
EventStatus = PostReceive();
|
|
|
|
if (EventStatus == RPC_S_OK)
|
|
DoNotComplete = TRUE;
|
|
else
|
|
LocalReadBuffer = NULL;
|
|
}
|
|
else
|
|
{
|
|
LocalReadBuffer = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Coalesced read, save extra data. Very uncommon
|
|
|
|
ASSERT(LocalBufferLength > MessageSize);
|
|
|
|
// The first message and size will be returned
|
|
|
|
LocalReadBuffer = pReadBuffer;
|
|
|
|
ExtraSize = LocalBufferLength - MessageSize;
|
|
|
|
LocalBufferLength = MessageSize;
|
|
|
|
// Try to find a good size of the extra PDU(s)
|
|
if (ExtraSize < sizeof(CONN_RPC_HEADER))
|
|
{
|
|
// Not a whole header, we'll assume gPostSize;
|
|
|
|
AllocSize = gPostSize;
|
|
}
|
|
else
|
|
{
|
|
#ifdef _M_IA64
|
|
// The first packet may not contain a number of bytes
|
|
// that align the second on an 8-byte boundary. Hence, the
|
|
// structure may end up unaligned.
|
|
AllocSize = MessageLengthUnaligned((PCONN_RPC_HEADER)(pReadBuffer
|
|
+ MessageSize));
|
|
#else
|
|
AllocSize = MessageLength((PCONN_RPC_HEADER)(pReadBuffer
|
|
+ MessageSize));
|
|
#endif
|
|
}
|
|
|
|
if (AllocSize < ExtraSize)
|
|
{
|
|
// This can happen if there are more than two PDUs coalesced together
|
|
// in the buffer. Or if the PDU is invalid. Or if the iPostSize is
|
|
// smaller than the next PDU.
|
|
AllocSize = ExtraSize;
|
|
}
|
|
|
|
// Allocate a new buffer to save the extra data for the next read.
|
|
NewBuffer = (BYTE *)RpcAllocateBuffer(AllocSize);
|
|
|
|
if (0 == NewBuffer)
|
|
{
|
|
// We have a complete request. We could process the request and
|
|
// close the connection only after trying to send the reply.
|
|
|
|
LocalReadBuffer = NULL;
|
|
LocalBufferLength = 0;
|
|
|
|
EventStatus = RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(pReadBuffer);
|
|
|
|
// Save away extra data for the next receive
|
|
RpcpMemoryCopy(NewBuffer,
|
|
pReadBuffer + LocalBufferLength,
|
|
ExtraSize);
|
|
pReadBuffer = NewBuffer;
|
|
iLastRead = ExtraSize;
|
|
MaxReadBuffer = AllocSize;
|
|
|
|
ASSERT(iLastRead <= MaxReadBuffer);
|
|
|
|
EventStatus = RPC_S_OK;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// in failure cases we keep the buffer. We will
|
|
// free it on Abort.
|
|
LocalReadBuffer = NULL;
|
|
}
|
|
|
|
if (DoNotComplete == FALSE)
|
|
{
|
|
*Buffer = LocalReadBuffer;
|
|
*BufferLength = LocalBufferLength;
|
|
|
|
if (gfRPCVerifierEnabled)
|
|
{
|
|
// check whether we are on the proxy side or client side.
|
|
// Currently only those two use the HTTP2FragmentReceiver
|
|
if (TopChannel->IsProxyChannel())
|
|
{
|
|
CorruptionInject(ServerReceive,
|
|
(UINT *)&LocalBufferLength,
|
|
(void **)&LocalReadBuffer);
|
|
}
|
|
else
|
|
{
|
|
CorruptionInject(ClientReceive,
|
|
(UINT *)&LocalBufferLength,
|
|
(void **)&LocalReadBuffer);
|
|
}
|
|
}
|
|
|
|
EventStatus = UpperLayer->ReceiveComplete(EventStatus,
|
|
TrafficType,
|
|
LocalReadBuffer,
|
|
LocalBufferLength
|
|
);
|
|
|
|
EventStatus = AsyncCompleteHelper(EventStatus);
|
|
// don't touch the this pointer after AsyncCompleteHelper.
|
|
// It could be gone.
|
|
}
|
|
else
|
|
{
|
|
*Buffer = NULL;
|
|
*BufferLength = 0;
|
|
EventStatus = RPC_S_OK;
|
|
}
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_RECV_COMPLETE, HTTP2LOG_OT_FRAGMENT_RECEIVER, *BufferLength);
|
|
|
|
return EventStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2FragmentReceiver::DepositReceivedData (
|
|
IN ULONG DataSize,
|
|
IN BYTE *Data
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Deposits data directly in the receive queue. That is, the
|
|
data arriving from this method will be posted in such a way as
|
|
if they were received from the network on a previous receive.
|
|
This means receive complete will not be issued for this call
|
|
and another receive must be issued to retrieve the data passed
|
|
in.
|
|
|
|
Arguments:
|
|
|
|
DataSize - the size of the data buffer.
|
|
|
|
Data - the buffer itself. This function makes its own copy of the
|
|
data. Caller is responsible for freeing the passed in
|
|
parameter.
|
|
|
|
Return Value:
|
|
|
|
Notes:
|
|
|
|
Currently the method is structured to function correctly only
|
|
when no data already received exist. It will ASSERT if data
|
|
are already present. It is straightforward to rework the method
|
|
to append the incoming data to existing data, but it's not
|
|
necessary as it will be currently called on first data chunk
|
|
only.
|
|
|
|
--*/
|
|
{
|
|
// make sure that this happens only when there are no
|
|
// accumulated data
|
|
ASSERT (iLastRead == 0);
|
|
|
|
if (pReadBuffer == NULL)
|
|
{
|
|
pReadBuffer = (BYTE *)RpcAllocateBuffer(DataSize);
|
|
if (pReadBuffer == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
RpcpMemoryCopy (pReadBuffer, Data, DataSize);
|
|
iLastRead = DataSize;
|
|
MaxReadBuffer = DataSize;
|
|
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2WinHttpTransportChannel
|
|
*********************************************************************/
|
|
|
|
// our public constants are aligned with HTTP constants. Even though it is
|
|
// unlikely for either to change, make sure they don't. If they do, we need
|
|
// a remapping function as we use them interchangeably in the code
|
|
C_ASSERT(WINHTTP_AUTH_SCHEME_BASIC == RPC_C_HTTP_AUTHN_SCHEME_BASIC);
|
|
C_ASSERT(WINHTTP_AUTH_SCHEME_NTLM == RPC_C_HTTP_AUTHN_SCHEME_NTLM);
|
|
C_ASSERT(WINHTTP_AUTH_SCHEME_PASSPORT == RPC_C_HTTP_AUTHN_SCHEME_PASSPORT);
|
|
C_ASSERT(WINHTTP_AUTH_SCHEME_DIGEST == RPC_C_HTTP_AUTHN_SCHEME_DIGEST);
|
|
C_ASSERT(WINHTTP_AUTH_SCHEME_NEGOTIATE == RPC_C_HTTP_AUTHN_SCHEME_NEGOTIATE);
|
|
|
|
HTTP2WinHttpTransportChannel::HTTP2WinHttpTransportChannel (
|
|
OUT RPC_STATUS *RpcStatus
|
|
) : Mutex (RpcStatus)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
HTTP2WinHttpTransportChannel constructor.
|
|
|
|
Arguments:
|
|
|
|
RpcStatus - on output will contain the result of the
|
|
initialization.
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
hSession = NULL;
|
|
hConnect = NULL;
|
|
hRequest = NULL;
|
|
SyncEvent = NULL;
|
|
|
|
RpcpInitializeListHead(&BufferQueueHead);
|
|
|
|
SendsPending = 0;
|
|
|
|
State = whtcsNew;
|
|
|
|
AsyncError = RPC_S_INTERNAL_ERROR;
|
|
|
|
HttpCredentials = NULL;
|
|
|
|
KeepAlive = FALSE;
|
|
|
|
CredentialsSetForScheme = 0;
|
|
|
|
PreviousRequestContentLength = -1;
|
|
|
|
ChosenAuthScheme = 0;
|
|
|
|
DelayedReceiveTrafficType = http2ttNone;
|
|
|
|
CurrentSendContext = NULL;
|
|
}
|
|
|
|
const RPC_CHAR ContentLengthHeader[] = L"Content-Length:";
|
|
|
|
RPC_STATUS HTTP2WinHttpTransportChannel::Open (
|
|
IN HTTPResolverHint *Hint,
|
|
IN const RPC_CHAR *Verb,
|
|
IN const RPC_CHAR *Url,
|
|
IN const RPC_CHAR *AcceptType,
|
|
IN ULONG ContentLength,
|
|
IN ULONG CallTimeout,
|
|
IN RPC_HTTP_TRANSPORT_CREDENTIALS_W *HttpCredentials,
|
|
IN ULONG ChosenAuthScheme,
|
|
IN const BYTE *AdditionalData OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Opens the connection to the proxy. We know that a failed Open
|
|
will be followed by Abort.
|
|
|
|
Arguments:
|
|
|
|
Hint - the resolver hint
|
|
|
|
Verb - the verb to use.
|
|
|
|
Url - the url to connect to.
|
|
|
|
AcceptType - string representation of the accept type.
|
|
|
|
ContentLength - the content length for the request (i.e. the
|
|
channel lifetime)
|
|
|
|
CallTimeout - the timeout for the operation
|
|
|
|
HttpCredentials - the HTTP transport credentials
|
|
|
|
ChosenAuthScheme - the chosen auth scheme. 0 if no auth scheme is chosen.
|
|
|
|
AdditionalData - additional data to send with the header. Must be set iff
|
|
AdditionalDataLength != 0
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error.
|
|
|
|
--*/
|
|
{
|
|
BOOL HttpResult = FALSE;
|
|
DWORD dwReadBufferSizeSize = sizeof(ULONG);
|
|
ULONG WinHttpAccessType;
|
|
LPCWSTR AcceptTypes[2];
|
|
ULONG LastError;
|
|
RPC_CHAR *UnicodeString;
|
|
ULONG UnicodeStringSize; // in characters including null terminated NULL
|
|
RPC_STATUS RpcStatus;
|
|
ULONG FlagsToAdd;
|
|
RPC_HTTP_TRANSPORT_CREDENTIALS_W *TransHttpCredentials;
|
|
ULONG AdditionalDataLengthToUse;
|
|
ULONG ContentLengthToUse;
|
|
ULONG BytesAvailable;
|
|
RPC_CHAR ContentLengthString[40]; // enough space for "Content-Length:" + channel lifetime
|
|
BOOL IsInChannel;
|
|
BOOL TestOverrideUsed;
|
|
RPC_CHAR *User;
|
|
RPC_CHAR *Password;
|
|
RPC_CHAR *Domain;
|
|
HANDLE LocalEvent = NULL;
|
|
ULONG SecLevel;
|
|
BOOL LanManHashDisabled;
|
|
ULONG DomainAndUserLength; // length in characters not including null terminator
|
|
ULONG DomainLength; // length in characters not including null terminator
|
|
ULONG UserLength; // length in characters not including null terminator
|
|
RPC_CHAR *DomainAndUserName;
|
|
RPC_CHAR *CurrentPos;
|
|
ULONG HttpProxyLength; // length in characters not including null terminator
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_OPEN, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
(ULONG_PTR)ContentLength);
|
|
|
|
this->HttpCredentials = HttpCredentials;
|
|
|
|
// Open can be called multiple times to send opening requests.
|
|
// Make sure general initialization is done only once.
|
|
if (hSession == NULL)
|
|
{
|
|
ASSERT(hConnect == NULL);
|
|
|
|
State = whtcsOpeningRequest;
|
|
|
|
ASSERT(Hint->AccessType != rpcpatUnknown);
|
|
if (Hint->AccessType == rpcpatDirect)
|
|
{
|
|
WinHttpAccessType = WINHTTP_ACCESS_TYPE_NO_PROXY;
|
|
UnicodeString = NULL;
|
|
UnicodeStringSize = 0;
|
|
}
|
|
else
|
|
{
|
|
WinHttpAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
|
|
|
|
// 1 is terminating NULL, 5 is the max port number for a USHORT (65536)
|
|
// and 1 is the column b/n them
|
|
HttpProxyLength = RpcpStringLengthA(Hint->HTTPProxy);
|
|
UnicodeStringSize = HttpProxyLength + 1 + 6;
|
|
UnicodeString = new RPC_CHAR [UnicodeStringSize];
|
|
|
|
if (UnicodeString == NULL)
|
|
{
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_OPEN, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
RPC_S_OUT_OF_MEMORY);
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
FullAnsiToUnicode(Hint->HTTPProxy, UnicodeString);
|
|
// go to the end of the string and append the port
|
|
CurrentPos = UnicodeString + HttpProxyLength;
|
|
*CurrentPos = ':';
|
|
CurrentPos ++;
|
|
PortNumberToEndpoint(Hint->HTTPProxyPort, CurrentPos);
|
|
}
|
|
|
|
// Use WinHttpOpen to obtain a session handle.
|
|
hSession = WinHttpOpenImp( L"MSRPC",
|
|
WinHttpAccessType,
|
|
UnicodeString,
|
|
WINHTTP_NO_PROXY_BYPASS,
|
|
WINHTTP_FLAG_ASYNC
|
|
);
|
|
if (!hSession)
|
|
{
|
|
VALIDATE(GetLastError())
|
|
{
|
|
ERROR_NOT_ENOUGH_MEMORY
|
|
} END_VALIDATE;
|
|
|
|
if (UnicodeString)
|
|
delete [] UnicodeString;
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_OPEN, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
RPC_S_OUT_OF_MEMORY);
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
// Set the callback to be used by WinHttp to notify us of IO completion.
|
|
WinHttpSetStatusCallbackImp( hSession,
|
|
WinHttpCallback,
|
|
WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS,
|
|
NULL // Reserved: must be NULL
|
|
);
|
|
|
|
// Set the communication timeout.
|
|
HttpResult = WinHttpSetOptionImp( hSession,
|
|
WINHTTP_OPTION_CONNECT_TIMEOUT,
|
|
(LPVOID)&CallTimeout,
|
|
sizeof(ULONG)
|
|
);
|
|
|
|
// this function cannot fail unless we give it invalid parameters
|
|
ASSERT(HttpResult == TRUE);
|
|
|
|
// Set the send/receive timeout.
|
|
CallTimeout = 30 * 60 * 1000;
|
|
HttpResult = WinHttpSetOptionImp( hSession,
|
|
WINHTTP_OPTION_SEND_TIMEOUT,
|
|
(LPVOID)&CallTimeout,
|
|
sizeof(ULONG)
|
|
);
|
|
|
|
// this function cannot fail unless we give it invalid parameters
|
|
ASSERT(HttpResult == TRUE);
|
|
|
|
CallTimeout = 30 * 60 * 1000;
|
|
HttpResult = WinHttpSetOptionImp( hSession,
|
|
WINHTTP_OPTION_RECEIVE_TIMEOUT,
|
|
(LPVOID)&CallTimeout,
|
|
sizeof(ULONG)
|
|
);
|
|
|
|
// this function cannot fail unless we give it invalid parameters
|
|
ASSERT(HttpResult == TRUE);
|
|
|
|
RpcStatus = TopChannel->IsInChannel(&IsInChannel);
|
|
// this cannot fail here. We're opening the channel
|
|
ASSERT(RpcStatus == RPC_S_OK);
|
|
|
|
if (IsInChannel && InChannelTargetTestOverride)
|
|
{
|
|
TestOverrideUsed = TRUE;
|
|
UnicodeString = InChannelTargetTestOverride;
|
|
}
|
|
else if (!IsInChannel && OutChannelTargetTestOverride)
|
|
{
|
|
TestOverrideUsed = TRUE;
|
|
UnicodeString = OutChannelTargetTestOverride;
|
|
}
|
|
else
|
|
{
|
|
TestOverrideUsed = FALSE;
|
|
|
|
if (Hint->ProxyNameLength + 1 > UnicodeStringSize)
|
|
{
|
|
if (UnicodeString)
|
|
delete [] UnicodeString;
|
|
UnicodeString = new RPC_CHAR [Hint->ProxyNameLength + 1];
|
|
if (UnicodeString == NULL)
|
|
{
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_OPEN, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
RPC_S_OUT_OF_MEMORY);
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
|
|
FullAnsiToUnicode(Hint->RpcProxy, UnicodeString);
|
|
}
|
|
|
|
// Specify an HTTP server to talk to.
|
|
hConnect = WinHttpConnectImp( hSession,
|
|
UnicodeString,
|
|
Hint->RpcProxyPort,
|
|
NULL // Reserved: must be NULL
|
|
);
|
|
|
|
if (TestOverrideUsed == FALSE)
|
|
delete [] UnicodeString;
|
|
|
|
if (!hConnect)
|
|
{
|
|
VALIDATE(GetLastError())
|
|
{
|
|
ERROR_NOT_ENOUGH_MEMORY
|
|
} END_VALIDATE;
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_OPEN, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
RPC_S_OUT_OF_MEMORY);
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
// Create an HTTP Request handle.
|
|
AcceptTypes[0] = AcceptType;
|
|
AcceptTypes[1] = NULL;
|
|
|
|
if (HttpCredentials && (HttpCredentials->Flags & RPC_C_HTTP_FLAG_USE_SSL))
|
|
FlagsToAdd = WINHTTP_FLAG_SECURE;
|
|
else
|
|
FlagsToAdd = 0;
|
|
hRequest = WinHttpOpenRequestImp( hConnect,
|
|
Verb,
|
|
Url,
|
|
NULL, // Version: HTTP/1.1
|
|
WINHTTP_NO_REFERER, // Referer: none
|
|
AcceptTypes, // AcceptTypes: all
|
|
WINHTTP_FLAG_REFRESH | FlagsToAdd // Flags
|
|
);
|
|
if (!hRequest)
|
|
{
|
|
VALIDATE(GetLastError())
|
|
{
|
|
ERROR_NOT_ENOUGH_MEMORY
|
|
} END_VALIDATE;
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_OPEN, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
RPC_S_OUT_OF_MEMORY);
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
// Query the optimal read buffer size.
|
|
// We can only query the buffer size from the request handle.
|
|
HttpResult = WinHttpQueryOptionImp( hRequest,
|
|
WINHTTP_OPTION_READ_BUFFER_SIZE,
|
|
(LPVOID)&iPostSize,
|
|
&dwReadBufferSizeSize
|
|
);
|
|
// this cannot fail unless we give it invalid parameters
|
|
ASSERT (HttpResult == TRUE);
|
|
|
|
ASSERT(dwReadBufferSizeSize != 0);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(hConnect != NULL);
|
|
ASSERT(hRequest != NULL);
|
|
}
|
|
|
|
// do we have a winner? If yes, have we already set the credentials for
|
|
// this scheme? Note that for Basic we need to set them every time.
|
|
if (ChosenAuthScheme
|
|
&&
|
|
(
|
|
(ChosenAuthScheme != CredentialsSetForScheme)
|
|
||
|
|
(ChosenAuthScheme == RPC_C_HTTP_AUTHN_SCHEME_BASIC)
|
|
)
|
|
)
|
|
{
|
|
// yes. Just use it
|
|
ASSERT(HttpCredentials);
|
|
|
|
// we will set the auto logon policy to low (i.e. send NTLM credentials)
|
|
// in two cases. One is if SSL & mutual auth are used. The second is if LM
|
|
// hash is disabled (i.e. the NTLM negotiate leg does not expose user credentials)
|
|
// first, check whether the hash is enabled
|
|
RpcStatus = IsLanManHashDisabled(&LanManHashDisabled);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
VALIDATE(RpcStatus)
|
|
{
|
|
RPC_S_OUT_OF_MEMORY
|
|
} END_VALIDATE;
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_OPEN, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
RpcStatus);
|
|
return RpcStatus;
|
|
}
|
|
|
|
if (
|
|
(
|
|
(HttpCredentials->Flags & RPC_C_HTTP_FLAG_USE_SSL)
|
|
&&
|
|
(HttpCredentials->ServerCertificateSubject)
|
|
)
|
|
||
|
|
(LanManHashDisabled)
|
|
)
|
|
{
|
|
SecLevel = WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW;
|
|
HttpResult = WinHttpSetOptionImp( hRequest,
|
|
WINHTTP_OPTION_AUTOLOGON_POLICY,
|
|
&SecLevel,
|
|
sizeof(ULONG)
|
|
);
|
|
|
|
// this function cannot fail unless we give it invalid parameters
|
|
ASSERT(HttpResult == TRUE);
|
|
}
|
|
|
|
TransHttpCredentials = I_RpcTransGetHttpCredentials(HttpCredentials);
|
|
if (TransHttpCredentials == NULL)
|
|
{
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_OPEN, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
RPC_S_OUT_OF_MEMORY);
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
if (TransHttpCredentials->TransportCredentials)
|
|
{
|
|
User = TransHttpCredentials->TransportCredentials->User;
|
|
Domain = TransHttpCredentials->TransportCredentials->Domain;
|
|
Password = TransHttpCredentials->TransportCredentials->Password;
|
|
DomainLength = RpcpStringLength(Domain);
|
|
UserLength = RpcpStringLength(User);
|
|
|
|
// add 1 for '\'
|
|
DomainAndUserLength = DomainLength + 1 + UserLength;
|
|
// add 1 for terminator
|
|
DomainAndUserName = new RPC_CHAR [DomainAndUserLength + 1];
|
|
if (DomainAndUserName == NULL)
|
|
{
|
|
I_RpcTransFreeHttpCredentials(TransHttpCredentials);
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_OPEN, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
RPC_S_OUT_OF_MEMORY);
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
RpcpMemoryCopy(DomainAndUserName, Domain, DomainLength * 2);
|
|
DomainAndUserName[DomainLength] = '\\';
|
|
RpcpMemoryCopy(DomainAndUserName + DomainLength + 1, User, UserLength * 2);
|
|
DomainAndUserName[DomainLength + 1 + UserLength] = '\0';
|
|
}
|
|
else
|
|
{
|
|
if (ChosenAuthScheme == RPC_C_HTTP_AUTHN_SCHEME_BASIC)
|
|
{
|
|
// Basic does not support implicit credentials
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_OPEN, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
RPC_S_ACCESS_DENIED);
|
|
return RPC_S_ACCESS_DENIED;
|
|
}
|
|
User = NULL;
|
|
Password = NULL;
|
|
DomainAndUserName = NULL;
|
|
}
|
|
|
|
HttpResult = WinHttpSetCredentialsImp (hRequest,
|
|
WINHTTP_AUTH_TARGET_SERVER,
|
|
ChosenAuthScheme,
|
|
DomainAndUserName,
|
|
Password,
|
|
NULL
|
|
);
|
|
|
|
// success or error, free the domain and user name
|
|
if (DomainAndUserName)
|
|
{
|
|
// technically speaking, we don't have to zero out user and domain name
|
|
// since they are not secret. However, the way the heap works it is likely
|
|
// that they will be next to our credentials, which are not encrypted very
|
|
// strongly. So wipe out the domain and user to prevent an attacker from
|
|
// using them to locate the credentials
|
|
SecureZeroMemory(DomainAndUserName, DomainAndUserLength);
|
|
delete [] DomainAndUserName;
|
|
}
|
|
|
|
if (!HttpResult)
|
|
{
|
|
LastError = GetLastError();
|
|
|
|
VALIDATE(LastError)
|
|
{
|
|
ERROR_NOT_ENOUGH_MEMORY
|
|
} END_VALIDATE;
|
|
}
|
|
|
|
I_RpcTransFreeHttpCredentials(TransHttpCredentials);
|
|
|
|
if (!HttpResult)
|
|
{
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_OPEN, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
RPC_S_OUT_OF_MEMORY);
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
// remember that we have already set credentials for this scheme
|
|
CredentialsSetForScheme = ChosenAuthScheme;
|
|
}
|
|
|
|
LocalEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
if (LocalEvent == NULL)
|
|
{
|
|
RpcStatus = RPC_S_OUT_OF_MEMORY;
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
SyncEvent = LocalEvent;
|
|
LastError = RPC_S_OK;
|
|
|
|
// Send a Request.
|
|
if (AdditionalData)
|
|
{
|
|
// if additional data to append, send them immediately
|
|
AdditionalDataLengthToUse = ContentLength;
|
|
ContentLengthToUse = ContentLength;
|
|
}
|
|
else
|
|
{
|
|
AdditionalDataLengthToUse = 0;
|
|
ContentLengthToUse = ContentLength;
|
|
}
|
|
|
|
if ((PreviousRequestContentLength != -1) && (ContentLengthToUse != PreviousRequestContentLength))
|
|
{
|
|
// WinHttp normally doesn't update the content-length header if you reuse the
|
|
// request. Do that now.
|
|
RpcpMemoryCopy(ContentLengthString, ContentLengthHeader, sizeof(ContentLengthHeader));
|
|
RpcpItow(ContentLengthToUse, ContentLengthString + (sizeof(ContentLengthHeader) / sizeof(RPC_CHAR)) - 1, 10);
|
|
HttpResult = WinHttpAddRequestHeadersImp (hRequest,
|
|
ContentLengthString,
|
|
-1, // dwHeadersLength - have WinHttp calculate it
|
|
WINHTTP_ADDREQ_FLAG_REPLACE
|
|
);
|
|
|
|
if (!HttpResult)
|
|
{
|
|
LastError = GetLastError();
|
|
|
|
VALIDATE(LastError)
|
|
{
|
|
ERROR_NOT_ENOUGH_MEMORY
|
|
} END_VALIDATE;
|
|
|
|
RpcStatus = LastError;
|
|
goto CleanupAndExit;
|
|
}
|
|
}
|
|
|
|
PreviousRequestContentLength = ContentLengthToUse;
|
|
|
|
State = whtcsSendingRequest;
|
|
|
|
HttpResult = WinHttpSendRequestImp( hRequest,
|
|
WINHTTP_NO_ADDITIONAL_HEADERS, // Additional headers
|
|
0, // Length of the additional headers
|
|
(LPVOID)AdditionalData, // Optional data to append to the request
|
|
AdditionalDataLengthToUse, // Length of the optional data
|
|
ContentLengthToUse, // Length in bytes of the total data sent
|
|
(DWORD_PTR) this // Application-specified context for this request
|
|
);
|
|
if (!HttpResult)
|
|
{
|
|
SyncEvent = NULL;
|
|
|
|
LastError = GetLastError();
|
|
}
|
|
else
|
|
{
|
|
// Sleep waiting for the send request to be completed.
|
|
LastError = WaitForSingleObject(SyncEvent, INFINITE);
|
|
SyncEvent = NULL;
|
|
ASSERT(State == whtcsSentRequest);
|
|
ASSERT(AsyncError != RPC_S_INTERNAL_ERROR);
|
|
LastError = AsyncError;
|
|
AsyncError = RPC_S_INTERNAL_ERROR;
|
|
}
|
|
|
|
if (LastError != RPC_S_OK)
|
|
{
|
|
VALIDATE(LastError)
|
|
{
|
|
ERROR_WINHTTP_CANNOT_CONNECT,
|
|
ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED,
|
|
ERROR_WINHTTP_CONNECTION_ERROR,
|
|
ERROR_WINHTTP_INVALID_SERVER_RESPONSE,
|
|
ERROR_WINHTTP_INVALID_URL,
|
|
ERROR_WINHTTP_LOGIN_FAILURE,
|
|
ERROR_WINHTTP_NAME_NOT_RESOLVED,
|
|
ERROR_WINHTTP_OUT_OF_HANDLES,
|
|
ERROR_WINHTTP_REDIRECT_FAILED,
|
|
ERROR_WINHTTP_RESEND_REQUEST,
|
|
ERROR_WINHTTP_SECURE_FAILURE,
|
|
ERROR_WINHTTP_SHUTDOWN,
|
|
ERROR_WINHTTP_TIMEOUT,
|
|
ERROR_NOT_ENOUGH_MEMORY,
|
|
ERROR_NOT_SUPPORTED,
|
|
RPC_P_RECEIVE_FAILED,
|
|
RPC_P_SEND_FAILED,
|
|
RPC_S_OUT_OF_MEMORY,
|
|
RPC_S_ACCESS_DENIED,
|
|
ERROR_NO_SYSTEM_RESOURCES,
|
|
ERROR_COMMITMENT_LIMIT,
|
|
ERROR_NOT_ENOUGH_QUOTA
|
|
} END_VALIDATE;
|
|
|
|
switch (LastError)
|
|
{
|
|
case ERROR_WINHTTP_CANNOT_CONNECT:
|
|
case ERROR_WINHTTP_CONNECTION_ERROR:
|
|
case ERROR_WINHTTP_INVALID_URL:
|
|
case ERROR_WINHTTP_NAME_NOT_RESOLVED:
|
|
case ERROR_WINHTTP_REDIRECT_FAILED:
|
|
case ERROR_WINHTTP_RESEND_REQUEST:
|
|
case ERROR_WINHTTP_SHUTDOWN:
|
|
case RPC_P_RECEIVE_FAILED:
|
|
case RPC_P_SEND_FAILED:
|
|
RpcStatus = RPC_S_SERVER_UNAVAILABLE;
|
|
break;
|
|
|
|
case ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED:
|
|
case ERROR_WINHTTP_SECURE_FAILURE:
|
|
RpcStatus = RPC_S_ACCESS_DENIED;
|
|
break;
|
|
|
|
case ERROR_WINHTTP_INVALID_SERVER_RESPONSE:
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
break;
|
|
|
|
case ERROR_WINHTTP_OUT_OF_HANDLES:
|
|
case ERROR_NOT_ENOUGH_MEMORY:
|
|
case RPC_S_OUT_OF_MEMORY:
|
|
case ERROR_NO_SYSTEM_RESOURCES:
|
|
case ERROR_COMMITMENT_LIMIT:
|
|
case ERROR_NOT_ENOUGH_QUOTA :
|
|
RpcStatus = RPC_S_OUT_OF_MEMORY;
|
|
break;
|
|
|
|
case ERROR_NOT_SUPPORTED:
|
|
RpcStatus = RPC_S_CANNOT_SUPPORT;
|
|
break;
|
|
|
|
case ERROR_WINHTTP_TIMEOUT:
|
|
RpcStatus = RPC_S_CALL_CANCELLED;
|
|
break;
|
|
|
|
default:
|
|
// acess denied doesn't get remapped
|
|
ASSERT(LastError == RPC_S_ACCESS_DENIED);
|
|
RpcStatus = LastError;
|
|
break;
|
|
}
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_OPEN, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
RpcStatus);
|
|
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_OPEN, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
RPC_S_OK);
|
|
|
|
RpcStatus = RPC_S_OK;
|
|
|
|
CleanupAndExit:
|
|
if (LocalEvent != NULL)
|
|
CloseHandle(LocalEvent);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2WinHttpTransportChannel::Send (
|
|
IN OUT HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send request
|
|
|
|
Arguments:
|
|
|
|
SendContext - the send context
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
BOOL HttpResult = TRUE;
|
|
ULONG LastError;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_SEND, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
(ULONG_PTR)SendContext);
|
|
|
|
Mutex.Request();
|
|
SendsPending ++;
|
|
ASSERT(SendsPending >= 0);
|
|
if (SendsPending > 1)
|
|
{
|
|
// queue and exit
|
|
SendContext->SetListEntryUsed();
|
|
RpcpInsertTailList(&BufferQueueHead, &SendContext->ListEntry);
|
|
Mutex.Clear();
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_SEND, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
SendsPending);
|
|
|
|
return RPC_S_OK;
|
|
}
|
|
Mutex.Clear();
|
|
|
|
ASSERT(State == whtcsSentRequest);
|
|
|
|
State = whtcsWriting;
|
|
|
|
CurrentSendContext = SendContext;
|
|
|
|
HttpResult = WinHttpWriteDataImp(hRequest,
|
|
SendContext->pWriteBuffer,
|
|
SendContext->maxWriteBuffer,
|
|
NULL // Number of bytes sent will be provided on async completion.
|
|
);
|
|
if (HttpResult == FALSE)
|
|
{
|
|
// Revert the state if a write could not be posted.
|
|
State = whtcsSentRequest;
|
|
|
|
LastError = GetLastError();
|
|
|
|
VALIDATE(LastError)
|
|
{
|
|
ERROR_NOT_ENOUGH_MEMORY,
|
|
ERROR_WINHTTP_CONNECTION_ERROR,
|
|
ERROR_WINHTTP_INVALID_SERVER_RESPONSE,
|
|
ERROR_WINHTTP_RESEND_REQUEST,
|
|
ERROR_WINHTTP_SHUTDOWN,
|
|
ERROR_WINHTTP_INTERNAL_ERROR
|
|
} END_VALIDATE;
|
|
|
|
RpcStatus = RPC_P_SEND_FAILED;
|
|
}
|
|
else
|
|
RpcStatus = RPC_S_OK;
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_SEND, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
RpcStatus);
|
|
|
|
return RpcStatus;
|
|
};
|
|
|
|
RPC_STATUS HTTP2WinHttpTransportChannel::Receive (
|
|
IN HTTP2TrafficType TrafficType
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Receive request
|
|
|
|
Arguments:
|
|
|
|
TrafficType - the type of traffic we want to receive
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
BOOL HttpResult;
|
|
ULONG LastError;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_RECV, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
TrafficType);
|
|
|
|
//
|
|
// Before we can do any receives, we need to do a WinHttpReceiveResponse
|
|
// if it has not been issued yet.
|
|
//
|
|
// This call only needs to be done before the first Receive.
|
|
//
|
|
|
|
if (State != whtcsReceivedResponse)
|
|
{
|
|
Mutex.Request();
|
|
|
|
// if there are still sends, we have indicated our
|
|
// traffic type in the DelayedReceiveTrafficType.
|
|
// Just exit, and the last send will do the receive
|
|
// work.
|
|
if (SendsPending > 0)
|
|
{
|
|
if (TrafficType == http2ttRTS)
|
|
DelayedReceiveTrafficType = http2ttRTSWithSpecialBit;
|
|
else if (TrafficType == http2ttData)
|
|
DelayedReceiveTrafficType = http2ttDataWithSpecialBit;
|
|
else
|
|
{
|
|
ASSERT(TrafficType == http2ttRaw);
|
|
DelayedReceiveTrafficType = http2ttRawWithSpecialBit;
|
|
}
|
|
Mutex.Clear();
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_RECV, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
SendsPending);
|
|
|
|
return RPC_S_OK;
|
|
}
|
|
Mutex.Clear();
|
|
|
|
DelayedReceiveTrafficType = TrafficType;
|
|
|
|
ASSERT(State == whtcsSentRequest);
|
|
|
|
State = whtcsReceivingResponse;
|
|
|
|
HttpResult = WinHttpReceiveResponseImp(hRequest, NULL);
|
|
|
|
// If the function returned FALSE, then it has failed syncronously.
|
|
if (!HttpResult)
|
|
{
|
|
DelayedReceiveTrafficType = http2ttNone;
|
|
|
|
LastError = GetLastError();
|
|
|
|
VALIDATE(LastError)
|
|
{
|
|
ERROR_WINHTTP_CANNOT_CONNECT,
|
|
ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED,
|
|
ERROR_WINHTTP_CONNECTION_ERROR,
|
|
ERROR_WINHTTP_INVALID_SERVER_RESPONSE,
|
|
ERROR_WINHTTP_INVALID_URL,
|
|
ERROR_WINHTTP_LOGIN_FAILURE,
|
|
ERROR_WINHTTP_NAME_NOT_RESOLVED,
|
|
ERROR_WINHTTP_OUT_OF_HANDLES,
|
|
ERROR_WINHTTP_REDIRECT_FAILED,
|
|
ERROR_WINHTTP_RESEND_REQUEST,
|
|
ERROR_WINHTTP_SECURE_FAILURE,
|
|
ERROR_WINHTTP_SHUTDOWN,
|
|
ERROR_WINHTTP_TIMEOUT,
|
|
ERROR_NOT_SUPPORTED,
|
|
ERROR_WINHTTP_INTERNAL_ERROR
|
|
} END_VALIDATE;
|
|
|
|
switch (LastError)
|
|
{
|
|
case ERROR_WINHTTP_CONNECTION_ERROR:
|
|
case ERROR_WINHTTP_REDIRECT_FAILED:
|
|
case ERROR_WINHTTP_RESEND_REQUEST:
|
|
case ERROR_WINHTTP_SHUTDOWN:
|
|
case ERROR_WINHTTP_SECURE_FAILURE:
|
|
RpcStatus = RPC_P_RECEIVE_FAILED;
|
|
break;
|
|
|
|
case ERROR_WINHTTP_INVALID_SERVER_RESPONSE:
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
break;
|
|
|
|
case ERROR_NOT_SUPPORTED:
|
|
RpcStatus = RPC_S_CANNOT_SUPPORT;
|
|
break;
|
|
|
|
case ERROR_WINHTTP_TIMEOUT:
|
|
RpcStatus = RPC_S_CALL_CANCELLED;
|
|
break;
|
|
|
|
default:
|
|
RpcStatus = RPC_S_OUT_OF_MEMORY;
|
|
break;
|
|
}
|
|
|
|
VALIDATE(RpcStatus)
|
|
{
|
|
RPC_S_OUT_OF_MEMORY,
|
|
RPC_S_OUT_OF_RESOURCES,
|
|
RPC_P_RECEIVE_FAILED,
|
|
RPC_S_CALL_CANCELLED,
|
|
RPC_P_SEND_FAILED,
|
|
RPC_P_CONNECTION_SHUTDOWN,
|
|
RPC_P_TIMEOUT
|
|
} END_VALIDATE;
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_RECV, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
RpcStatus);
|
|
|
|
return RpcStatus;
|
|
}
|
|
else
|
|
{
|
|
// If the function returned TRUE, then it will complete asyncronously.
|
|
// We should return. All additional work will be done on a separate thread
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_RECV, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
RPC_S_OK);
|
|
|
|
return RPC_S_OK;
|
|
}
|
|
}
|
|
|
|
RpcStatus = HTTP2FragmentReceiver::Receive(TrafficType);
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_RECV, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
RpcStatus);
|
|
|
|
return RpcStatus;
|
|
};
|
|
|
|
RPC_STATUS HTTP2WinHttpTransportChannel::SendComplete (
|
|
IN RPC_STATUS EventStatus,
|
|
IN OUT HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send complete notification
|
|
|
|
Arguments:
|
|
|
|
EventStatus - status of the operation
|
|
|
|
SendContext - the send context
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
HTTP2SendContext *QueuedSendContext;
|
|
LIST_ENTRY *QueuedListEntry;
|
|
ULONG LocalSendsPending;
|
|
BOOL HttpResult;
|
|
ULONG LastError;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_SEND_COMPLETE, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
EventStatus);
|
|
|
|
CurrentSendContext = NULL;
|
|
|
|
Mutex.Request();
|
|
// decrement this in advance so that if we post another send on send
|
|
// complete, it doesn't get queued
|
|
LocalSendsPending = -- SendsPending;
|
|
ASSERT(SendsPending >= 0);
|
|
|
|
Mutex.Clear();
|
|
|
|
// If we are processing a failed send-complete, this call may abort
|
|
// the channels and complete pending sends.
|
|
EventStatus = HTTP2TransportChannel::SendComplete(EventStatus, SendContext);
|
|
|
|
if ((EventStatus == RPC_S_OK)
|
|
|| (EventStatus == RPC_P_PACKET_CONSUMED) )
|
|
{
|
|
QueuedSendContext = NULL;
|
|
|
|
// Check if we have a queued send context.
|
|
// Because we pre-decremented SendsPending, we have "borrowed"
|
|
// a count and there is a queued context iff the count is above 0.
|
|
if (LocalSendsPending > 0)
|
|
{
|
|
Mutex.Request();
|
|
QueuedListEntry = RpcpRemoveHeadList(&BufferQueueHead);
|
|
ASSERT(QueuedListEntry);
|
|
ASSERT(QueuedListEntry != &BufferQueueHead);
|
|
QueuedSendContext = CONTAINING_RECORD(QueuedListEntry, HTTP2SendContext, ListEntry);
|
|
Mutex.Clear();
|
|
QueuedSendContext->SetListEntryUnused();
|
|
}
|
|
|
|
if (QueuedSendContext)
|
|
{
|
|
ASSERT(State == whtcsSentRequest);
|
|
|
|
State = whtcsWriting;
|
|
|
|
// need to synchronize with aborts (rule 9)
|
|
RpcStatus = TopChannel->BeginSimpleSubmitAsync();
|
|
|
|
CurrentSendContext = QueuedSendContext;
|
|
NumberOfBytesTransferred = QueuedSendContext->maxWriteBuffer;
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
HttpResult = WinHttpWriteDataImp(hRequest,
|
|
QueuedSendContext->pWriteBuffer,
|
|
QueuedSendContext->maxWriteBuffer,
|
|
NULL // Number of bytes sent will be provided on async completion.
|
|
);
|
|
|
|
TopChannel->FinishSubmitAsync();
|
|
|
|
if (HttpResult == FALSE)
|
|
{
|
|
// Revert the state if a write could not be posted.
|
|
State = whtcsSentRequest;
|
|
|
|
LastError = GetLastError();
|
|
|
|
VALIDATE(LastError)
|
|
{
|
|
ERROR_WINHTTP_CONNECTION_ERROR,
|
|
ERROR_WINHTTP_INVALID_SERVER_RESPONSE,
|
|
ERROR_WINHTTP_RESEND_REQUEST,
|
|
ERROR_WINHTTP_SHUTDOWN
|
|
} END_VALIDATE;
|
|
|
|
// the send failed. We don't get a notification for it
|
|
// so we must issue one. We do this by posting direct send
|
|
AsyncError = RPC_P_SEND_FAILED;
|
|
ASSERT(CurrentSendContext == QueuedSendContext);
|
|
|
|
(void) COMMON_PostRuntimeEvent(HTTP2_WINHTTP_DIRECT_SEND,
|
|
this
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// nothing to do - notification will come asynchronously
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Revert the state if a write could not be posted.
|
|
State = whtcsSentRequest;
|
|
|
|
AsyncError = RPC_P_SEND_FAILED;
|
|
ASSERT(CurrentSendContext == QueuedSendContext);
|
|
|
|
(void) COMMON_PostRuntimeEvent(HTTP2_WINHTTP_DIRECT_SEND,
|
|
this
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// if a receive has registered itself and we're the one who finished the
|
|
// sends, do the receive
|
|
if (
|
|
(
|
|
(DelayedReceiveTrafficType == http2ttRTSWithSpecialBit)
|
|
|| (DelayedReceiveTrafficType == http2ttDataWithSpecialBit)
|
|
|| (DelayedReceiveTrafficType == http2ttRawWithSpecialBit)
|
|
)
|
|
&&
|
|
(LocalSendsPending == 0)
|
|
)
|
|
{
|
|
if (DelayedReceiveTrafficType == http2ttRTSWithSpecialBit)
|
|
DelayedReceiveTrafficType = http2ttRTS;
|
|
else if (DelayedReceiveTrafficType == http2ttRawWithSpecialBit)
|
|
DelayedReceiveTrafficType = http2ttRaw;
|
|
else
|
|
{
|
|
ASSERT(DelayedReceiveTrafficType == http2ttDataWithSpecialBit);
|
|
DelayedReceiveTrafficType = http2ttData;
|
|
}
|
|
|
|
RpcStatus = TopChannel->BeginSimpleSubmitAsync();
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
RpcStatus = Receive(DelayedReceiveTrafficType);
|
|
|
|
TopChannel->FinishSubmitAsync();
|
|
}
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
// offload the result as direct receive. We use the refcount of the receive
|
|
// to complete the operation on the worker thread.
|
|
AsyncError = RpcStatus;
|
|
|
|
(void) COMMON_PostRuntimeEvent(HTTP2_WINHTTP_DIRECT_RECV,
|
|
this
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// when it completes, it will issue its own notification
|
|
}
|
|
}
|
|
|
|
// don't call AsyncCompleteHelper here. We will always be called from DirectSendComplete
|
|
// which will call AsyncCompleteHelper for us
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_SEND_COMPLETE, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
EventStatus);
|
|
|
|
return EventStatus;
|
|
}
|
|
|
|
//
|
|
// WinHttpCloseHandle may return failure in low-memory conditions.
|
|
// We will just re-try several times and record that a handle has been leaked.
|
|
// We should remove this code when:
|
|
// 647932 WinHttpCloseHandle fails in low memory conditions
|
|
// is fixed.
|
|
//
|
|
|
|
unsigned int nWinHttpHandlesLeaked = 0;
|
|
|
|
void HTTP2WinHttpTransportChannel::TryClosingWinHttpHandle (
|
|
IN HINTERNET *pHandle
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Tries to close a WinHttp handle.
|
|
|
|
Arguments:
|
|
|
|
hHandle - Pointer to a handle to close. Handle is set to NULL after lcosing.
|
|
|
|
--*/
|
|
{
|
|
BOOL HttpResult;
|
|
unsigned int nRetries = 10;
|
|
|
|
do
|
|
{
|
|
HttpResult = WinHttpCloseHandleImp(*pHandle);
|
|
if (!HttpResult)
|
|
{
|
|
nRetries--;
|
|
Sleep (10);
|
|
}
|
|
}
|
|
while (!HttpResult && nRetries > 0);
|
|
|
|
if (nRetries == 0)
|
|
{
|
|
nWinHttpHandlesLeaked++;
|
|
}
|
|
|
|
*pHandle = NULL;
|
|
}
|
|
|
|
void HTTP2WinHttpTransportChannel::Abort (
|
|
IN RPC_STATUS RpcStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Abort the channel
|
|
|
|
Arguments:
|
|
|
|
RpcStatus - the error code with which we abort
|
|
|
|
Return Value:
|
|
|
|
Notes:
|
|
|
|
This method must be idempotent. It may be called multiple times.
|
|
|
|
--*/
|
|
{
|
|
HTTP2SendContext *QueuedSendContext;
|
|
LIST_ENTRY *QueuedListEntry;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_ABORT, HTTP2LOG_OT_WINHTTP_CHANNEL, RpcStatus);
|
|
|
|
if (hRequest)
|
|
{
|
|
TryClosingWinHttpHandle(&hRequest);
|
|
}
|
|
|
|
if (hConnect)
|
|
{
|
|
TryClosingWinHttpHandle(&hConnect);
|
|
}
|
|
|
|
if (hSession)
|
|
{
|
|
TryClosingWinHttpHandle(&hSession);
|
|
}
|
|
|
|
Mutex.Request();
|
|
// If there are more then 1 pending sends, then some sends must have been queued.
|
|
// We will abort the queued sends.
|
|
for (; SendsPending > 1; )
|
|
{
|
|
ASSERT(!RpcpIsListEmpty(&BufferQueueHead));
|
|
QueuedListEntry = RpcpfRemoveHeadList(&BufferQueueHead);
|
|
QueuedSendContext = CONTAINING_RECORD(QueuedListEntry, HTTP2SendContext, ListEntry);
|
|
|
|
-- SendsPending;
|
|
ASSERT(SendsPending > 0);
|
|
HTTP2TransportChannel::SendComplete(RpcStatus, QueuedSendContext);
|
|
AsyncCompleteHelper(RpcStatus);
|
|
}
|
|
Mutex.Clear();
|
|
}
|
|
|
|
void HTTP2WinHttpTransportChannel::FreeObject (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Frees the object. Acts like a destructor for the
|
|
channel.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_FREE_OBJECT, HTTP2LOG_OT_WINHTTP_CHANNEL, 0);
|
|
|
|
if (pReadBuffer)
|
|
{
|
|
RpcFreeBuffer(pReadBuffer);
|
|
pReadBuffer = NULL;
|
|
}
|
|
|
|
HTTP2WinHttpTransportChannel::~HTTP2WinHttpTransportChannel();
|
|
}
|
|
|
|
RPC_STATUS HTTP2WinHttpTransportChannel::DirectReceiveComplete (
|
|
OUT BYTE **ReceivedBuffer,
|
|
OUT ULONG *ReceivedBufferLength,
|
|
OUT void **RuntimeConnection
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Direct receive completion (i.e. we posted a receive
|
|
to ourselves)
|
|
|
|
Arguments:
|
|
|
|
ReceivedBuffer - the buffer that we received.
|
|
|
|
ReceivedBufferLength - the length of the received
|
|
buffer
|
|
|
|
RuntimeConnection - the connection to return to the runtime
|
|
if the packet is not consumed.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK, RPC_P_PACKET_CONSUMED or RPC_S_* errors.
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
BOOL HttpResult;
|
|
ULONG WaitResult;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_RECV_COMPLETE, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
AsyncError);
|
|
|
|
*RuntimeConnection = TopChannel->GetRuntimeConnection();
|
|
|
|
// two cases - previous coalesced read and a real read
|
|
if (iLastRead && iLastRead == MaxReadBuffer)
|
|
{
|
|
// previous coalesced read
|
|
*ReceivedBufferLength = MaxReadBuffer;
|
|
iLastRead = 0;
|
|
RpcStatus = RPC_S_OK;
|
|
}
|
|
else
|
|
{
|
|
// real read
|
|
ASSERT(AsyncError != RPC_S_INTERNAL_ERROR);
|
|
|
|
if ((AsyncError == RPC_S_OK)
|
|
&& (NumberOfBytesTransferred == 0))
|
|
{
|
|
// zero bytes transferred indicates end of request.
|
|
AsyncError = RPC_P_RECEIVE_FAILED;
|
|
}
|
|
|
|
RpcStatus = AsyncError;
|
|
AsyncError = RPC_S_INTERNAL_ERROR;
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
if (pReadBuffer == NULL)
|
|
{
|
|
if (NumberOfBytesTransferred > iPostSize)
|
|
MaxReadBuffer = NumberOfBytesTransferred;
|
|
else
|
|
MaxReadBuffer = iPostSize;
|
|
pReadBuffer = (BYTE *)RpcAllocateBuffer(MaxReadBuffer);
|
|
// fall through for error check below
|
|
}
|
|
else if (MaxReadBuffer - iLastRead < NumberOfBytesTransferred)
|
|
{
|
|
ASSERT(iLastRead < MaxReadBuffer);
|
|
|
|
// Buffer too small for the message.
|
|
RpcStatus = TransConnectionReallocPacket(NULL,
|
|
&pReadBuffer,
|
|
iLastRead,
|
|
iLastRead + NumberOfBytesTransferred
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
RpcFreeBuffer(pReadBuffer);
|
|
pReadBuffer = NULL;
|
|
}
|
|
|
|
MaxReadBuffer = iLastRead + NumberOfBytesTransferred;
|
|
}
|
|
else
|
|
{
|
|
// buffer should be enough - no need to reallocate
|
|
ASSERT(iLastRead < MaxReadBuffer);
|
|
}
|
|
|
|
if (pReadBuffer == NULL)
|
|
{
|
|
RpcStatus = RPC_S_OUT_OF_MEMORY;
|
|
NumberOfBytesTransferred = 0;
|
|
}
|
|
else
|
|
{
|
|
// we need to temporarily get into submission context to synchronize
|
|
// with Aborts
|
|
RpcStatus = TopChannel->BeginSimpleSubmitAsync();
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
ASSERT(SyncEvent == NULL);
|
|
SyncEvent = I_RpcTransGetThreadEvent();
|
|
ResetEvent(SyncEvent);
|
|
|
|
HttpResult = WinHttpReadDataImp(hRequest,
|
|
pReadBuffer + iLastRead,
|
|
NumberOfBytesTransferred,
|
|
NULL // Number of bytes read will be provided on async completion.
|
|
);
|
|
|
|
// wait for read complete to finish
|
|
WaitResult = WaitForSingleObject(SyncEvent, INFINITE);
|
|
// this cannot fail
|
|
ASSERT(WaitResult == WAIT_OBJECT_0);
|
|
SyncEvent = NULL;
|
|
|
|
// the data are available. We cannot possibly fail
|
|
ASSERT(HttpResult);
|
|
TopChannel->FinishSubmitAsync();
|
|
|
|
VALIDATE (AsyncError)
|
|
{
|
|
RPC_P_RECEIVE_FAILED,
|
|
RPC_S_OK
|
|
} END_VALIDATE;
|
|
|
|
RpcStatus = AsyncError;
|
|
AsyncError = RPC_S_INTERNAL_ERROR;
|
|
// fall through with the status
|
|
}
|
|
else
|
|
{
|
|
// fall through with the error.
|
|
}
|
|
}
|
|
}
|
|
|
|
*ReceivedBufferLength = NumberOfBytesTransferred;
|
|
}
|
|
|
|
RpcStatus = HTTP2WinHttpTransportChannel::ReceiveComplete(RpcStatus,
|
|
http2ttRaw,
|
|
ReceivedBuffer,
|
|
(UINT *)ReceivedBufferLength
|
|
);
|
|
|
|
// did we receive an incomplete buffer?
|
|
if ((RpcStatus == RPC_S_OK) && (*ReceivedBuffer == NULL))
|
|
{
|
|
// hide it from the runtime
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
// AsyncCompleteHelper has already been called in ReceiveComplete
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_RECV_COMPLETE, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
RpcStatus);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2WinHttpTransportChannel::DirectSendComplete (
|
|
OUT BYTE **SentBuffer,
|
|
OUT void **SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Direct send complete notification. Complete the send
|
|
passing it only through channels that have seen it (i.e.
|
|
above us).
|
|
|
|
Arguments:
|
|
|
|
SentBuffer - on output the buffer that we tried to send
|
|
|
|
SendContext - on output contains the send context as
|
|
seen by the runtime
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK to return error to runtime
|
|
RPC_P_PACKET_CONSUMED - to hide packet from runtime
|
|
RPC_S_* error - return error to runtime
|
|
|
|
--*/
|
|
{
|
|
HTTP2SendContext *LocalCurrentSendContext;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_DIRECT_SEND_COMPLETE, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
AsyncError);
|
|
|
|
ASSERT(AsyncError != RPC_S_INTERNAL_ERROR);
|
|
|
|
ASSERT(CurrentSendContext);
|
|
|
|
State = whtcsSentRequest;
|
|
|
|
LocalCurrentSendContext = CurrentSendContext;
|
|
|
|
// CurrentSendContext is zeroed out by the call.
|
|
RpcStatus = HTTP2WinHttpTransportChannel::SendComplete(AsyncError, LocalCurrentSendContext);
|
|
|
|
if (RpcStatus != RPC_P_PACKET_CONSUMED)
|
|
{
|
|
// this will return to the runtime. Make sure it is valid
|
|
I_RpcTransVerifyClientRuntimeCallFromContext(LocalCurrentSendContext);
|
|
*SendContext = LocalCurrentSendContext;
|
|
*SentBuffer = LocalCurrentSendContext->pWriteBuffer;
|
|
}
|
|
else
|
|
{
|
|
// the packet was a transport packet - it won't be seen by the runtime
|
|
*SendContext = NULL;
|
|
*SentBuffer = NULL;
|
|
}
|
|
|
|
RpcStatus = AsyncCompleteHelper(RpcStatus);
|
|
// do not touch this pointer after here unless the list was not-empty
|
|
// (which implies we still have refcounts)
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_DIRECT_SEND_COMPLETE, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
RpcStatus);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
void HTTP2WinHttpTransportChannel::DelayedReceive (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Performs a delayed receive. The first receive on an WinHttp
|
|
channel is delayed because we must receive the headers before
|
|
we can do the actual receive.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
Note: Will be called from upcall context
|
|
|
|
--*/
|
|
{
|
|
BOOL HttpResult;
|
|
ULONG LastError;
|
|
RPC_STATUS RpcStatus;
|
|
BOOL InSubmissionContext;
|
|
BYTE *Ignored;
|
|
UINT BufferLength;
|
|
ULONG StatusCode;
|
|
ULONG StatusCodeLength;
|
|
ULONG HttpStatus;
|
|
RPC_CHAR ConnectionOptions[40];
|
|
RPC_CHAR *ConnectionOptionsToUse;
|
|
ULONG ConnectionOptionsLength;
|
|
int i;
|
|
RPC_CHAR *KeepAliveString;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_WHTTP_DELAYED_RECV, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
AsyncError);
|
|
|
|
// Check if we have received an async failure.
|
|
LastError = AsyncError;
|
|
AsyncError = RPC_S_INTERNAL_ERROR;
|
|
RpcStatus = RPC_S_OK;
|
|
InSubmissionContext = FALSE;
|
|
|
|
if (LastError == RPC_S_OK)
|
|
{
|
|
// get into submission context
|
|
RpcStatus = TopChannel->BeginSimpleSubmitAsync();
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
InSubmissionContext = TRUE;
|
|
|
|
StatusCodeLength = sizeof(StatusCode);
|
|
HttpResult = WinHttpQueryHeadersImp (
|
|
hRequest,
|
|
WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
|
|
WINHTTP_HEADER_NAME_BY_INDEX,
|
|
&StatusCode,
|
|
&StatusCodeLength,
|
|
WINHTTP_NO_HEADER_INDEX
|
|
);
|
|
|
|
if (!HttpResult)
|
|
{
|
|
LastError = GetLastError();
|
|
}
|
|
else
|
|
{
|
|
if (StatusCode != HTTP_STATUS_OK)
|
|
{
|
|
if ((StatusCode >= RPC_S_INVALID_STRING_BINDING) && (StatusCode <= RPC_X_BAD_STUB_DATA))
|
|
{
|
|
// if it is an RPC error code, just return it.
|
|
RpcStatus = StatusCode;
|
|
}
|
|
else if ((StatusCode == HTTP_STATUS_NOT_FOUND)
|
|
|| (StatusCode == HTTP_STATUS_BAD_METHOD)
|
|
|| (StatusCode == HTTP_STATUS_BAD_METHOD)
|
|
|| (StatusCode == HTTP_STATUS_SERVER_ERROR)
|
|
|| (StatusCode == HTTP_STATUS_NOT_SUPPORTED)
|
|
|| (StatusCode == HTTP_STATUS_SERVICE_UNAVAIL) )
|
|
{
|
|
RpcStatus = RPC_S_SERVER_UNAVAILABLE;
|
|
}
|
|
else if (StatusCode == HTTP_STATUS_REQUEST_TOO_LARGE)
|
|
RpcStatus = RPC_S_SERVER_OUT_OF_MEMORY;
|
|
else if (StatusCode == HTTP_STATUS_PROXY_AUTH_REQ)
|
|
{
|
|
if ((HttpCredentials == NULL)
|
|
|| (HttpCredentials->AuthenticationTarget & RPC_C_HTTP_AUTHN_TARGET_PROXY) == 0)
|
|
{
|
|
// we were not asked to authenticate against a proxy. Just fail
|
|
RpcStatus = RPC_S_ACCESS_DENIED;
|
|
}
|
|
else
|
|
{
|
|
ChosenAuthScheme = NegotiateAuthScheme();
|
|
if (ChosenAuthScheme == 0)
|
|
RpcStatus = RPC_S_ACCESS_DENIED;
|
|
else
|
|
{
|
|
State = whtcsDraining;
|
|
HttpResult = WinHttpQueryDataAvailableImp(hRequest,
|
|
NULL // Number of bytes available will be provided on async completion.
|
|
);
|
|
|
|
ASSERT(HttpResult);
|
|
RpcStatus = RPC_S_OK;
|
|
goto CleanupAndExit;
|
|
}
|
|
}
|
|
}
|
|
else if (StatusCode == HTTP_STATUS_DENIED)
|
|
{
|
|
if ((HttpCredentials == NULL)
|
|
|| (HttpCredentials->AuthenticationTarget & RPC_C_HTTP_AUTHN_TARGET_SERVER) == 0)
|
|
{
|
|
// we were not asked to authenticate against a server. Just fail
|
|
RpcStatus = RPC_S_ACCESS_DENIED;
|
|
}
|
|
else
|
|
{
|
|
ChosenAuthScheme = NegotiateAuthScheme();
|
|
if (ChosenAuthScheme == 0)
|
|
RpcStatus = RPC_S_ACCESS_DENIED;
|
|
else
|
|
{
|
|
State = whtcsDraining;
|
|
HttpResult = WinHttpQueryDataAvailableImp(hRequest,
|
|
NULL // Number of bytes available will be provided on async completion.
|
|
);
|
|
|
|
ASSERT(HttpResult);
|
|
|
|
RpcStatus = RPC_S_OK;
|
|
goto CleanupAndExit;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
}
|
|
else
|
|
{
|
|
// RpcStatus is already set above
|
|
ASSERT(RpcStatus == RPC_S_OK);
|
|
|
|
ConnectionOptionsLength = sizeof(ConnectionOptions);
|
|
ConnectionOptionsToUse = ConnectionOptions;
|
|
|
|
for (i = 0; i < 2; i ++)
|
|
{
|
|
HttpResult = WinHttpQueryHeadersImp (
|
|
hRequest,
|
|
WINHTTP_QUERY_CONNECTION,
|
|
WINHTTP_HEADER_NAME_BY_INDEX,
|
|
ConnectionOptionsToUse,
|
|
&ConnectionOptionsLength,
|
|
WINHTTP_NO_HEADER_INDEX
|
|
);
|
|
|
|
if (!HttpResult)
|
|
{
|
|
LastError = GetLastError();
|
|
if (LastError == ERROR_INSUFFICIENT_BUFFER)
|
|
{
|
|
ConnectionOptionsToUse = new RPC_CHAR[ConnectionOptionsLength];
|
|
if (ConnectionOptionsToUse == NULL)
|
|
{
|
|
LastError = RPC_S_OUT_OF_MEMORY;
|
|
// fall through with the error below
|
|
break;
|
|
}
|
|
}
|
|
else if (LastError == ERROR_WINHTTP_HEADER_NOT_FOUND)
|
|
{
|
|
// we did not get keep alives. This is ok
|
|
LastError = RPC_S_OK;
|
|
KeepAlive = FALSE;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
LastError = RPC_S_OUT_OF_MEMORY;
|
|
// fall through with the error below
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LastError = RPC_S_OK;
|
|
break;
|
|
}
|
|
} // for (i ...
|
|
|
|
ASSERT(LastError != ERROR_INSUFFICIENT_BUFFER);
|
|
if (LastError == RPC_S_OK)
|
|
{
|
|
// we got the connection options. Do we have keep alive?
|
|
KeepAliveString = RpcpStrStr(ConnectionOptionsToUse, L"Keep-Alive");
|
|
if (KeepAliveString)
|
|
KeepAlive = TRUE;
|
|
}
|
|
|
|
if (ConnectionOptionsToUse != ConnectionOptions)
|
|
delete [] ConnectionOptionsToUse;
|
|
|
|
} // StatusCode == HTTP_STATUS_OK
|
|
} // WinHttpQueryHeadersImp succeeded
|
|
} // BeginSimpleSubmitAsync succeeded
|
|
else
|
|
{
|
|
// BeginSimpleSubmitAsync failed - fall through with the error
|
|
// RpcStatus and success LastError
|
|
ASSERT(LastError == RPC_S_OK);
|
|
}
|
|
}
|
|
|
|
if (LastError != RPC_S_OK)
|
|
{
|
|
VALIDATE(LastError)
|
|
{
|
|
ERROR_WINHTTP_CANNOT_CONNECT,
|
|
ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED,
|
|
ERROR_WINHTTP_CONNECTION_ERROR,
|
|
ERROR_WINHTTP_INVALID_SERVER_RESPONSE,
|
|
ERROR_WINHTTP_INVALID_URL,
|
|
ERROR_WINHTTP_LOGIN_FAILURE,
|
|
ERROR_WINHTTP_NAME_NOT_RESOLVED,
|
|
ERROR_WINHTTP_OUT_OF_HANDLES,
|
|
ERROR_WINHTTP_REDIRECT_FAILED,
|
|
ERROR_WINHTTP_RESEND_REQUEST,
|
|
ERROR_WINHTTP_SECURE_FAILURE,
|
|
ERROR_WINHTTP_SHUTDOWN,
|
|
ERROR_WINHTTP_TIMEOUT,
|
|
ERROR_NOT_SUPPORTED,
|
|
RPC_P_SEND_FAILED,
|
|
RPC_P_RECEIVE_FAILED,
|
|
RPC_P_AUTH_NEEDED
|
|
} END_VALIDATE;
|
|
|
|
switch (LastError)
|
|
{
|
|
case ERROR_WINHTTP_CONNECTION_ERROR:
|
|
case ERROR_WINHTTP_REDIRECT_FAILED:
|
|
case ERROR_WINHTTP_RESEND_REQUEST:
|
|
case ERROR_WINHTTP_SHUTDOWN:
|
|
case ERROR_WINHTTP_CANNOT_CONNECT:
|
|
case ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED:
|
|
case ERROR_WINHTTP_INVALID_URL:
|
|
case ERROR_WINHTTP_LOGIN_FAILURE:
|
|
case ERROR_WINHTTP_NAME_NOT_RESOLVED:
|
|
case ERROR_WINHTTP_SECURE_FAILURE:
|
|
case RPC_P_AUTH_NEEDED:
|
|
RpcStatus = RPC_P_RECEIVE_FAILED;
|
|
break;
|
|
|
|
case ERROR_WINHTTP_INVALID_SERVER_RESPONSE:
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
break;
|
|
|
|
case ERROR_NOT_SUPPORTED:
|
|
RpcStatus = RPC_S_CANNOT_SUPPORT;
|
|
break;
|
|
|
|
case ERROR_WINHTTP_TIMEOUT:
|
|
RpcStatus = RPC_S_CALL_CANCELLED;
|
|
break;
|
|
|
|
case RPC_P_SEND_FAILED:
|
|
case RPC_P_RECEIVE_FAILED:
|
|
RpcStatus = LastError;
|
|
break;
|
|
|
|
default:
|
|
RpcStatus = RPC_S_OUT_OF_MEMORY;
|
|
break;
|
|
}
|
|
|
|
VALIDATE(RpcStatus)
|
|
{
|
|
RPC_S_OUT_OF_MEMORY,
|
|
RPC_S_OUT_OF_RESOURCES,
|
|
RPC_P_RECEIVE_FAILED,
|
|
RPC_S_CALL_CANCELLED,
|
|
RPC_P_SEND_FAILED,
|
|
RPC_P_CONNECTION_SHUTDOWN,
|
|
RPC_P_TIMEOUT
|
|
} END_VALIDATE;
|
|
}
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
ASSERT(InSubmissionContext);
|
|
|
|
RpcStatus = HTTP2FragmentReceiver::Receive(DelayedReceiveTrafficType);
|
|
}
|
|
|
|
CleanupAndExit:
|
|
if (InSubmissionContext)
|
|
{
|
|
TopChannel->FinishSubmitAsync();
|
|
}
|
|
|
|
DelayedReceiveTrafficType = http2ttNone;
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
// we got a failure. Issue receive complete. Since DelayedReceive
|
|
// happens only on channel recycling, and then we know we
|
|
// issue RTS receive, we don't need to indicate this to the runtime
|
|
BufferLength = 0;
|
|
RpcStatus = ReceiveComplete(RpcStatus,
|
|
DelayedReceiveTrafficType,
|
|
&Ignored,
|
|
&BufferLength
|
|
);
|
|
|
|
ASSERT(RpcStatus == RPC_P_PACKET_CONSUMED);
|
|
}
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_WHTTP_DELAYED_RECV, HTTP2LOG_OT_WINHTTP_CHANNEL,
|
|
RpcStatus);
|
|
}
|
|
|
|
void HTTP2WinHttpTransportChannel::VerifyServerCredentials (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Verifies that the server credentials match the subject info we
|
|
were given.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
BOOL HttpResult;
|
|
PCERT_CONTEXT CertContext;
|
|
ULONG OptionSize;
|
|
RPC_STATUS RpcStatus;
|
|
RPC_CHAR *StringSPN;
|
|
RPC_STATUS AbortError;
|
|
|
|
// make sure nobody has touched the async error after open
|
|
ASSERT((AsyncError == RPC_S_OK)
|
|
|| ((AsyncError == RPC_S_INTERNAL_ERROR)));
|
|
|
|
// if no credentials, nothing to verify
|
|
if ((HttpCredentials == NULL)
|
|
|| (HttpCredentials->ServerCertificateSubject == NULL))
|
|
return;
|
|
|
|
OptionSize = sizeof(PCERT_CONTEXT);
|
|
HttpResult = WinHttpQueryOptionImp(hRequest,
|
|
WINHTTP_OPTION_SERVER_CERT_CONTEXT,
|
|
&CertContext,
|
|
&OptionSize
|
|
);
|
|
|
|
if (!HttpResult)
|
|
{
|
|
AsyncError = GetLastError();
|
|
VALIDATE(AsyncError)
|
|
{
|
|
ERROR_NOT_ENOUGH_MEMORY,
|
|
ERROR_INVALID_OPERATION
|
|
} END_VALIDATE;
|
|
|
|
switch (AsyncError)
|
|
{
|
|
case ERROR_INVALID_OPERATION:
|
|
// we will get this when we ask for the certificate of non
|
|
// SSL connection
|
|
AsyncError = RPC_S_ACCESS_DENIED;
|
|
break;
|
|
|
|
default:
|
|
AbortError = RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
RpcStatus = I_RpcTransCertMatchPrincipalName(CertContext, HttpCredentials->ServerCertificateSubject);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortError = AsyncError = RpcStatus;
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
return;
|
|
|
|
AbortAndExit:
|
|
ASSERT(AsyncError != ERROR_SUCCESS);
|
|
// HTTP2WinHttpTransportChannel::Abort is idempotent. We'll call it now to
|
|
// tell WinHttp to abort, and ClientOpen will call it again. This is ok.
|
|
Abort(AbortError);
|
|
}
|
|
|
|
RPC_STATUS HTTP2WinHttpTransportChannel::PostReceive (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Posts a receive to WinHttp.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
Note: May be called from both submission and upcall context
|
|
|
|
--*/
|
|
{
|
|
BOOL HttpResult;
|
|
RPC_STATUS RpcStatus;
|
|
ULONG LastError;
|
|
ULONG AvailableLength;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_RECV, HTTP2LOG_OT_WINHTTP_CHANNEL, 0);
|
|
|
|
ASSERT(State == whtcsReceivedResponse);
|
|
|
|
State = whtcsReading;
|
|
|
|
RpcStatus = TopChannel->BeginSimpleSubmitAsync();
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
// Revert the state if a read could not be posted.
|
|
State = whtcsReceivedResponse;
|
|
return RpcStatus;
|
|
}
|
|
|
|
HttpResult = WinHttpQueryDataAvailableImp (hRequest,
|
|
NULL // Number of bytes available will be provided on async completion.
|
|
);
|
|
|
|
TopChannel->FinishSubmitAsync();
|
|
|
|
if (!HttpResult)
|
|
{
|
|
LastError = GetLastError();
|
|
|
|
VALIDATE(LastError)
|
|
{
|
|
ERROR_NOT_ENOUGH_MEMORY,
|
|
ERROR_WINHTTP_CONNECTION_ERROR,
|
|
ERROR_WINHTTP_INVALID_SERVER_RESPONSE,
|
|
ERROR_WINHTTP_RESEND_REQUEST,
|
|
ERROR_WINHTTP_SHUTDOWN
|
|
} END_VALIDATE;
|
|
|
|
// Revert the state if a read could not be posted.
|
|
State = whtcsReceivedResponse;
|
|
RpcStatus = RPC_P_RECEIVE_FAILED;
|
|
}
|
|
else
|
|
{
|
|
// If the function returned non-zero,
|
|
// then it will complete asyncronously.
|
|
// Nothing to do here, we will receive async notification.
|
|
RpcStatus = RPC_S_OK;
|
|
}
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_RECV, HTTP2LOG_OT_WINHTTP_CHANNEL, RpcStatus);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
ULONG HTTP2WinHttpTransportChannel::GetPostRuntimeEvent (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Gets the message to be posted to the runtime.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
The message to post to the runtime
|
|
|
|
--*/
|
|
{
|
|
return HTTP2_WINHTTP_DIRECT_RECV;
|
|
}
|
|
|
|
ULONG HTTP2WinHttpTransportChannel::NegotiateAuthScheme (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Negotiates an auth scheme supported by client and server/proxy
|
|
according to preference rules.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
The negotiated scheme or 0 if no scheme could be negotiated.
|
|
|
|
Notes:
|
|
|
|
The actual server/proxy supported/preferred schemes will be retrieved
|
|
from hRequest.
|
|
|
|
--*/
|
|
{
|
|
BOOL HttpResult;
|
|
ULONG Ignored;
|
|
ULONG ServerSupportedSchemes; // we use Server for brevity, but this
|
|
ULONG ServerPreferredScheme; // applies to proxies as well
|
|
ULONG *ClientSupportedSchemes;
|
|
ULONG CountOfClientSupportedSchemes;
|
|
int i;
|
|
|
|
HttpResult = WinHttpQueryAuthSchemesImp (hRequest,
|
|
&ServerSupportedSchemes,
|
|
&ServerPreferredScheme,
|
|
&Ignored // pdwAuthTarget - we have already determined this
|
|
// from the error code - ignore now.
|
|
);
|
|
|
|
if (!HttpResult)
|
|
return 0;
|
|
|
|
// first, if we support the server preference, we just choose
|
|
// that.
|
|
CountOfClientSupportedSchemes = HttpCredentials->NumberOfAuthnSchemes;
|
|
ClientSupportedSchemes = HttpCredentials->AuthnSchemes;
|
|
|
|
ASSERT(CountOfClientSupportedSchemes > 0);
|
|
ASSERT(ClientSupportedSchemes != NULL);
|
|
|
|
for (i = 0; i < CountOfClientSupportedSchemes; i ++)
|
|
{
|
|
if (ServerPreferredScheme == ClientSupportedSchemes[i])
|
|
return ServerPreferredScheme;
|
|
}
|
|
|
|
// client doesn't support what the server asks for. Try whether the server
|
|
// supports what the client prefers
|
|
for (i = 0; i < CountOfClientSupportedSchemes; i ++)
|
|
{
|
|
if (ServerSupportedSchemes & ClientSupportedSchemes[i])
|
|
return ClientSupportedSchemes[i];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void HTTP2WinHttpTransportChannel::ContinueDrainChannel (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Continue draining the channel after authentication challenge. We
|
|
need to drain the channel before we can proceed with the next
|
|
request. The number of bytes received is in NumberOfBytesTransferred.
|
|
If the channel was aborted in the meantime, issue receive
|
|
complete.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
BYTE *Buffer;
|
|
RPC_STATUS RpcStatus;
|
|
BOOL HttpResult;
|
|
HANDLE LocalSyncEvent;
|
|
|
|
// read the reported bytes. Then query again. If the
|
|
// number of bytes reported is 0, issue a receive
|
|
// for RPC_P_AUTH_NEEDED
|
|
if (NumberOfBytesTransferred > 0)
|
|
{
|
|
ASSERT(State == whtcsDraining);
|
|
|
|
Buffer = (BYTE *)RpcAllocateBuffer(NumberOfBytesTransferred);
|
|
if (Buffer == NULL)
|
|
{
|
|
AsyncError = RPC_S_OUT_OF_MEMORY;
|
|
(void) COMMON_PostRuntimeEvent(HTTP2_WINHTTP_DIRECT_RECV,
|
|
this
|
|
);
|
|
return;
|
|
}
|
|
|
|
// get into submissions context in order to safely access the
|
|
// request
|
|
RpcStatus = TopChannel->BeginSimpleSubmitAsync();
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
RpcFreeBuffer(Buffer);
|
|
AsyncError = RpcStatus;
|
|
(void) COMMON_PostRuntimeEvent(HTTP2_WINHTTP_DIRECT_RECV,
|
|
this
|
|
);
|
|
return;
|
|
}
|
|
|
|
ASSERT(SyncEvent == NULL);
|
|
|
|
LocalSyncEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
|
|
if (LocalSyncEvent == NULL)
|
|
{
|
|
TopChannel->FinishSubmitAsync();
|
|
RpcFreeBuffer(Buffer);
|
|
AsyncError = RPC_S_OUT_OF_MEMORY;
|
|
(void) COMMON_PostRuntimeEvent(HTTP2_WINHTTP_DIRECT_RECV,
|
|
this
|
|
);
|
|
return;
|
|
}
|
|
|
|
// read complete expects this state. Substitute it for draining
|
|
// for now. We'll restore it back later.
|
|
State = whtcsReceivedResponse;
|
|
SyncEvent = LocalSyncEvent;
|
|
|
|
HttpResult = WinHttpReadDataImp (hRequest,
|
|
Buffer,
|
|
NumberOfBytesTransferred,
|
|
NULL // Number of bytes read will be provided on async completion.
|
|
);
|
|
|
|
// read complete expects this state. Substitute it for draining
|
|
// for now. We'll restore it back later.
|
|
State = whtcsDraining;
|
|
SyncEvent = NULL;
|
|
CloseHandle(LocalSyncEvent);
|
|
|
|
// the data are here. This cannot fail
|
|
ASSERT(HttpResult);
|
|
|
|
RpcFreeBuffer(Buffer);
|
|
|
|
// ask for more
|
|
HttpResult = WinHttpQueryDataAvailableImp (hRequest,
|
|
NULL // Number of bytes available will be provided on async completion.
|
|
);
|
|
|
|
ASSERT(HttpResult);
|
|
|
|
TopChannel->FinishSubmitAsync();
|
|
|
|
// if WinHttp has the data, it will complete the QueryDataAvailable
|
|
// on the same thread as we are causing a recursive call to
|
|
// ContinueDrainChannel. If the recursion completed with success, we
|
|
// will already have RPC_P_AUTH_NEEDED for the AsyncError. Check
|
|
// for this case and bail out. All is done - we just need to get
|
|
// out of here
|
|
if (AsyncError == RPC_P_AUTH_NEEDED)
|
|
{
|
|
return;
|
|
}
|
|
else if (AsyncError == RPC_S_INTERNAL_ERROR)
|
|
{
|
|
// if WinHttpQueryDataAvailable resulted in recursive call
|
|
// of this function, deeper recursive levels may have set
|
|
// AsyncError to RPC_S_INTERNAL_ERROR. Reset if back
|
|
// once we pop out of the recursion. Since we will set it
|
|
// back on output from this function, this is a no-op.
|
|
AsyncError = RPC_S_OK;
|
|
}
|
|
|
|
VALIDATE (AsyncError)
|
|
{
|
|
RPC_P_RECEIVE_FAILED,
|
|
RPC_S_OK
|
|
} END_VALIDATE;
|
|
|
|
if (AsyncError != RPC_S_OK)
|
|
{
|
|
(void) COMMON_PostRuntimeEvent(HTTP2_WINHTTP_DIRECT_RECV,
|
|
this
|
|
);
|
|
return;
|
|
}
|
|
|
|
AsyncError = RPC_S_INTERNAL_ERROR;
|
|
}
|
|
else
|
|
{
|
|
AsyncError = RPC_P_AUTH_NEEDED;
|
|
(void) COMMON_PostRuntimeEvent(HTTP2_WINHTTP_DIRECT_RECV,
|
|
this
|
|
);
|
|
}
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2ProxySocketTransportChannel
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS HTTP2ProxySocketTransportChannel::AsyncCompleteHelper (
|
|
IN RPC_STATUS CurrentStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Helper routine that helps complete an async operation
|
|
|
|
Arguments:
|
|
|
|
CurrentStatus - the current status of the operation
|
|
|
|
Return Value:
|
|
|
|
The status to return to the runtime.
|
|
|
|
--*/
|
|
{
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_COMPLETE_HELPER, HTTP2LOG_OT_PROXY_SOCKET_CHANNEL, CurrentStatus);
|
|
|
|
ProxyAsyncCompleteHelper(TopChannel, CurrentStatus);
|
|
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2IISTransportChannel
|
|
*********************************************************************/
|
|
|
|
const char ServerErrorString[] = "HTTP/1.0 503 RPC Error: %X\r\n\r\n";
|
|
|
|
DWORD
|
|
ReplyToClientWithStatus (
|
|
IN EXTENSION_CONTROL_BLOCK *pECB,
|
|
IN RPC_STATUS RpcStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sends a reply to the client with the given error code as error.
|
|
|
|
Arguments:
|
|
|
|
pECB - extension control block
|
|
RpcStatus - error code to be returned to client
|
|
|
|
Return Value:
|
|
|
|
Return value appropriate for return to IIS (i.e. HSE_STATUS_*)
|
|
|
|
--*/
|
|
{
|
|
// size is the error string + 10 space for the error code
|
|
char Buffer[sizeof(ServerErrorString) + 10];
|
|
ULONG Size;
|
|
ULONG Status;
|
|
BOOL Result;
|
|
DWORD dwFlags = (HSE_IO_SYNC | HSE_IO_NODELAY);
|
|
|
|
sprintf (Buffer,
|
|
ServerErrorString,
|
|
RpcStatus
|
|
);
|
|
|
|
Size = RpcpStringLengthA(Buffer);
|
|
|
|
if (!pECB->WriteClient(pECB->ConnID, Buffer, &Size, dwFlags))
|
|
{
|
|
Status = GetLastError();
|
|
#ifdef DBG_ERROR
|
|
DbgPrint("ReplyToClientWithStatus(): failed: %d\n", Status);
|
|
#endif
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
else
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
|
|
RPC_STATUS HTTP2IISTransportChannel::Receive (
|
|
IN HTTP2TrafficType TrafficType
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Receive request
|
|
|
|
Arguments:
|
|
|
|
TrafficType - the type of traffic we want to receive
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
// if some data arrived with the ECB, pass them down for
|
|
// potential defragmentation
|
|
if (ECBDataConsumed == FALSE)
|
|
{
|
|
|
|
ECBDataConsumed = TRUE;
|
|
|
|
if (ControlBlock->cbAvailable > 0)
|
|
{
|
|
RpcStatus = DepositReceivedData (ControlBlock->cbAvailable,
|
|
ControlBlock->lpbData
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
return RpcStatus;
|
|
}
|
|
// fall through to the actual receive
|
|
}
|
|
|
|
// ask the fragment receiver to perform a receive
|
|
return HTTP2FragmentReceiver::Receive (TrafficType);
|
|
}
|
|
|
|
RPC_STATUS HTTP2IISTransportChannel::ReceiveComplete (
|
|
IN RPC_STATUS EventStatus,
|
|
IN HTTP2TrafficType TrafficType,
|
|
IN BYTE *Buffer,
|
|
IN UINT BufferLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Receive complete notification.
|
|
|
|
Arguments:
|
|
|
|
EventStatus - status of the operation
|
|
|
|
TrafficType - the type of traffic we have received
|
|
|
|
Buffer - the received buffer (success only)
|
|
|
|
BufferLength - the length of the received buffer (success only)
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
// make sure nobody gets here. Everybody should be using the internal
|
|
// version
|
|
ASSERT(0);
|
|
return RPC_S_INTERNAL_ERROR;
|
|
}
|
|
|
|
void HTTP2IISTransportChannel::Abort (
|
|
IN RPC_STATUS RpcStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Abort the channel
|
|
|
|
Arguments:
|
|
|
|
RpcStatus - the error code with which we abort
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
BOOL Result;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_ABORT, HTTP2LOG_OT_IIS_CHANNEL, RpcStatus);
|
|
|
|
ReplyToClientWithStatus(ControlBlock, RpcStatus);
|
|
|
|
// must abort the IIS session
|
|
Result = ControlBlock->ServerSupportFunction( ControlBlock->ConnID,
|
|
HSE_REQ_CLOSE_CONNECTION,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
|
|
if (Result == FALSE)
|
|
{
|
|
ASSERT(GetLastError() == WSAECONNRESET);
|
|
}
|
|
}
|
|
|
|
void HTTP2IISTransportChannel::FreeObject (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Frees the object. Acts like a destructor for the
|
|
channel.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_FREE_OBJECT, HTTP2LOG_OT_IIS_CHANNEL, 0);
|
|
|
|
FreeIISControlBlock();
|
|
|
|
if (pReadBuffer)
|
|
{
|
|
RpcFreeBuffer(pReadBuffer);
|
|
pReadBuffer = NULL;
|
|
}
|
|
|
|
HTTP2IISTransportChannel::~HTTP2IISTransportChannel();
|
|
}
|
|
|
|
void HTTP2IISTransportChannel::IOCompleted (
|
|
IN ULONG Bytes,
|
|
DWORD Error
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
An IO completed. Figure out what IO and what to do with it.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
BYTE *Ignored;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_IIS_IO_COMPLETED, HTTP2LOG_OT_IIS_CHANNEL, Error);
|
|
|
|
if (Direction == iistcdReceive)
|
|
{
|
|
(void) ReceiveCompleteInternal(Error ? RPC_P_RECEIVE_FAILED : RPC_S_OK,
|
|
http2ttRaw,
|
|
TRUE, // ReadCompleted
|
|
&Ignored,
|
|
(UINT *)&Bytes
|
|
);
|
|
}
|
|
else
|
|
{
|
|
if (Error == ERROR_SUCCESS)
|
|
{
|
|
ASSERT(Bytes == CurrentSendContext->maxWriteBuffer);
|
|
}
|
|
|
|
(void) SendComplete(Error ? RPC_P_SEND_FAILED : RPC_S_OK, CurrentSendContext);
|
|
}
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_IIS_IO_COMPLETED, HTTP2LOG_OT_IIS_CHANNEL, 0);
|
|
}
|
|
|
|
void HTTP2IISTransportChannel::DirectReceive (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Direct receive callback from the thread pool
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
ULONG Bytes;
|
|
RPC_STATUS RpcStatus;
|
|
BYTE *Ignored;
|
|
|
|
Bytes = iLastRead;
|
|
iLastRead = 0;
|
|
|
|
RpcStatus = ReceiveCompleteInternal(RPC_S_OK,
|
|
http2ttRaw,
|
|
FALSE, // ReadCompleted
|
|
&Ignored,
|
|
(UINT *)&Bytes
|
|
);
|
|
|
|
ASSERT(RpcStatus != RPC_S_INTERNAL_ERROR);
|
|
ASSERT(RpcStatus != RPC_P_PARTIAL_RECEIVE);
|
|
}
|
|
|
|
RPC_STATUS HTTP2IISTransportChannel::ReceiveCompleteInternal (
|
|
IN RPC_STATUS EventStatus,
|
|
IN HTTP2TrafficType TrafficType,
|
|
IN BOOL ReadCompleted,
|
|
IN OUT BYTE **Buffer,
|
|
IN OUT UINT *BufferLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Receive complete notification for the IIS transport channel. Somewhat
|
|
different signature than normal receive complete.
|
|
|
|
Arguments:
|
|
|
|
EventStatus - status of the operation
|
|
|
|
TrafficType - the type of traffic we have received
|
|
|
|
ReadCompleted - non-zero if a read completed. FALSE if it hasn't.
|
|
|
|
Buffer - the buffer. Must be NULL at this level on input. On
|
|
output contains the buffer for the current receive. If NULL
|
|
on output, we did not have a full packet. Undefined on failure.
|
|
|
|
BufferLength - the actual number of bytes received. On output the
|
|
number of bytes for the current packet. If 0 on output,
|
|
we did not have a complete packet. Undefined on failure.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
if (ReadCompleted)
|
|
ReadsPending --;
|
|
ASSERT(ReadsPending == 0);
|
|
|
|
return HTTP2FragmentReceiver::ReceiveComplete (EventStatus,
|
|
TrafficType,
|
|
Buffer,
|
|
BufferLength
|
|
);
|
|
}
|
|
|
|
void HTTP2IISTransportChannel::FreeIISControlBlock (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Frees the IIS control block associated with this channel
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
BOOL Result;
|
|
|
|
if (IISCloseEnabled)
|
|
{
|
|
Result = ControlBlock->ServerSupportFunction( ControlBlock->ConnID,
|
|
HSE_REQ_DONE_WITH_SESSION,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
|
|
ASSERT(Result);
|
|
}
|
|
|
|
ControlBlock = NULL;
|
|
}
|
|
|
|
RPC_STATUS HTTP2IISTransportChannel::AsyncCompleteHelper (
|
|
IN RPC_STATUS CurrentStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
A helper function that completes an async io.
|
|
|
|
Arguments:
|
|
|
|
CurrentStatus - the status with which the complete
|
|
notification completed.
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
return ProxyAsyncCompleteHelper(TopChannel, CurrentStatus);
|
|
}
|
|
|
|
RPC_STATUS HTTP2IISTransportChannel::PostReceive (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Posts a receive to IIS.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
Note: May be called from both submission and upcall context
|
|
|
|
--*/
|
|
{
|
|
BOOL Result;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_RECV, HTTP2LOG_OT_IIS_CHANNEL, 0);
|
|
|
|
RpcStatus = TopChannel->BeginSimpleSubmitAsync();
|
|
if (RpcStatus != RPC_S_OK)
|
|
return RpcStatus;
|
|
|
|
ASSERT (Direction == iistcdReceive);
|
|
|
|
if (pReadBuffer == NULL)
|
|
{
|
|
pReadBuffer = (BYTE *)RpcAllocateBuffer(iPostSize);
|
|
if (pReadBuffer == NULL)
|
|
{
|
|
TopChannel->FinishSubmitAsync();
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_RECV, HTTP2LOG_OT_IIS_CHANNEL, RPC_S_OUT_OF_MEMORY);
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
MaxReadBuffer = iPostSize;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(iLastRead < MaxReadBuffer);
|
|
}
|
|
|
|
ReadsPending ++;
|
|
ASSERT(ReadsPending == 1);
|
|
|
|
BytesToTransfer = MaxReadBuffer - iLastRead;
|
|
|
|
Result = ControlBlock->ServerSupportFunction(ControlBlock->ConnID,
|
|
HSE_REQ_ASYNC_READ_CLIENT,
|
|
pReadBuffer + iLastRead,
|
|
&BytesToTransfer,
|
|
&IISIoFlags
|
|
);
|
|
|
|
TopChannel->FinishSubmitAsync();
|
|
|
|
if (Result == FALSE)
|
|
{
|
|
ReadsPending --;
|
|
ASSERT(ReadsPending == 0);
|
|
|
|
ASSERT(GetLastError() != ERROR_INVALID_PARAMETER);
|
|
|
|
RpcStatus = RPC_P_RECEIVE_FAILED;
|
|
}
|
|
else
|
|
RpcStatus = RPC_S_OK;
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_RECV, HTTP2LOG_OT_IIS_CHANNEL, RpcStatus);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
ULONG HTTP2IISTransportChannel::GetPostRuntimeEvent (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Gets the message to be posted to the runtime.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
The message to post to the runtime
|
|
|
|
--*/
|
|
{
|
|
return IN_PROXY_IIS_DIRECT_RECV;
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2IISSenderTransportChannel
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS HTTP2IISSenderTransportChannel::Send (
|
|
IN OUT HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send request
|
|
|
|
Arguments:
|
|
|
|
SendContext - the send context
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
BOOL Result;
|
|
ULONG LocalSendsPending;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_SEND, HTTP2LOG_OT_IIS_SENDER_CHANNEL, (ULONG_PTR)SendContext);
|
|
|
|
Mutex.Request();
|
|
LocalSendsPending = SendsPending.Increment();
|
|
if ((LocalSendsPending > 1) || ReadsPending)
|
|
{
|
|
// queue and exit
|
|
SendContext->SetListEntryUsed();
|
|
RpcpInsertTailList(&BufferQueueHead, &SendContext->ListEntry);
|
|
Mutex.Clear();
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_SEND, HTTP2LOG_OT_IIS_SENDER_CHANNEL, 1);
|
|
|
|
return RPC_S_OK;
|
|
}
|
|
Mutex.Clear();
|
|
|
|
if (Direction == iistcdReceive)
|
|
ReverseDirection();
|
|
|
|
CurrentSendContext = SendContext;
|
|
BytesToTransfer = SendContext->maxWriteBuffer;
|
|
Result = ControlBlock->WriteClient(ControlBlock->ConnID,
|
|
SendContext->pWriteBuffer,
|
|
&BytesToTransfer,
|
|
IISIoFlags
|
|
);
|
|
|
|
if (Result == FALSE)
|
|
{
|
|
ASSERT(GetLastError() != ERROR_INVALID_PARAMETER);
|
|
RpcStatus = RPC_P_SEND_FAILED;
|
|
}
|
|
else
|
|
RpcStatus = RPC_S_OK;
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_SEND, HTTP2LOG_OT_IIS_SENDER_CHANNEL, RpcStatus);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2IISSenderTransportChannel::SendComplete (
|
|
IN RPC_STATUS EventStatus,
|
|
IN OUT HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send complete notification
|
|
|
|
Arguments:
|
|
|
|
EventStatus - status of the operation
|
|
|
|
SendContext - the send context
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
ULONG LocalSendsPending;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_SEND_COMPLETE, HTTP2LOG_OT_IIS_SENDER_CHANNEL, EventStatus);
|
|
|
|
CurrentSendContext = NULL;
|
|
|
|
// decrement this in advance so that if we post another send on send
|
|
// complete, it doesn't get queued
|
|
LocalSendsPending = SendsPending.Decrement();
|
|
|
|
EventStatus = HTTP2TransportChannel::SendComplete(EventStatus, SendContext);
|
|
|
|
if ((EventStatus == RPC_S_OK)
|
|
|| (EventStatus == RPC_P_PACKET_CONSUMED) )
|
|
{
|
|
EventStatus = SendQueuedContextIfNecessary (LocalSendsPending, EventStatus);
|
|
}
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_SEND_COMPLETE, HTTP2LOG_OT_IIS_SENDER_CHANNEL, EventStatus);
|
|
|
|
return AsyncCompleteHelper(EventStatus);
|
|
}
|
|
|
|
void HTTP2IISSenderTransportChannel::Abort (
|
|
IN RPC_STATUS RpcStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Abort the channel
|
|
|
|
Arguments:
|
|
|
|
RpcStatus - the error code with which we abort
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
HTTP2SendContext *QueuedSendContext;
|
|
LIST_ENTRY *QueuedListEntry;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_ABORT, HTTP2LOG_OT_IIS_SENDER_CHANNEL, RpcStatus);
|
|
|
|
HTTP2IISTransportChannel::Abort(RpcStatus);
|
|
|
|
Mutex.Request();
|
|
if (SendsPending.GetInteger() > 1)
|
|
{
|
|
ASSERT(!RpcpIsListEmpty(&BufferQueueHead));
|
|
QueuedListEntry = RpcpfRemoveHeadList(&BufferQueueHead);
|
|
do
|
|
{
|
|
QueuedSendContext = CONTAINING_RECORD(QueuedListEntry, HTTP2SendContext, ListEntry);
|
|
|
|
SendsPending.Decrement();
|
|
HTTP2TransportChannel::SendComplete(RpcStatus, QueuedSendContext);
|
|
TopChannel->RemoveReference();
|
|
|
|
QueuedListEntry = RpcpfRemoveHeadList(&BufferQueueHead);
|
|
}
|
|
while (QueuedListEntry != &BufferQueueHead);
|
|
}
|
|
Mutex.Clear();
|
|
}
|
|
|
|
void HTTP2IISSenderTransportChannel::FreeObject (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Frees the object. Acts like a destructor for the
|
|
channel.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_FREE_OBJECT, HTTP2LOG_OT_IIS_SENDER_CHANNEL, 0);
|
|
|
|
FreeIISControlBlock();
|
|
|
|
HTTP2IISSenderTransportChannel::~HTTP2IISSenderTransportChannel();
|
|
}
|
|
|
|
RPC_STATUS HTTP2IISSenderTransportChannel::ReceiveCompleteInternal (
|
|
IN RPC_STATUS EventStatus,
|
|
IN HTTP2TrafficType TrafficType,
|
|
IN BOOL ReadCompleted,
|
|
IN OUT BYTE **Buffer,
|
|
IN OUT UINT *BufferLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Receive complete notification for the IIS sender transport channel. Somewhat
|
|
different signature than normal receive complete.
|
|
|
|
Arguments:
|
|
|
|
EventStatus - status of the operation
|
|
|
|
TrafficType - the type of traffic we have received
|
|
|
|
ReadCompleted - non-zero if a read completed. FALSE if it hasn't.
|
|
|
|
Buffer - the buffer. Must be NULL at this level on input. On
|
|
output contains the buffer for the current receive. If NULL
|
|
on output, we did not have a full packet. Undefined on failure.
|
|
|
|
BufferLength - the actual number of bytes received. On output the
|
|
number of bytes for the current packet. If 0 on output,
|
|
we did not have a complete packet. Undefined on failure.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
ULONG LocalSendsPending;
|
|
|
|
Mutex.Request();
|
|
// decrease the reads pending and check the sends within the mutex -
|
|
// this ensures atomicity with respect to the send path's (which is
|
|
// the only path we race with) increase of the sends and check of the
|
|
// reads
|
|
if (ReadCompleted)
|
|
ReadsPending --;
|
|
ASSERT(ReadsPending == 0);
|
|
|
|
LocalSendsPending = SendsPending.GetInteger();
|
|
Mutex.Clear();
|
|
|
|
RpcStatus = HTTP2FragmentReceiver::ReceiveComplete (EventStatus,
|
|
TrafficType,
|
|
Buffer,
|
|
BufferLength
|
|
);
|
|
|
|
if ((RpcStatus == RPC_P_PACKET_CONSUMED)
|
|
||
|
|
(
|
|
(RpcStatus == RPC_S_OK)
|
|
&&
|
|
(*Buffer != NULL)
|
|
)
|
|
)
|
|
{
|
|
RpcStatus = SendQueuedContextIfNecessary (LocalSendsPending, RpcStatus);
|
|
}
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2IISSenderTransportChannel::SendQueuedContextIfNecessary (
|
|
IN ULONG LocalSendsPending,
|
|
IN RPC_STATUS EventStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Checks if any send contexts are queued for sending, and if yes, sends
|
|
the first one (which on completion will send the next, etc).
|
|
|
|
Arguments:
|
|
|
|
LocalSendsPending - the number of sends pending at the time the current
|
|
operation completed save for the count of the current operation (if it
|
|
was send)
|
|
|
|
EventStatus - the RPC Status so far. Must be a success error status
|
|
(RPC_S_OK or RPC_P_PACKET_CONSUMED)
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
HTTP2SendContext *QueuedSendContext;
|
|
LIST_ENTRY *QueuedListEntry;
|
|
BOOL Result;
|
|
|
|
if (LocalSendsPending != 0)
|
|
{
|
|
Mutex.Request();
|
|
|
|
if (Direction == iistcdReceive)
|
|
ReverseDirection();
|
|
|
|
QueuedListEntry = RpcpRemoveHeadList(&BufferQueueHead);
|
|
// it is possible that if an abort executed between getting LocalSendsPending
|
|
// in caller and grabbing the mutex here that the list is empty.
|
|
ASSERT(QueuedListEntry);
|
|
if (QueuedListEntry == &BufferQueueHead)
|
|
{
|
|
Mutex.Clear();
|
|
return EventStatus;
|
|
}
|
|
QueuedSendContext = CONTAINING_RECORD(QueuedListEntry, HTTP2SendContext, ListEntry);
|
|
Mutex.Clear();
|
|
QueuedSendContext->SetListEntryUnused();
|
|
|
|
// need to synchronize with aborts (rule 9)
|
|
EventStatus = TopChannel->BeginSimpleSubmitAsync();
|
|
if (EventStatus == RPC_S_OK)
|
|
{
|
|
CurrentSendContext = QueuedSendContext;
|
|
BytesToTransfer = QueuedSendContext->maxWriteBuffer;
|
|
Result = ControlBlock->WriteClient(ControlBlock->ConnID,
|
|
QueuedSendContext->pWriteBuffer,
|
|
&BytesToTransfer,
|
|
IISIoFlags
|
|
);
|
|
|
|
if (Result == FALSE)
|
|
{
|
|
EventStatus = RPC_P_SEND_FAILED;
|
|
ASSERT(GetLastError() != ERROR_INVALID_PARAMETER);
|
|
// the send failed. We don't get a notification for it
|
|
// so we must issue one. Reference for the send we
|
|
// failed to post is already added
|
|
EventStatus = HTTP2TransportChannel::SendComplete(EventStatus, QueuedSendContext);
|
|
TopChannel->RemoveReference();
|
|
}
|
|
else
|
|
{
|
|
// must already be ok
|
|
ASSERT(EventStatus == RPC_S_OK);
|
|
}
|
|
|
|
TopChannel->FinishSubmitAsync();
|
|
}
|
|
}
|
|
|
|
return EventStatus;
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2GenericReceiver
|
|
*********************************************************************/
|
|
|
|
void HTTP2GenericReceiver::FreeObject (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Frees the object. Acts like a destructor for the
|
|
channel.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
if (LowerLayer)
|
|
LowerLayer->FreeObject();
|
|
|
|
HTTP2GenericReceiver::~HTTP2GenericReceiver();
|
|
}
|
|
|
|
void HTTP2GenericReceiver::TransferStateToNewReceiver (
|
|
OUT HTTP2GenericReceiver *NewReceiver
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Transfers all the settings from this receiver (i.e. the state
|
|
of the receive) to a new one.
|
|
|
|
Arguments:
|
|
|
|
NewReceiver - the new receiver to transfer the settings to
|
|
|
|
Return Value:
|
|
|
|
Notes:
|
|
|
|
This must be called in an upcall context (i.e. no real receives
|
|
pending) and the channel on which this is called must be non-default
|
|
by now.
|
|
|
|
--*/
|
|
{
|
|
NewReceiver->ReceiveWindow = ReceiveWindow;
|
|
}
|
|
|
|
RPC_STATUS HTTP2GenericReceiver::BytesReceivedNotification (
|
|
IN ULONG Bytes
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Notifies channel that bytes have been received.
|
|
|
|
Arguments:
|
|
|
|
Bytes - the number of data bytes received.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK if the received bytes did not violate the
|
|
flow control protocol. RPC_S_PROTOCOL error otherwise.
|
|
|
|
--*/
|
|
{
|
|
Mutex.VerifyOwned();
|
|
|
|
BytesReceived += Bytes;
|
|
BytesInWindow += Bytes;
|
|
ASSERT(BytesInWindow <= ReceiveWindow);
|
|
FreeWindowAdvertised -= Bytes;
|
|
|
|
if (FreeWindowAdvertised < 0)
|
|
{
|
|
ASSERT(0);
|
|
// sender sent data even though
|
|
// we told it we don't have enough window
|
|
// to receive it - protocol violation
|
|
return RPC_S_PROTOCOL_ERROR;
|
|
}
|
|
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
void HTTP2GenericReceiver::BytesConsumedNotification (
|
|
IN ULONG Bytes,
|
|
IN BOOL OwnsMutex,
|
|
OUT BOOL *IssueAck,
|
|
OUT ULONG *BytesReceivedForAck,
|
|
OUT ULONG *WindowForAck
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Notifies channel that bytes have been consumed and can
|
|
be freed from the receive window of the channel.
|
|
|
|
Arguments:
|
|
|
|
Bytes - the number of data bytes consumed.
|
|
|
|
OwnsMutex - non-zero if the mutex for the channel is
|
|
already owned.
|
|
|
|
IssueAck - must be FALSE on input. If the caller needs
|
|
to issue an Ack, it will be set to non-zero on
|
|
output.
|
|
|
|
BytesReceivedForAck - on output, if IssueAck is non-zero,
|
|
it will contain the bytes received to put in the
|
|
ack packet. If IssueAck is FALSE, it is undefined.
|
|
|
|
WindowForAck - on output, if IssueAck is non-zero,
|
|
it will contain the window available to put in the
|
|
ack packet. If IssueAck is FALSE, it is undefined.
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
ULONG ReceiveWindowThreshold;
|
|
|
|
if (OwnsMutex)
|
|
{
|
|
Mutex.VerifyOwned();
|
|
}
|
|
else
|
|
{
|
|
Mutex.Request();
|
|
}
|
|
|
|
ASSERT(*IssueAck == FALSE);
|
|
|
|
BytesInWindow -= Bytes;
|
|
// make sure we don't wrap
|
|
ASSERT(BytesInWindow <= ReceiveWindow);
|
|
ReceiveWindowThreshold = ReceiveWindow >> 1;
|
|
if (FreeWindowAdvertised < (LONG)ReceiveWindowThreshold)
|
|
{
|
|
// we fell below the threshold. ACK our current window
|
|
*IssueAck = TRUE;
|
|
FreeWindowAdvertised = ReceiveWindow - BytesInWindow;
|
|
ASSERT(FreeWindowAdvertised >= 0);
|
|
*BytesReceivedForAck = BytesReceived;
|
|
*WindowForAck = FreeWindowAdvertised;
|
|
}
|
|
|
|
if (OwnsMutex == FALSE)
|
|
{
|
|
Mutex.Clear();
|
|
}
|
|
}
|
|
|
|
RPC_STATUS HTTP2GenericReceiver::SendFlowControlAck (
|
|
IN ULONG BytesReceivedForAck,
|
|
IN ULONG WindowForAck
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sends a flow control Ack packet.
|
|
|
|
Arguments:
|
|
|
|
BytesReceivedForAck - the number of bytes received while
|
|
we were issuing the Ack
|
|
|
|
WindowForAck - the window available when BytesReceivedForAck
|
|
bytes have been received
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* for error
|
|
|
|
Notes:
|
|
|
|
This must be called in a neutral context.
|
|
|
|
--*/
|
|
{
|
|
return TopChannel->ForwardFlowControlAck (BytesReceivedForAck,
|
|
WindowForAck
|
|
);
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2EndpointReceiver
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS HTTP2EndpointReceiver::Receive (
|
|
IN HTTP2TrafficType TrafficType
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Receive request
|
|
|
|
Arguments:
|
|
|
|
TrafficType - the type of traffic we want to receive
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
BOOL PostReceive;
|
|
RPC_STATUS RpcStatus;
|
|
BOOL DequeuePacket;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_RECV, HTTP2LOG_OT_ENDPOINT_RECEIVER, TrafficType);
|
|
|
|
DequeuePacket = FALSE;
|
|
PostReceive = FALSE;
|
|
Mutex.Request();
|
|
|
|
switch (TrafficType)
|
|
{
|
|
case http2ttNone:
|
|
ASSERT(0);
|
|
RpcStatus = RPC_S_INTERNAL_ERROR;
|
|
Mutex.Clear();
|
|
return RpcStatus;
|
|
|
|
case http2ttRTS:
|
|
// we cannot issue two RTS receives.
|
|
ASSERT((ReceivesPosted & http2ttRTS) == 0);
|
|
// if we have RTS receives queued, dequeue one and
|
|
// complete it
|
|
if (ReceivesQueued == http2ttRTS)
|
|
{
|
|
ASSERT(DirectCompletePosted == FALSE);
|
|
DirectCompletePosted = TRUE;
|
|
DequeuePacket = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// we have no packets queued, or only data packets
|
|
// queued. If we have no data request pending, create
|
|
// a request pending. Otherwise just add ourselves to
|
|
// the map
|
|
if (ReceivesPosted)
|
|
{
|
|
ASSERT(ReceivesPosted == http2ttData);
|
|
ReceivesPosted = http2ttAny;
|
|
}
|
|
else
|
|
{
|
|
PostReceive = TRUE;
|
|
ReceivesPosted = http2ttRTS;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case http2ttData:
|
|
// we cannot issue two Data receives.
|
|
ASSERT((ReceivesPosted & http2ttData) == 0);
|
|
// if we have Data receives queued, dequeue one and
|
|
// complete it
|
|
if (ReceivesQueued == http2ttData)
|
|
{
|
|
ASSERT(DirectCompletePosted == FALSE);
|
|
DirectCompletePosted = TRUE;
|
|
DequeuePacket = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// we have no packets queued, or only RTS packets
|
|
// queued. If we have no RTS request pending, create
|
|
// a request pending. Otherwise just add ourselves to
|
|
// the map
|
|
if (ReceivesPosted)
|
|
{
|
|
ASSERT(ReceivesPosted == http2ttRTS);
|
|
ReceivesPosted = http2ttAny;
|
|
}
|
|
else
|
|
{
|
|
PostReceive = TRUE;
|
|
ReceivesPosted = http2ttData;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ASSERT(0);
|
|
RpcStatus = RPC_S_INTERNAL_ERROR;
|
|
Mutex.Clear();
|
|
return RpcStatus;
|
|
}
|
|
|
|
// only one of PostReceive and DequeuePacket can be set here.
|
|
// Neither is ok too.
|
|
ASSERT((PostReceive ^ DequeuePacket)
|
|
|| ((PostReceive == FALSE)
|
|
&&
|
|
(DequeuePacket == FALSE)
|
|
)
|
|
);
|
|
|
|
Mutex.Clear();
|
|
|
|
if (DequeuePacket)
|
|
{
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_RECV, HTTP2LOG_OT_ENDPOINT_RECEIVER, 0);
|
|
(void) COMMON_PostRuntimeEvent(HTTP2_DIRECT_RECEIVE,
|
|
this
|
|
);
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
if (PostReceive == FALSE)
|
|
{
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_RECV, HTTP2LOG_OT_ENDPOINT_RECEIVER, 1);
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
RpcStatus = HTTP2TransportChannel::Receive(http2ttRaw);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
// we have indicated our receive as pending, yet we
|
|
// couldn't submit it. Not good. Must attempt to
|
|
// remove it from the pending variable unless somebody
|
|
// else already did (which is possible if we wanted
|
|
// to submit data and there was already pending RTS).
|
|
Mutex.Request();
|
|
|
|
switch (ReceivesPosted)
|
|
{
|
|
case http2ttNone:
|
|
// should not be possible
|
|
ASSERT(FALSE);
|
|
RpcStatus = RPC_S_INTERNAL_ERROR;
|
|
Mutex.Clear();
|
|
return RpcStatus;
|
|
|
|
case http2ttData:
|
|
if (TrafficType == http2ttData)
|
|
ReceivesPosted = http2ttNone;
|
|
else
|
|
{
|
|
// not possible that we submitted RTS
|
|
// and have data pending but not RTS
|
|
ASSERT(0);
|
|
Mutex.Clear();
|
|
return RPC_S_INTERNAL_ERROR;
|
|
}
|
|
break;
|
|
|
|
case http2ttRTS:
|
|
if (TrafficType == http2ttRTS)
|
|
ReceivesPosted = http2ttNone;
|
|
else
|
|
{
|
|
// possible that we attempted to submit data,
|
|
// but while we were trying, an async RTS submission
|
|
// failed, and we indicated it asynchronously. In this
|
|
// case we must not indicate the failure synchronously
|
|
RpcStatus = RPC_S_OK;
|
|
}
|
|
break;
|
|
|
|
case http2ttAny:
|
|
if (TrafficType == http2ttRTS)
|
|
ReceivesPosted = http2ttData;
|
|
else
|
|
ReceivesPosted = http2ttRTS;
|
|
break;
|
|
|
|
default:
|
|
ASSERT(0);
|
|
Mutex.Clear();
|
|
return RPC_S_INTERNAL_ERROR;
|
|
}
|
|
|
|
Mutex.Clear();
|
|
}
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_RECV, HTTP2LOG_OT_ENDPOINT_RECEIVER, 3);
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2EndpointReceiver::ReceiveComplete (
|
|
IN RPC_STATUS EventStatus,
|
|
IN HTTP2TrafficType TrafficType,
|
|
IN BYTE *Buffer,
|
|
IN UINT BufferLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Receive complete notification.
|
|
|
|
Arguments:
|
|
|
|
EventStatus - status of the operation
|
|
|
|
TrafficType - the type of traffic we have received
|
|
|
|
Buffer - the received buffer (success only)
|
|
|
|
BufferLength - the length of the received buffer (success only)
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
HTTP2TrafficType NewReceivesPosted;
|
|
HTTP2TrafficType ThisCompleteTrafficType;
|
|
RPC_STATUS RpcStatus;
|
|
RPC_STATUS RpcStatus2;
|
|
BOOL BufferQueued;
|
|
RPC_STATUS RTSStatus;
|
|
RPC_STATUS DataStatus;
|
|
BOOL ReceiveCompletesFailed;
|
|
BYTE *CurrentPosition;
|
|
USHORT PacketFlags;
|
|
BOOL IssueAck;
|
|
ULONG BytesReceivedForAck;
|
|
ULONG WindowForAck;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_RECV_COMPLETE, HTTP2LOG_OT_ENDPOINT_RECEIVER, EventStatus);
|
|
|
|
BufferQueued = FALSE;
|
|
Mutex.Request();
|
|
|
|
if (EventStatus == RPC_S_OK)
|
|
{
|
|
if (IsRTSPacket(Buffer))
|
|
ThisCompleteTrafficType = http2ttRTS;
|
|
else
|
|
{
|
|
ThisCompleteTrafficType = http2ttData;
|
|
EventStatus = BytesReceivedNotification(BufferLength
|
|
);
|
|
|
|
if (EventStatus != RPC_S_OK)
|
|
{
|
|
// fall through with an error. Don't free the buffer
|
|
// (Rule 34).
|
|
}
|
|
}
|
|
}
|
|
|
|
if (EventStatus != RPC_S_OK)
|
|
{
|
|
if (ReceivesPosted == http2ttAny)
|
|
ThisCompleteTrafficType = http2ttData;
|
|
else
|
|
ThisCompleteTrafficType = ReceivesPosted;
|
|
}
|
|
|
|
ReceiveCompletesFailed = FALSE;
|
|
|
|
switch (ThisCompleteTrafficType)
|
|
{
|
|
case http2ttData:
|
|
if ((ReceivesPosted & http2ttData) == FALSE)
|
|
{
|
|
// we haven't asked for data, but we get some. We'll
|
|
// have to queue it
|
|
ASSERT(ReceivesQueued != http2ttRTS);
|
|
ReceivesQueued = http2ttData;
|
|
if (BufferQueue.PutOnQueue(Buffer, BufferLength))
|
|
{
|
|
ReceiveCompletesFailed = TRUE;
|
|
RpcFreeBuffer(Buffer);
|
|
}
|
|
BufferQueued = TRUE;
|
|
}
|
|
else
|
|
{
|
|
ReceivesPosted = (HTTP2TrafficType)(ReceivesPosted ^ http2ttData);
|
|
NewReceivesPosted = ReceivesPosted;
|
|
}
|
|
break;
|
|
|
|
case http2ttRTS:
|
|
if ((ReceivesPosted & http2ttRTS) == FALSE)
|
|
{
|
|
// we haven't asked for RTS, but we get some. We'll
|
|
// have to queue it
|
|
ASSERT(ReceivesQueued != http2ttData);
|
|
ReceivesQueued = http2ttRTS;
|
|
if (BufferQueue.PutOnQueue(Buffer, BufferLength))
|
|
{
|
|
ReceiveCompletesFailed = TRUE;
|
|
RpcFreeBuffer(Buffer);
|
|
}
|
|
BufferQueued = TRUE;
|
|
}
|
|
else
|
|
{
|
|
ReceivesPosted = (HTTP2TrafficType)(ReceivesPosted ^ http2ttRTS);
|
|
NewReceivesPosted = ReceivesPosted;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ASSERT(0);
|
|
break;
|
|
}
|
|
|
|
IssueAck = FALSE;
|
|
|
|
if ((BufferQueued == FALSE)
|
|
&& (ReceiveCompletesFailed == FALSE)
|
|
&& (ThisCompleteTrafficType == http2ttData)
|
|
&& (EventStatus == RPC_S_OK))
|
|
{
|
|
// we know the data will be consumed immediately
|
|
BytesConsumedNotification (BufferLength,
|
|
TRUE, // OwnsMutex
|
|
&IssueAck,
|
|
&BytesReceivedForAck,
|
|
&WindowForAck
|
|
);
|
|
}
|
|
|
|
Mutex.Clear();
|
|
|
|
if (IssueAck)
|
|
{
|
|
RpcStatus = SendFlowControlAck (BytesReceivedForAck,
|
|
WindowForAck
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
// turn this into a failure
|
|
EventStatus = RpcStatus;
|
|
// fall through to issuing the notification
|
|
if (EventStatus == RPC_P_SEND_FAILED)
|
|
EventStatus = RPC_P_RECEIVE_FAILED;
|
|
}
|
|
}
|
|
|
|
if (ReceiveCompletesFailed)
|
|
{
|
|
// we got an unwanted receive, and couldn't queue it.
|
|
// Abort the connection
|
|
TopChannel->AbortConnection(RPC_S_OUT_OF_MEMORY);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
if (BufferQueued)
|
|
{
|
|
ASSERT(ReceivesPosted != http2ttNone);
|
|
|
|
// the packet was not of the type we wanted.
|
|
// Submit another receive hoping to get what we want
|
|
RpcStatus = TopChannel->BeginSubmitAsync();
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
// we know we have one type of receive in the map. Nobody
|
|
// can post another. Just post ours
|
|
RpcStatus = HTTP2TransportChannel::Receive(http2ttRaw);
|
|
TopChannel->FinishSubmitAsync();
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
TopChannel->RemoveReference();
|
|
}
|
|
}
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
// we failed to submit the receive. We have to issue notification for it
|
|
RpcStatus = HTTP2TransportChannel::ReceiveComplete(RpcStatus,
|
|
ReceivesPosted,
|
|
NULL, // Buffer
|
|
0 // BufferLength
|
|
);
|
|
|
|
TopChannel->AbortConnection(RpcStatus);
|
|
}
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_RECV_COMPLETE, HTTP2LOG_OT_ENDPOINT_RECEIVER, RPC_P_PACKET_CONSUMED);
|
|
|
|
// if we have queued, this means the runtime did not
|
|
// ask for this packet. Don't let it see it.
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
// The buffer was not queued - pass it up
|
|
RpcStatus = HTTP2TransportChannel::ReceiveComplete(EventStatus,
|
|
ThisCompleteTrafficType,
|
|
Buffer,
|
|
BufferLength
|
|
);
|
|
|
|
if (NewReceivesPosted != http2ttNone)
|
|
{
|
|
// if we left something in the map, nobody could have
|
|
// posted a raw receive - they would have just upgraded the
|
|
// map. It could still have been aborted though
|
|
ASSERT((NewReceivesPosted == http2ttRTS)
|
|
||
|
|
(NewReceivesPosted == http2ttData));
|
|
|
|
// see what was left as pending recieve, and
|
|
// actually submit that. We do that only if we didn't
|
|
// fail before. If we did, don't bother
|
|
if (EventStatus == RPC_S_OK)
|
|
{
|
|
RpcStatus2 = TopChannel->BeginSimpleSubmitAsync();
|
|
if (RpcStatus2 == RPC_S_OK)
|
|
{
|
|
RpcStatus2 = HTTP2TransportChannel::Receive(http2ttRaw);
|
|
|
|
TopChannel->FinishSubmitAsync();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// transfer the error code
|
|
RpcStatus2 = EventStatus;
|
|
}
|
|
|
|
if (RpcStatus2 != RPC_S_OK)
|
|
{
|
|
// we failed to submit the receive. We have to issue notification for it
|
|
RpcStatus2 = HTTP2TransportChannel::ReceiveComplete(RpcStatus2,
|
|
NewReceivesPosted,
|
|
NULL, // Buffer
|
|
0 // BufferLength
|
|
);
|
|
|
|
if (NewReceivesPosted == http2ttRTS)
|
|
{
|
|
ASSERT(RpcStatus2 != RPC_S_OK);
|
|
}
|
|
|
|
TopChannel->RemoveReference(); // remove reference for the receive complete
|
|
}
|
|
}
|
|
|
|
// here, ThisCompleteTrafficType is the type of completed receive and
|
|
// RpcStatus is the status for it. NewReceivesPosted is the next submit we received
|
|
// (http2ttNone if none) and RpcStatus2 is the status for it.
|
|
|
|
// make sure nobody has left unconsumed success RTS packets
|
|
if (ThisCompleteTrafficType == http2ttRTS)
|
|
{
|
|
ASSERT(RpcStatus != RPC_S_OK);
|
|
}
|
|
|
|
// if there is only one receive type, no merging is necessary - just return
|
|
if (NewReceivesPosted == http2ttNone)
|
|
{
|
|
// consume RTS receives
|
|
if (ThisCompleteTrafficType == http2ttRTS)
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_RECV_COMPLETE, HTTP2LOG_OT_ENDPOINT_RECEIVER, RpcStatus);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
// Process them and determine the appropriate return code. If we
|
|
// have two receives, we merge them as per the table below
|
|
// N First Receive Second Receive Return value Note
|
|
// -- -------------- ----------------- ------------- ---------
|
|
// 1 DS RS S
|
|
// 2 DS RC S
|
|
// 3 DS RF S Abort
|
|
// 4 DF RS F
|
|
// 5 DF RC F
|
|
// 6 DF RF F Choose first receive error
|
|
// 7 RS DS Invalid combination (first RTS should have been consumed)
|
|
// 8 RC DS C
|
|
// 9 RF DS S Abort
|
|
// 10 RS DF Invalid combination (first RTS should have been consumed)
|
|
// 11 RC DF C Abort
|
|
// 12 RF DF F Choose first receive error
|
|
|
|
if (ThisCompleteTrafficType == http2ttData)
|
|
{
|
|
ASSERT(NewReceivesPosted == http2ttRTS);
|
|
DataStatus = RpcStatus;
|
|
RTSStatus = RpcStatus2;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(NewReceivesPosted == http2ttData);
|
|
DataStatus = RpcStatus2;
|
|
RTSStatus = RpcStatus;
|
|
}
|
|
|
|
if (DataStatus == RPC_S_OK)
|
|
{
|
|
if (RTSStatus == RPC_S_OK)
|
|
{
|
|
// case 1 - just return ok
|
|
RpcStatus = RTSStatus;
|
|
}
|
|
else if (RTSStatus == RPC_P_PACKET_CONSUMED)
|
|
{
|
|
// case 2
|
|
if (ThisCompleteTrafficType == http2ttData)
|
|
{
|
|
RpcStatus = DataStatus;
|
|
}
|
|
else
|
|
{
|
|
// case 8
|
|
RpcStatus = RTSStatus;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// cases 3 & 9
|
|
TopChannel->AbortConnection(RTSStatus);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (RTSStatus == RPC_S_OK)
|
|
{
|
|
// case 4
|
|
RpcStatus = DataStatus;
|
|
}
|
|
else if (RTSStatus == RPC_P_PACKET_CONSUMED)
|
|
{
|
|
// case 5
|
|
if (ThisCompleteTrafficType == http2ttData)
|
|
{
|
|
RpcStatus = DataStatus;
|
|
}
|
|
else
|
|
{
|
|
// case 11
|
|
TopChannel->AbortConnection(DataStatus);
|
|
RpcStatus = RTSStatus;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// cases 6 & 12
|
|
// nothing to do. First error is already in RpcStatus
|
|
}
|
|
}
|
|
|
|
if ((RpcStatus == RPC_P_CONNECTION_SHUTDOWN)
|
|
|| (RpcStatus == RPC_P_CONNECTION_CLOSED)
|
|
|| (RpcStatus == RPC_P_SEND_FAILED)
|
|
|| (RpcStatus == RPC_S_OUT_OF_MEMORY))
|
|
RpcStatus = RPC_P_RECEIVE_FAILED;
|
|
|
|
VALIDATE (RpcStatus)
|
|
{
|
|
RPC_S_OK,
|
|
RPC_P_PACKET_CONSUMED,
|
|
RPC_P_RECEIVE_FAILED
|
|
} END_VALIDATE;
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_RECV_COMPLETE, HTTP2LOG_OT_ENDPOINT_RECEIVER, RpcStatus);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
void HTTP2EndpointReceiver::Abort (
|
|
IN RPC_STATUS RpcStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Abort the channel
|
|
|
|
Arguments:
|
|
|
|
RpcStatus - the error code with which we abort
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
BYTE *CurrentBuffer;
|
|
UINT Ignored;
|
|
ULONG SizeOfQueueToLeave;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_ABORT, HTTP2LOG_OT_ENDPOINT_RECEIVER, RpcStatus);
|
|
|
|
HTTP2TransportChannel::Abort(RpcStatus);
|
|
|
|
Mutex.Request();
|
|
|
|
// if there is a direct complete posted, we have to
|
|
// leave one element in the queue, because the
|
|
// direct complete routine will need it
|
|
if (DirectCompletePosted)
|
|
SizeOfQueueToLeave = 1;
|
|
else
|
|
SizeOfQueueToLeave = 0;
|
|
|
|
while (BufferQueue.Size() > SizeOfQueueToLeave)
|
|
{
|
|
CurrentBuffer = (BYTE *) BufferQueue.TakeOffEndOfQueue(&Ignored);
|
|
// the elements in the queue are unwanted anyway -
|
|
// they don't have refcounts or anything else - just
|
|
// free them
|
|
RpcFreeBuffer(CurrentBuffer);
|
|
}
|
|
// If we have taken elements off the queue,
|
|
// mark that there are no longer any receives queued on this channel.
|
|
if (SizeOfQueueToLeave == 0)
|
|
{
|
|
ReceivesQueued = http2ttNone;
|
|
}
|
|
|
|
Mutex.Clear();
|
|
}
|
|
|
|
void HTTP2EndpointReceiver::FreeObject (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Frees the object. Acts like a destructor for the
|
|
channel.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
if (LowerLayer)
|
|
LowerLayer->FreeObject();
|
|
|
|
HTTP2EndpointReceiver::~HTTP2EndpointReceiver();
|
|
}
|
|
|
|
RPC_STATUS HTTP2EndpointReceiver::DirectReceiveComplete (
|
|
OUT BYTE **ReceivedBuffer,
|
|
OUT ULONG *ReceivedBufferLength,
|
|
OUT void **RuntimeConnection,
|
|
OUT BOOL *IsServer
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Direct receive completion (i.e. we posted a receive
|
|
to ourselves). We can be called in only one case -
|
|
a receive was submitted and there were already
|
|
queued receives.
|
|
|
|
Arguments:
|
|
|
|
ReceivedBuffer - the buffer that we received.
|
|
|
|
ReceivedBufferLength - the length of the received
|
|
buffer
|
|
|
|
RuntimeConnection - the connection to return to the runtime
|
|
if the packet is not consumed.
|
|
|
|
IsServer - non-zero if the server
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK, RPC_P_PACKET_CONSUMED or RPC_S_* errors.
|
|
|
|
Notes:
|
|
|
|
The directly posted receive carries a reference count
|
|
|
|
--*/
|
|
{
|
|
BYTE *Buffer;
|
|
ULONG BufferLength;
|
|
RPC_STATUS RpcStatus;
|
|
HTTP2TrafficType QueuedPacketsType;
|
|
BOOL IssueAck;
|
|
ULONG BytesReceivedForAck;
|
|
ULONG WindowForAck;
|
|
BOOL PacketNeedsFlowControl;
|
|
|
|
*IsServer = this->IsServer;
|
|
*RuntimeConnection = TopChannel->GetRuntimeConnection();
|
|
|
|
// dequeue a packet
|
|
// we cannot have a queue and a posted receive at the same time
|
|
ASSERT((ReceivesPosted & ReceivesQueued) == FALSE);
|
|
|
|
Mutex.Request();
|
|
|
|
ASSERT(DirectCompletePosted);
|
|
ASSERT(DirectReceiveInProgress == FALSE);
|
|
// they must be set in this order, because if a thread in
|
|
// TransferStateToNewReceiver synchronizes with us, it will check
|
|
// them in reverse order.
|
|
InterlockedIncrement((long *)&DirectReceiveInProgress);
|
|
DirectCompletePosted = FALSE;
|
|
|
|
QueuedPacketsType = ReceivesQueued;
|
|
|
|
Buffer = (BYTE *)BufferQueue.TakeOffQueue((UINT *)&BufferLength);
|
|
// even if aborted, at least one buffer must have been left for us
|
|
// because we had the DirectCompletePosted flag set
|
|
ASSERT (Buffer);
|
|
|
|
if (BufferQueue.IsQueueEmpty())
|
|
ReceivesQueued = http2ttNone;
|
|
|
|
PacketNeedsFlowControl = (((ULONG_PTR)Buffer & 1) == 0);
|
|
Buffer = (BYTE *)(((ULONG_PTR)Buffer) & (~(ULONG_PTR)1));
|
|
|
|
RpcStatus = RPC_S_OK;
|
|
*ReceivedBuffer = Buffer;
|
|
*ReceivedBufferLength = BufferLength;
|
|
|
|
// we know the data will be consumed.
|
|
IssueAck = FALSE;
|
|
if ((QueuedPacketsType == http2ttData) && PacketNeedsFlowControl)
|
|
{
|
|
BytesConsumedNotification (BufferLength,
|
|
TRUE, // OwnsMutex
|
|
&IssueAck,
|
|
&BytesReceivedForAck,
|
|
&WindowForAck
|
|
);
|
|
}
|
|
|
|
Mutex.Clear();
|
|
|
|
if (IssueAck)
|
|
{
|
|
RpcStatus = SendFlowControlAck (BytesReceivedForAck,
|
|
WindowForAck
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
// turn this into a failure. Note that we must supply a buffer
|
|
// on failure, hence we don't free it (Rule 34)
|
|
// fall through to issuing the notification
|
|
}
|
|
}
|
|
|
|
// decrement if before receive complete. In receive complete
|
|
// we may post another receive and cause a race
|
|
InterlockedDecrement((long *)&DirectReceiveInProgress);
|
|
|
|
// one of three things must happen here if this is a client. Either this
|
|
// is RTS packet, or somebody sync waits for this packet, or the connection
|
|
// is not exclusive
|
|
if (!this->IsServer)
|
|
{
|
|
// actually do the checks
|
|
if ((QueuedPacketsType != http2ttRTS)
|
|
&& (((HTTP2ClientChannel *)UpperLayer)->IsSyncRecvPending() == FALSE)
|
|
&& (I_RpcTransIsClientConnectionExclusive(*RuntimeConnection))
|
|
)
|
|
{
|
|
ASSERT(0);
|
|
// make it hold on free builds
|
|
*((ULONG *)0) = RpcStatus;
|
|
}
|
|
}
|
|
|
|
RpcStatus = HTTP2TransportChannel::ReceiveComplete(RpcStatus,
|
|
QueuedPacketsType,
|
|
Buffer,
|
|
BufferLength
|
|
);
|
|
|
|
if (QueuedPacketsType == http2ttRTS)
|
|
{
|
|
ASSERT(RpcStatus != RPC_S_OK);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
}
|
|
else
|
|
{
|
|
if (RpcStatus == RPC_P_SEND_FAILED)
|
|
RpcStatus = RPC_P_RECEIVE_FAILED;
|
|
}
|
|
|
|
RpcStatus = AsyncCompleteHelper(RpcStatus);
|
|
|
|
VALIDATE(RpcStatus)
|
|
{
|
|
RPC_P_CONNECTION_CLOSED,
|
|
RPC_P_RECEIVE_FAILED,
|
|
RPC_P_CONNECTION_SHUTDOWN,
|
|
RPC_P_PACKET_CONSUMED,
|
|
RPC_S_OK
|
|
} END_VALIDATE;
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2EndpointReceiver::TransferStateToNewReceiver (
|
|
OUT HTTP2EndpointReceiver *NewReceiver
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Transfers all the settings from this receiver (i.e. the state
|
|
of the receive) to a new one.
|
|
|
|
Arguments:
|
|
|
|
NewReceiver - the new receiver to transfer the settings to
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* errors.
|
|
|
|
Notes:
|
|
|
|
This must be called in an upcall context (i.e. no real receives
|
|
pending) and the channel on which this is called must be non-default
|
|
by now.
|
|
|
|
--*/
|
|
{
|
|
void *Buffer;
|
|
UINT BufferLength;
|
|
void *QueueElement;
|
|
int Result;
|
|
BOOL QueueTransferred;
|
|
|
|
// this channel is not a default channel by now. We know that
|
|
// there may be receives in progress, but no new receives will be
|
|
// submitted.
|
|
while (TRUE)
|
|
{
|
|
Mutex.Request();
|
|
if (DirectCompletePosted || DirectReceiveInProgress)
|
|
{
|
|
Mutex.Clear();
|
|
Sleep(5);
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
NewReceiver->Mutex.Request();
|
|
|
|
// transfer the settings for the base class
|
|
HTTP2GenericReceiver::TransferStateToNewReceiver(NewReceiver);
|
|
|
|
if (NewReceiver->BufferQueue.IsQueueEmpty() == FALSE)
|
|
{
|
|
// the only way we can end up here is if the channel replacement
|
|
// took so long that the peer started pinging us. This is a protocol
|
|
// error.
|
|
NewReceiver->Mutex.Clear();
|
|
Mutex.Clear();
|
|
return RPC_S_PROTOCOL_ERROR;
|
|
}
|
|
|
|
QueueTransferred = FALSE;
|
|
while (TRUE)
|
|
{
|
|
QueueElement = BufferQueue.TakeOffEndOfQueue(&BufferLength);
|
|
if (QueueElement == 0)
|
|
{
|
|
QueueTransferred = TRUE;
|
|
break;
|
|
}
|
|
|
|
if (NewReceiver->BufferQueue.PutOnFrontOfQueue((void *)((ULONG_PTR)QueueElement | 1), BufferLength) != 0)
|
|
{
|
|
// guaranteed to succeed since we never decrease buffers
|
|
BufferQueue.PutOnFrontOfQueue(QueueElement, BufferLength);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (QueueTransferred == FALSE)
|
|
{
|
|
// failure - out of memory. Since the buffers are unwanted
|
|
// we can just return failure. Both channels will be
|
|
// aborted
|
|
NewReceiver->Mutex.Clear();
|
|
Mutex.Clear();
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
NewReceiver->ReceivesQueued = ReceivesQueued;
|
|
|
|
// Mark that there are no longer any receives queued on this channel.
|
|
ReceivesQueued = http2ttNone;
|
|
|
|
// we never transfer data receives. They will be transferred by our caller
|
|
// We also preserve existing receives on the new receiver. There is a race where
|
|
// data receives may have ended up on the new channel. That's ok as long as they
|
|
// are off the old.
|
|
ASSERT(((NewReceiver->ReceivesPosted & http2ttData) == 0)
|
|
|| ((ReceivesPosted & http2ttData) == 0));
|
|
NewReceiver->ReceivesPosted
|
|
= (HTTP2TrafficType)(NewReceiver->ReceivesPosted | (ReceivesPosted & (~http2ttData)));
|
|
|
|
// direct complete posted cannot be true here
|
|
ASSERT(DirectCompletePosted == FALSE);
|
|
|
|
NewReceiver->Mutex.Clear();
|
|
|
|
Mutex.Clear();
|
|
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2ProxyReceiver
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS HTTP2ProxyReceiver::ReceiveComplete (
|
|
IN RPC_STATUS EventStatus,
|
|
IN HTTP2TrafficType TrafficType,
|
|
IN BYTE *Buffer,
|
|
IN UINT BufferLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Receive complete notification.
|
|
|
|
Arguments:
|
|
|
|
EventStatus - status of the operation
|
|
|
|
TrafficType - the type of traffic we have received
|
|
|
|
Buffer - the received buffer (success only)
|
|
|
|
BufferLength - the length of the received buffer (success only)
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
if ((EventStatus == RPC_S_OK) && (IsRTSPacket(Buffer) == FALSE))
|
|
{
|
|
Mutex.Request();
|
|
EventStatus = BytesReceivedNotification(BufferLength
|
|
);
|
|
Mutex.Clear();
|
|
|
|
if (EventStatus != RPC_S_OK)
|
|
{
|
|
// consume the packet and fall through with an error
|
|
RpcFreeBuffer(Buffer);
|
|
}
|
|
}
|
|
|
|
return HTTP2TransportChannel::ReceiveComplete(EventStatus,
|
|
TrafficType,
|
|
Buffer,
|
|
BufferLength
|
|
);
|
|
}
|
|
|
|
void HTTP2ProxyReceiver::Abort (
|
|
IN RPC_STATUS RpcStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Abort the channel.
|
|
|
|
Arguments:
|
|
|
|
RpcStatus - the error code with which we abort
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
BYTE *CurrentBuffer;
|
|
UINT Ignored;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_ABORT, HTTP2LOG_OT_PROXY_RECEIVER, RpcStatus);
|
|
|
|
HTTP2TransportChannel::Abort(RpcStatus);
|
|
|
|
Mutex.Request();
|
|
|
|
while (BufferQueue.Size() > 0)
|
|
{
|
|
CurrentBuffer = (BYTE *) BufferQueue.TakeOffQueue(&Ignored);
|
|
// the elements in the queue are unwanted anyway -
|
|
// they don't have refcounts or anything else - just
|
|
// free them
|
|
RpcFreeBuffer(CurrentBuffer);
|
|
}
|
|
Mutex.Clear();
|
|
}
|
|
|
|
void HTTP2ProxyReceiver::FreeObject (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Frees the object. Acts like a destructor for the
|
|
channel.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
if (LowerLayer)
|
|
LowerLayer->FreeObject();
|
|
|
|
HTTP2ProxyReceiver::~HTTP2ProxyReceiver();
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2PlugChannel
|
|
*********************************************************************/
|
|
|
|
C_ASSERT(http2plRTSPlugged == http2ttRTS);
|
|
C_ASSERT(http2plDataPlugged == http2ttData);
|
|
|
|
RPC_STATUS HTTP2PlugChannel::Send (
|
|
IN OUT HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send request
|
|
|
|
Arguments:
|
|
|
|
SendContext - the send context
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
HTTP2TrafficType SendType;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
#if DBG
|
|
TrafficSentOnChannel = TRUE;
|
|
#endif // DBG
|
|
|
|
SendType = SendContext->TrafficType;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_SEND, HTTP2LOG_OT_PLUG_CHANNEL, PtrToUlong(SendContext));
|
|
|
|
ASSERT((SendType == http2ttData)
|
|
|| (SendType == http2ttRTS)
|
|
|| (SendType == http2ttRaw) );
|
|
|
|
// if the plug level says this packet should not go through, queue it
|
|
// This means the traffic is no raw, and the plug level is less than
|
|
// the send type. Since the constants are ordered this comparison is
|
|
// sufficient
|
|
if ((SendType != http2ttRaw) && (PlugLevel <= SendType))
|
|
{
|
|
SendContext->SetListEntryUsed();
|
|
Mutex.Request();
|
|
// queue and exit
|
|
if (SendContext->Flags & SendContextFlagPutInFront)
|
|
{
|
|
RpcpfInsertHeadList(&BufferQueueHead, &SendContext->ListEntry);
|
|
}
|
|
else
|
|
{
|
|
RpcpfInsertTailList(&BufferQueueHead, &SendContext->ListEntry);
|
|
}
|
|
Mutex.Clear();
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_SEND, HTTP2LOG_OT_PLUG_CHANNEL, 0);
|
|
return RPC_S_OK;
|
|
}
|
|
else
|
|
{
|
|
// can't put in front on unplugged channel
|
|
ASSERT((SendContext->Flags & SendContextFlagPutInFront) == 0);
|
|
RpcStatus = HTTP2TransportChannel::Send(SendContext);
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_SEND, HTTP2LOG_OT_PLUG_CHANNEL, RpcStatus);
|
|
return RpcStatus;
|
|
}
|
|
}
|
|
|
|
void HTTP2PlugChannel::Abort (
|
|
IN RPC_STATUS RpcStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Abort the channel
|
|
|
|
Arguments:
|
|
|
|
RpcStatus - the error code with which we abort
|
|
|
|
Return Value:
|
|
|
|
Note: All sends carry a refcount. We must fully complete the sends
|
|
|
|
--*/
|
|
{
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_ABORT, HTTP2LOG_OT_PLUG_CHANNEL, RpcStatus);
|
|
|
|
HTTP2TransportChannel::Abort(RpcStatus);
|
|
|
|
SendFailedStatus = RpcStatus;
|
|
|
|
// Abort is made from submission context. We
|
|
// know it is synchronized with other submissions
|
|
// and we cannot issue upcalls for it. We will just post
|
|
// as many direct send completions as there are buffers in
|
|
// the queue. When the post comes around, it will dequeue
|
|
// and free one buffer for every post.
|
|
// We know that after abort there will be no more submissions,
|
|
// so not dequeuing them is fine
|
|
|
|
if (!RpcpIsListEmpty(&BufferQueueHead))
|
|
{
|
|
(void) COMMON_PostRuntimeEvent(PLUG_CHANNEL_DIRECT_SEND,
|
|
this
|
|
);
|
|
}
|
|
}
|
|
|
|
void HTTP2PlugChannel::FreeObject (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Frees the object. Acts like a destructor for the
|
|
channel.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
if (LowerLayer)
|
|
LowerLayer->FreeObject();
|
|
|
|
HTTP2PlugChannel::~HTTP2PlugChannel();
|
|
}
|
|
|
|
void HTTP2PlugChannel::Reset (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Reset the channel for next open/send/receive. This is
|
|
used in submission context only and implies there are no
|
|
pending operations on the channel. It is used on the client
|
|
during opening the connection to do quick negotiation on the
|
|
same connection instead of opening a new connection every time.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
ASSERT(SendFailedStatus);
|
|
ASSERT(RpcpIsListEmpty(&BufferQueueHead));
|
|
PlugLevel = http2plDataPlugged;
|
|
LowerLayer->Reset();
|
|
}
|
|
|
|
RPC_STATUS HTTP2PlugChannel::DirectSendComplete (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Direct send complete notification. Complete the send
|
|
passing it only through channels that have seen it (i.e.
|
|
above us)
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK
|
|
|
|
--*/
|
|
{
|
|
HTTP2SendContext *QueuedSendContext;
|
|
LIST_ENTRY *QueuedListEntry;
|
|
RPC_STATUS RpcStatus;
|
|
BOOL IsListEmpty;
|
|
|
|
ASSERT(SendFailedStatus != RPC_S_INTERNAL_ERROR);
|
|
|
|
// we wouldn't have a post if there wasn't something
|
|
// in the list.
|
|
ASSERT(!RpcpIsListEmpty(&BufferQueueHead));
|
|
|
|
do
|
|
{
|
|
QueuedListEntry = RpcpfRemoveHeadList(&BufferQueueHead);
|
|
|
|
// capture the state of the list before we do AsyncCompleteHelper.
|
|
// after that, the this object may be gone.
|
|
IsListEmpty = RpcpIsListEmpty(&BufferQueueHead);
|
|
|
|
QueuedSendContext = CONTAINING_RECORD(QueuedListEntry, HTTP2SendContext, ListEntry);
|
|
|
|
RpcStatus = HTTP2TransportChannel::SendComplete(SendFailedStatus, QueuedSendContext);
|
|
|
|
// we don't care about the return code.
|
|
(void) AsyncCompleteHelper(RpcStatus);
|
|
|
|
// if the list is empty by now, the this object is gone. Don't touch anything
|
|
}
|
|
while (!IsListEmpty);
|
|
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
RPC_STATUS HTTP2PlugChannel::Unplug (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Unplugs the channel. This means that all bottled up traffic
|
|
starts flowing forward.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* errors
|
|
|
|
--*/
|
|
{
|
|
HTTP2SendContext *QueuedSendContext;
|
|
LIST_ENTRY *QueuedListEntry;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
// first, send pending traffic. Then open the channel. Otherwise
|
|
// traffic may get out of order
|
|
|
|
while (TRUE)
|
|
{
|
|
Mutex.Request();
|
|
QueuedListEntry = RpcpfRemoveHeadList(&BufferQueueHead);
|
|
// if we had non-zero elements ...
|
|
if (QueuedListEntry != &BufferQueueHead)
|
|
{
|
|
Mutex.Clear();
|
|
}
|
|
else
|
|
{
|
|
// we have zero elements - just unplug the channel
|
|
PlugLevel = http2plUnplugged;
|
|
Mutex.Clear();
|
|
RpcStatus = RPC_S_OK;
|
|
break;
|
|
}
|
|
|
|
QueuedSendContext = CONTAINING_RECORD(QueuedListEntry, HTTP2SendContext, ListEntry);
|
|
|
|
QueuedSendContext->SetListEntryUnused();
|
|
|
|
// get into submission context - rule 9.
|
|
RpcStatus = TopChannel->BeginSimpleSubmitAsync();
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
RpcStatus = HTTP2TransportChannel::Send(QueuedSendContext);
|
|
TopChannel->FinishSubmitAsync();
|
|
}
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
QueuedSendContext->SetListEntryUsed();
|
|
Mutex.Request();
|
|
RpcpfInsertHeadList(&BufferQueueHead, QueuedListEntry);
|
|
Mutex.Clear();
|
|
break;
|
|
}
|
|
}
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
void HTTP2PlugChannel::SetStrongPlug (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Upgrades the default plug level (http2plDataPlugged) to
|
|
RTS (http2plRTSPlugged)
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
// make sure we haven't done any sending on the channel. The
|
|
// channel cannot change plug levels after the first send
|
|
ASSERT(TrafficSentOnChannel == FALSE);
|
|
PlugLevel = http2plRTSPlugged;
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2ProxyPlugChannel
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS HTTP2ProxyPlugChannel::AsyncCompleteHelper (
|
|
IN RPC_STATUS CurrentStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
A helper function that completes an async io.
|
|
|
|
Arguments:
|
|
|
|
CurrentStatus - the status with which the complete
|
|
notification completed.
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
return ProxyAsyncCompleteHelper(TopChannel, CurrentStatus);
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2FlowControlSender
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS HTTP2FlowControlSender::Send (
|
|
IN OUT HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send request
|
|
|
|
Arguments:
|
|
|
|
SendContext - the send context
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
if SendContextFlagSendLast is set, the following semantics applies:
|
|
RPC_S_OK - no sends are pending. Last context directly sent
|
|
ERROR_IO_PENDING - sends were pending. When they are all drained
|
|
top channel and virtual connection will be notified through
|
|
the LastPacketSentNotification mechanism
|
|
RPC_S_* errors occured during synchronous send
|
|
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
HTTP2SendContext *LocalSendContext;
|
|
|
|
if (SendContext->TrafficType == http2ttData)
|
|
{
|
|
// we can't send data without knowing the receive window
|
|
// of the peer
|
|
ASSERT(PeerReceiveWindow != 0);
|
|
}
|
|
|
|
if (SendContext->Flags & SendContextFlagSendLast)
|
|
{
|
|
// register the last send. We know if this is called, no
|
|
// new sends will be submitted. However, we race with the
|
|
// send complete thread
|
|
InterlockedExchangePointer((PVOID *)&SendContextOnDrain, SendContext);
|
|
|
|
if (SendsPending.GetInteger() == 0)
|
|
{
|
|
// no sends are pending. Attempt to grab back the context
|
|
// and do it synchronously
|
|
LocalSendContext =
|
|
(HTTP2SendContext *)InterlockedExchangePointer((PVOID *)&SendContextOnDrain, NULL);
|
|
if (LocalSendContext)
|
|
{
|
|
// we managed to grab it back. We have won the right to
|
|
// synchronously submit the last context.
|
|
RpcStatus = HTTP2TransportChannel::Send(LocalSendContext);
|
|
// return ok or an error
|
|
return RpcStatus;
|
|
}
|
|
}
|
|
|
|
// either there are sends pending, or we lost the race and we have to
|
|
// rely on asynchronous notifications
|
|
return ERROR_IO_PENDING;
|
|
}
|
|
|
|
SendsPending.Increment();
|
|
|
|
return SendInternal(SendContext,
|
|
FALSE // IgnoreQueuedPackets
|
|
);
|
|
}
|
|
|
|
RPC_STATUS HTTP2FlowControlSender::SendComplete (
|
|
IN RPC_STATUS EventStatus,
|
|
IN OUT HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send complete notification
|
|
|
|
Arguments:
|
|
|
|
EventStatus - the status of the send
|
|
SendContext - send context
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
RPC_STATUS RpcStatus2;
|
|
int LocalSendsPending;
|
|
HTTP2SendContext *LocalSendContextOnDrain;
|
|
|
|
LocalSendsPending = SendsPending.Decrement();
|
|
// in the case of Last packet to send completing, the counter will wrap to -1 here because
|
|
// the last send is not present in SendsPending. That's ok.
|
|
|
|
RpcStatus = HTTP2TransportChannel::SendComplete(EventStatus, SendContext);
|
|
|
|
if (LocalSendsPending == 0)
|
|
{
|
|
LocalSendContextOnDrain = (HTTP2SendContext *) SendContextOnDrain;
|
|
if (LocalSendContextOnDrain)
|
|
{
|
|
// try to consume the SendContextOnDrain in a thread safe manner
|
|
// in respect to a thread that is setting it. In the cases where SendContextOnDrain
|
|
// will be called the channel is already detached, so we know no new sends will
|
|
// be submitted and we don't need to worry about the race with SendsPending going
|
|
// up again
|
|
LocalSendContextOnDrain =
|
|
(HTTP2SendContext *)InterlockedCompareExchangePointer((PVOID *)&SendContextOnDrain,
|
|
NULL,
|
|
LocalSendContextOnDrain
|
|
);
|
|
|
|
if (LocalSendContextOnDrain)
|
|
{
|
|
// remove the reference for the previous send
|
|
TopChannel->RemoveReference();
|
|
|
|
// last packet must be RTS.
|
|
ASSERT(LocalSendContextOnDrain->TrafficType == http2ttRTS);
|
|
RpcStatus = TopChannel->LastPacketSentNotification(LocalSendContextOnDrain);
|
|
// don't care about return code. This channel is dying anyway
|
|
RpcStatus2 = TopChannel->Send(LocalSendContextOnDrain);
|
|
|
|
// the second error takes precedence here
|
|
if (RpcStatus2 != RPC_S_OK)
|
|
RpcStatus = RpcStatus2;
|
|
}
|
|
}
|
|
}
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
|
|
void HTTP2FlowControlSender::Abort (
|
|
IN RPC_STATUS RpcStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Abort the channel
|
|
|
|
Arguments:
|
|
|
|
RpcStatus - the error code with which we abort
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
// we have a bunch of sends carrying ref-counts, etc.
|
|
// We must make sure they are completed.
|
|
|
|
HTTP2TransportChannel::Abort(RpcStatus);
|
|
|
|
// we know we are synchronized with everybody else
|
|
if (!RpcpIsListEmpty(&BufferQueueHead))
|
|
{
|
|
AbortStatus = RpcStatus;
|
|
(void) COMMON_PostRuntimeEvent(HTTP2_FLOW_CONTROL_DIRECT_SEND,
|
|
this
|
|
);
|
|
}
|
|
}
|
|
|
|
void HTTP2FlowControlSender::FreeObject (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Frees the object. Acts like a destructor for the
|
|
channel.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
if (LowerLayer)
|
|
LowerLayer->FreeObject();
|
|
|
|
HTTP2FlowControlSender::~HTTP2FlowControlSender();
|
|
}
|
|
|
|
void HTTP2FlowControlSender::SendCancelled (
|
|
IN HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
A lower channel cancelled a send already passed through this channel.
|
|
|
|
Arguments:
|
|
|
|
SendContext - the send context of the send that was cancelled
|
|
|
|
Return Value:
|
|
|
|
Note:
|
|
|
|
The channel must not be receiving new requests by now (i.e.
|
|
it must be non-default and fully drained)
|
|
|
|
--*/
|
|
{
|
|
SendsPending.Decrement();
|
|
|
|
UpperLayer->SendCancelled(SendContext);
|
|
}
|
|
|
|
RPC_STATUS HTTP2FlowControlSender::FlowControlAckNotify (
|
|
IN ULONG BytesReceivedForAck,
|
|
IN ULONG WindowForAck
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Notifies the channel that a flow control ack has arrived.
|
|
|
|
Arguments:
|
|
|
|
BytesReceivedForAck - the bytes received from the ack packet
|
|
|
|
WindowForAck - the available window advertised in the ack
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_PROTOCOL_ERROR if the received values are bogus
|
|
|
|
--*/
|
|
{
|
|
LIST_ENTRY *CurrentListEntry;
|
|
LIST_ENTRY *NextListEntry;
|
|
HTTP2SendContext *SendContext;
|
|
RPC_STATUS RpcStatus;
|
|
BOOL ChannelNeedsRecycling;
|
|
|
|
#if 0
|
|
DbgPrint("%X: HTTP2FlowControlSender::FlowControlAckNotify: %d; %d; %d\n",
|
|
GetCurrentProcessId(),
|
|
DataBytesSent,
|
|
BytesReceivedForAck,
|
|
WindowForAck
|
|
);
|
|
#endif
|
|
|
|
RpcStatus = RPC_S_OK;
|
|
ChannelNeedsRecycling = FALSE;
|
|
|
|
Mutex.Request();
|
|
CORRUPTION_ASSERT((DataBytesSent - BytesReceivedForAck) <= PeerReceiveWindow);
|
|
if ((DataBytesSent - BytesReceivedForAck) > PeerReceiveWindow)
|
|
{
|
|
Mutex.Clear();
|
|
return RPC_S_PROTOCOL_ERROR;
|
|
}
|
|
|
|
PeerAvailableWindow = WindowForAck - (DataBytesSent - BytesReceivedForAck);
|
|
CORRUPTION_ASSERT(PeerAvailableWindow <= PeerReceiveWindow);
|
|
if (PeerAvailableWindow > PeerReceiveWindow)
|
|
{
|
|
Mutex.Clear();
|
|
return RPC_S_PROTOCOL_ERROR;
|
|
}
|
|
|
|
// did we free up enough window to send some of our queued buffers?
|
|
CurrentListEntry = BufferQueueHead.Flink;
|
|
while (CurrentListEntry != &BufferQueueHead)
|
|
{
|
|
SendContext = CONTAINING_RECORD(CurrentListEntry, HTTP2SendContext, ListEntry);
|
|
if (SendContext->maxWriteBuffer <= PeerAvailableWindow)
|
|
{
|
|
SendContext->SetListEntryUnused();
|
|
RpcpfRemoveHeadList(&BufferQueueHead);
|
|
// set the CurrentListEntry for the next iteration of the loop
|
|
CurrentListEntry = BufferQueueHead.Flink;
|
|
// send it through this channel. This will update DataBytesSent
|
|
// and PeerAvailableWindow
|
|
RpcStatus = SendInternal(SendContext,
|
|
TRUE // IgnoreQueuedPackets
|
|
);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
if (RpcStatus != RPC_P_CHANNEL_NEEDS_RECYCLING)
|
|
{
|
|
// we failed to send - stick back the current context and
|
|
// return error. This will cause caller to abort and
|
|
// this will complete all queued sends
|
|
SendContext->SetListEntryUsed();
|
|
RpcpfInsertHeadList(&BufferQueueHead, &SendContext->ListEntry);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// remeber that we need to return RPC_P_CHANNEL_NEEDS_RECYCLING at
|
|
// the end
|
|
ChannelNeedsRecycling = TRUE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// we don't have enough space to send more. Break out of the loop
|
|
break;
|
|
}
|
|
}
|
|
Mutex.Clear();
|
|
|
|
if (ChannelNeedsRecycling)
|
|
return RPC_P_CHANNEL_NEEDS_RECYCLING;
|
|
else
|
|
return RpcStatus;
|
|
}
|
|
|
|
void HTTP2FlowControlSender::GetBufferQueue (
|
|
OUT LIST_ENTRY *NewQueueHead
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Grab all queued buffers and pile them on the list head
|
|
that we passed to it. All refcounts must be removed (i.e.
|
|
undone).
|
|
|
|
Arguments:
|
|
|
|
NewQueueHead - new queue heads to pile buffers on
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
LIST_ENTRY *CurrentListEntry;
|
|
LIST_ENTRY *NextListEntry;
|
|
HTTP2SendContext *SendContext;
|
|
|
|
ASSERT(RpcpIsListEmpty(NewQueueHead));
|
|
|
|
Mutex.Request();
|
|
CurrentListEntry = BufferQueueHead.Flink;
|
|
while (CurrentListEntry != &BufferQueueHead)
|
|
{
|
|
SendContext = CONTAINING_RECORD(CurrentListEntry, HTTP2SendContext, ListEntry);
|
|
SendContext->SetListEntryUnused();
|
|
NextListEntry = CurrentListEntry->Flink;
|
|
RpcpfInsertHeadList(NewQueueHead, CurrentListEntry);
|
|
UpperLayer->SendCancelled(SendContext);
|
|
CurrentListEntry = NextListEntry;
|
|
}
|
|
RpcpInitializeListHead(&BufferQueueHead);
|
|
Mutex.Clear();
|
|
}
|
|
|
|
RPC_STATUS HTTP2FlowControlSender::DirectSendComplete (
|
|
OUT BOOL *IsServer,
|
|
OUT BOOL *SendToRuntime,
|
|
OUT void **SendContext,
|
|
OUT BUFFER *Buffer,
|
|
OUT UINT *BufferLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Direct send complete notification. Complete the send
|
|
passing it only through channels that have seen it (i.e.
|
|
above us). Note that we will get one notification for
|
|
all buffered sends. We must empty the whole queue, and post
|
|
one notification for each buffer in the queue
|
|
|
|
Arguments:
|
|
|
|
IsServer - in all cases MUST be set to TRUE or FALSE.
|
|
|
|
SendToRuntime - in all cases MUST be set to TRUE or FALSE. If FALSE,
|
|
it won't be sent to the runtime (used by proxies)
|
|
|
|
SendContext - on output contains the send context as
|
|
seen by the runtime
|
|
|
|
Buffer - on output the buffer that we tried to send
|
|
|
|
BufferLength - on output the length of the buffer we tried to send
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK to return error to runtime
|
|
RPC_P_PACKET_CONSUMED - to hide packet from runtime
|
|
RPC_S_* error - return error to runtime
|
|
|
|
--*/
|
|
{
|
|
LIST_ENTRY *CurrentListEntry;
|
|
HTTP2SendContext *CurrentSendContext;
|
|
RPC_STATUS RpcStatus;
|
|
BOOL PostAnotherReceive;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_DIRECT_SEND_COMPLETE, HTTP2LOG_OT_FLOW_CONTROL_SENDER,
|
|
!RpcpIsListEmpty(&BufferQueueHead));
|
|
*IsServer = (BOOL)(this->IsServer);
|
|
*SendToRuntime = (BOOL)(this->SendToRuntime);
|
|
|
|
// this should only get called when we are aborted. This
|
|
// ensures that we are single threaded in the code
|
|
// below
|
|
TopChannel->VerifyAborted();
|
|
|
|
CurrentListEntry = RpcpfRemoveHeadList(&BufferQueueHead);
|
|
ASSERT(CurrentListEntry != &BufferQueueHead);
|
|
|
|
CurrentSendContext = CONTAINING_RECORD(CurrentListEntry, HTTP2SendContext, ListEntry);
|
|
CurrentSendContext->SetListEntryUnused();
|
|
|
|
ASSERT(AbortStatus != RPC_S_OK);
|
|
|
|
RpcStatus = HTTP2TransportChannel::SendComplete(AbortStatus, CurrentSendContext);
|
|
|
|
PostAnotherReceive = !(RpcpIsListEmpty(&BufferQueueHead));
|
|
|
|
if ((RpcStatus != RPC_P_PACKET_CONSUMED) && this->SendToRuntime)
|
|
{
|
|
// this will return to the runtime. Make sure it is valid
|
|
if (this->IsServer)
|
|
I_RpcTransVerifyServerRuntimeCallFromContext(CurrentSendContext);
|
|
else
|
|
I_RpcTransVerifyClientRuntimeCallFromContext(CurrentSendContext);
|
|
*SendContext = CurrentSendContext;
|
|
*Buffer = CurrentSendContext->pWriteBuffer;
|
|
*BufferLength = CurrentSendContext->maxWriteBuffer;
|
|
}
|
|
else
|
|
{
|
|
// the packet was a transport packet - it won't be seen by the runtime
|
|
*SendContext = NULL;
|
|
*Buffer = NULL;
|
|
*BufferLength = 0;
|
|
}
|
|
|
|
RpcStatus = AsyncCompleteHelper(RpcStatus);
|
|
// do not touch this pointer after here unless the list was not-empty
|
|
// (which implies we still have refcounts)
|
|
|
|
if (PostAnotherReceive)
|
|
{
|
|
(void) COMMON_PostRuntimeEvent(HTTP2_FLOW_CONTROL_DIRECT_SEND,
|
|
this
|
|
);
|
|
}
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_DIRECT_SEND_COMPLETE, HTTP2LOG_OT_FLOW_CONTROL_SENDER,
|
|
PostAnotherReceive);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2FlowControlSender::SendInternal (
|
|
IN OUT HTTP2SendContext *SendContext,
|
|
IN BOOL IgnoreQueuedBuffers
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send request without incrementing SendsPending counter
|
|
and without handling SendContextFlagSendLast
|
|
|
|
Arguments:
|
|
|
|
SendContext - the send context
|
|
|
|
IgnoreQueuedBuffers - if non-zero, the send will proceed even if
|
|
there are queued buffers. If FALSE, the send will be queued
|
|
if there are queued buffers.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
HTTP2SendContext *LocalSendContext;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_SEND, HTTP2LOG_OT_FLOW_CONTROL_SENDER, PtrToUlong(SendContext));
|
|
|
|
if (SendContext->TrafficType == http2ttData)
|
|
{
|
|
Mutex.Request();
|
|
// if the peer doesn't have enough window to accept this packet
|
|
// or there are queued packets and we were told not to ignore them,
|
|
// we have to queue it. Otherwise we can send it
|
|
if (
|
|
(PeerAvailableWindow < SendContext->maxWriteBuffer)
|
|
||
|
|
(
|
|
(IgnoreQueuedBuffers == FALSE)
|
|
&&
|
|
(BufferQueueHead.Flink != &BufferQueueHead)
|
|
)
|
|
)
|
|
{
|
|
// either the receiver doesn't have enough window or
|
|
// we have pending buffers
|
|
SendContext->SetListEntryUsed();
|
|
RpcpfInsertTailList(&BufferQueueHead, &SendContext->ListEntry);
|
|
Mutex.Clear();
|
|
#if DBG_ERROR
|
|
DbgPrint("Flow controlling sends ...%p\n", this);
|
|
#endif
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_SEND, HTTP2LOG_OT_FLOW_CONTROL_SENDER, 0);
|
|
return RPC_S_OK;
|
|
}
|
|
else
|
|
{
|
|
// yes, update counters and continue with send
|
|
DataBytesSent += SendContext->maxWriteBuffer;
|
|
PeerAvailableWindow -= SendContext->maxWriteBuffer;
|
|
}
|
|
Mutex.Clear();
|
|
}
|
|
|
|
RpcStatus = HTTP2TransportChannel::Send(SendContext);
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_SEND, HTTP2LOG_OT_FLOW_CONTROL_SENDER, RpcStatus);
|
|
return RpcStatus;
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2PingOriginator
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS HTTP2PingOriginator::Send (
|
|
IN OUT HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send request
|
|
|
|
Arguments:
|
|
|
|
SendContext - the send context
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
ConsecutivePingsOnInterval = 0;
|
|
|
|
return SendInternal(SendContext);
|
|
}
|
|
|
|
RPC_STATUS HTTP2PingOriginator::SendComplete (
|
|
IN RPC_STATUS EventStatus,
|
|
IN OUT HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send complete notification. Consume packets generated by us
|
|
and forward everything else up.
|
|
|
|
Arguments:
|
|
|
|
EventStatus - the status of the send
|
|
|
|
SendContext - send context
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
if ((SendContext->TrafficType == http2ttRTS)
|
|
&& (TrustedIsPingPacket(SendContext->pWriteBuffer)))
|
|
{
|
|
// this is a packet we generated. Eat it up
|
|
FreeRTSPacket(SendContext);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
return HTTP2TransportChannel::SendComplete(EventStatus, SendContext);
|
|
}
|
|
|
|
RPC_STATUS HTTP2PingOriginator::SetKeepAliveTimeout (
|
|
IN BOOL TurnOn,
|
|
IN BOOL bProtectIO,
|
|
IN KEEPALIVE_TIMEOUT_UNITS Units,
|
|
IN OUT KEEPALIVE_TIMEOUT KATime,
|
|
IN ULONG KAInterval OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Change the keep alive value on the channel
|
|
|
|
Arguments:
|
|
|
|
TurnOn - if non-zero, keep alives are turned on. If zero, keep alives
|
|
are turned off.
|
|
|
|
bProtectIO - non-zero if IO needs to be protected against async close
|
|
of the connection. Ignored for this function since we are always
|
|
protected when we start a new submit.
|
|
|
|
Units - in what units is KATime
|
|
|
|
KATime - how much to wait before turning on keep alives. Ignored in this
|
|
function.
|
|
|
|
KAInterval - the interval between keep alives
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
ULONG LocalNewPingInterval;
|
|
|
|
// technically the time stamp can be 0, but this would
|
|
// be extremely rare
|
|
ASSERT(LastPacketSentTimestamp);
|
|
ASSERT(Units == tuMilliseconds);
|
|
|
|
if (TurnOn == FALSE)
|
|
KeepAliveInterval = 0;
|
|
else
|
|
KeepAliveInterval = KAInterval;
|
|
|
|
LocalNewPingInterval = GetPingInterval(ConnectionTimeout,
|
|
KeepAliveInterval
|
|
);
|
|
|
|
return SetNewPingInterval(LocalNewPingInterval);
|
|
}
|
|
|
|
void HTTP2PingOriginator::Abort (
|
|
IN RPC_STATUS RpcStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Abort the channel
|
|
|
|
Arguments:
|
|
|
|
RpcStatus - the error code with which we abort
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
HTTP2TransportChannel::Abort(RpcStatus);
|
|
|
|
// we are already synchronized with everybody. Just
|
|
// call the internal function
|
|
DisablePingsInternal();
|
|
}
|
|
|
|
void HTTP2PingOriginator::FreeObject (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Frees the object. Acts like a destructor for the
|
|
channel.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
if (LowerLayer)
|
|
LowerLayer->FreeObject();
|
|
|
|
HTTP2PingOriginator::~HTTP2PingOriginator();
|
|
}
|
|
|
|
void HTTP2PingOriginator::SendCancelled (
|
|
IN HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
A lower channel cancelled a send already passed through this channel.
|
|
|
|
Arguments:
|
|
|
|
SendContext - the send context of the send that was cancelled
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
// a call was cancelled. We don't know what was the last sent
|
|
// time before that, so the only safe thing to do is send another
|
|
// ping. This should be extremely rare as it happens only sometimes
|
|
// during channel recycling.
|
|
|
|
RpcStatus = ReferenceFromCallback();
|
|
|
|
// if already aborted, don't bother
|
|
if (RpcStatus != RPC_S_OK)
|
|
return;
|
|
|
|
// we don't care about the result. The channel is dying. If we
|
|
// managed to submit the ping, it's better. If not, we hope the
|
|
// channel will last for long enough in order to complete the
|
|
// recycle process
|
|
RpcStatus = SendPingPacket();
|
|
|
|
// SendCancelled will be called only when the channel is close to the
|
|
// end of its recycling. We cannot get another recycling request here.
|
|
ASSERT(RpcStatus != RPC_P_CHANNEL_NEEDS_RECYCLING);
|
|
|
|
TopChannel->FinishSubmitAsync();
|
|
|
|
UpperLayer->SendCancelled(SendContext);
|
|
}
|
|
|
|
void HTTP2PingOriginator::Reset (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Reset the channel for next open/send/receive. This is
|
|
used in submission context only and implies there are no
|
|
pending operations on the channel. It is used on the client
|
|
during opening the connection to do quick negotiation on the
|
|
same connection instead of opening a new connection every time.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
LastPacketSentTimestamp = 0;
|
|
|
|
LowerLayer->Reset();
|
|
}
|
|
|
|
RPC_STATUS HTTP2PingOriginator::SetConnectionTimeout (
|
|
IN ULONG ConnectionTimeout
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sets the connection timeout for the ping channel. The ping channel
|
|
does not ping when initialized. This call starts the process. It is
|
|
synchronized with DisablePings but not with Aborts.
|
|
|
|
Arguments:
|
|
|
|
ConnectionTimeout - the connection timeout in milliseconds
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
ULONG LocalNewPingInterval;
|
|
|
|
// we don't accept anything less than the minimum timeout
|
|
if (ConnectionTimeout <= MinimumConnectionTimeout)
|
|
return RPC_S_PROTOCOL_ERROR;
|
|
|
|
// technically the time stamp can be 0, but this would
|
|
// be extremely rare
|
|
ASSERT(LastPacketSentTimestamp);
|
|
|
|
if (OverrideMinimumConnectionTimeout)
|
|
this->ConnectionTimeout = min(ConnectionTimeout, OverrideMinimumConnectionTimeout);
|
|
else
|
|
this->ConnectionTimeout = ConnectionTimeout;
|
|
|
|
LocalNewPingInterval = GetPingInterval(ConnectionTimeout,
|
|
KeepAliveInterval
|
|
);
|
|
|
|
return SetNewPingInterval(LocalNewPingInterval);
|
|
}
|
|
|
|
void HTTP2PingOriginator::DisablePings (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Disables the pings for the channel. Synchronized with
|
|
SetConnectionTimeout but not with Aborts
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
// synchronize with aborts and then call internal
|
|
// routine
|
|
RpcStatus = TopChannel->BeginSimpleSubmitAsync();
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
DisablePingsInternal();
|
|
TopChannel->FinishSubmitAsync();
|
|
}
|
|
}
|
|
|
|
void HTTP2PingOriginator::TimerCallback (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Timer callback routine - a periodic timer fired.
|
|
Figure out what type of timer it was, and take
|
|
appropriate action.
|
|
|
|
N.B. We enter this routine with BeginSubmitAsync
|
|
called on this channel.
|
|
|
|
N.B. We enter this routine with one refcount on
|
|
the top channel.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
ULONG CurrentTickCount;
|
|
ULONG LocalLastSentTickCount;
|
|
RPC_STATUS RpcStatus;
|
|
ULONG LocalPingInterval;
|
|
BOOL PingPacketSent;
|
|
|
|
LocalLastSentTickCount = LastPacketSentTimestamp;
|
|
CurrentTickCount = NtGetTickCount();
|
|
|
|
// if less than the grace period has expired since the last
|
|
// packet was sent, don't bother to send a ping
|
|
if (CurrentTickCount - LocalLastSentTickCount >= GetGracePeriod())
|
|
{
|
|
PingPacketSent = TRUE;
|
|
ConsecutivePingsOnInterval ++;
|
|
|
|
#if DBG_ERROR
|
|
DbgPrint("Timer expired. No recent activity - sending ping ...\n");
|
|
#endif
|
|
RpcStatus = SendPingPacket();
|
|
|
|
if ((RpcStatus != RPC_S_OK)
|
|
&& (RpcStatus != RPC_P_CHANNEL_NEEDS_RECYCLING))
|
|
{
|
|
// if this fails, SendPingPacket did not take over
|
|
// the refcount. In this case we hand over the
|
|
// refcount to HTTP2_ABORT_CONNECTION which still
|
|
// needs one refcount to operate.
|
|
#if DBG_ERROR
|
|
DbgPrint("Ping failed. Aborting connection.\n");
|
|
#endif
|
|
TopChannel->FinishSubmitAsync();
|
|
// offload the aborting to a worker thread (rule 33)
|
|
(void) COMMON_PostRuntimeEvent(HTTP2_ABORT_CONNECTION,
|
|
TopChannel
|
|
);
|
|
return;
|
|
}
|
|
|
|
// if SendPingPacket succeeds, it took over
|
|
// the refcount. We have no refcount in this code path. The only
|
|
// thing that prevents a problem is that we haven't called
|
|
// FinishSubmitAsync yet. Until we call this, we cannot be
|
|
// aborted.
|
|
if (ConsecutivePingsOnInterval >= ThresholdConsecutivePingsOnInterval)
|
|
{
|
|
LocalPingInterval = ScaleBackPingInterval();
|
|
|
|
if (LocalPingInterval > PingInterval)
|
|
{
|
|
// we need to scale back. We can't do it from the timer callback, so we
|
|
// need to offload to a worker thread for this
|
|
|
|
ConsecutivePingsOnInterval = 0;
|
|
|
|
// add a reference for the offloaded work item
|
|
TopChannel->AddReference();
|
|
|
|
(void) COMMON_PostRuntimeEvent(HTTP2_RESCHEDULE_TIMER,
|
|
this
|
|
);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PingPacketSent = FALSE;
|
|
#if DBG_ERROR
|
|
DbgPrint("Timer expired. Recent activity on channel detected - no ping necessary\n");
|
|
#endif
|
|
}
|
|
|
|
if (PingPacketSent != FALSE)
|
|
{
|
|
if (RpcStatus == RPC_P_CHANNEL_NEEDS_RECYCLING)
|
|
{
|
|
// we have the timer callback reference here which protects us. Once we
|
|
// offload to a worker thread, the reference doesn't hold. Add another
|
|
// reference for this.
|
|
TopChannel->AddReference();
|
|
|
|
TopChannel->FinishSubmitAsync();
|
|
|
|
// offload the recycling to a worker thread. This is necessary because
|
|
// the recycling will abort on failure which violates rule 33.
|
|
(void) COMMON_PostRuntimeEvent(HTTP2_RECYCLE_CHANNEL,
|
|
TopChannel
|
|
);
|
|
}
|
|
else
|
|
{
|
|
TopChannel->FinishSubmitAsync();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TopChannel->FinishSubmitAsync();
|
|
|
|
// drop the reference added for us by the timer callback
|
|
TopChannel->RemoveReference();
|
|
}
|
|
}
|
|
|
|
RPC_STATUS HTTP2PingOriginator::ReferenceFromCallback (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
References a ping originator object from the callback
|
|
routine.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
return TopChannel->BeginSubmitAsync();
|
|
}
|
|
|
|
RPC_STATUS HTTP2PingOriginator::SetNewPingInterval (
|
|
IN ULONG NewPingInterval
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Puts into effect the new ping interval. This means
|
|
cancelling the old interval (if any) and setting
|
|
the timer for the new. Must NOT be called from
|
|
timer callbacks or we will deadlock.
|
|
|
|
Arguments:
|
|
|
|
NewPingInterval - the new ping interval to use
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
BOOL Result;
|
|
|
|
// the new interval is different than the old. Need to update
|
|
// and reschedule
|
|
PingInterval = NewPingInterval;
|
|
ConsecutivePingsOnInterval = 0;
|
|
|
|
// synchronize with Aborts
|
|
RpcStatus = TopChannel->BeginSimpleSubmitAsync();
|
|
if (RpcStatus != RPC_S_OK)
|
|
return RpcStatus;
|
|
|
|
if (PingTimer)
|
|
{
|
|
DisablePingsInternal();
|
|
}
|
|
|
|
Result = CreateTimerQueueTimer(&PingTimer,
|
|
NULL,
|
|
HTTP2PingTimerCallback,
|
|
this,
|
|
PingInterval, // time to first fire
|
|
PingInterval, // periodic interval
|
|
WT_EXECUTELONGFUNCTION
|
|
);
|
|
|
|
if (Result == FALSE)
|
|
{
|
|
PingTimer = NULL;
|
|
TopChannel->FinishSubmitAsync();
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
// add one reference for the timer callback we have set up
|
|
TopChannel->AddReference();
|
|
|
|
TopChannel->FinishSubmitAsync();
|
|
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
void HTTP2PingOriginator::RescheduleTimer (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Reschedules a timer. This means scale back a timer.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
ULONG LocalPingInterval;
|
|
|
|
LocalPingInterval = ScaleBackPingInterval();
|
|
|
|
if (LocalPingInterval > PingInterval)
|
|
{
|
|
// ignore the result. Scaling back is a best effort.
|
|
// If it fails, that's ok.
|
|
(void) SetNewPingInterval(LocalPingInterval);
|
|
}
|
|
|
|
// remove the reference for the work item
|
|
TopChannel->RemoveReference();
|
|
}
|
|
|
|
void HTTP2PingOriginator::DisablePingsInternal (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Disables the pings for the channel. Must be synchronized with
|
|
SetConnectionTimeout, Abort and DisablePings
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
BOOL Result;
|
|
|
|
if (PingTimer)
|
|
{
|
|
Result = DeleteTimerQueueTimer(NULL,
|
|
PingTimer,
|
|
INVALID_HANDLE_VALUE // tell the timer function to wait for all callbacks
|
|
// to complete before returning
|
|
);
|
|
|
|
#if DBG
|
|
// during process shutdown the loader termination code will
|
|
// shutdown threads (including the NTDLL thread pool threads)
|
|
// before it indicates to anybody that it is doing so. This ASSERT
|
|
// will fire in such cases causing random stress breaks. Disable it.
|
|
// ASSERT(Result);
|
|
#endif // DBG
|
|
|
|
// we added one reference for the timer callback. Remove it
|
|
TopChannel->RemoveReference();
|
|
|
|
PingTimer = NULL;
|
|
}
|
|
}
|
|
|
|
RPC_STATUS HTTP2PingOriginator::SendPingPacket (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sends a ping packet on this channel. Must be called with AsyncSubmit
|
|
started.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
HTTP2SendContext *PingPacket;
|
|
ULONG PingPacketSize;
|
|
|
|
PingPacket = AllocateAndInitializePingPacket();
|
|
if (PingPacket == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
|
|
PingPacketSize = PingPacket->maxWriteBuffer;
|
|
|
|
RpcStatus = SendInternal(PingPacket);
|
|
if ((RpcStatus != RPC_S_OK) && (RpcStatus != RPC_P_CHANNEL_NEEDS_RECYCLING))
|
|
FreeRTSPacket(PingPacket);
|
|
else if (NotifyTopChannelForPings)
|
|
TopChannel->PingTrafficSentNotify(PingPacketSize);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2PingOriginator::SendInternal (
|
|
IN OUT HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send request
|
|
|
|
Arguments:
|
|
|
|
SendContext - the send context
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
LastPacketSentTimestamp = NtGetTickCount();
|
|
|
|
return HTTP2TransportChannel::Send(SendContext);
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2PingReceiver
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS HTTP2PingReceiver::ReceiveComplete (
|
|
IN RPC_STATUS EventStatus,
|
|
IN HTTP2TrafficType TrafficType,
|
|
IN BYTE *Buffer,
|
|
IN UINT BufferLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Receive complete notification.
|
|
|
|
Arguments:
|
|
|
|
EventStatus - status of the operation
|
|
|
|
TrafficType - the type of traffic we have received
|
|
|
|
Buffer - the received buffer (success only)
|
|
|
|
BufferLength - the length of the received buffer (success only)
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
if (EventStatus == RPC_S_OK)
|
|
{
|
|
if (IsRTSPacket(Buffer) && UntrustedIsPingPacket(Buffer, BufferLength))
|
|
{
|
|
// this is a ping packet. Consume it and post another receive if
|
|
// necessary
|
|
if (PostAnotherReceive)
|
|
{
|
|
EventStatus = TopChannel->BeginSubmitAsync();
|
|
if (EventStatus == RPC_S_OK)
|
|
{
|
|
EventStatus = HTTP2TransportChannel::Receive(http2ttRaw);
|
|
TopChannel->FinishSubmitAsync();
|
|
if (EventStatus != RPC_S_OK)
|
|
TopChannel->RemoveReference();
|
|
}
|
|
}
|
|
|
|
if (EventStatus == RPC_S_OK)
|
|
{
|
|
// we free the buffer only in success case. In failure case
|
|
// we need a buffer to pass to receive complete down.
|
|
RpcFreeBuffer(Buffer);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
else
|
|
{
|
|
// fall through to indicating a receive failure below
|
|
}
|
|
}
|
|
}
|
|
|
|
return HTTP2TransportChannel::ReceiveComplete(EventStatus, TrafficType, Buffer, BufferLength);
|
|
}
|
|
|
|
void HTTP2PingReceiver::FreeObject (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Frees the object. Acts like a destructor for the
|
|
channel.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
if (LowerLayer)
|
|
LowerLayer->FreeObject();
|
|
|
|
HTTP2PingReceiver::~HTTP2PingReceiver();
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2ChannelDataOriginator
|
|
*********************************************************************/
|
|
|
|
HTTP2ChannelDataOriginator::HTTP2ChannelDataOriginator (
|
|
IN ULONG ChannelLifetime,
|
|
IN BOOL IsServer,
|
|
OUT RPC_STATUS *Status
|
|
) : Mutex(Status,
|
|
FALSE, // pre-allocate semaphore
|
|
5000 // spin count
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
HTTP2ChannelDataOriginator constructor
|
|
|
|
Arguments:
|
|
|
|
ChannelLifetime - the lifetime read from the registry
|
|
|
|
IsServer - non-zero if this is a server side data originator.
|
|
0 otherwise.
|
|
|
|
Status - on input RPC_S_OK. On output, the result of the constructor.
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
RpcpInitializeListHead(&BufferQueueHead);
|
|
|
|
this->ChannelLifetime = ChannelLifetime;
|
|
NonreservedLifetime = ChannelLifetime;
|
|
if (IsServer)
|
|
NonreservedLifetime -= ServerReservedChannelLifetime;
|
|
else
|
|
NonreservedLifetime -= ClientReservedChannelLifetime;
|
|
this->IsServer = IsServer;
|
|
BytesSentOnChannel = 0;
|
|
ChannelReplacementTriggered = FALSE;
|
|
AbortStatus = RPC_S_OK;
|
|
#if DBG
|
|
RawDataAlreadySent = FALSE;
|
|
#endif // DBG
|
|
}
|
|
|
|
|
|
RPC_STATUS HTTP2ChannelDataOriginator::Send (
|
|
IN OUT HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send request
|
|
|
|
Arguments:
|
|
|
|
SendContext - the send context
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
ULONG NewBytesSentOnChannel;
|
|
BOOL ChannelReplacementNeeded;
|
|
RPC_STATUS RpcStatus;
|
|
ULONG LocalBytesSentOnChannel;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_SEND, HTTP2LOG_OT_CDATA_ORIGINATOR, BytesSentOnChannel);
|
|
|
|
ChannelReplacementNeeded = FALSE;
|
|
|
|
// if this is raw traffic, don't count it
|
|
if (SendContext->TrafficType == http2ttRaw)
|
|
{
|
|
RawDataBeingSent();
|
|
}
|
|
// otherwise, count it only if the traffic is not specifically exempt
|
|
else if ((SendContext->Flags & SendContextFlagNonChannelData) == 0)
|
|
{
|
|
// we don't always take the mutex. We know that the bytes sent will only
|
|
// grow. If we think it is a good time to recycle the channel, the fact that
|
|
// another thread is also sending in a race condition with us makes it even
|
|
// more so. We just need to be careful to properly update the BytesSendOnChannel
|
|
// at the end
|
|
LocalBytesSentOnChannel = BytesSentOnChannel;
|
|
NewBytesSentOnChannel = LocalBytesSentOnChannel + SendContext->maxWriteBuffer;
|
|
if ((NewBytesSentOnChannel > NonreservedLifetime) || ChannelReplacementTriggered)
|
|
{
|
|
Mutex.Request();
|
|
|
|
// now that we have the mutex, check again. Sometimes the channel
|
|
// can start sending from 0 again (e.g. out proxy negotiates a new
|
|
// out channel with the client and server is ready to start from 0)
|
|
// This can happen in restart channel, which is also protected by the
|
|
// mutex
|
|
|
|
LocalBytesSentOnChannel = BytesSentOnChannel;
|
|
NewBytesSentOnChannel = LocalBytesSentOnChannel + SendContext->maxWriteBuffer;
|
|
if ((NewBytesSentOnChannel > NonreservedLifetime) || ChannelReplacementTriggered)
|
|
{
|
|
if (ChannelReplacementTriggered == FALSE)
|
|
{
|
|
ChannelReplacementNeeded = TRUE;
|
|
ChannelReplacementTriggered = TRUE;
|
|
}
|
|
|
|
// if this is data, queue it
|
|
if (SendContext->TrafficType == http2ttData)
|
|
{
|
|
SendContext->SetListEntryUsed();
|
|
RpcpfInsertTailList(&BufferQueueHead, &SendContext->ListEntry);
|
|
Mutex.Clear();
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_SEND, HTTP2LOG_OT_CDATA_ORIGINATOR, BytesSentOnChannel);
|
|
|
|
if (ChannelReplacementNeeded)
|
|
{
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_CHANNEL_RECYCLE, HTTP2LOG_OT_CDATA_ORIGINATOR, NewBytesSentOnChannel);
|
|
return RPC_P_CHANNEL_NEEDS_RECYCLING;
|
|
}
|
|
else
|
|
return RPC_S_OK;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(SendContext->TrafficType == http2ttRTS);
|
|
// fall through to sending below
|
|
}
|
|
}
|
|
|
|
Mutex.Clear();
|
|
// either channel got reset or this was RTS traffic. Fall through to
|
|
// sending
|
|
}
|
|
|
|
// update BytesSentOnChannel in thread safe manner
|
|
do
|
|
{
|
|
LocalBytesSentOnChannel = BytesSentOnChannel;
|
|
NewBytesSentOnChannel = LocalBytesSentOnChannel + SendContext->maxWriteBuffer;
|
|
}
|
|
while (InterlockedCompareExchange((LONG *)&BytesSentOnChannel,
|
|
NewBytesSentOnChannel,
|
|
LocalBytesSentOnChannel) != LocalBytesSentOnChannel);
|
|
}
|
|
|
|
RpcStatus = HTTP2TransportChannel::Send(SendContext);
|
|
|
|
if (ChannelReplacementNeeded && (RpcStatus == RPC_S_OK))
|
|
{
|
|
#if DBG
|
|
DbgPrintEx(DPFLTR_RPCPROXY_ID,
|
|
DPFLTR_TRACE_LEVEL,
|
|
"RPCRT4: Indicating channel needs recycling %p %d\n",
|
|
this,
|
|
IsServer);
|
|
#endif // DBG
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_CHANNEL_RECYCLE, HTTP2LOG_OT_CDATA_ORIGINATOR, NewBytesSentOnChannel);
|
|
return RPC_P_CHANNEL_NEEDS_RECYCLING;
|
|
}
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_SEND, HTTP2LOG_OT_CDATA_ORIGINATOR, BytesSentOnChannel);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
void HTTP2ChannelDataOriginator::Abort (
|
|
IN RPC_STATUS RpcStatus
|
|
)
|
|
{
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_ABORT, HTTP2LOG_OT_CDATA_ORIGINATOR, RpcStatus);
|
|
|
|
// we have a bunch of sends carrying ref-counts, etc.
|
|
// We must make sure they are completed.
|
|
|
|
HTTP2TransportChannel::Abort(RpcStatus);
|
|
|
|
// we know we are synchronized with everybody else
|
|
if (!RpcpIsListEmpty(&BufferQueueHead))
|
|
{
|
|
AbortStatus = RpcStatus;
|
|
(void) COMMON_PostRuntimeEvent(CHANNEL_DATA_ORIGINATOR_DIRECT_SEND,
|
|
this
|
|
);
|
|
}
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_ABORT, HTTP2LOG_OT_CDATA_ORIGINATOR, RpcStatus);
|
|
}
|
|
|
|
void HTTP2ChannelDataOriginator::FreeObject (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Frees the object. Acts like a destructor for the
|
|
channel.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
if (LowerLayer)
|
|
LowerLayer->FreeObject();
|
|
|
|
HTTP2ChannelDataOriginator::~HTTP2ChannelDataOriginator();
|
|
}
|
|
|
|
void HTTP2ChannelDataOriginator::Reset (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Reset the channel for next open/send/receive. This is
|
|
used in submission context only and implies there are no
|
|
pending operations on the channel. It is used on the client
|
|
during opening the connection to do quick negotiation on the
|
|
same connection instead of opening a new connection every time.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
#if DBG
|
|
RawDataAlreadySent = FALSE;
|
|
#endif // DBG
|
|
ASSERT(RpcpIsListEmpty(&BufferQueueHead));
|
|
LowerLayer->Reset();
|
|
}
|
|
|
|
void HTTP2ChannelDataOriginator::GetBufferQueue (
|
|
OUT LIST_ENTRY *NewQueueHead
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Grab all queued buffers and pile them on the list head
|
|
that we passed to it. All refcounts must be removed (i.e.
|
|
undone). Called in submission context only and we know there
|
|
will be no more sends. Therefore we are single threaded.
|
|
|
|
Arguments:
|
|
|
|
NewQueueHead - new queue heads to pile buffers on
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
LIST_ENTRY *CurrentListEntry;
|
|
LIST_ENTRY *NextListEntry;
|
|
HTTP2SendContext *SendContext;
|
|
|
|
ASSERT(RpcpIsListEmpty(NewQueueHead));
|
|
|
|
CurrentListEntry = BufferQueueHead.Flink;
|
|
while (CurrentListEntry != &BufferQueueHead)
|
|
{
|
|
SendContext = CONTAINING_RECORD(CurrentListEntry, HTTP2SendContext, ListEntry);
|
|
SendContext->SetListEntryUnused();
|
|
NextListEntry = CurrentListEntry->Flink;
|
|
RpcpfInsertHeadList(NewQueueHead, CurrentListEntry);
|
|
UpperLayer->SendCancelled(SendContext);
|
|
CurrentListEntry = NextListEntry;
|
|
}
|
|
RpcpInitializeListHead(&BufferQueueHead);
|
|
}
|
|
|
|
RPC_STATUS HTTP2ChannelDataOriginator::DirectSendComplete (
|
|
OUT BOOL *IsServer,
|
|
OUT void **SendContext,
|
|
OUT BUFFER *Buffer,
|
|
OUT UINT *BufferLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Direct send complete notification. Complete the send
|
|
passing it only through channels that have seen it (i.e.
|
|
above us). Note that we will get one notification for
|
|
all buffered sends. We must empty the whole queue, and post
|
|
one notification for each buffer in the queue
|
|
|
|
Arguments:
|
|
|
|
IsServer - in all cases MUST be set to TRUE or FALSE.
|
|
|
|
SendContext - on output contains the send context as
|
|
seen by the runtime
|
|
|
|
Buffer - on output the buffer that we tried to send
|
|
|
|
BufferLength - on output the length of the buffer we tried to send
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK to return error to runtime
|
|
RPC_P_PACKET_CONSUMED - to hide packet from runtime
|
|
RPC_S_* error - return error to runtime
|
|
|
|
--*/
|
|
{
|
|
LIST_ENTRY *CurrentListEntry;
|
|
HTTP2SendContext *CurrentSendContext;
|
|
RPC_STATUS RpcStatus;
|
|
BOOL PostAnotherReceive;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_DIRECT_SEND_COMPLETE, HTTP2LOG_OT_CDATA_ORIGINATOR,
|
|
!RpcpIsListEmpty(&BufferQueueHead));
|
|
*IsServer = this->IsServer;
|
|
|
|
// this should only get called when we are aborted. This
|
|
// ensures that we are single threaded in the code
|
|
// below
|
|
TopChannel->VerifyAborted();
|
|
|
|
CurrentListEntry = RpcpfRemoveHeadList(&BufferQueueHead);
|
|
ASSERT(CurrentListEntry != &BufferQueueHead);
|
|
|
|
CurrentSendContext = CONTAINING_RECORD(CurrentListEntry, HTTP2SendContext, ListEntry);
|
|
CurrentSendContext->SetListEntryUnused();
|
|
|
|
ASSERT(AbortStatus != RPC_S_OK);
|
|
|
|
RpcStatus = HTTP2TransportChannel::SendComplete(AbortStatus, CurrentSendContext);
|
|
|
|
PostAnotherReceive = !(RpcpIsListEmpty(&BufferQueueHead));
|
|
|
|
if (RpcStatus != RPC_P_PACKET_CONSUMED)
|
|
{
|
|
// this will return to the runtime. Make sure it is valid
|
|
if (this->IsServer)
|
|
I_RpcTransVerifyServerRuntimeCallFromContext(CurrentSendContext);
|
|
else
|
|
I_RpcTransVerifyClientRuntimeCallFromContext(CurrentSendContext);
|
|
*SendContext = CurrentSendContext;
|
|
*Buffer = CurrentSendContext->pWriteBuffer;
|
|
*BufferLength = CurrentSendContext->maxWriteBuffer;
|
|
}
|
|
else
|
|
{
|
|
// the packet was a transport packet - it won't be seen by the runtime
|
|
*SendContext = NULL;
|
|
*Buffer = NULL;
|
|
*BufferLength = 0;
|
|
}
|
|
|
|
RpcStatus = AsyncCompleteHelper(RpcStatus);
|
|
// do not touch this pointer after here unless the list was not-empty
|
|
// (which implies we still have refcounts)
|
|
|
|
if (PostAnotherReceive)
|
|
{
|
|
(void) COMMON_PostRuntimeEvent(CHANNEL_DATA_ORIGINATOR_DIRECT_SEND,
|
|
this
|
|
);
|
|
}
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_DIRECT_SEND_COMPLETE, HTTP2LOG_OT_CDATA_ORIGINATOR,
|
|
PostAnotherReceive);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2ChannelDataOriginator::RestartChannel (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Restart the channel. Somehow the channel lifetime became
|
|
fully available again, and we can start from 0. This happens
|
|
when the out proxy renegotiates the out channel with the client
|
|
and we can keep using the server channels again.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK
|
|
RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
LIST_ENTRY *CurrentListEntry;
|
|
HTTP2SendContext *SendContext;
|
|
ULONG NewBytesSentOnChannel = 0;
|
|
ULONG BytesForThisSend;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
// the channel must have been plugged
|
|
ASSERT(BytesSentOnChannel > NonreservedLifetime);
|
|
|
|
Mutex.Request();
|
|
// grab all queued packets and send them out
|
|
CurrentListEntry = BufferQueueHead.Flink;
|
|
while (CurrentListEntry != &BufferQueueHead)
|
|
{
|
|
SendContext = CONTAINING_RECORD(CurrentListEntry, HTTP2SendContext, ListEntry);
|
|
SendContext->SetListEntryUnused();
|
|
ASSERT(SendContext->TrafficType == http2ttData);
|
|
|
|
BytesForThisSend = SendContext->maxWriteBuffer;
|
|
|
|
// assume success of the send and remove the element from the queue.
|
|
// This is necessary because if the send succeeds, there is a race
|
|
// condition with the send complete path
|
|
(void) RpcpfRemoveHeadList(&BufferQueueHead);
|
|
|
|
RpcStatus = HTTP2TransportChannel::Send(SendContext);
|
|
|
|
ASSERT(RpcStatus != RPC_P_CHANNEL_NEEDS_RECYCLING);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
// failure. We should issue send complete for all queued sends
|
|
// including the current one. However, it is easier for us to add back
|
|
// the currently failed send and return failure to caller. Caller will
|
|
// abort and there we will issue send complete for all pending sends.
|
|
SendContext->SetListEntryUsed();
|
|
RpcpfInsertHeadList(&BufferQueueHead, CurrentListEntry);
|
|
Mutex.Clear();
|
|
// return failure to the caller. This will cause the caller to abort the
|
|
// channel, and all sends will be completed.
|
|
return RpcStatus;
|
|
}
|
|
|
|
NewBytesSentOnChannel += BytesForThisSend;
|
|
|
|
ASSERT(NewBytesSentOnChannel < NonreservedLifetime);
|
|
|
|
// process the next element (which by now has become the first since
|
|
// we removed the successfully sent one).
|
|
CurrentListEntry = BufferQueueHead.Flink;
|
|
}
|
|
|
|
// reset the counters
|
|
ChannelReplacementTriggered = FALSE;
|
|
BytesSentOnChannel = NewBytesSentOnChannel;
|
|
|
|
Mutex.Clear();
|
|
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
RPC_STATUS HTTP2ChannelDataOriginator::NotifyTrafficSent (
|
|
IN ULONG TrafficSentSize
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Notifies the channel that bytes were sent on the wire. Channel
|
|
reports back whether channel recycling should occur.
|
|
|
|
Arguments:
|
|
|
|
TrafficSentSize - the number of bytes sent.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_P_CHANNEL_NEEDS_RECYCLING.
|
|
|
|
--*/
|
|
{
|
|
ULONG LocalBytesSentOnChannel;
|
|
ULONG NewBytesSentOnChannel;
|
|
BOOL ChannelReplacementNeeded;
|
|
|
|
ChannelReplacementNeeded = FALSE;
|
|
|
|
// this is very rare. Don't bother to take the mutex opportunistically.
|
|
// Just make sure that we do use interlocks because no all paths take
|
|
// the mutex. The mutex synchronizes us with Restart
|
|
Mutex.Request();
|
|
|
|
LocalBytesSentOnChannel = BytesSentOnChannel;
|
|
NewBytesSentOnChannel = LocalBytesSentOnChannel + TrafficSentSize;
|
|
if (NewBytesSentOnChannel > NonreservedLifetime)
|
|
{
|
|
if (ChannelReplacementTriggered == FALSE)
|
|
{
|
|
ChannelReplacementNeeded = TRUE;
|
|
ChannelReplacementTriggered = TRUE;
|
|
}
|
|
}
|
|
|
|
Mutex.Clear();
|
|
|
|
// update BytesSentOnChannel in thread safe manner
|
|
do
|
|
{
|
|
LocalBytesSentOnChannel = BytesSentOnChannel;
|
|
NewBytesSentOnChannel = LocalBytesSentOnChannel + TrafficSentSize;
|
|
}
|
|
while (InterlockedCompareExchange((LONG *)&BytesSentOnChannel,
|
|
NewBytesSentOnChannel,
|
|
LocalBytesSentOnChannel) != LocalBytesSentOnChannel);
|
|
|
|
if (ChannelReplacementNeeded)
|
|
return RPC_P_CHANNEL_NEEDS_RECYCLING;
|
|
else
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2Channel
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS HTTP2Channel::Send (
|
|
IN OUT HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send request
|
|
|
|
Arguments:
|
|
|
|
SendContext - the send context
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
if (SendContext->Flags & SendContextFlagPluggedChannel)
|
|
{
|
|
BeginSubmitAsyncNonFailing();
|
|
}
|
|
else
|
|
{
|
|
RpcStatus = BeginSubmitAsync();
|
|
if (RpcStatus != RPC_S_OK)
|
|
return RpcStatus;
|
|
}
|
|
|
|
RpcStatus = LowerLayer->Send(SendContext);
|
|
|
|
FinishSubmitAsync();
|
|
|
|
if ((RpcStatus != RPC_S_OK)
|
|
&& (RpcStatus != ERROR_IO_PENDING)
|
|
&& (RpcStatus != RPC_P_CHANNEL_NEEDS_RECYCLING))
|
|
{
|
|
RemoveReference(); // remove the reference for the async send
|
|
}
|
|
|
|
return(RpcStatus);
|
|
}
|
|
|
|
RPC_STATUS HTTP2Channel::Receive (
|
|
IN HTTP2TrafficType TrafficType
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Receive request
|
|
|
|
Arguments:
|
|
|
|
TrafficType - the type of traffic we want to receive
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
RpcStatus = BeginSubmitAsync();
|
|
if (RpcStatus != RPC_S_OK)
|
|
return RpcStatus;
|
|
|
|
RpcStatus = LowerLayer->Receive(TrafficType);
|
|
|
|
FinishSubmitAsync();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
RemoveReference(); // remove the reference for the async receive
|
|
|
|
return(RpcStatus);
|
|
}
|
|
|
|
RPC_STATUS HTTP2Channel::SendComplete (
|
|
IN RPC_STATUS EventStatus,
|
|
IN OUT HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send complete notification
|
|
|
|
Arguments:
|
|
|
|
EventStatus - status of the operation
|
|
SendContext - the send context
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
RpcStatus = CheckSendCompleteForSync(EventStatus,
|
|
SendContext
|
|
);
|
|
|
|
if (RpcStatus != RPC_P_PACKET_CONSUMED)
|
|
{
|
|
RpcStatus = ForwardUpSendComplete(EventStatus,
|
|
SendContext
|
|
);
|
|
}
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2Channel::ReceiveComplete (
|
|
IN RPC_STATUS EventStatus,
|
|
IN HTTP2TrafficType TrafficType,
|
|
IN BYTE *Buffer,
|
|
IN UINT BufferLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Receive complete notification complete notification
|
|
|
|
Arguments:
|
|
|
|
EventStatus - status of the operation
|
|
|
|
TrafficType - the type of traffic we have received
|
|
|
|
Buffer - the received buffer (success only)
|
|
|
|
BufferLength - the length of the received buffer (success only)
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_RECV_COMPLETE, HTTP2LOG_OT_CHANNEL, (ULONG_PTR)EventStatus);
|
|
|
|
RpcStatus = CheckReceiveCompleteForSync(EventStatus,
|
|
TrafficType,
|
|
Buffer,
|
|
BufferLength
|
|
);
|
|
|
|
if (RpcStatus != RPC_P_PACKET_CONSUMED)
|
|
{
|
|
RpcStatus = ForwardUpReceiveComplete(EventStatus,
|
|
Buffer,
|
|
BufferLength
|
|
);
|
|
}
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_RECV_COMPLETE, HTTP2LOG_OT_CHANNEL, (ULONG_PTR)RpcStatus);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
|
|
void HTTP2Channel::PrepareForSyncSend (
|
|
IN ULONG BufferLength,
|
|
IN BYTE *Buffer,
|
|
IN OUT HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Prepares a SendContext for SyncSend
|
|
|
|
Arguments:
|
|
|
|
BufferLength - the length of the buffer
|
|
|
|
Buffer - the buffer to send
|
|
|
|
SendContext - a memory block of sufficient size to initialize a send context
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
SendContext->u.SyncEvent = I_RpcTransGetThreadEvent();
|
|
ResetEvent(SendContext->u.SyncEvent);
|
|
SendContext->SetListEntryUnused();
|
|
SendContext->maxWriteBuffer = BufferLength;
|
|
SendContext->pWriteBuffer = Buffer;
|
|
// SendContext->Write.pAsyncObject = NULL; // this will be initialized in the bottom layer
|
|
SendContext->Write.ol.Internal = STATUS_PENDING;
|
|
SendContext->TrafficType = http2ttData;
|
|
SendContext->Write.ol.OffsetHigh = 0;
|
|
SendContext->Flags = 0;
|
|
SendContext->UserData = 0;
|
|
}
|
|
|
|
|
|
RPC_STATUS HTTP2Channel::SyncSend (
|
|
IN HTTP2TrafficType TrafficType,
|
|
IN ULONG BufferLength,
|
|
IN BYTE *Buffer,
|
|
IN BOOL fDisableCancelCheck,
|
|
IN ULONG Timeout,
|
|
IN BASE_ASYNC_OBJECT *Connection,
|
|
IN HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Emulate a sync send using lower level async primitives
|
|
|
|
Arguments:
|
|
|
|
TrafficType - the type of traffic
|
|
|
|
BufferLength - the length of the buffer
|
|
|
|
Buffer - the buffer to send
|
|
|
|
fDisableCancelCheck - don't do checks for cancels. Can be
|
|
used as optimization
|
|
|
|
Timeout - the call timeout
|
|
|
|
Connection - the transport connection object. Used for cancelling.
|
|
|
|
SendContext - a memory block of sufficient size to initialize a send context
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_SEND, HTTP2LOG_OT_CHANNEL, 0);
|
|
|
|
PrepareForSyncSend (BufferLength,
|
|
Buffer,
|
|
SendContext);
|
|
|
|
RpcStatus = HTTP2Channel::Send(SendContext);
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_SEND, HTTP2LOG_OT_CHANNEL, RpcStatus);
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2Channel::ForwardTraffic (
|
|
IN BYTE *Packet,
|
|
IN ULONG PacketLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
On receiving channels forwards to the sending channel.
|
|
On sending channels sends down. This implementation
|
|
is for a sending channel (since all sending channels
|
|
are the same). Receiving channels must override it.
|
|
|
|
Arguments:
|
|
|
|
Packet - the packet to forward
|
|
|
|
PacketLength - the length of the packet
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
HTTP2SendContext *SendContext;
|
|
|
|
SendContext = AllocateAndInitializeContextFromPacket(Packet,
|
|
PacketLength
|
|
);
|
|
|
|
if (SendContext != NULL)
|
|
{
|
|
return Send(SendContext);
|
|
}
|
|
else
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
RPC_STATUS HTTP2Channel::ForwardFlowControlAck (
|
|
IN ULONG BytesReceivedForAck,
|
|
IN ULONG WindowForAck
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Forwards a flow control ack. Receiving channels don't
|
|
need this. Sending channels must override to forward
|
|
to the right place.
|
|
|
|
Arguments:
|
|
|
|
BytesReceivedForAck - the bytes received when the ACK was issued
|
|
|
|
WindowForAck - the free window when the ACK was issued.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_*
|
|
|
|
--*/
|
|
{
|
|
// we should never be here
|
|
ASSERT(0);
|
|
return RPC_S_INTERNAL_ERROR;
|
|
}
|
|
|
|
RPC_STATUS HTTP2Channel::AsyncCompleteHelper (
|
|
IN RPC_STATUS CurrentStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Helper routine that helps complete an async operation
|
|
|
|
Arguments:
|
|
|
|
CurrentStatus - the current status of the operation
|
|
|
|
Return Value:
|
|
|
|
The status to return to the runtime.
|
|
|
|
--*/
|
|
{
|
|
HTTP2VirtualConnection *VirtualConnection;
|
|
|
|
ASSERT(CurrentStatus != RPC_S_CANNOT_SUPPORT);
|
|
ASSERT(CurrentStatus != RPC_S_INTERNAL_ERROR);
|
|
|
|
if (CurrentStatus == RPC_P_CHANNEL_NEEDS_RECYCLING)
|
|
{
|
|
// recycle the parent connection
|
|
VirtualConnection = LockParentPointer();
|
|
if (VirtualConnection)
|
|
{
|
|
CurrentStatus = VirtualConnection->RecycleChannel(
|
|
TRUE // IsFromUpcall
|
|
);
|
|
UnlockParentPointer();
|
|
}
|
|
else
|
|
{
|
|
CurrentStatus = RPC_S_OK;
|
|
}
|
|
}
|
|
else if ((CurrentStatus != RPC_S_OK)
|
|
&&
|
|
(CurrentStatus != RPC_P_PACKET_CONSUMED))
|
|
{
|
|
// if this failed, abort the whole connection
|
|
AbortConnection(CurrentStatus);
|
|
}
|
|
|
|
RemoveReference();
|
|
|
|
return CurrentStatus;
|
|
}
|
|
|
|
void HTTP2Channel::Abort (
|
|
IN RPC_STATUS RpcStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Aborts the channel and all of the stack below it. The
|
|
request must come from above or from neutral context -
|
|
never from submit context from below. Otherwise we
|
|
will deadlock when we drain the upcalls
|
|
|
|
Arguments:
|
|
|
|
RpcStatus - the error to abort with
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
BOOL Result;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_ABORT, HTTP2LOG_OT_CHANNEL, RpcStatus);
|
|
|
|
ASSERT(RpcStatus != RPC_P_CHANNEL_NEEDS_RECYCLING);
|
|
|
|
Result = InitiateAbort();
|
|
if (Result)
|
|
{
|
|
SetAbortReason(RpcStatus);
|
|
// forward it down
|
|
LowerLayer->Abort(RpcStatus);
|
|
}
|
|
}
|
|
|
|
void HTTP2Channel::AbortConnection (
|
|
IN RPC_STATUS AbortReason
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Aborts the virtual connection.
|
|
|
|
Arguments:
|
|
|
|
RpcStatus - the error to abort with
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
HTTP2VirtualConnection *VirtualConnection;
|
|
|
|
// abort the parent connection
|
|
VirtualConnection = LockParentPointer();
|
|
if (VirtualConnection)
|
|
{
|
|
VirtualConnection->AbortChannels(AbortReason);
|
|
UnlockParentPointer();
|
|
}
|
|
else
|
|
{
|
|
// abort this channel at least
|
|
Abort(AbortReason);
|
|
}
|
|
}
|
|
|
|
void HTTP2Channel::AbortAndDestroyConnection (
|
|
IN RPC_STATUS AbortStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Aborts and destroys the virtual connection.
|
|
|
|
Arguments:
|
|
|
|
AbortStatus - the status to abort the connection
|
|
with.
|
|
|
|
Return Value:
|
|
|
|
Note: The method is idempotent
|
|
|
|
--*/
|
|
{
|
|
HTTP2VirtualConnection *VirtualConnection;
|
|
BOOL Result;
|
|
|
|
// first, tell connection to destroy itself (almost entirely)
|
|
VirtualConnection = LockParentPointer();
|
|
if (VirtualConnection == NULL)
|
|
{
|
|
// abort ourselves at least
|
|
Abort(AbortStatus);
|
|
return;
|
|
}
|
|
|
|
Result = VirtualConnection->AbortAndDestroy(TRUE, // IsFromChannel
|
|
ChannelId,
|
|
AbortStatus);
|
|
|
|
UnlockParentPointer();
|
|
|
|
// if somebody is already destroying it, just return
|
|
if (Result == FALSE)
|
|
return;
|
|
|
|
// because we have called AbortAndDestroy, we know the connection
|
|
// will stay for us. Synchronize with upcalls from this channel
|
|
DrainUpcallsAndFreeParent();
|
|
|
|
// now VirtualConnection is a pointer disconnected from everybody
|
|
// that we can destroy at our leisure
|
|
delete VirtualConnection;
|
|
}
|
|
|
|
RPC_STATUS HTTP2Channel::CheckSendCompleteForSync (
|
|
IN RPC_STATUS EventStatus,
|
|
IN OUT HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send complete notification. Checks for sync operation,
|
|
and if yes, completes the sync send and consumes
|
|
the packet.
|
|
|
|
Arguments:
|
|
|
|
EventStatus - status of the operation
|
|
|
|
SendContext - the send context
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
// was this a sync send?
|
|
if (SendContext->u.SyncEvent)
|
|
{
|
|
// yes, consume it
|
|
SendContext->Write.ol.Internal = (ULONG)EventStatus;
|
|
SendContext->Write.ol.OffsetHigh = 1;
|
|
SetEvent(SendContext->u.SyncEvent);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
RPC_STATUS HTTP2Channel::ForwardUpSendComplete (
|
|
IN RPC_STATUS EventStatus,
|
|
IN OUT HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send complete notification. Forwards the send complete to the
|
|
virtual connection.
|
|
|
|
Arguments:
|
|
|
|
EventStatus - status of the operation
|
|
|
|
SendContext - the send context
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
HTTP2VirtualConnection *VirtualConnection;
|
|
RPC_STATUS RpcStatus;
|
|
BOOL IsRTSPacket;
|
|
|
|
VirtualConnection = LockParentPointer();
|
|
// if parent has already detached, just return back
|
|
if (VirtualConnection == NULL)
|
|
{
|
|
// in some cases the parent will detach without aborting
|
|
if (EventStatus == RPC_S_OK)
|
|
{
|
|
if (SendContext->TrafficType == http2ttRTS)
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
else
|
|
RpcStatus = EventStatus; // already ok
|
|
}
|
|
else
|
|
{
|
|
// Abort in these cases (Abort is idempotent)
|
|
Abort(EventStatus);
|
|
RpcStatus = EventStatus;
|
|
}
|
|
IsRTSPacket = (SendContext->TrafficType == http2ttRTS);
|
|
// if we have data sends pending, and we are an endpoint,
|
|
// we shouldn't have disconnected
|
|
if (Flags.GetFlag(ProxyChannelType) == FALSE)
|
|
{
|
|
ASSERT(IsRTSPacket);
|
|
}
|
|
|
|
FreeSendContextAndPossiblyData(SendContext);
|
|
|
|
if (IsRTSPacket)
|
|
return RPC_P_PACKET_CONSUMED;
|
|
else
|
|
return RpcStatus;
|
|
}
|
|
|
|
RpcStatus = VirtualConnection->SendComplete(EventStatus,
|
|
SendContext,
|
|
ChannelId
|
|
);
|
|
|
|
UnlockParentPointer();
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
|
|
RPC_STATUS HTTP2Channel::CheckReceiveCompleteForSync (
|
|
IN RPC_STATUS EventStatus,
|
|
IN HTTP2TrafficType TrafficType,
|
|
IN BYTE *Buffer,
|
|
IN UINT BufferLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Receive complete notification. Checks if the receive was
|
|
sync, and if yes, fires event and consumes the packet. For
|
|
base class it's always not for us (base class does not
|
|
support sync receives)
|
|
|
|
Arguments:
|
|
|
|
EventStatus - status of the operation
|
|
|
|
TrafficType - the type of traffic we received
|
|
|
|
Buffer - the received buffer (success only)
|
|
|
|
BufferLength - the length of the received buffer (success only)
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
// not for us after all. Let it continue
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
RPC_STATUS HTTP2Channel::ForwardUpReceiveComplete (
|
|
IN RPC_STATUS EventStatus,
|
|
IN BYTE *Buffer,
|
|
IN UINT BufferLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Receive complete notification. Forwards the receive
|
|
complete to the virtual connection
|
|
|
|
Arguments:
|
|
|
|
EventStatus - status of the operation
|
|
|
|
Buffer - the received buffer (success only)
|
|
|
|
BufferLength - the length of the received buffer (success only)
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
HTTP2VirtualConnection *VirtualConnection;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
VirtualConnection = LockParentPointer();
|
|
// if parent has already detached, just return back
|
|
if (VirtualConnection == NULL)
|
|
{
|
|
// in some cases the parent will detach without aborting
|
|
// Abort in these cases (Abort is idempotent)
|
|
Abort(RPC_P_CONNECTION_SHUTDOWN);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
RpcStatus = VirtualConnection->ReceiveComplete(EventStatus,
|
|
Buffer,
|
|
BufferLength,
|
|
ChannelId
|
|
);
|
|
|
|
UnlockParentPointer();
|
|
|
|
if (RpcStatus == RPC_P_ABORT_NEEDED)
|
|
{
|
|
// in some cases the parent cannot abort because the channel
|
|
// is already detached from the parent. In such cases it will
|
|
// tell us to abort. (Abort is idempotent)
|
|
Abort(RPC_P_CONNECTION_SHUTDOWN);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2Channel::SetKeepAliveTimeout (
|
|
IN BOOL TurnOn,
|
|
IN BOOL bProtectIO,
|
|
IN KEEPALIVE_TIMEOUT_UNITS Units,
|
|
IN OUT KEEPALIVE_TIMEOUT KATime,
|
|
IN ULONG KAInterval OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Change the keep alive value on the channel
|
|
|
|
Arguments:
|
|
|
|
TurnOn - if non-zero, keep alives are turned on. If zero, keep alives
|
|
are turned off.
|
|
|
|
bProtectIO - non-zero if IO needs to be protected against async close
|
|
of the connection.
|
|
|
|
Units - in what units is KATime
|
|
|
|
KATime - how much to wait before turning on keep alives
|
|
|
|
KAInterval - the interval between keep alives
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
// many channels don't support this and
|
|
// shouldn't be called with it. Those who do support it
|
|
// should override it.
|
|
|
|
ASSERT(FALSE);
|
|
return RPC_S_INTERNAL_ERROR;
|
|
}
|
|
|
|
RPC_STATUS HTTP2Channel::LastPacketSentNotification (
|
|
IN HTTP2SendContext *LastSendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
When a lower channel wants to notify the top
|
|
channel that the last packet has been sent,
|
|
they call this function. Must be called from
|
|
an upcall/neutral context. Only flow control
|
|
senders support past packet notifications
|
|
|
|
Arguments:
|
|
|
|
LastSendContext - the context we're sending
|
|
|
|
Return Value:
|
|
|
|
The value to return to the bottom channel/runtime.
|
|
|
|
--*/
|
|
{
|
|
ASSERT(0);
|
|
return RPC_S_INTERNAL_ERROR;
|
|
}
|
|
|
|
void HTTP2Channel::SendCancelled (
|
|
IN HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
A lower channel cancelled a send already passed through this channel.
|
|
|
|
Arguments:
|
|
|
|
SendContext - the send context of the send that was cancelled
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
RemoveReference();
|
|
}
|
|
|
|
void HTTP2Channel::PingTrafficSentNotify (
|
|
IN ULONG PingTrafficSize
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Notifies a channel that ping traffic has been sent.
|
|
|
|
Arguments:
|
|
|
|
PingTrafficSize - the size of the ping traffic sent.
|
|
|
|
--*/
|
|
{
|
|
// nobody should be here. Channels that use that must
|
|
// override.
|
|
ASSERT(0);
|
|
}
|
|
|
|
void HTTP2Channel::FreeObject (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Frees a client in channel object
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
// make sure we have been aborted
|
|
ASSERT(Aborted.GetInteger() > 0);
|
|
|
|
LowerLayer->FreeObject();
|
|
|
|
// the client channel is the top of the stack. Just free us
|
|
// which will free the whole stack
|
|
delete this;
|
|
}
|
|
|
|
RPC_STATUS HTTP2Channel::ForwardFlowControlAckOnDefaultChannel (
|
|
IN BOOL IsInChannel,
|
|
IN ForwardDestinations Destination,
|
|
IN ULONG BytesReceivedForAck,
|
|
IN ULONG WindowForAck
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Forwards a flow control ack on the default channel
|
|
|
|
Arguments:
|
|
|
|
IsInChannel - non-zero if the IN channel is to be used. FALSE
|
|
otherwise
|
|
|
|
Destination - where to forward to.
|
|
|
|
BytesReceivedForAck - the bytes received when the ACK was issued
|
|
|
|
WindowForAck - the free window when the ACK was issued.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_*
|
|
|
|
Notes:
|
|
|
|
If on an endpoint, called from a neutral context only. Proxies
|
|
call it in submission context.
|
|
|
|
--*/
|
|
{
|
|
HTTP2VirtualConnection *VirtualConnection;
|
|
HTTP2SendContext *SendContext;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
VirtualConnection = LockParentPointer();
|
|
if (VirtualConnection == NULL)
|
|
return RPC_P_CONNECTION_SHUTDOWN;
|
|
|
|
// allocate and initalize the flow control ACK packet
|
|
SendContext = AllocateAndInitializeFlowControlAckPacketWithDestination (
|
|
Destination,
|
|
BytesReceivedForAck,
|
|
WindowForAck,
|
|
VirtualConnection->MapChannelIdToCookie(ChannelId)
|
|
);
|
|
|
|
if (SendContext == NULL)
|
|
{
|
|
UnlockParentPointer();
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
RpcStatus = VirtualConnection->SendTrafficOnDefaultChannel(IsInChannel,
|
|
SendContext
|
|
);
|
|
|
|
UnlockParentPointer();
|
|
|
|
if ((RpcStatus != RPC_S_OK) && (RpcStatus != RPC_P_CHANNEL_NEEDS_RECYCLING))
|
|
FreeRTSPacket(SendContext);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2Channel::ForwardFlowControlAckOnThisChannel (
|
|
IN ULONG BytesReceivedForAck,
|
|
IN ULONG WindowForAck,
|
|
IN BOOL NonChannelData
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Forwards a flow control ack on this channel
|
|
|
|
Arguments:
|
|
|
|
BytesReceivedForAck - the bytes received when the ACK was issued
|
|
|
|
WindowForAck - the free window when the ACK was issued.
|
|
|
|
NonChannelData - non-zero if the data being sent don't go on the HTTP
|
|
channel. FALSE if they do
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_*
|
|
|
|
Notes:
|
|
|
|
This must be called in upcall or neutral context only
|
|
|
|
--*/
|
|
{
|
|
HTTP2SendContext *SendContext;
|
|
RPC_STATUS RpcStatus;
|
|
HTTP2VirtualConnection *VirtualConnection;
|
|
|
|
VirtualConnection = LockParentPointer();
|
|
if (VirtualConnection == NULL)
|
|
return RPC_P_CONNECTION_SHUTDOWN;
|
|
|
|
// allocate and initalize the flow control ACK packet
|
|
SendContext = AllocateAndInitializeFlowControlAckPacket (
|
|
BytesReceivedForAck,
|
|
WindowForAck,
|
|
VirtualConnection->MapChannelIdToCookie(ChannelId)
|
|
);
|
|
|
|
UnlockParentPointer();
|
|
|
|
if (SendContext == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
|
|
if (NonChannelData)
|
|
SendContext->Flags |= SendContextFlagNonChannelData;
|
|
|
|
RpcStatus = Send(SendContext);
|
|
|
|
// this can be called on the server, or on the proxy. If on the server,
|
|
// it will be called with NonChannelData. This means we cannot have
|
|
// channel recycle indication here.
|
|
ASSERT(RpcStatus != RPC_P_CHANNEL_NEEDS_RECYCLING);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
FreeRTSPacket(SendContext);
|
|
}
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2Channel::HandleSendResultFromNeutralContext (
|
|
IN RPC_STATUS CurrentStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Handles the result code from send from a neutral context.
|
|
This includes checking for channel recycling and intiating
|
|
one if necessary.
|
|
|
|
Arguments:
|
|
|
|
CurrentStatus - the status from the send operation
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_*. Callers may ignore it since all cleanup was
|
|
done.
|
|
|
|
Notes:
|
|
|
|
This must be called in upcall or neutral context only
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
HTTP2VirtualConnection *VirtualConnection;
|
|
|
|
ASSERT(CurrentStatus != RPC_S_CANNOT_SUPPORT);
|
|
ASSERT(CurrentStatus != RPC_S_INTERNAL_ERROR);
|
|
|
|
if (CurrentStatus == RPC_P_CHANNEL_NEEDS_RECYCLING)
|
|
{
|
|
// recycle the parent connection
|
|
VirtualConnection = LockParentPointer();
|
|
if (VirtualConnection)
|
|
{
|
|
RpcStatus = VirtualConnection->RecycleChannel(
|
|
TRUE // IsFromUpcall
|
|
);
|
|
UnlockParentPointer();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
// if this failed, abort the whole connection
|
|
AbortConnection(CurrentStatus);
|
|
}
|
|
|
|
CurrentStatus = RpcStatus;
|
|
}
|
|
else
|
|
{
|
|
// nothing to do - the channel is dying anyway
|
|
CurrentStatus = RPC_P_CONNECTION_SHUTDOWN;
|
|
}
|
|
}
|
|
|
|
return CurrentStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2Channel::IsInChannel (
|
|
OUT BOOL *InChannel
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Checks if the current channel is an in channel or an
|
|
out channel.
|
|
|
|
Arguments:
|
|
|
|
InChannel - on output will be set to non-zero if this is an
|
|
in channel. It will be set to 0 if this is an out channel.
|
|
Undefined on failure.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_P_CONNECTION_SHUTDOWN. If the parent has detached,
|
|
RPC_P_CONNECTION_SHUTDOWN will be returned. In all other cases
|
|
success is returned.
|
|
|
|
--*/
|
|
{
|
|
HTTP2VirtualConnection *VirtualConnection;
|
|
|
|
VirtualConnection = LockParentPointer();
|
|
if (VirtualConnection)
|
|
{
|
|
VirtualConnection->VerifyValidChannelId(ChannelId);
|
|
*InChannel = VirtualConnection->IsInChannel(ChannelId);
|
|
UnlockParentPointer();
|
|
return RPC_S_OK;
|
|
}
|
|
else
|
|
return RPC_P_CONNECTION_SHUTDOWN;
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2VirtualConnection
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS HTTP2VirtualConnection::Send (
|
|
IN UINT Length,
|
|
IN BUFFER Buffer,
|
|
IN PVOID SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send on an HTTP client virtual connection. Proxies don't
|
|
override that. Other virtual connections may override it.
|
|
|
|
Arguments:
|
|
|
|
Length - The length of the data to send.
|
|
Buffer - The data to send.
|
|
SendContext - A buffer of at least SendContextSize bytes
|
|
which will be used during the call and returned
|
|
when the send completes.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
Note:
|
|
|
|
Can be called from runtime/neutral context only.
|
|
|
|
--*/
|
|
{
|
|
HTTP2ChannelPointer *ChannelPtr;
|
|
HTTP2Channel *Channel;
|
|
HTTP2SendContext *HttpSendContext;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
HttpSendContext = (HTTP2SendContext *)SendContext;
|
|
HttpSendContext->SetListEntryUnused();
|
|
HttpSendContext->maxWriteBuffer = Length;
|
|
HttpSendContext->pWriteBuffer = Buffer;
|
|
HttpSendContext->TrafficType = http2ttData;
|
|
HttpSendContext->u.SyncEvent = NULL;
|
|
HttpSendContext->Flags = 0;
|
|
HttpSendContext->UserData = 0;
|
|
|
|
Channel = LockDefaultSendChannel(&ChannelPtr);
|
|
if (Channel)
|
|
{
|
|
RpcStatus = Channel->Send(HttpSendContext);
|
|
ChannelPtr->UnlockChannelPointer();
|
|
}
|
|
else
|
|
{
|
|
RpcStatus = RPC_P_SEND_FAILED;
|
|
}
|
|
|
|
RpcStatus = StartChannelRecyclingIfNecessary(RpcStatus,
|
|
FALSE // IsFromUpcall
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
Abort();
|
|
// Note that send can't really fail with protocol error. When
|
|
// it happens it has simply picked the error with which
|
|
// the connection was aborted. This is as good as a failed send.
|
|
if ((RpcStatus == RPC_P_CONNECTION_SHUTDOWN)
|
|
|| (RpcStatus == RPC_P_CONNECTION_CLOSED)
|
|
|| (RpcStatus == RPC_P_RECEIVE_FAILED)
|
|
|| (RpcStatus == RPC_S_PROTOCOL_ERROR) )
|
|
{
|
|
RpcStatus = RPC_P_SEND_FAILED;
|
|
}
|
|
}
|
|
|
|
VALIDATE(RpcStatus)
|
|
{
|
|
RPC_S_OK,
|
|
RPC_S_OUT_OF_MEMORY,
|
|
RPC_S_OUT_OF_RESOURCES,
|
|
RPC_P_SEND_FAILED
|
|
} END_VALIDATE;
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2VirtualConnection::Receive (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Post a receive on a HTTP client virtual connection.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
HTTP2ChannelPointer *ChannelPtr;
|
|
HTTP2Channel *Channel;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
Channel = LockDefaultReceiveChannel(&ChannelPtr);
|
|
if (Channel)
|
|
{
|
|
RpcStatus = Channel->Receive(http2ttData);
|
|
ChannelPtr->UnlockChannelPointer();
|
|
}
|
|
else
|
|
{
|
|
RpcStatus = RPC_P_CONNECTION_CLOSED;
|
|
}
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
Abort();
|
|
}
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2VirtualConnection::SyncSend (
|
|
IN ULONG BufferLength,
|
|
IN BYTE *Buffer,
|
|
IN BOOL fDisableShutdownCheck,
|
|
IN BOOL fDisableCancelCheck,
|
|
IN ULONG Timeout
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Do a sync send on an HTTP connection.
|
|
|
|
Arguments:
|
|
|
|
BufferLength - the length of the data to send.
|
|
|
|
Buffer - the data to send.
|
|
|
|
fDisableShutdownCheck - ignored
|
|
|
|
fDisableCancelCheck - runtime indicates no cancel
|
|
will be attempted on this send. Can be used
|
|
as optimization hint by the transport
|
|
|
|
Timeout - send timeout (call timeout)
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
RPC_STATUS RpcStatus2;
|
|
HTTP2SendContext LocalSendContext;
|
|
HTTP2Channel *Channel;
|
|
HTTP2ChannelPointer *ChannelPtr;
|
|
|
|
// we will convert a sync send to an async send
|
|
// make sure there is a thread to pick up the completion
|
|
RpcStatus = HTTPTransInfo->CreateThread();
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
if (RpcStatus == RPC_S_OUT_OF_THREADS)
|
|
RpcStatus = RPC_S_OUT_OF_RESOURCES;
|
|
|
|
VALIDATE(RpcStatus)
|
|
{
|
|
RPC_S_OK,
|
|
RPC_S_OUT_OF_MEMORY,
|
|
RPC_S_OUT_OF_RESOURCES,
|
|
RPC_P_SEND_FAILED,
|
|
RPC_S_CALL_CANCELLED,
|
|
RPC_P_RECEIVE_COMPLETE,
|
|
RPC_P_TIMEOUT
|
|
} END_VALIDATE;
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
Channel = LockDefaultSendChannel (&ChannelPtr);
|
|
if (Channel == NULL)
|
|
{
|
|
return RPC_P_SEND_FAILED;
|
|
}
|
|
|
|
RpcStatus = Channel->SyncSend(http2ttData,
|
|
BufferLength,
|
|
Buffer,
|
|
fDisableCancelCheck,
|
|
Timeout,
|
|
this,
|
|
&LocalSendContext
|
|
);
|
|
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
if (RpcStatus == RPC_P_CHANNEL_NEEDS_RECYCLING)
|
|
{
|
|
// get the ball rolling with the recycle
|
|
RpcStatus = RecycleChannel(
|
|
FALSE // IsFromUpcall
|
|
);
|
|
|
|
// ok or not, we have to wait for IO to complete
|
|
RpcStatus2 = WaitForSyncSend(this,
|
|
&LocalSendContext,
|
|
this,
|
|
fDisableCancelCheck,
|
|
Timeout
|
|
);
|
|
|
|
if ((RpcStatus2 == RPC_S_OK) && (RpcStatus != RPC_S_OK))
|
|
RpcStatus2 = RpcStatus;
|
|
|
|
if ((RpcStatus2 == RPC_P_CONNECTION_SHUTDOWN)
|
|
|| (RpcStatus2 == RPC_P_RECEIVE_FAILED)
|
|
|| (RpcStatus2 == RPC_P_CONNECTION_CLOSED)
|
|
|| (RpcStatus2 == RPC_S_SERVER_UNAVAILABLE)
|
|
|| (RpcStatus2 == RPC_S_PROTOCOL_ERROR) )
|
|
RpcStatus2 = RPC_P_SEND_FAILED;
|
|
|
|
VALIDATE(RpcStatus2)
|
|
{
|
|
RPC_S_OK,
|
|
RPC_S_OUT_OF_MEMORY,
|
|
RPC_S_OUT_OF_RESOURCES,
|
|
RPC_P_SEND_FAILED,
|
|
RPC_S_CALL_CANCELLED,
|
|
RPC_P_RECEIVE_COMPLETE,
|
|
RPC_P_TIMEOUT,
|
|
}
|
|
END_VALIDATE;
|
|
|
|
|
|
return RpcStatus2;
|
|
}
|
|
else
|
|
{
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
RpcStatus = WaitForSyncSend(this,
|
|
&LocalSendContext,
|
|
this,
|
|
fDisableCancelCheck,
|
|
Timeout
|
|
);
|
|
}
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
if ((RpcStatus == RPC_P_RECEIVE_FAILED)
|
|
|| (RpcStatus == RPC_P_CONNECTION_CLOSED)
|
|
|| (RpcStatus == RPC_P_CONNECTION_SHUTDOWN)
|
|
|| (RpcStatus == RPC_S_SERVER_UNAVAILABLE)
|
|
|| (RpcStatus == RPC_S_PROTOCOL_ERROR) )
|
|
RpcStatus = RPC_P_SEND_FAILED;
|
|
}
|
|
|
|
VALIDATE(RpcStatus)
|
|
{
|
|
RPC_S_OK,
|
|
RPC_S_OUT_OF_MEMORY,
|
|
RPC_S_OUT_OF_RESOURCES,
|
|
RPC_P_SEND_FAILED,
|
|
RPC_S_CALL_CANCELLED,
|
|
RPC_P_RECEIVE_COMPLETE,
|
|
RPC_P_TIMEOUT,
|
|
RPC_S_SERVER_UNAVAILABLE
|
|
}
|
|
END_VALIDATE;
|
|
|
|
return RpcStatus;
|
|
}
|
|
}
|
|
|
|
RPC_STATUS HTTP2VirtualConnection::SyncRecv (
|
|
IN BYTE **Buffer,
|
|
IN ULONG *BufferLength,
|
|
IN ULONG Timeout
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Do a sync receive on an HTTP connection.
|
|
|
|
Arguments:
|
|
|
|
Buffer - if successful, points to a buffer containing the next PDU.
|
|
BufferLength - if successful, contains the length of the message.
|
|
Timeout - the amount of time to wait for the receive. If -1, we wait
|
|
infinitely.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
// nobody should be calling SyncRecv on the base connection
|
|
ASSERT(0);
|
|
return RPC_S_INTERNAL_ERROR;
|
|
}
|
|
|
|
void HTTP2VirtualConnection::Close (
|
|
IN BOOL DontFlush
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Closes an HTTP connection. Proxies don't
|
|
override that. Other virtual connections may override it.
|
|
|
|
Arguments:
|
|
|
|
DontFlush - non-zero if all buffers need to be flushed
|
|
before closing the connection. Zero otherwise.
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
Abort();
|
|
}
|
|
|
|
RPC_STATUS HTTP2VirtualConnection::TurnOnOffKeepAlives (
|
|
IN BOOL TurnOn,
|
|
IN BOOL bProtectIO,
|
|
IN BOOL IsFromUpcall,
|
|
IN KEEPALIVE_TIMEOUT_UNITS Units,
|
|
IN OUT KEEPALIVE_TIMEOUT KATime,
|
|
IN ULONG KAInterval OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Turns on keep alives for HTTP. Proxies don't
|
|
override that. Other virtual connections may override it.
|
|
|
|
Arguments:
|
|
|
|
TurnOn - if non-zero, keep alives are turned on. If zero, keep alives
|
|
are turned off.
|
|
|
|
bProtectIO - non-zero if IO needs to be protected against async close
|
|
of the connection.
|
|
|
|
IsFromUpcall - non-zero if called from upcall context. Zero otherwise.
|
|
|
|
Units - in what units is KATime
|
|
|
|
KATime - how much to wait before turning on keep alives
|
|
|
|
KAInterval - the interval between keep alives
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* / Win32 errors on failure
|
|
|
|
Note:
|
|
|
|
If we use it on the server, we must protect
|
|
the connection against async aborts.
|
|
|
|
--*/
|
|
{
|
|
// The server doesn't support this for Whistler. Think
|
|
// about it for Longhorn. Client overrides it.
|
|
ASSERT(FALSE);
|
|
return RPC_S_INTERNAL_ERROR;
|
|
}
|
|
|
|
RPC_STATUS HTTP2VirtualConnection::QueryClientAddress (
|
|
OUT RPC_CHAR **pNetworkAddress
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Returns the IP address of the client on a connection as a string.
|
|
|
|
This is a server side function. Assert on the client. Proxies don't
|
|
override that. Other virtual connections may override it.
|
|
|
|
Arguments:
|
|
|
|
NetworkAddress - Will contain string on success.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
ASSERT(FALSE);
|
|
return RPC_S_INTERNAL_ERROR;
|
|
}
|
|
|
|
RPC_STATUS HTTP2VirtualConnection::QueryLocalAddress (
|
|
IN OUT void *Buffer,
|
|
IN OUT unsigned long *BufferSize,
|
|
OUT unsigned long *AddressFormat
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Returns the local IP address of a connection.
|
|
|
|
This is a server side function. Assert on the client. Proxies don't
|
|
override that. Other virtual connections may override it.
|
|
|
|
Arguments:
|
|
|
|
Buffer - The buffer that will receive the output address
|
|
|
|
BufferSize - the size of the supplied Buffer on input. On output the
|
|
number of bytes written to the buffer. If the buffer is too small
|
|
to receive all the output data, ERROR_MORE_DATA is returned,
|
|
nothing is written to the buffer, and BufferSize is set to
|
|
the size of the buffer needed to return all the data.
|
|
|
|
AddressFormat - a constant indicating the format of the returned address.
|
|
Currently supported are RPC_P_ADDR_FORMAT_TCP_IPV4 and
|
|
RPC_P_ADDR_FORMAT_TCP_IPV6. Undefined on failure.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
ASSERT(FALSE);
|
|
return RPC_S_INTERNAL_ERROR;
|
|
}
|
|
|
|
RPC_STATUS HTTP2VirtualConnection::QueryClientId(
|
|
OUT RPC_CLIENT_PROCESS_IDENTIFIER *ClientProcess
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
For secure protocols (which TCP/IP is not) this is supposed to
|
|
give an ID which will be shared by all clients from the same
|
|
process. This prevents one user from grabbing another users
|
|
association group and using their context handles.
|
|
|
|
Since TCP/IP is not secure we return the IP address of the
|
|
client machine. This limits the attacks to other processes
|
|
running on the client machine which is better than nothing.
|
|
|
|
This is a server side function. Assert on the client. Proxies don't
|
|
override that. Other virtual connections may override it.
|
|
|
|
Arguments:
|
|
|
|
ClientProcess - Transport identification of the "client".
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
ASSERT(0);
|
|
return RPC_S_INTERNAL_ERROR;
|
|
}
|
|
|
|
RPC_STATUS HTTP2VirtualConnection::QueryClientIpAddress (
|
|
IN OUT RPC_CLIENT_IP_ADDRESS *ClientIpAddress
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Returns the IP address of the client on a connection.
|
|
|
|
This is a server side function. Assert on the client. Proxies don't
|
|
override that. Other virtual connections may override it.
|
|
|
|
Arguments:
|
|
|
|
ClientIpAddress - Will contain the ip address on success.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
ASSERT(FALSE);
|
|
return RPC_S_INTERNAL_ERROR;
|
|
}
|
|
|
|
void HTTP2VirtualConnection::AbortChannels (
|
|
IN RPC_STATUS RpcStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Aborts an HTTP connection but does not disconnect
|
|
the channels. Can be called from above, upcall, or
|
|
neutral context, but not from submit context!
|
|
|
|
Arguments:
|
|
|
|
RpcStatus - the error to abort the channels with
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
HTTP2ChannelPointer *Channels[4];
|
|
HTTP2Channel *CurrentChannel;
|
|
int i;
|
|
|
|
ASSERT(RpcStatus != RPC_P_CHANNEL_NEEDS_RECYCLING);
|
|
|
|
// quick optimization. Don't abort already aborted channels
|
|
// All channels are protected against double abortion - this
|
|
// is just an optimization
|
|
if (Aborted.GetInteger() > 0)
|
|
return;
|
|
|
|
Channels[0] = &InChannels[0];
|
|
Channels[1] = &InChannels[1];
|
|
Channels[2] = &OutChannels[0];
|
|
Channels[3] = &OutChannels[1];
|
|
|
|
for (i = 0; i < 4; i ++)
|
|
{
|
|
CurrentChannel = Channels[i]->LockChannelPointer();
|
|
if (CurrentChannel)
|
|
{
|
|
CurrentChannel->Abort(RpcStatus);
|
|
Channels[i]->UnlockChannelPointer();
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL HTTP2VirtualConnection::AbortAndDestroy (
|
|
IN BOOL IsFromChannel,
|
|
IN int CallingChannelId,
|
|
IN RPC_STATUS AbortStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Aborts and destroys a connection. This is safe to
|
|
call from an upcall, as long as the calling channel
|
|
passes in its channel id. Actually the destruction
|
|
does not happen here. The caller has the obligation
|
|
to destroy it after synchronizing its upcalls.
|
|
|
|
Arguments:
|
|
|
|
IsFromChannel - non-zero if the call comes from a channel.
|
|
Zero otherwise.
|
|
|
|
CallingChannelId - the id of the calling channel. If IsFromChannel
|
|
is FALSE, this argument should be ignored.
|
|
|
|
AbortStatus - the error to abort the connection with.
|
|
|
|
Return Value:
|
|
|
|
non-zero - caller may destroy the connection.
|
|
FALSE - destruction is already in progress. Caller
|
|
must not destroy the connection.
|
|
|
|
--*/
|
|
{
|
|
if (IsFromChannel)
|
|
{
|
|
VerifyValidChannelId(CallingChannelId);
|
|
}
|
|
|
|
// abort the channels themselves
|
|
AbortChannels(AbortStatus);
|
|
|
|
// we got to the destructive phase of the abort
|
|
// guard against double aborts
|
|
if (Aborted.Increment() > 1)
|
|
return FALSE;
|
|
|
|
DisconnectChannels(IsFromChannel, CallingChannelId);
|
|
|
|
// we have disconnected all but the channel on which we received
|
|
// this call.
|
|
return TRUE;
|
|
}
|
|
|
|
void HTTP2VirtualConnection::LastPacketSentNotification (
|
|
IN int ChannelId,
|
|
IN HTTP2SendContext *LastSendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
When a channel wants to notify the virtual connection
|
|
that the last packet has been sent, they call this function.
|
|
Must be called from an upcall/neutral context. Only flow control
|
|
senders generated past packet notifications
|
|
|
|
Arguments:
|
|
|
|
ChannelId - the channelfor which this notification is.
|
|
|
|
LastSendContext - the send context for the last send
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
ASSERT(0);
|
|
}
|
|
|
|
RPC_STATUS HTTP2VirtualConnection::PostReceiveOnChannel (
|
|
IN HTTP2ChannelPointer *ChannelPtr,
|
|
IN HTTP2TrafficType TrafficType
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Posts a receceive on specified channel
|
|
|
|
Arguments:
|
|
|
|
ChannelPtr - the channel pointer to post the receive on
|
|
|
|
TrafficType - the type of traffic we wish to receive
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
HTTP2Channel *Channel;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
if (ChannelPtr == NULL)
|
|
{
|
|
// This should never happen.
|
|
ASSERT(0);
|
|
return RPC_S_INTERNAL_ERROR;
|
|
}
|
|
|
|
Channel = ChannelPtr->LockChannelPointer();
|
|
if (Channel)
|
|
{
|
|
RpcStatus = Channel->Receive(TrafficType);
|
|
ChannelPtr->UnlockChannelPointer();
|
|
}
|
|
else
|
|
RpcStatus = RPC_P_CONNECTION_CLOSED;
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2VirtualConnection::PostReceiveOnDefaultChannel (
|
|
IN BOOL IsInChannel,
|
|
IN HTTP2TrafficType TrafficType
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Posts a receceive on the default channel for the specified type
|
|
|
|
Arguments:
|
|
|
|
IsInChannel - if non-zero, post a receive on default in channel.
|
|
If 0, post a receive on default out channel
|
|
|
|
TrafficType - the type of traffic we wish to receive
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
HTTP2Channel *Channel;
|
|
HTTP2ChannelPointer *ChannelPtr;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
if (IsInChannel)
|
|
Channel = LockDefaultInChannel(&ChannelPtr);
|
|
else
|
|
Channel = LockDefaultOutChannel(&ChannelPtr);
|
|
|
|
if (Channel)
|
|
{
|
|
RpcStatus = Channel->Receive(TrafficType);
|
|
ChannelPtr->UnlockChannelPointer();
|
|
}
|
|
else
|
|
RpcStatus = RPC_P_CONNECTION_CLOSED;
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2VirtualConnection::ForwardTrafficToChannel (
|
|
IN HTTP2ChannelPointer *ChannelPtr,
|
|
IN BYTE *Packet,
|
|
IN ULONG PacketLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Forwards the given packet on the given channel
|
|
|
|
Arguments:
|
|
|
|
ChannelPtr - the channel pointer
|
|
|
|
Packet - the packet to forward
|
|
|
|
PacketLength - the length of the packet to forward
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
HTTP2Channel *Channel;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
Channel = ChannelPtr->LockChannelPointer();
|
|
if (Channel)
|
|
{
|
|
RpcStatus = Channel->ForwardTraffic(Packet, PacketLength);
|
|
ChannelPtr->UnlockChannelPointer();
|
|
}
|
|
else
|
|
{
|
|
RpcStatus = RPC_P_CONNECTION_CLOSED;
|
|
}
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2VirtualConnection::ForwardTrafficToDefaultChannel (
|
|
IN BOOL IsInChannel,
|
|
IN BYTE *Packet,
|
|
IN ULONG PacketLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Forwards the given packet on the given channel
|
|
|
|
Arguments:
|
|
|
|
IsInChannel - if non-zero, forward to default in channel.
|
|
If 0, forward to default out channel
|
|
|
|
Packet - the packet to forward
|
|
|
|
PacketLength - the length of the packet to forward
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
HTTP2Channel *Channel;
|
|
HTTP2ChannelPointer *ChannelPtr;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
if (IsInChannel)
|
|
Channel = LockDefaultInChannel(&ChannelPtr);
|
|
else
|
|
Channel = LockDefaultOutChannel(&ChannelPtr);
|
|
|
|
if (Channel)
|
|
{
|
|
RpcStatus = Channel->ForwardTraffic(Packet, PacketLength);
|
|
ChannelPtr->UnlockChannelPointer();
|
|
}
|
|
else
|
|
{
|
|
RpcStatus = RPC_P_CONNECTION_CLOSED;
|
|
}
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2VirtualConnection::SendTrafficOnChannel (
|
|
IN HTTP2ChannelPointer *ChannelPtr,
|
|
IN HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sends the given packet on the given channel
|
|
|
|
Arguments:
|
|
|
|
ChannelPtr - the channel pointer on which to send.
|
|
|
|
SendContext - context to send
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
HTTP2Channel *Channel;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
Channel = ChannelPtr->LockChannelPointer();
|
|
|
|
if (Channel)
|
|
{
|
|
RpcStatus = Channel->Send(SendContext);
|
|
ChannelPtr->UnlockChannelPointer();
|
|
}
|
|
else
|
|
{
|
|
RpcStatus = RPC_P_CONNECTION_CLOSED;
|
|
}
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2VirtualConnection::SendTrafficOnDefaultChannel (
|
|
IN BOOL IsInChannel,
|
|
IN HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sends the given packet on the given channel
|
|
|
|
Arguments:
|
|
|
|
IsInChannel - if non-zero, send on default in channel.
|
|
If 0, send on default out channel
|
|
|
|
SendContext - context to send
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
HTTP2Channel *Channel;
|
|
HTTP2ChannelPointer *ChannelPtr;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
if (IsInChannel)
|
|
Channel = LockDefaultInChannel(&ChannelPtr);
|
|
else
|
|
Channel = LockDefaultOutChannel(&ChannelPtr);
|
|
|
|
if (Channel)
|
|
{
|
|
RpcStatus = Channel->Send(SendContext);
|
|
ChannelPtr->UnlockChannelPointer();
|
|
}
|
|
else
|
|
{
|
|
RpcStatus = RPC_P_CONNECTION_CLOSED;
|
|
}
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2VirtualConnection::RecycleChannel (
|
|
IN BOOL IsFromUpcall
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initiates channel recycling. Each endpoint supports
|
|
initiating recycling of only one channel, so it knows
|
|
which one it is.
|
|
Endpoints override that. On proxies it shouldn't be called
|
|
at all.
|
|
|
|
Arguments:
|
|
|
|
IsFromUpcall - non-zero if it comes from upcall. Zero otherwise.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK of the recycling operation started successfully.
|
|
RPC_S_* error for errors.
|
|
|
|
--*/
|
|
{
|
|
ASSERT(0);
|
|
return RPC_S_INTERNAL_ERROR;
|
|
}
|
|
|
|
RPC_STATUS HTTP2VirtualConnection::StartChannelRecyclingIfNecessary (
|
|
IN RPC_STATUS RpcStatus,
|
|
IN BOOL IsFromUpcall
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Checks the result of the send for channel recycle indication, and if one
|
|
is present, initiate channel recycle
|
|
|
|
Arguments:
|
|
|
|
RpcStatus - the return code from the Send operation.
|
|
|
|
IsFromUpcall - non-zero if this was called from an upcall. Zero otherwise.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_* errors - the channel recycling failed to start.
|
|
any success code will be the passed in success code turned around.
|
|
If this function starts with a failure, the failure will be turned around
|
|
|
|
Notes:
|
|
|
|
May be called in an upcall or runtime context only.
|
|
|
|
--*/
|
|
{
|
|
if (RpcStatus == RPC_P_CHANNEL_NEEDS_RECYCLING)
|
|
RpcStatus = RecycleChannel(IsFromUpcall);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
HTTP2Channel *HTTP2VirtualConnection::MapCookieToChannelPointer (
|
|
IN HTTP2Cookie *ChannelCookie,
|
|
OUT HTTP2ChannelPointer **ChannelPtr
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Maps a channel cookie to a channel pointer. A channel will be selected only if
|
|
it is the default channel as well. The returned channel is locked.
|
|
|
|
Arguments:
|
|
|
|
ChannelCookie - the cookie for the channel.
|
|
|
|
ChannelPtr - the channel pointer. On NULL return value this is undefined.
|
|
|
|
Return Value:
|
|
|
|
The channel if the channel was found or NULL
|
|
if the channel was not found. During some recycling scenarios
|
|
the channel may not be there, or the returning channel pointer
|
|
may have a detached channel
|
|
|
|
--*/
|
|
{
|
|
volatile int *DefaultChannelSelector;
|
|
int TargetChannelSelector;
|
|
HTTP2ChannelPointer *LocalChannelPtr;
|
|
HTTP2Channel *Channel;
|
|
|
|
if (InChannelCookies[0].Compare(ChannelCookie) == 0)
|
|
{
|
|
LocalChannelPtr = &InChannels[0];
|
|
DefaultChannelSelector = &DefaultInChannelSelector;
|
|
TargetChannelSelector = 0;
|
|
}
|
|
else if (InChannelCookies[1].Compare(ChannelCookie) == 0)
|
|
{
|
|
LocalChannelPtr = &InChannels[1];
|
|
DefaultChannelSelector = &DefaultInChannelSelector;
|
|
TargetChannelSelector = 1;
|
|
}
|
|
else if (OutChannelCookies[0].Compare(ChannelCookie) == 0)
|
|
{
|
|
LocalChannelPtr = &OutChannels[0];
|
|
DefaultChannelSelector = &DefaultOutChannelSelector;
|
|
TargetChannelSelector = 0;
|
|
}
|
|
else if (OutChannelCookies[1].Compare(ChannelCookie) == 0)
|
|
{
|
|
LocalChannelPtr = &OutChannels[1];
|
|
DefaultChannelSelector = &DefaultOutChannelSelector;
|
|
TargetChannelSelector = 1;
|
|
}
|
|
else
|
|
return NULL;
|
|
|
|
Channel = LocalChannelPtr->LockChannelPointer();
|
|
if (Channel)
|
|
{
|
|
if (*DefaultChannelSelector == TargetChannelSelector)
|
|
{
|
|
// if we locked the channel and it is the right channel,
|
|
// use it
|
|
*ChannelPtr = LocalChannelPtr;
|
|
return Channel;
|
|
}
|
|
LocalChannelPtr->UnlockChannelPointer();
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
HTTP2Channel *HTTP2VirtualConnection::MapCookieToAnyChannelPointer (
|
|
IN HTTP2Cookie *ChannelCookie,
|
|
OUT HTTP2ChannelPointer **ChannelPtr
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Maps a channel cookie to a channel pointer. Unlike MapCookieToChannelPointer,
|
|
channel will be selected regardless of whether it is default.The returned
|
|
channel is locked.
|
|
|
|
Arguments:
|
|
|
|
ChannelCookie - the cookie for the channel.
|
|
|
|
ChannelPtr - the channel pointer. On NULL return value this is undefined.
|
|
|
|
Return Value:
|
|
|
|
The channel if the channel was found or NULL
|
|
if the channel was not found. During some recycling scenarios
|
|
the channel may not be there, or the returning channel pointer
|
|
may have a detached channel
|
|
|
|
--*/
|
|
{
|
|
HTTP2ChannelPointer *LocalChannelPtr;
|
|
HTTP2Channel *Channel;
|
|
|
|
if (InChannelCookies[0].Compare(ChannelCookie) == 0)
|
|
{
|
|
LocalChannelPtr = &InChannels[0];
|
|
}
|
|
else if (InChannelCookies[1].Compare(ChannelCookie) == 0)
|
|
{
|
|
LocalChannelPtr = &InChannels[1];
|
|
}
|
|
else if (OutChannelCookies[0].Compare(ChannelCookie) == 0)
|
|
{
|
|
LocalChannelPtr = &OutChannels[0];
|
|
}
|
|
else if (OutChannelCookies[1].Compare(ChannelCookie) == 0)
|
|
{
|
|
LocalChannelPtr = &OutChannels[1];
|
|
}
|
|
else
|
|
return NULL;
|
|
|
|
Channel = LocalChannelPtr->LockChannelPointer();
|
|
if (Channel)
|
|
{
|
|
// if we locked the channel and it is the right channel,
|
|
// use it
|
|
*ChannelPtr = LocalChannelPtr;
|
|
return Channel;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
HTTP2Channel *HTTP2VirtualConnection::LockDefaultSendChannel (
|
|
OUT HTTP2ChannelPointer **ChannelPtr
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Locks the send channel. Most connections don't override that.
|
|
|
|
Arguments:
|
|
|
|
ChannelPtr - on success, the channel pointer to use.
|
|
|
|
Return Value:
|
|
|
|
The locked channel or NULL (same semantics as LockDefaultOutChannel)
|
|
|
|
--*/
|
|
{
|
|
return LockDefaultOutChannel(ChannelPtr);
|
|
}
|
|
|
|
HTTP2Channel *HTTP2VirtualConnection::LockDefaultReceiveChannel (
|
|
OUT HTTP2ChannelPointer **ChannelPtr
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Locks the receive channel. Most connections don't override that.
|
|
|
|
Arguments:
|
|
|
|
ChannelPtr - on success, the channel pointer to use.
|
|
|
|
Return Value:
|
|
|
|
The locked channel or NULL (same semantics as LockDefaultInChannel)
|
|
|
|
--*/
|
|
{
|
|
return LockDefaultInChannel(ChannelPtr);
|
|
}
|
|
|
|
void HTTP2VirtualConnection::SetFirstInChannel (
|
|
IN HTTP2Channel *NewChannel
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sets the passed in channel as first default in channel.
|
|
Typically used during building a stack.
|
|
|
|
Arguments:
|
|
|
|
NewChannel - new in channel.
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
int InChannelId;
|
|
|
|
InChannelId = AllocateChannelId();
|
|
|
|
DefaultInChannelSelector = 0;
|
|
NewChannel->SetChannelId(InChannelId);
|
|
InChannels[0].SetChannel(NewChannel);
|
|
InChannelIds[0] = InChannelId;
|
|
}
|
|
|
|
void HTTP2VirtualConnection::SetFirstOutChannel (
|
|
IN HTTP2Channel *NewChannel
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sets the passed in channel as first default out channel.
|
|
Typically used during building a stack.
|
|
|
|
Arguments:
|
|
|
|
NewChannel - new out channel.
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
int OutChannelId;
|
|
|
|
OutChannelId = AllocateChannelId();
|
|
|
|
DefaultOutChannelSelector = 0;
|
|
NewChannel->SetChannelId(OutChannelId);
|
|
OutChannels[0].SetChannel(NewChannel);
|
|
OutChannelIds[0] = OutChannelId;
|
|
}
|
|
|
|
void HTTP2VirtualConnection::SetNonDefaultInChannel (
|
|
IN HTTP2Channel *NewChannel
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sets the non default in channel. Used during channel
|
|
recycling. Note that this MUST be called by the code
|
|
that received RPC_P_CHANNEL_NEEDS_RECYCLING, because
|
|
it is not thread safe.
|
|
|
|
Arguments:
|
|
|
|
NewChannel - new in channel.
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
int InChannelId;
|
|
int NonDefaultInChannelSelector;
|
|
|
|
InChannelId = AllocateChannelId();
|
|
NonDefaultInChannelSelector = GetNonDefaultInChannelSelector();
|
|
|
|
NewChannel->SetChannelId(InChannelId);
|
|
InChannels[NonDefaultInChannelSelector].SetChannel(NewChannel);
|
|
InChannelIds[NonDefaultInChannelSelector] = InChannelId;
|
|
}
|
|
|
|
void HTTP2VirtualConnection::SetNonDefaultOutChannel (
|
|
IN HTTP2Channel *NewChannel
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sets the non default out channel. Used during channel
|
|
recycling. Note that this MUST be called by the code
|
|
that received RPC_P_CHANNEL_NEEDS_RECYCLING, because
|
|
it is not thread safe.
|
|
|
|
Arguments:
|
|
|
|
NewChannel - new out channel.
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
int OutChannelId;
|
|
int NonDefaultOutChannelSelector;
|
|
|
|
OutChannelId = AllocateChannelId();
|
|
NonDefaultOutChannelSelector = GetNonDefaultOutChannelSelector();
|
|
|
|
NewChannel->SetChannelId(OutChannelId);
|
|
OutChannels[NonDefaultOutChannelSelector].SetChannel(NewChannel);
|
|
OutChannelIds[NonDefaultOutChannelSelector] = OutChannelId;
|
|
}
|
|
|
|
void HTTP2VirtualConnection::DisconnectChannels (
|
|
IN BOOL ExemptChannel,
|
|
IN int ExemptChannelId
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Disconnects all channels. Must be called from runtime
|
|
or neutral context. Cannot be called from upcall or
|
|
submit context unless an exempt channel is given
|
|
Note that call must synchronize to ensure we're the only
|
|
thread doing the disconnect
|
|
|
|
Arguments:
|
|
|
|
ExemptChannel - non-zero if ExemptChannelId contains a
|
|
valid exempt channel id. FALSE otherwise.
|
|
|
|
ExemptChannelId - if ExemptChannel is non-zero, this argument
|
|
is the id of a channel that will be disconnected, but not
|
|
synchronized with up calls.
|
|
If ExampleChannel is FALSE, this argument is undefined
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
HTTP2ChannelPointer *Channels[4];
|
|
int ChannelIds[4];
|
|
HTTP2Channel *CurrentChannel;
|
|
int i;
|
|
|
|
// we should be the only thread aborting - just disconnect everybody
|
|
Channels[0] = &InChannels[0];
|
|
ChannelIds[0] = InChannelIds[0];
|
|
|
|
Channels[1] = &InChannels[1];
|
|
ChannelIds[1] = InChannelIds[1];
|
|
|
|
Channels[2] = &OutChannels[0];
|
|
ChannelIds[2] = OutChannelIds[0];
|
|
|
|
Channels[3] = &OutChannels[1];
|
|
ChannelIds[3] = OutChannelIds[1];
|
|
|
|
for (i = 0; i < 4; i ++)
|
|
{
|
|
if ((ExemptChannel == FALSE)
|
|
|| ((ExemptChannel != FALSE) && (ExemptChannelId != ChannelIds[i])))
|
|
{
|
|
// disconnect the channel
|
|
Channels[i]->FreeChannelPointer(TRUE,
|
|
FALSE, // CalledFromUpcallContext
|
|
FALSE, // Abort
|
|
RPC_S_OK
|
|
);
|
|
}
|
|
else
|
|
{
|
|
Channels[i]->FreeChannelPointer(FALSE,
|
|
FALSE, // CalledFromUpcallContext
|
|
FALSE, // Abort
|
|
RPC_S_OK
|
|
);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/*********************************************************************
|
|
HTTP2ClientChannel
|
|
*********************************************************************/
|
|
|
|
const char *HeaderFragment1 = " http://";
|
|
const int HeaderFragment1Length = 8; // length of " http://"
|
|
const char *HeaderFragment2 = "/rpc/rpcproxy.dll?";
|
|
const int HeaderFragment2Length = 18; // length of /rpc/rpcproxy.dll?
|
|
const RPC_CHAR *HeaderFragment2W = L"/rpc/rpcproxy.dll?";
|
|
const int HeaderFragment2WLength = 36; // length of wide /rpc/rpcproxy.dll?
|
|
const char *HeaderFragment3 = ":";
|
|
const int HeaderFragment3Length = 1; // length of :
|
|
const RPC_CHAR *HeaderFragment3W = L":";
|
|
const int HeaderFragment3WLength = 2; // length of wide :
|
|
const char *HeaderFragment4 = " HTTP/1.1\r\nAccept:application/rpc\r\nUser-Agent:MSRPC\r\nHost:";
|
|
const int HeaderFragment4Length = 58; // length of " HTTP/1.1\r\nAccept:application/rpc\r\nUser-Agent:MSRPC\r\nHost:"
|
|
const char *HeaderFragment5 = "\r\nContent-Length:";
|
|
const int HeaderFragment5Length = 17; // "\r\nlength of Content-Length:"
|
|
const char *HeaderFragment6 = "\r\nConnection: Keep-Alive\r\nCache-control:no-cache\r\nPragma:no-cache\r\n\r\n";
|
|
const int HeaderFragment6Length = 69; // length of \r\nConnection: Keep-Alive\r\nCache-control:no-cache\r\nPragma:no-cache\r\n\r\n
|
|
|
|
const RPC_CHAR *HeaderAcceptType = L"application/rpc";
|
|
|
|
RPC_STATUS HTTP2ClientChannel::ClientOpen (
|
|
IN HTTPResolverHint *Hint,
|
|
IN const char *Verb,
|
|
IN int VerbLength,
|
|
IN BOOL InChannel,
|
|
IN BOOL ReplacementChannel,
|
|
IN BOOL UseWinHttp,
|
|
IN RPC_HTTP_TRANSPORT_CREDENTIALS_W *HttpCredentials, OPTIONAL
|
|
IN ULONG ChosenAuthScheme, OPTIONAL
|
|
IN HTTP2WinHttpTransportChannel *WinHttpChannel, OPTIONAL
|
|
IN ULONG CallTimeout,
|
|
IN const BYTE *AdditionalData, OPTIONAL
|
|
IN ULONG AdditionalDataLength OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sends the HTTP establishment header on
|
|
the in/out channel.
|
|
|
|
Arguments:
|
|
|
|
Hint - the resolver hint
|
|
|
|
Verb - the verb to use.
|
|
|
|
VerbLength - the length of the verb (in characters, not including
|
|
null terminator).
|
|
|
|
InChannel - non-zero if this is an in channel open. In such case we use
|
|
the channel lifetime as the content length. If 0, this is an out
|
|
channel and we use the real content length + some additional space.
|
|
The additional space depends on the ReplacementChannel parameter
|
|
|
|
ReplacementChannel - non-zero if this is a replacement channel. Zero
|
|
otherwise. If it is a replacement channel, we add the size of D4/A3.
|
|
Else, we use the size of D1/A1. This is valid only for out channels
|
|
|
|
UseWinHttp - non-zero if we should use WinHttp for bottom level communications
|
|
|
|
HttpCredentials - the encrypted Http Credentials to use. Ignored unless UseWinHttp
|
|
is non-zero.
|
|
|
|
ChosenAuthScheme - the chosen auth scheme. 0 if no auth scheme is chosen.
|
|
|
|
WinHttpChannel - the winhttp channel to use for opening. Ignored unless UseWinHttp
|
|
is non-zero.
|
|
|
|
CallTimeout - the call timeout for this call.
|
|
|
|
AdditionalData - additional data to send with the header. Must be set iff
|
|
AdditionalDataLength != 0
|
|
|
|
AdditionalDataLength - the length of the additional data to send with the header.
|
|
Must be set iff AdditionalLength != NULL
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
ULONG MemorySize;
|
|
char *Buffer;
|
|
RPC_CHAR *BufferW;
|
|
RPC_CHAR *OriginalBufferW;
|
|
RPC_CHAR *VerbW;
|
|
char *Header;
|
|
char ServerPortString[6];
|
|
ULONG ServerPortStringLength;
|
|
HTTP2SendContext *SendContext;
|
|
RPC_STATUS RpcStatus;
|
|
char ContentLengthString[6];
|
|
ULONG AdditionalLength;
|
|
ULONG ContentLengthStringLength; // without terminating NULL
|
|
char *ContentLengthToUse;
|
|
|
|
// we have plenty of parameters. Verify them
|
|
// We can't have chosen auth scheme without credentials
|
|
// or WinHttpChannel
|
|
if (ChosenAuthScheme)
|
|
{
|
|
ASSERT(HttpCredentials);
|
|
ASSERT(WinHttpChannel);
|
|
}
|
|
|
|
// we can't have additional data without data and vice versa
|
|
if (AdditionalData)
|
|
{
|
|
ASSERT(AdditionalDataLength);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(AdditionalDataLength == 0);
|
|
}
|
|
|
|
PortNumberToEndpointA(Hint->ServerPort, ServerPortString);
|
|
ServerPortStringLength = RpcpStringLengthA(ServerPortString);
|
|
|
|
// determine the content length
|
|
if (AdditionalData)
|
|
{
|
|
AdditionalLength = AdditionalDataLength;
|
|
if (!UseWinHttp)
|
|
{
|
|
RpcpItoa(AdditionalLength, ContentLengthString, 10);
|
|
ContentLengthStringLength = RpcpStringLengthA(ContentLengthString);
|
|
ContentLengthToUse = ContentLengthString;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (InChannel == FALSE)
|
|
{
|
|
if (ReplacementChannel)
|
|
AdditionalLength = GetD4_A3TotalLength() + GetD4_A11TotalLength();
|
|
else
|
|
AdditionalLength = GetD1_A1TotalLength();
|
|
if (!UseWinHttp)
|
|
{
|
|
RpcpItoa(AdditionalLength, ContentLengthString, 10);
|
|
ContentLengthStringLength = RpcpStringLengthA(ContentLengthString);
|
|
ContentLengthToUse = ContentLengthString;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (UseWinHttp)
|
|
{
|
|
AdditionalLength = DefaultChannelLifetime;
|
|
}
|
|
else
|
|
{
|
|
ContentLengthStringLength = DefaultChannelLifetimeStringLength;
|
|
ContentLengthToUse = DefaultChannelLifetimeString;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (UseWinHttp)
|
|
{
|
|
ASSERT(WinHttpChannel != NULL);
|
|
|
|
VerbW = new RPC_CHAR[VerbLength + 1];
|
|
if (VerbW == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
|
|
FullAnsiToUnicode((char *)Verb, VerbW);
|
|
|
|
MemorySize = HeaderFragment2Length
|
|
+ Hint->ServerNameLength
|
|
+ HeaderFragment3Length
|
|
+ ServerPortStringLength
|
|
+ 1
|
|
;
|
|
|
|
BufferW = (RPC_CHAR *)RpcAllocateBuffer(MemorySize * sizeof(RPC_CHAR));
|
|
if (BufferW == NULL)
|
|
{
|
|
delete [] VerbW;
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
OriginalBufferW = BufferW;
|
|
|
|
RpcpMemoryCopy(BufferW, HeaderFragment2W, HeaderFragment2WLength);
|
|
BufferW += HeaderFragment2Length;
|
|
|
|
FullAnsiToUnicode(Hint->RpcServer, BufferW);
|
|
BufferW += Hint->ServerNameLength;
|
|
|
|
RpcpMemoryCopy(BufferW, HeaderFragment3W, HeaderFragment3WLength);
|
|
BufferW += HeaderFragment3Length;
|
|
|
|
FullAnsiToUnicode(ServerPortString, BufferW);
|
|
|
|
RpcStatus = WinHttpChannel->Open (Hint,
|
|
VerbW,
|
|
OriginalBufferW, // Url
|
|
HeaderAcceptType,
|
|
AdditionalLength,
|
|
CallTimeout,
|
|
HttpCredentials,
|
|
ChosenAuthScheme,
|
|
AdditionalData
|
|
);
|
|
|
|
delete [] VerbW;
|
|
RpcFreeBuffer(OriginalBufferW);
|
|
}
|
|
else
|
|
{
|
|
MemorySize = SIZE_OF_OBJECT_AND_PADDING(HTTP2SendContext)
|
|
+ VerbLength
|
|
+ HeaderFragment1Length
|
|
+ Hint->ProxyNameLength
|
|
+ HeaderFragment2Length
|
|
+ Hint->ServerNameLength
|
|
+ HeaderFragment3Length
|
|
+ ServerPortStringLength
|
|
+ HeaderFragment4Length
|
|
+ Hint->ProxyNameLength
|
|
+ HeaderFragment5Length
|
|
+ ContentLengthStringLength
|
|
+ HeaderFragment6Length
|
|
;
|
|
|
|
if (AdditionalDataLength)
|
|
MemorySize += AdditionalDataLength;
|
|
|
|
Buffer = (char *)RpcAllocateBuffer(MemorySize);
|
|
if (Buffer == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
|
|
SendContext = (HTTP2SendContext *)Buffer;
|
|
Buffer += SIZE_OF_OBJECT_AND_PADDING(HTTP2SendContext);
|
|
Header = Buffer;
|
|
|
|
RpcpMemoryCopy(Buffer, Verb, VerbLength);
|
|
Buffer += VerbLength;
|
|
|
|
RpcpMemoryCopy(Buffer, HeaderFragment1, HeaderFragment1Length);
|
|
Buffer += HeaderFragment1Length;
|
|
|
|
RpcpMemoryCopy(Buffer, Hint->RpcProxy, Hint->ProxyNameLength);
|
|
Buffer += Hint->ProxyNameLength;
|
|
|
|
RpcpMemoryCopy(Buffer, HeaderFragment2, HeaderFragment2Length);
|
|
Buffer += HeaderFragment2Length;
|
|
|
|
RpcpMemoryCopy(Buffer, Hint->RpcServer, Hint->ServerNameLength);
|
|
Buffer += Hint->ServerNameLength;
|
|
|
|
RpcpMemoryCopy(Buffer, HeaderFragment3, HeaderFragment3Length);
|
|
Buffer += HeaderFragment3Length;
|
|
|
|
RpcpMemoryCopy(Buffer, ServerPortString, ServerPortStringLength);
|
|
Buffer += ServerPortStringLength;
|
|
|
|
RpcpMemoryCopy(Buffer, HeaderFragment4, HeaderFragment4Length);
|
|
Buffer += HeaderFragment4Length;
|
|
|
|
RpcpMemoryCopy(Buffer, Hint->RpcProxy, Hint->ProxyNameLength);
|
|
Buffer += Hint->ProxyNameLength;
|
|
|
|
RpcpMemoryCopy(Buffer, HeaderFragment5, HeaderFragment5Length);
|
|
Buffer += HeaderFragment5Length;
|
|
|
|
RpcpMemoryCopy(Buffer, ContentLengthToUse, ContentLengthStringLength);
|
|
Buffer += ContentLengthStringLength;
|
|
|
|
RpcpMemoryCopy(Buffer, HeaderFragment6, HeaderFragment6Length);
|
|
|
|
if (AdditionalDataLength)
|
|
{
|
|
Buffer += HeaderFragment6Length;
|
|
RpcpMemoryCopy(Buffer, AdditionalData, AdditionalDataLength);
|
|
}
|
|
|
|
#if DBG
|
|
SendContext->ListEntryUsed = FALSE;
|
|
#endif
|
|
SendContext->maxWriteBuffer = MemorySize - SIZE_OF_OBJECT_AND_PADDING(HTTP2SendContext);
|
|
SendContext->pWriteBuffer = (BUFFER)Header;
|
|
SendContext->u.SyncEvent = NULL;
|
|
SendContext->TrafficType = http2ttRaw;
|
|
SendContext->Flags = 0;
|
|
SendContext->UserData = 0;
|
|
|
|
RpcStatus = BeginSubmitAsync();
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
RpcFreeBuffer(SendContext);
|
|
return RpcStatus;
|
|
}
|
|
|
|
RpcStatus = LowerLayer->Send(SendContext);
|
|
|
|
FinishSubmitAsync();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
RpcFreeBuffer(SendContext);
|
|
RemoveReference();
|
|
}
|
|
}
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2ClientChannel::SendComplete (
|
|
IN RPC_STATUS EventStatus,
|
|
IN OUT HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send complete notification
|
|
|
|
Arguments:
|
|
|
|
EventStatus - the status of the send
|
|
|
|
SendContext - send context
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
HTTP2VirtualConnection *VirtualConnection;
|
|
|
|
RpcStatus = HTTP2Channel::CheckSendCompleteForSync(EventStatus,
|
|
SendContext
|
|
);
|
|
|
|
if (RpcStatus != RPC_P_PACKET_CONSUMED)
|
|
{
|
|
// is this our client open packet?
|
|
if (SendContext->TrafficType == http2ttRaw)
|
|
{
|
|
if (EventStatus != RPC_S_OK)
|
|
{
|
|
VirtualConnection = (HTTP2VirtualConnection *)LockParentPointer();
|
|
if (VirtualConnection != NULL)
|
|
{
|
|
VirtualConnection->Abort();
|
|
UnlockParentPointer();
|
|
}
|
|
}
|
|
|
|
RpcFreeBuffer(SendContext);
|
|
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
}
|
|
else if (RpcStatus == RPC_S_OK)
|
|
{
|
|
// doesn't seem like our traffic. Forward it up.
|
|
RpcStatus = ForwardUpSendComplete(EventStatus,
|
|
SendContext
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// must be an error - just fall through
|
|
}
|
|
}
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2ClientChannel::CheckReceiveCompleteForSync (
|
|
IN RPC_STATUS EventStatus,
|
|
IN HTTP2TrafficType TrafficType,
|
|
IN BYTE *Buffer,
|
|
IN UINT BufferLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Receive complete notification. Checks if the receive was
|
|
sync, and if yes, fires event and consumes the packet.
|
|
|
|
Arguments:
|
|
|
|
EventStatus - status of the operation
|
|
|
|
TrafficType - the type of traffic we received
|
|
|
|
Buffer - the received buffer (success only)
|
|
|
|
BufferLength - the length of the received buffer (success only)
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
HANDLE hEvent;
|
|
|
|
// RTS receives are never sync even if there
|
|
// is a sync waiter
|
|
if (TrafficType == http2ttRTS)
|
|
return RPC_S_OK;
|
|
|
|
// was this a sync receive?
|
|
if (Ol.ReceiveOverlapped.hEvent)
|
|
{
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_CHECK_RECV_COMPLETE, HTTP2LOG_OT_CLIENT_CHANNEL, (ULONG_PTR)Buffer);
|
|
|
|
// yes, consume it
|
|
if (EventStatus == RPC_S_OK)
|
|
{
|
|
ASSERT(Buffer != NULL);
|
|
}
|
|
Ol.ReceiveOverlapped.Buffer = Buffer;
|
|
Ol.ReceiveOverlapped.BufferLength = BufferLength;
|
|
hEvent = Ol.ReceiveOverlapped.hEvent;
|
|
Ol.ReceiveOverlapped.Internal = (ULONG)EventStatus;
|
|
Ol.ReceiveOverlapped.IOCompleted = TRUE;
|
|
SetEvent(hEvent);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
// wasn't for us after all. Let it continue
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
void HTTP2ClientChannel::WaitInfiniteForSyncReceive (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Waits infinitely for a sync recv to complete.
|
|
Channel must be aborted before this is called.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
ASSERT(Aborted.GetInteger() > 0);
|
|
|
|
UTIL_WaitForSyncHTTP2IO(&Ol.Overlapped,
|
|
Ol.ReceiveOverlapped.hEvent,
|
|
FALSE, // Alertable
|
|
INFINITE);
|
|
}
|
|
|
|
RPC_STATUS HTTP2ClientChannel::SubmitSyncRecv (
|
|
IN HTTP2TrafficType TrafficType
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Submits a sync recv.
|
|
|
|
Arguments:
|
|
|
|
TrafficType - the type of traffic
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_SYNC_RECV, HTTP2LOG_OT_CLIENT_CHANNEL, TrafficType);
|
|
|
|
// transfer the settings from parameters to the receive overlapped
|
|
Ol.ReceiveOverlapped.hEvent = I_RpcTransGetThreadEvent();
|
|
ResetEvent(Ol.ReceiveOverlapped.hEvent);
|
|
Ol.ReceiveOverlapped.IOCompleted = FALSE;
|
|
|
|
// submit the actual receive
|
|
return HTTP2Channel::Receive(TrafficType);
|
|
}
|
|
|
|
RPC_STATUS HTTP2ClientChannel::WaitForSyncRecv (
|
|
IN BYTE **Buffer,
|
|
IN ULONG *BufferLength,
|
|
IN ULONG Timeout,
|
|
IN ULONG ConnectionTimeout,
|
|
IN BASE_ASYNC_OBJECT *Connection,
|
|
OUT BOOL *AbortNeeded,
|
|
OUT BOOL *IoPending
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Waits for a sync receive to complete.
|
|
|
|
Arguments:
|
|
|
|
Buffer - on success will contain the received buffer. On failure
|
|
is undefined.
|
|
|
|
BufferLength - on success will contain the length of the buffer.
|
|
On failure is undefined.
|
|
|
|
Timeout - the call timeout
|
|
|
|
ConnectionTimeout - the connection timeout
|
|
|
|
Connection - the transport connection object
|
|
|
|
AbortNeeded - must be FALSE on entry. This function will set it to
|
|
non-zero if abort and wait are needed.
|
|
|
|
WaitPending - must be FALSE on entry. If on return there is an Io
|
|
pending, it will be set to non-zero
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
DWORD dwActualTimeout;
|
|
BOOL fWaitOnConnectionTimeout;
|
|
BOOL fSetKeepAliveVals;
|
|
KEEPALIVE_TIMEOUT KATimeout;
|
|
RPC_STATUS RpcStatus2;
|
|
|
|
ASSERT(*AbortNeeded == FALSE);
|
|
ASSERT(*IoPending == FALSE);
|
|
|
|
// if there's a per operation timeout, use the lesser of the operation
|
|
// and connection timeout
|
|
ASSERT(ConnectionTimeout);
|
|
|
|
if (Timeout != INFINITE)
|
|
{
|
|
if (Timeout <= ConnectionTimeout)
|
|
{
|
|
dwActualTimeout = Timeout;
|
|
fWaitOnConnectionTimeout = FALSE;
|
|
}
|
|
else
|
|
{
|
|
dwActualTimeout = ConnectionTimeout;
|
|
fWaitOnConnectionTimeout = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// wait on the connection timeout
|
|
dwActualTimeout = ConnectionTimeout;
|
|
fWaitOnConnectionTimeout = TRUE;
|
|
}
|
|
|
|
fSetKeepAliveVals = FALSE;
|
|
|
|
do
|
|
{
|
|
|
|
//
|
|
// Wait for the pending receive to complete
|
|
//
|
|
|
|
RpcStatus = UTIL_GetOverlappedHTTP2ResultEx(Connection,
|
|
&Ol.Overlapped,
|
|
Ol.ReceiveOverlapped.hEvent,
|
|
TRUE, // Alertable
|
|
dwActualTimeout);
|
|
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
// if we timed out ...
|
|
if (RpcStatus == RPC_P_TIMEOUT)
|
|
{
|
|
ASSERT(dwActualTimeout != INFINITE);
|
|
|
|
// if we waited on the per connection timeout ...
|
|
if (fWaitOnConnectionTimeout)
|
|
{
|
|
ASSERT(ConnectionTimeout != INFINITE);
|
|
if (Timeout == INFINITE)
|
|
{
|
|
// enable keep alives and wait forever
|
|
dwActualTimeout = INFINITE;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(ConnectionTimeout < Timeout);
|
|
|
|
// enable keep alives and wait the difference
|
|
dwActualTimeout = Timeout - ConnectionTimeout;
|
|
fWaitOnConnectionTimeout = FALSE;
|
|
}
|
|
// Enable aggressive keepalives on the socket if lower layers
|
|
// support it.
|
|
KATimeout.Milliseconds = ConnectionTimeout;
|
|
RpcStatus2 = SetKeepAliveTimeout (
|
|
TRUE, // TurnOn
|
|
FALSE, // bProtectIO
|
|
tuMilliseconds,
|
|
KATimeout,
|
|
KATimeout.Milliseconds
|
|
);
|
|
|
|
if (RpcStatus2 != RPC_S_OK)
|
|
{
|
|
*AbortNeeded = TRUE;
|
|
*IoPending = TRUE;
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
fSetKeepAliveVals = TRUE;
|
|
|
|
continue;
|
|
}
|
|
// else we have chosen the per operation timeout and
|
|
// have timed out on that - time to bail out
|
|
}
|
|
|
|
// Normal error path
|
|
if ((RpcStatus == RPC_S_CALL_CANCELLED) || (RpcStatus == RPC_P_TIMEOUT))
|
|
{
|
|
if ((RpcStatus == RPC_P_TIMEOUT) && fWaitOnConnectionTimeout)
|
|
{
|
|
RpcStatus = RPC_P_RECEIVE_FAILED;
|
|
}
|
|
*AbortNeeded = TRUE;
|
|
*IoPending = TRUE;
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
*AbortNeeded = TRUE;
|
|
// connection was aborted - no need to turn off keep alives
|
|
goto CleanupAndExit;
|
|
}
|
|
}
|
|
while (RpcStatus == RPC_P_TIMEOUT);
|
|
|
|
if (fSetKeepAliveVals)
|
|
{
|
|
// Call completed, clear keep alives. Turning off is a best
|
|
// effort. Ignore failures
|
|
KATimeout.Milliseconds = 0;
|
|
(void) SetKeepAliveTimeout(
|
|
FALSE, // TurnOn
|
|
FALSE, // bProtectIO
|
|
tuMilliseconds,
|
|
KATimeout,
|
|
KATimeout.Milliseconds
|
|
);
|
|
}
|
|
|
|
*Buffer = Ol.ReceiveOverlapped.Buffer;
|
|
*BufferLength = Ol.ReceiveOverlapped.BufferLength;
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
ASSERT(*Buffer != NULL);
|
|
}
|
|
|
|
CleanupAndExit:
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_SYNC_RECV, HTTP2LOG_OT_CLIENT_CHANNEL, RpcStatus);
|
|
|
|
if (*IoPending == FALSE)
|
|
{
|
|
// consume the event if there will be no wait
|
|
Ol.ReceiveOverlapped.hEvent = NULL;
|
|
}
|
|
return RpcStatus;
|
|
}
|
|
|
|
void HTTP2ClientChannel::AbortConnection (
|
|
IN RPC_STATUS AbortReason
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Aborts the client virtual connection. The only
|
|
difference from HTTP2Channel::AbortConnection
|
|
is that it specifically calls AbortChannels on
|
|
the client virtual connection. This is necessary
|
|
because AbortChannels is not virtual.
|
|
|
|
Arguments:
|
|
|
|
RpcStatus - the error to abort with
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
HTTP2ClientVirtualConnection *VirtualConnection;
|
|
|
|
// abort the parent connection
|
|
VirtualConnection = (HTTP2ClientVirtualConnection *)LockParentPointer();
|
|
if (VirtualConnection)
|
|
{
|
|
VirtualConnection->AbortChannels(AbortReason);
|
|
UnlockParentPointer();
|
|
}
|
|
else
|
|
{
|
|
// abort this channel at least
|
|
Abort(AbortReason);
|
|
}
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2ClientInChannel
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS HTTP2ClientInChannel::ClientOpen (
|
|
IN HTTPResolverHint *Hint,
|
|
IN const char *Verb,
|
|
IN int VerbLength,
|
|
IN BOOL UseWinHttp,
|
|
IN RPC_HTTP_TRANSPORT_CREDENTIALS_W *HttpCredentials,
|
|
IN ULONG ChosenAuthScheme,
|
|
IN ULONG CallTimeout,
|
|
IN const BYTE *AdditionalData, OPTIONAL
|
|
IN ULONG AdditionalDataLength OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sends the HTTP establishment header on
|
|
the in channel.
|
|
|
|
Arguments:
|
|
|
|
Hint - the resolver hint
|
|
|
|
Verb - the verb to use.
|
|
|
|
VerbLength - the length of the verb (in characters, not including
|
|
null terminator).
|
|
|
|
UseWinHttp - non-zero if WinHttp needs to be used. 0 otherwise
|
|
|
|
HttpCredentials - encrypted transport credentials
|
|
|
|
ChosenAuthScheme - the chosen auth scheme. 0 if no auth scheme is chosen.
|
|
|
|
CallTimeout - the call timeout for this call
|
|
|
|
AdditionalData - additional data to send with the header. Must be set iff
|
|
AdditionalDataLength != 0
|
|
|
|
AdditionalDataLength - the length of the additional data to send with the header.
|
|
Must be set iff AdditionalLength != NULL
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
return HTTP2ClientChannel::ClientOpen(Hint,
|
|
Verb,
|
|
VerbLength,
|
|
TRUE, // InChannel
|
|
FALSE, // ReplacementChannel
|
|
UseWinHttp,
|
|
HttpCredentials,
|
|
ChosenAuthScheme,
|
|
GetWinHttpConnection(),
|
|
CallTimeout,
|
|
AdditionalData,
|
|
AdditionalDataLength
|
|
);
|
|
}
|
|
|
|
RPC_STATUS HTTP2ClientInChannel::SetKeepAliveTimeout (
|
|
IN BOOL TurnOn,
|
|
IN BOOL bProtectIO,
|
|
IN KEEPALIVE_TIMEOUT_UNITS Units,
|
|
IN OUT KEEPALIVE_TIMEOUT KATime,
|
|
IN ULONG KAInterval OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Change the keep alive value on the channel
|
|
|
|
Arguments:
|
|
|
|
TurnOn - if non-zero, keep alives are turned on. If zero, keep alives
|
|
are turned off.
|
|
|
|
bProtectIO - non-zero if IO needs to be protected against async close
|
|
of the connection.
|
|
|
|
Units - in what units is KATime
|
|
|
|
KATime - how much to wait before turning on keep alives
|
|
|
|
KAInterval - the interval between keep alives
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error.
|
|
May return RPC_P_CHANNEL_NEEDS_RECYCLING - caller needs to handle.
|
|
|
|
--*/
|
|
{
|
|
HTTP2SendContext *KeepAliveChangeContext;
|
|
RPC_STATUS RpcStatus;
|
|
BOOL WasChannelRecyclingTriggered;
|
|
|
|
ASSERT(Units == tuMilliseconds);
|
|
|
|
// HTTP keep alives are heavy weight. Moderate the caller's
|
|
// settings
|
|
if (TurnOn)
|
|
{
|
|
if (KAInterval < MinimumClientSideKeepAliveInterval)
|
|
KAInterval = MinimumClientSideKeepAliveInterval;
|
|
}
|
|
else
|
|
{
|
|
KAInterval = 0;
|
|
}
|
|
|
|
// tell the proxy to change it's keepalives and then
|
|
// ask our ping originator to change its keepalives as well
|
|
KeepAliveChangeContext = AllocateAndInitializeKeepAliveChangePacket (KAInterval);
|
|
if (KeepAliveChangeContext == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
|
|
RpcStatus = Send(KeepAliveChangeContext);
|
|
|
|
WasChannelRecyclingTriggered = FALSE;
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
if (RpcStatus == RPC_P_CHANNEL_NEEDS_RECYCLING)
|
|
WasChannelRecyclingTriggered = TRUE;
|
|
else
|
|
{
|
|
FreeRTSPacket(KeepAliveChangeContext);
|
|
return RpcStatus;
|
|
}
|
|
}
|
|
|
|
// get into submission context
|
|
RpcStatus = BeginSimpleSubmitAsync();
|
|
if (RpcStatus != RPC_S_OK)
|
|
return RpcStatus;
|
|
|
|
// ask our ping channel to change as well
|
|
RpcStatus = GetPingOriginatorChannel()->SetKeepAliveTimeout (
|
|
TurnOn,
|
|
bProtectIO,
|
|
Units,
|
|
KATime,
|
|
KAInterval
|
|
);
|
|
|
|
FinishSubmitAsync();
|
|
|
|
// if we failed for other reasons or channel recycling was not triggered
|
|
// at all, return the current status
|
|
if ((WasChannelRecyclingTriggered == FALSE) || (RpcStatus != RPC_S_OK))
|
|
return RpcStatus;
|
|
else
|
|
return RPC_P_CHANNEL_NEEDS_RECYCLING;
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2ClientOutChannel
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS HTTP2ClientOutChannel::ClientOpen (
|
|
IN HTTPResolverHint *Hint,
|
|
IN const char *Verb,
|
|
IN int VerbLength,
|
|
IN BOOL ReplacementChannel,
|
|
IN BOOL UseWinHttp,
|
|
IN RPC_HTTP_TRANSPORT_CREDENTIALS_W *HttpCredentials,
|
|
IN ULONG ChosenAuthScheme,
|
|
IN ULONG CallTimeout,
|
|
IN const BYTE *AdditionalData, OPTIONAL
|
|
IN ULONG AdditionalDataLength OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sends the HTTP establishment header on
|
|
the out channel.
|
|
|
|
Arguments:
|
|
|
|
Hint - the resolver hint
|
|
|
|
Verb - the verb to use.
|
|
|
|
VerbLength - the length of the verb (in characters, not including
|
|
null terminator).
|
|
|
|
ReplacementChannel - non-zero if this is a replacement channel.
|
|
Zero if it is not.
|
|
|
|
UseWinHttp - non-zero if WinHttp needs to be used. 0 otherwise
|
|
|
|
HttpCredentials - encrypted transport credentials
|
|
|
|
ChosenAuthScheme - the chosen auth scheme. 0 if no auth scheme is chosen.
|
|
|
|
CallTimeout - the call timeout for this call.
|
|
|
|
AdditionalData - additional data to send with the header. Must be set iff
|
|
AdditionalDataLength != 0
|
|
|
|
AdditionalDataLength - the length of the additional data to send with the header.
|
|
Must be set iff AdditionalLength != NULL
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
return HTTP2ClientChannel::ClientOpen(Hint,
|
|
Verb,
|
|
VerbLength,
|
|
FALSE, // InChannel
|
|
ReplacementChannel,
|
|
UseWinHttp,
|
|
HttpCredentials,
|
|
ChosenAuthScheme,
|
|
GetWinHttpConnection(),
|
|
CallTimeout,
|
|
AdditionalData,
|
|
AdditionalDataLength
|
|
);
|
|
}
|
|
|
|
RPC_STATUS HTTP2ClientOutChannel::ForwardFlowControlAck (
|
|
IN ULONG BytesReceivedForAck,
|
|
IN ULONG WindowForAck
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Forwards a flow control ack to the out proxy through the in proxy
|
|
|
|
Arguments:
|
|
|
|
BytesReceivedForAck - the bytes received when the ACK was issued
|
|
|
|
WindowForAck - the free window when the ACK was issued.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_*
|
|
|
|
Notes:
|
|
|
|
Must be called from neutral context only.
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
RpcStatus = ForwardFlowControlAckOnDefaultChannel(TRUE, // IsInChannel
|
|
fdOutProxy,
|
|
BytesReceivedForAck,
|
|
WindowForAck
|
|
);
|
|
|
|
RpcStatus = HandleSendResultFromNeutralContext(RpcStatus);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2ClientOutChannel::SetKeepAliveTimeout (
|
|
IN BOOL TurnOn,
|
|
IN BOOL bProtectIO,
|
|
IN KEEPALIVE_TIMEOUT_UNITS Units,
|
|
IN OUT KEEPALIVE_TIMEOUT KATime,
|
|
IN ULONG KAInterval OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Change the keep alive value on the channel
|
|
|
|
Arguments:
|
|
|
|
TurnOn - if non-zero, keep alives are turned on. If zero, keep alives
|
|
are turned off.
|
|
|
|
bProtectIO - non-zero if IO needs to be protected against async close
|
|
of the connection.
|
|
|
|
Units - in what units is KATime
|
|
|
|
KATime - how much to wait before turning on keep alives
|
|
|
|
KAInterval - the interval between keep alives
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
HTTP2ClientVirtualConnection *VirtualConnection;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
// turn around the request through the connection
|
|
VirtualConnection = LockParentPointer();
|
|
if (VirtualConnection == NULL)
|
|
return RPC_P_CONNECTION_SHUTDOWN;
|
|
|
|
RpcStatus = VirtualConnection->TurnOnOffKeepAlives(
|
|
TurnOn,
|
|
bProtectIO,
|
|
TRUE, // IsFromUpcall
|
|
Units,
|
|
KATime,
|
|
KAInterval
|
|
);
|
|
|
|
UnlockParentPointer();
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2ClientVirtualConnection
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS HTTP2ClientVirtualConnection::ClientOpen (
|
|
IN HTTPResolverHint *Hint,
|
|
IN BOOL HintWasInitialized,
|
|
IN UINT ConnTimeout,
|
|
IN ULONG CallTimeout
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Opens a client side virtual connection.
|
|
|
|
Arguments:
|
|
|
|
Hint - the resolver hint
|
|
|
|
HintWasInitialized - the hint was initialized on input.
|
|
|
|
ConnTimeout - connection timeout
|
|
|
|
CallTimeout - operation timeout
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
return ClientOpenInternal (
|
|
Hint,
|
|
HintWasInitialized,
|
|
ConnTimeout,
|
|
CallTimeout,
|
|
TRUE, // OpenInChannel
|
|
TRUE, // OpenOutChannel
|
|
FALSE, // IsReplacementChannel
|
|
FALSE // IsFromUpcall
|
|
);
|
|
}
|
|
|
|
RPC_STATUS HTTP2ClientVirtualConnection::SendComplete (
|
|
IN RPC_STATUS EventStatus,
|
|
IN OUT HTTP2SendContext *SendContext,
|
|
IN int ChannelId
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Called by lower layers to indicate send complete.
|
|
|
|
Arguments:
|
|
|
|
EventStatus - status of the operation
|
|
|
|
SendContext - the context for the send complete
|
|
|
|
ChannelId - which channel completed the operation
|
|
|
|
Return Value:
|
|
|
|
RPC_P_PACKET_CONSUMED if the packet was consumed and should
|
|
be hidden from the runtime.
|
|
RPC_S_OK if the packet was processed successfully.
|
|
RPC_S_* error if there was an error while processing the
|
|
packet.
|
|
|
|
--*/
|
|
{
|
|
HTTP2TrafficType TrafficType;
|
|
|
|
VerifyValidChannelId(ChannelId);
|
|
|
|
if (EventStatus == RPC_S_OK)
|
|
{
|
|
// successful sends are always no-ops on the
|
|
// client. Just cleanup if necessary and
|
|
// return
|
|
if (SendContext->TrafficType == http2ttRTS)
|
|
{
|
|
FreeSendContextAndPossiblyData(SendContext);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
else
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
// we know the send failed
|
|
if (IsDefaultInChannel(ChannelId) || IsDefaultOutChannel(ChannelId))
|
|
{
|
|
// on a default channel such error is fatal
|
|
AbortChannels(EventStatus);
|
|
|
|
if (SendContext->TrafficType == http2ttRTS)
|
|
{
|
|
FreeSendContextAndPossiblyData(SendContext);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
ASSERT(SendContext->TrafficType == http2ttData);
|
|
|
|
return EventStatus;
|
|
}
|
|
else
|
|
{
|
|
// all data sends go on default channels. RTS sends
|
|
// may go either way. If this is RTS, this is fatal.
|
|
// Otherwise, ignore.
|
|
if (SendContext->TrafficType == http2ttRTS)
|
|
{
|
|
FreeSendContextAndPossiblyData(SendContext);
|
|
AbortChannels(EventStatus);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
else
|
|
return RPC_S_OK;
|
|
}
|
|
}
|
|
|
|
RPC_STATUS HTTP2ClientVirtualConnection::ReceiveComplete (
|
|
IN RPC_STATUS EventStatus,
|
|
IN BYTE *Buffer,
|
|
IN UINT BufferLength,
|
|
IN int ChannelId
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Called by lower layers to indicate receive complete
|
|
|
|
Arguments:
|
|
|
|
EventStatus - RPC_S_OK for success or RPC_S_* for error
|
|
Buffer - buffer received
|
|
BufferLength - length of buffer received
|
|
ChannelId - which channel completed the operation
|
|
|
|
Return Value:
|
|
|
|
RPC_P_PACKET_CONSUMED if the packet was consumed and should
|
|
be hidden from the runtime.
|
|
RPC_P_ABORT_NEEDED - if the channel needs to be aborted but
|
|
couldn't be aborted in this function because it was detached.
|
|
After aborting, the semantics is same as RPC_P_PACKET_CONSUMED.
|
|
RPC_S_OK if the packet was processed successfully.
|
|
RPC_S_* error if there was an error while processing the
|
|
packet.
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
ULONG ProxyConnectionTimeout;
|
|
BOOL BufferFreed = FALSE;
|
|
BOOL WakeOpenThread;
|
|
HTTP2ClientInChannel *InChannel;
|
|
HTTP2ClientOutChannel *OutChannel;
|
|
HTTP2ClientOutChannel *OutChannel2;
|
|
HTTP2ClientInChannel *NewInChannel;
|
|
HTTP2ChannelPointer *ChannelPtr;
|
|
HTTP2ChannelPointer *NewChannelPtr;
|
|
LIST_ENTRY NewBufferHead;
|
|
LIST_ENTRY *CurrentListEntry;
|
|
LIST_ENTRY *PrevListEntry;
|
|
HTTP2SendContext *QueuedSendContext;
|
|
BOOL MutexReleased;
|
|
HTTP2SendContext *A5Context; // may be D2/A5 or D3/A5
|
|
HTTP2SendContext *D4_A7Context;
|
|
HTTP2SendContext *PingContext;
|
|
BOOL IsD2_A4;
|
|
HTTP2ClientOpenedPacketType ClientPacketType;
|
|
BOOL DataReceivePosted;
|
|
HTTP2StateValues NewState;
|
|
HTTP2StateValues ExpectedState;
|
|
ULONG BytesReceivedForAck;
|
|
ULONG WindowForAck;
|
|
HTTP2Cookie CookieForChannel;
|
|
ULONG InProxyReceiveWindow;
|
|
BOOL UseWinHttp;
|
|
KEEPALIVE_TIMEOUT KATimeout;
|
|
|
|
VerifyValidChannelId(ChannelId);
|
|
|
|
if (
|
|
(InChannelState.State == http2svSearchProxy)
|
|
&&
|
|
(
|
|
(EventStatus != RPC_S_OK)
|
|
||
|
|
IsEchoPacket(Buffer, BufferLength)
|
|
)
|
|
)
|
|
{
|
|
InChannelState.Mutex.Request();
|
|
if (InChannelState.State == http2svSearchProxy)
|
|
{
|
|
InChannelState.Mutex.Clear();
|
|
|
|
if (IsInChannel(ChannelId))
|
|
{
|
|
InOpenStatus = EventStatus;
|
|
}
|
|
else
|
|
{
|
|
OutOpenStatus = EventStatus;
|
|
}
|
|
|
|
// we won the race. Wake up the open thread
|
|
WakeOpenThread = TRUE;
|
|
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
InChannelState.Mutex.Clear();
|
|
}
|
|
|
|
WakeOpenThread = FALSE;
|
|
if (IsInChannel(ChannelId))
|
|
{
|
|
if (EventStatus != RPC_P_AUTH_NEEDED)
|
|
{
|
|
// we shouldn't really be receiving stuff on the in channel
|
|
// unless there is an error
|
|
if (EventStatus != RPC_S_OK)
|
|
{
|
|
if (IsDefaultInChannel(ChannelId) == FALSE)
|
|
{
|
|
InChannelState.Mutex.Request();
|
|
if (InChannelState.State == http2svOpened)
|
|
{
|
|
// close on the non-default channel in open
|
|
// state is not an error for the connection
|
|
// just abort the channel in question
|
|
InChannelState.Mutex.Clear();
|
|
ChannelPtr = GetChannelPointerFromId(ChannelId);
|
|
InChannel = (HTTP2ClientInChannel *)ChannelPtr->LockChannelPointer();
|
|
if (InChannel)
|
|
{
|
|
InChannel->Abort(EventStatus);
|
|
ChannelPtr->UnlockChannelPointer();
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
}
|
|
else
|
|
RpcStatus = RPC_P_ABORT_NEEDED;
|
|
|
|
BufferFreed = TRUE;
|
|
return RpcStatus;
|
|
}
|
|
else
|
|
InChannelState.Mutex.Clear();
|
|
}
|
|
RpcStatus = EventStatus;
|
|
// in failed receives, we don't own the buffer
|
|
BufferFreed = TRUE;
|
|
}
|
|
else
|
|
{
|
|
if (IsEchoPacket(Buffer, BufferLength))
|
|
{
|
|
InOpenStatus = EventStatus;
|
|
WakeOpenThread = TRUE;
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
goto CleanupAndExit;
|
|
}
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
}
|
|
|
|
AbortChannels(RpcStatus);
|
|
}
|
|
else
|
|
{
|
|
// turn around the error code and fall through
|
|
RpcStatus = EventStatus;
|
|
}
|
|
|
|
// the runtime never posted a receive on the in
|
|
// channel. Don't let it see the packet
|
|
InOpenStatus = RpcStatus;
|
|
WakeOpenThread = TRUE;
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
}
|
|
else
|
|
{
|
|
// this is an out channel
|
|
if (EventStatus != RPC_S_OK)
|
|
{
|
|
if (EventStatus != RPC_P_AUTH_NEEDED)
|
|
{
|
|
AbortChannels(EventStatus);
|
|
RpcStatus = EventStatus;
|
|
|
|
if (RpcStatus == RPC_P_CONNECTION_SHUTDOWN)
|
|
RpcStatus = RPC_P_RECEIVE_FAILED;
|
|
|
|
OutOpenStatus = RpcStatus;
|
|
}
|
|
else
|
|
{
|
|
OutOpenStatus = EventStatus;
|
|
|
|
RpcStatus = RPC_P_RECEIVE_FAILED;
|
|
}
|
|
|
|
WakeOpenThread = TRUE;
|
|
|
|
// in failed receives we don't own the buffer
|
|
BufferFreed = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// verify state and act upon it if RTS
|
|
// for data we don't care
|
|
if (IsRTSPacket(Buffer))
|
|
{
|
|
if (IsOtherCmdPacket(Buffer, BufferLength))
|
|
{
|
|
// the only cmd packet we expect is flow control ack
|
|
// try to interpret it as such
|
|
RpcStatus = ParseAndFreeFlowControlAckPacketWithDestination (
|
|
Buffer,
|
|
BufferLength,
|
|
fdClient,
|
|
&BytesReceivedForAck,
|
|
&WindowForAck,
|
|
&CookieForChannel
|
|
);
|
|
|
|
BufferFreed = TRUE;
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
// notify the flow control sender about the ack
|
|
InChannel = (HTTP2ClientInChannel *)MapCookieToChannelPointer(
|
|
&CookieForChannel,
|
|
&ChannelPtr
|
|
);
|
|
|
|
if (InChannel && !IsInChannel(ChannelPtr))
|
|
{
|
|
CORRUPTION_ASSERT(0);
|
|
ChannelPtr->UnlockChannelPointer();
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
InChannel = NULL;
|
|
// fall through with the error
|
|
}
|
|
|
|
if (InChannel)
|
|
{
|
|
RpcStatus = InChannel->FlowControlAckNotify(BytesReceivedForAck,
|
|
WindowForAck
|
|
);
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
RpcStatus = StartChannelRecyclingIfNecessary(RpcStatus,
|
|
TRUE // IsFromUpcall
|
|
);
|
|
}
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
// post another receive
|
|
RpcStatus = PostReceiveOnChannel (GetChannelPointerFromId(ChannelId),
|
|
http2ttRTS
|
|
);
|
|
}
|
|
}
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RpcStatus);
|
|
|
|
OutOpenStatus = RpcStatus;
|
|
WakeOpenThread = TRUE;
|
|
}
|
|
|
|
// This is an RTS packet - consume it
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
goto CleanupAndExit;
|
|
}
|
|
else if (IsEchoPacket(Buffer, BufferLength))
|
|
{
|
|
OutOpenStatus = EventStatus;
|
|
WakeOpenThread = TRUE;
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
RpcStatus = HTTPTransInfo->CreateThread();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RpcStatus);
|
|
|
|
// This is an RTS packet - consume it
|
|
RpcFreeBuffer(Buffer);
|
|
BufferFreed = TRUE;
|
|
OutOpenStatus = RpcStatus;
|
|
WakeOpenThread = TRUE;
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
MutexReleased = FALSE;
|
|
InChannelState.Mutex.Request();
|
|
switch (InChannelState.State)
|
|
{
|
|
case http2svA3W:
|
|
RpcStatus = ParseAndFreeD1_A3(Buffer,
|
|
BufferLength,
|
|
&ProxyConnectionTimeout
|
|
);
|
|
|
|
// we don't really do anything with the ProxyConnectionTimeout - it's
|
|
// for debugging purposes only
|
|
|
|
BufferFreed = TRUE;
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
RpcStatus = PostReceiveOnChannel(&OutChannels[0], http2ttRTS);
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, IN_CHANNEL_STATE, http2svC2W, 1, 0);
|
|
InChannelState.State = http2svC2W;
|
|
}
|
|
else
|
|
{
|
|
OutOpenStatus = RpcStatus;
|
|
AbortChannels(RpcStatus);
|
|
WakeOpenThread = TRUE;
|
|
}
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
}
|
|
break;
|
|
|
|
case http2svC2W:
|
|
RpcStatus = ParseAndFreeD1_C2(Buffer,
|
|
BufferLength,
|
|
&ProtocolVersion,
|
|
&InProxyReceiveWindow,
|
|
&InProxyConnectionTimeout
|
|
);
|
|
|
|
BufferFreed = TRUE;
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
RpcStatus = PostReceiveOnChannel(&OutChannels[0], http2ttRTS);
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
// Set the in proxy timeout to the channel
|
|
InChannel = LockDefaultInChannel(&ChannelPtr);
|
|
if (InChannel != NULL)
|
|
{
|
|
InChannel->SetPeerReceiveWindow(InProxyReceiveWindow);
|
|
RpcStatus = InChannel->SetConnectionTimeout(InProxyConnectionTimeout);
|
|
ChannelPtr->UnlockChannelPointer();
|
|
}
|
|
else
|
|
RpcStatus = RPC_P_CONNECTION_CLOSED;
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RpcStatus);
|
|
OutOpenStatus = RpcStatus;
|
|
WakeOpenThread = TRUE;
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, IN_CHANNEL_STATE, http2svOpened, 1, 0);
|
|
InChannelState.State = http2svOpened;
|
|
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, OUT_CHANNEL_STATE, http2svOpened, 1, 0);
|
|
OutChannelState.State = http2svOpened;
|
|
|
|
InOpenStatus = OutOpenStatus = RPC_S_OK;
|
|
SetClientOpenInEvent();
|
|
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// all the opened states are handled here
|
|
ASSERT((InChannelState.State == http2svOpened)
|
|
|| (InChannelState.State == http2svOpened_A4W) );
|
|
ASSERT((OutChannelState.State == http2svOpened)
|
|
|| (OutChannelState.State == http2svOpened_A6W)
|
|
|| (OutChannelState.State == http2svOpened_A10W)
|
|
|| (OutChannelState.State == http2svOpened_B3W) );
|
|
|
|
RpcStatus = GetClientOpenedPacketType (Buffer,
|
|
BufferLength,
|
|
&ClientPacketType
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RpcStatus);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
|
|
switch (ClientPacketType)
|
|
{
|
|
case http2coptD2_A4:
|
|
case http2coptD3_A4:
|
|
// in channel must be in Opened_A4W
|
|
// out channel can be in any state
|
|
CORRUPTION_ASSERT(InChannelState.State == http2svOpened_A4W);
|
|
if (InChannelState.State != http2svOpened_A4W)
|
|
{
|
|
InChannelState.Mutex.Clear();
|
|
MutexReleased = TRUE;
|
|
AbortChannels(RPC_S_PROTOCOL_ERROR);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
|
|
InChannelState.Mutex.Clear();
|
|
MutexReleased = TRUE;
|
|
|
|
IsD2_A4 = IsD2_A4OrD3_A4(Buffer,
|
|
BufferLength
|
|
);
|
|
|
|
if (IsD2_A4)
|
|
{
|
|
RpcStatus = ParseAndFreeD2_A4(Buffer,
|
|
BufferLength,
|
|
fdClient,
|
|
&ProtocolVersion,
|
|
&InProxyReceiveWindow,
|
|
&InProxyConnectionTimeout
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// This must be a D3/A4
|
|
RpcStatus = ParseAndFreeD3_A4 (Buffer,
|
|
BufferLength
|
|
);
|
|
}
|
|
|
|
BufferFreed = TRUE;
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RpcStatus);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
|
|
RpcStatus = PostReceiveOnChannel(&OutChannels[DefaultOutChannelSelector],
|
|
http2ttRTS);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RpcStatus);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
|
|
// lock the old channel
|
|
InChannel = LockDefaultInChannel(&ChannelPtr);
|
|
if (InChannel == NULL)
|
|
{
|
|
AbortChannels(RPC_P_CONNECTION_CLOSED);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
|
|
if (IsD2_A4 == FALSE)
|
|
{
|
|
// capture the receive window from the old channel.
|
|
// We know the proxy is the same so the window must be
|
|
// the same as well
|
|
InProxyReceiveWindow = InChannel->GetPeerReceiveWindow();
|
|
}
|
|
|
|
// switch channels (new channel is still plugged)
|
|
SwitchDefaultInChannelSelector();
|
|
|
|
// wait for everybody that is in the process of using
|
|
// the old channel to get out
|
|
InChannel->DrainPendingSubmissions();
|
|
|
|
// leave 1 for our lock
|
|
ChannelPtr->DrainPendingLocks(1);
|
|
|
|
// lock new channel (by now it is default)
|
|
NewInChannel = LockDefaultInChannel(&NewChannelPtr);
|
|
if (NewInChannel == NULL)
|
|
{
|
|
ChannelPtr->UnlockChannelPointer();
|
|
AbortChannels(RPC_P_CONNECTION_CLOSED);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
|
|
NewInChannel->SetPeerReceiveWindow(InProxyReceiveWindow);
|
|
|
|
// Set the in proxy timeout to the new channel
|
|
RpcStatus = NewInChannel->SetConnectionTimeout(InProxyConnectionTimeout);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
NewChannelPtr->UnlockChannelPointer();
|
|
ChannelPtr->UnlockChannelPointer();
|
|
AbortChannels(RpcStatus);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
|
|
// if old flow control channel was queuing, grab all its buffers
|
|
// We must do this before the channel data originator to make sure
|
|
// the buffers end up behind the ones from the channel data
|
|
// originator
|
|
RpcpInitializeListHead(&NewBufferHead);
|
|
InChannel->GetFlowControlSenderBufferQueue(&NewBufferHead);
|
|
|
|
AddBufferQueueToChannel(&NewBufferHead, NewInChannel);
|
|
|
|
// GetChannelOriginatorBufferQueue can be called in submission
|
|
// context only. Get into submission context
|
|
RpcStatus = InChannel->BeginSimpleSubmitAsync();
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
NewChannelPtr->UnlockChannelPointer();
|
|
ChannelPtr->UnlockChannelPointer();
|
|
AbortChannels(RpcStatus);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
|
|
// if old channel channel data originator was queuing, grab all its buffers
|
|
RpcpInitializeListHead(&NewBufferHead);
|
|
InChannel->GetChannelOriginatorBufferQueue(&NewBufferHead);
|
|
|
|
InChannel->FinishSubmitAsync();
|
|
|
|
AddBufferQueueToChannel(&NewBufferHead, NewInChannel);
|
|
|
|
InChannelState.Mutex.Request();
|
|
// move channel state to opened
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, IN_CHANNEL_STATE, http2svOpened, 1, 0);
|
|
InChannelState.State = http2svOpened;
|
|
InChannelState.Mutex.Clear();
|
|
|
|
if (IsD2_A4)
|
|
{
|
|
// register the last packet to send with the old channel
|
|
A5Context = AllocateAndInitializeD2_A5 (
|
|
&InChannelCookies[DefaultInChannelSelector]
|
|
);
|
|
}
|
|
else
|
|
{
|
|
A5Context = AllocateAndInitializeD3_A5 (
|
|
&InChannelCookies[DefaultInChannelSelector]
|
|
);
|
|
}
|
|
|
|
if (A5Context == NULL)
|
|
{
|
|
ChannelPtr->UnlockChannelPointer();
|
|
NewChannelPtr->UnlockChannelPointer();
|
|
AbortChannels(RPC_P_CONNECTION_CLOSED);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
|
|
// make sure the packet is sent last
|
|
RpcStatus = InChannel->Send(A5Context);
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
UseWinHttp = ShouldUseWinHttp(HttpCredentials);
|
|
if (UseWinHttp)
|
|
{
|
|
InChannel->DisablePings();
|
|
RpcStatus = InChannel->Receive(http2ttRaw);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FreeRTSPacket(A5Context);
|
|
}
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
ChannelPtr->UnlockChannelPointer();
|
|
NewChannelPtr->UnlockChannelPointer();
|
|
AbortChannels(RPC_P_CONNECTION_CLOSED);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
|
|
// D2_A5 was sent. We must switch the
|
|
// default loopback and detach the channel.
|
|
// Note that we don't abort the channel - we
|
|
// just release the lifetime reference
|
|
// When the proxy closes the connection, then
|
|
// we will abort.
|
|
SwitchDefaultLoopbackChannelSelector();
|
|
ChannelPtr->UnlockChannelPointer();
|
|
ChannelPtr->FreeChannelPointer(
|
|
TRUE, // DrainUpcalls
|
|
FALSE, // CalledFromUpcallContext
|
|
FALSE, // Abort
|
|
RPC_S_OK
|
|
);
|
|
|
|
RpcStatus = NewInChannel->Unplug();
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
NewChannelPtr->UnlockChannelPointer();
|
|
AbortChannels(RpcStatus);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
|
|
NewChannelPtr->UnlockChannelPointer();
|
|
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
|
|
break;
|
|
|
|
case http2coptD4_A2:
|
|
// in channel can be in any opened state
|
|
// out channel must be in http2svOpened
|
|
CORRUPTION_ASSERT(OutChannelState.State == http2svOpened);
|
|
|
|
if (OutChannelState.State != http2svOpened)
|
|
{
|
|
AbortChannels(RpcStatus);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
InChannelState.Mutex.Clear();
|
|
MutexReleased = TRUE;
|
|
|
|
RpcStatus = ParseAndFreeD4_A2(Buffer,
|
|
BufferLength
|
|
);
|
|
|
|
BufferFreed = TRUE;
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RpcStatus);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
|
|
RpcStatus = OpenReplacementOutChannel();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RpcStatus);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
|
|
RpcStatus = PostReceiveOnDefaultChannel(FALSE, // IsInChannel
|
|
http2ttRTS);
|
|
if (RpcStatus != RPC_S_OK)
|
|
AbortChannels(RpcStatus);
|
|
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
|
|
break;
|
|
|
|
case http2coptD4_A6:
|
|
case http2coptD5_A6:
|
|
// in channel can be in any opened state
|
|
// out channel must be in http2svOpened_A6W
|
|
CORRUPTION_ASSERT(OutChannelState.State == http2svOpened_A6W);
|
|
|
|
if (OutChannelState.State != http2svOpened_A6W)
|
|
{
|
|
AbortChannels(RpcStatus);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
|
|
if (ClientPacketType == http2coptD4_A6)
|
|
NewState = http2svOpened_A10W;
|
|
else
|
|
NewState = http2svOpened_B3W;
|
|
|
|
// move channel state to new state
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, OUT_CHANNEL_STATE, NewState, 1, 0);
|
|
OutChannelState.State = NewState;
|
|
|
|
InChannelState.Mutex.Clear();
|
|
MutexReleased = TRUE;
|
|
|
|
if (ClientPacketType == http2coptD4_A6)
|
|
{
|
|
RpcStatus = ParseAndFreeD4_A6 (Buffer,
|
|
BufferLength,
|
|
fdClient,
|
|
&ProtocolVersion,
|
|
&ProxyConnectionTimeout
|
|
);
|
|
|
|
// we don't really do anything with ProxyConnectionTimeout. That's
|
|
// ok. It's there mostly for debugging.
|
|
}
|
|
else
|
|
{
|
|
RpcStatus = ParseAndFreeD5_A6 (Buffer,
|
|
BufferLength,
|
|
fdClient
|
|
);
|
|
}
|
|
|
|
BufferFreed = TRUE;
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RpcStatus);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
|
|
if (ClientPacketType == http2coptD5_A6)
|
|
{
|
|
// in the D5 case, we won't send A11. Since
|
|
// some proxies are picky about sending all
|
|
// the declared data before allowing receiving
|
|
// data to come in, we need to send an empty packet
|
|
// of the necessary size to keep the proxy happy.
|
|
PingContext = AllocateAndInitializePingPacketWithSize (
|
|
GetD4_A11TotalLength()
|
|
);
|
|
|
|
if (PingContext == NULL)
|
|
{
|
|
AbortChannels(RPC_S_OUT_OF_MEMORY);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
|
|
RpcStatus = SendTrafficOnChannel(
|
|
&OutChannels[GetNonDefaultOutChannelSelector()],
|
|
PingContext
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
FreeRTSPacket(PingContext);
|
|
AbortChannels(RpcStatus);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
}
|
|
|
|
D4_A7Context = AllocateAndInitializeD4_A7 (
|
|
fdServer,
|
|
&OutChannelCookies[GetNonDefaultOutChannelSelector()],
|
|
ProtocolVersion
|
|
);
|
|
|
|
if (D4_A7Context == NULL)
|
|
{
|
|
AbortChannels(RPC_S_OUT_OF_MEMORY);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
|
|
RpcStatus = SendTrafficOnDefaultChannel(TRUE, //IsInChannel
|
|
D4_A7Context
|
|
);
|
|
|
|
RpcStatus = StartChannelRecyclingIfNecessary(RpcStatus,
|
|
TRUE // IsFromUpcall
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
FreeRTSPacket(D4_A7Context);
|
|
AbortChannels(RpcStatus);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
|
|
RpcStatus = PostReceiveOnDefaultChannel (FALSE, // IsInChannel
|
|
http2ttRTS);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
AbortChannels(RpcStatus);
|
|
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
|
|
break;
|
|
|
|
case http2coptD4_A10:
|
|
case http2coptD5_B3:
|
|
// in channel can be in any opened state
|
|
if (ClientPacketType == http2coptD4_A10)
|
|
{
|
|
// out channel must be in http2svOpened_A10W
|
|
ASSERT(OutChannelState.State == http2svOpened_A10W);
|
|
|
|
if (OutChannelState.State != http2svOpened_A10W)
|
|
{
|
|
AbortChannels(RPC_S_PROTOCOL_ERROR);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
|
|
InChannelState.Mutex.Clear();
|
|
MutexReleased = TRUE;
|
|
|
|
RpcStatus = ParseD4_A10 (Buffer,
|
|
BufferLength
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RpcStatus);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// out channel must be in http2svOpened_B3W
|
|
CORRUPTION_ASSERT(OutChannelState.State == http2svOpened_B3W);
|
|
|
|
if (OutChannelState.State != http2svOpened_B3W)
|
|
{
|
|
AbortChannels(RPC_S_PROTOCOL_ERROR);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
|
|
InChannelState.Mutex.Clear();
|
|
MutexReleased = TRUE;
|
|
|
|
RpcStatus = ParseAndFreeD5_B3 (Buffer,
|
|
BufferLength
|
|
);
|
|
|
|
BufferFreed = TRUE;
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RpcStatus);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
}
|
|
|
|
ChannelPtr = GetChannelPointerFromId(ChannelId);
|
|
OutChannel = (HTTP2ClientOutChannel *)ChannelPtr->LockChannelPointer();
|
|
if (OutChannel == NULL)
|
|
{
|
|
AbortChannels(RPC_S_PROTOCOL_ERROR);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
|
|
NewChannelPtr = &OutChannels[GetNonDefaultOutChannelSelector()];
|
|
OutChannel2 = (HTTP2ClientOutChannel *)NewChannelPtr->LockChannelPointer ();
|
|
if (OutChannel2 == NULL)
|
|
{
|
|
ChannelPtr->UnlockChannelPointer();
|
|
AbortChannels(RPC_P_CONNECTION_SHUTDOWN);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
|
|
OutChannel2->BlockDataReceives();
|
|
|
|
// we're done with this channel. We want to switch
|
|
// channels and destroy
|
|
// and detach the channel.
|
|
// In the D5 case we can't switch earlier, because we
|
|
// have a race condition where folks may receive data
|
|
// on the new channel before they have drained the old,
|
|
// which may result in out-of-order delivery. Since
|
|
// data receives are blocked here, switching and draining
|
|
// is atomical in respect to the new channel
|
|
SwitchDefaultOutChannelSelector();
|
|
|
|
// make sure everybody who was submitting is out
|
|
OutChannel->DrainPendingSubmissions();
|
|
|
|
// 1 is for the lock that we have
|
|
ChannelPtr->DrainPendingLocks(1);
|
|
|
|
RpcStatus = OutChannel->TransferReceiveStateToNewChannel(OutChannel2);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
OutChannel2->UnblockDataReceives();
|
|
NewChannelPtr->UnlockChannelPointer();
|
|
|
|
ChannelPtr->UnlockChannelPointer();
|
|
AbortChannels(RPC_P_CONNECTION_SHUTDOWN);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
|
|
// if there is recv pending, but it is not an sync recv,
|
|
// make a note of that - we will resubmit it later. If it is
|
|
// sync, we will use the ReissueRecv mechanism to transfer
|
|
// the recv to the new channel
|
|
DataReceivePosted = FALSE;
|
|
if (OutChannel->IsDataReceivePosted())
|
|
{
|
|
if (OutChannel->IsSyncRecvPending())
|
|
{
|
|
// before we destroy, tell any pending recv to re-issue
|
|
// itself upon failure.
|
|
ReissueRecv = TRUE;
|
|
}
|
|
else
|
|
{
|
|
DataReceivePosted = TRUE;
|
|
}
|
|
}
|
|
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
|
|
if (ClientPacketType == http2coptD4_A10)
|
|
{
|
|
// after having transfered the receive settings,
|
|
// we can send D4/A11 to open the pipeline
|
|
RpcStatus = ForwardTrafficToDefaultChannel(FALSE, // IsInChannel
|
|
Buffer,
|
|
BufferLength
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
NewChannelPtr->UnlockChannelPointer();
|
|
OutChannel2->UnblockDataReceives();
|
|
AbortChannels(RPC_S_PROTOCOL_ERROR);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
|
|
// we no longer own the buffer
|
|
BufferFreed = TRUE;
|
|
}
|
|
|
|
// we couldn't unblock receives earlier because new receives
|
|
// must be synchronized w.r.t. D4/A11 - we can't post
|
|
// real receives before D4/A11 because it will switch WinHttp
|
|
// into receiveing mode. We also can't send D4/A11 before we
|
|
// have transferred the settings
|
|
OutChannel2->UnblockDataReceives();
|
|
NewChannelPtr->UnlockChannelPointer();
|
|
|
|
RpcStatus = PostReceiveOnDefaultChannel (FALSE, // IsInChannel
|
|
http2ttRTS);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RPC_S_PROTOCOL_ERROR);
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
}
|
|
|
|
// detach, abort and free lifetime reference
|
|
ChannelPtr->FreeChannelPointer(TRUE, // DrainUpCalls
|
|
TRUE, // CalledFromUpcallContext
|
|
TRUE, // Abort
|
|
RPC_P_CONNECTION_SHUTDOWN
|
|
);
|
|
|
|
InChannelState.Mutex.Request();
|
|
// we haven't posted a receive yet - there is no
|
|
// way the state of the channel will change
|
|
if (ClientPacketType == http2coptD4_A10)
|
|
ExpectedState = http2svOpened_A10W;
|
|
else
|
|
ExpectedState = http2svOpened_B3W;
|
|
|
|
ASSERT(OutChannelState.State == ExpectedState);
|
|
// move channel state to opened
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, OUT_CHANNEL_STATE, http2svOpened, 1, 0);
|
|
OutChannelState.State = http2svOpened;
|
|
InChannelState.Mutex.Clear();
|
|
|
|
if (DataReceivePosted)
|
|
{
|
|
RpcStatus = PostReceiveOnDefaultChannel (
|
|
FALSE, // IsInChannel
|
|
http2ttData
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RPC_S_PROTOCOL_ERROR);
|
|
}
|
|
}
|
|
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
|
|
default:
|
|
ASSERT(0);
|
|
RpcStatus = RPC_S_INTERNAL_ERROR;
|
|
}
|
|
} // switch (InChannelState.State)
|
|
if (MutexReleased == FALSE)
|
|
{
|
|
InChannelState.Mutex.Clear();
|
|
}
|
|
} // if (IsRTSPacket(Buffer))
|
|
else
|
|
{
|
|
RpcStatus = RPC_S_OK;
|
|
// data packet - ownership of the buffer passes to the runtime
|
|
BufferFreed = TRUE;
|
|
}
|
|
} // else of clause if (EventStatus != RPC_S_OK)
|
|
} // else of clause if (IsInChannel(ChannelId))
|
|
|
|
CleanupAndExit:
|
|
if (((RpcStatus != RPC_S_OK) && (RpcStatus != RPC_P_PACKET_CONSUMED))
|
|
|| WakeOpenThread)
|
|
{
|
|
if ((InChannelState.State == http2svA3W)
|
|
|| (InChannelState.State == http2svC2W)
|
|
|| (InChannelState.State == http2svSearchProxy)
|
|
|| WakeOpenThread
|
|
)
|
|
{
|
|
if (ClientOpenInEvent && (InOpenStatus != ERROR_IO_PENDING))
|
|
SetClientOpenInEvent();
|
|
else if (ClientOpenOutEvent && (OutOpenStatus != ERROR_IO_PENDING))
|
|
SetClientOpenOutEvent();
|
|
}
|
|
}
|
|
|
|
if (BufferFreed == FALSE)
|
|
RpcFreeBuffer(Buffer);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2ClientVirtualConnection::SyncRecv (
|
|
IN BYTE **Buffer,
|
|
IN ULONG *BufferLength,
|
|
IN ULONG Timeout
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Do a sync receive on an HTTP connection.
|
|
|
|
Arguments:
|
|
|
|
Buffer - if successful, points to a buffer containing the next PDU.
|
|
BufferLength - if successful, contains the length of the message.
|
|
Timeout - the amount of time to wait for the receive. If -1, we wait
|
|
infinitely.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
HTTP2ChannelPointer *ChannelPtr;
|
|
HTTP2ClientChannel *Channel;
|
|
RPC_STATUS RpcStatus;
|
|
BOOL AbortNeeded;
|
|
BOOL IoPending;
|
|
|
|
while (TRUE)
|
|
{
|
|
Channel = LockDefaultOutChannel(&ChannelPtr);
|
|
if (Channel)
|
|
{
|
|
RpcStatus = Channel->SubmitSyncRecv(http2ttData);
|
|
|
|
// keep a reference for the operations below
|
|
Channel->AddReference();
|
|
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
IoPending = FALSE;
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
AbortNeeded = FALSE;
|
|
RpcStatus = Channel->WaitForSyncRecv(Buffer,
|
|
BufferLength,
|
|
Timeout,
|
|
ConnectionTimeout,
|
|
this,
|
|
&AbortNeeded,
|
|
&IoPending
|
|
);
|
|
}
|
|
else
|
|
{
|
|
AbortNeeded = TRUE;
|
|
}
|
|
|
|
if (AbortNeeded)
|
|
{
|
|
Channel->Abort(RpcStatus);
|
|
}
|
|
|
|
if (IoPending)
|
|
{
|
|
Channel->WaitInfiniteForSyncReceive();
|
|
Channel->RemoveEvent();
|
|
}
|
|
|
|
if (AbortNeeded)
|
|
{
|
|
if (ReissueRecv)
|
|
{
|
|
ReissueRecv = FALSE;
|
|
// we don't re-issue on time outs
|
|
if (RpcStatus != RPC_S_CALL_CANCELLED)
|
|
{
|
|
Channel->RemoveReference();
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// ASSERT(!"This test should not have failing receives\n");
|
|
}
|
|
}
|
|
|
|
Channel->RemoveReference();
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
ASSERT(*Buffer != NULL);
|
|
ASSERT(IsBadWritePtr(*Buffer, 4) == FALSE);
|
|
}
|
|
|
|
if ((RpcStatus == RPC_P_CONNECTION_SHUTDOWN)
|
|
|| (RpcStatus == RPC_P_SEND_FAILED)
|
|
|| (RpcStatus == RPC_S_SERVER_UNAVAILABLE)
|
|
|| (RpcStatus == RPC_P_CONNECTION_CLOSED) )
|
|
{
|
|
RpcStatus = RPC_P_RECEIVE_FAILED;
|
|
}
|
|
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
RpcStatus = RPC_P_RECEIVE_FAILED;
|
|
break;
|
|
}
|
|
}
|
|
|
|
VALIDATE(RpcStatus)
|
|
{
|
|
RPC_S_OK,
|
|
RPC_S_OUT_OF_MEMORY,
|
|
RPC_S_OUT_OF_RESOURCES,
|
|
RPC_P_RECEIVE_FAILED,
|
|
RPC_S_CALL_CANCELLED,
|
|
RPC_P_SEND_FAILED,
|
|
RPC_P_CONNECTION_SHUTDOWN,
|
|
RPC_P_TIMEOUT
|
|
}
|
|
CORRUPTION_VALIDATE
|
|
{
|
|
RPC_S_PROTOCOL_ERROR
|
|
} CORRUPTION_END_VALIDATE;
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
void HTTP2ClientVirtualConnection::Abort (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Aborts an HTTP connection and disconnects the channels.
|
|
Must only come from the runtime.
|
|
Note: Don't call any virtual methods in this function. It
|
|
may be called in an environment without fully initialized
|
|
vtable.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_ABORT, HTTP2LOG_OT_CLIENT_VC, 0);
|
|
|
|
// abort the channels themselves
|
|
AbortChannels(RPC_P_CONNECTION_CLOSED);
|
|
|
|
// we got to the destructive phase of the abort
|
|
// guard against double aborts
|
|
if (Aborted.Increment() > 1)
|
|
return;
|
|
|
|
HTTP2VirtualConnection::DisconnectChannels(FALSE, 0);
|
|
|
|
// call destructor without freeing memory
|
|
HTTP2ClientVirtualConnection::~HTTP2ClientVirtualConnection();
|
|
}
|
|
|
|
void HTTP2ClientVirtualConnection::Close (
|
|
IN BOOL DontFlush
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Closes a client HTTP connection.
|
|
Note: Don't call virtual functions in this method.
|
|
It may be called in an environment without fully
|
|
initialized vtable.
|
|
|
|
Arguments:
|
|
|
|
DontFlush - non-zero if all buffers need to be flushed
|
|
before closing the connection. Zero otherwise.
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
HTTP2ClientVirtualConnection::Abort();
|
|
}
|
|
|
|
RPC_STATUS HTTP2ClientVirtualConnection::TurnOnOffKeepAlives (
|
|
IN BOOL TurnOn,
|
|
IN BOOL bProtectIO,
|
|
IN BOOL IsFromUpcall,
|
|
IN KEEPALIVE_TIMEOUT_UNITS Units,
|
|
IN OUT KEEPALIVE_TIMEOUT KATime,
|
|
IN ULONG KAInterval OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Turns on keep alives for HTTP.
|
|
|
|
Arguments:
|
|
|
|
TurnOn - if non-zero, keep alives are turned on. If zero, keep alives
|
|
are turned off.
|
|
|
|
bProtectIO - non-zero if IO needs to be protected against async close
|
|
of the connection.
|
|
|
|
IsFromUpcall - non-zero if called from upcall context. Zero otherwise.
|
|
|
|
Units - in what units is KATime
|
|
|
|
KATime - how much to wait before turning on keep alives
|
|
|
|
KAInterval - the interval between keep alives
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* / Win32 errors on failure
|
|
|
|
Note:
|
|
|
|
If we were to use it on the server, we must protect
|
|
the connection against async aborts.
|
|
Called in upcall or runtime context only.
|
|
|
|
--*/
|
|
{
|
|
HTTP2ClientInChannel *InChannel;
|
|
int i;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
if (TurnOn)
|
|
CurrentKeepAlive = KAInterval;
|
|
else
|
|
CurrentKeepAlive = 0;
|
|
|
|
// convert the timeout from runtime scale to transport scale
|
|
if (Units == tuRuntime)
|
|
{
|
|
ASSERT(KATime.RuntimeUnits != RPC_C_BINDING_INFINITE_TIMEOUT);
|
|
KATime.Milliseconds = ConvertRuntimeTimeoutToWSTimeout(KATime.RuntimeUnits);
|
|
Units = tuMilliseconds;
|
|
}
|
|
|
|
// make the change on both channels
|
|
for (i = 0; i < 2; i ++)
|
|
{
|
|
InChannel = (HTTP2ClientInChannel *)InChannels[i].LockChannelPointer();
|
|
if (InChannel != NULL)
|
|
{
|
|
RpcStatus = InChannel->SetKeepAliveTimeout (
|
|
TurnOn,
|
|
bProtectIO,
|
|
Units,
|
|
KATime,
|
|
KATime.Milliseconds
|
|
);
|
|
|
|
InChannels[i].UnlockChannelPointer();
|
|
|
|
RpcStatus = StartChannelRecyclingIfNecessary(RpcStatus,
|
|
IsFromUpcall
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
}
|
|
}
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2ClientVirtualConnection::RecycleChannel (
|
|
IN BOOL IsFromUpcall
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
An in channel recycle is initiated. This may be called
|
|
in an upcall or runtime context.
|
|
|
|
Arguments:
|
|
|
|
IsFromUpcall - non-zero if it comes from upcall. Zero otherwise.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
HTTP2ClientInChannel *NewInChannel;
|
|
int NonDefaultInChannelSelector;
|
|
HTTP2ChannelPointer *NewInChannelPtr;
|
|
HTTP2SendContext *D2_A1Context;
|
|
BOOL UseWinHttp;
|
|
|
|
#if DBG
|
|
DbgPrint("RPCRT4: %d Recycling IN channel\n", GetCurrentProcessId());
|
|
#endif
|
|
|
|
UseWinHttp = ShouldUseWinHttp(HttpCredentials);
|
|
|
|
// we shouldn't get recycle unless we're in an opened state
|
|
ASSERT(InChannelState.State == http2svOpened);
|
|
|
|
// create a new in channel
|
|
RpcStatus = ClientOpenInternal (&ConnectionHint,
|
|
TRUE, // HintWasInitialize
|
|
ConnectionTimeout,
|
|
DefaultReplacementChannelCallTimeout,
|
|
TRUE, // ClientOpenInChannel,
|
|
FALSE, // ClientOpenOutChannel
|
|
TRUE, // IsReplacementChannel
|
|
IsFromUpcall
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
// ClientOpenInternal Aborts on failure. No need to abort
|
|
// here
|
|
return RpcStatus;
|
|
}
|
|
|
|
NonDefaultInChannelSelector = GetNonDefaultInChannelSelector();
|
|
|
|
NewInChannelPtr = &InChannels[NonDefaultInChannelSelector];
|
|
NewInChannel = (HTTP2ClientInChannel *)NewInChannelPtr->LockChannelPointer();
|
|
|
|
if (NewInChannel == NULL)
|
|
{
|
|
Abort();
|
|
return RPC_P_CONNECTION_SHUTDOWN;
|
|
}
|
|
|
|
InChannelState.Mutex.Request();
|
|
if (InChannelState.State != http2svOpened)
|
|
{
|
|
InChannelState.Mutex.Clear();
|
|
NewInChannelPtr->UnlockChannelPointer();
|
|
Abort();
|
|
return RPC_P_CONNECTION_SHUTDOWN;
|
|
}
|
|
|
|
// move to Opened_A4W in anticipation of the send we will make.
|
|
InChannelState.State = http2svOpened_A4W;
|
|
InChannelState.Mutex.Clear();
|
|
|
|
D2_A1Context = AllocateAndInitializeD2_A1(ProtocolVersion,
|
|
&EmbeddedConnectionCookie,
|
|
&InChannelCookies[DefaultInChannelSelector],
|
|
&InChannelCookies[NonDefaultInChannelSelector]
|
|
);
|
|
if (D2_A1Context == NULL)
|
|
{
|
|
NewInChannelPtr->UnlockChannelPointer();
|
|
Abort();
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
RpcStatus = NewInChannel->Send(D2_A1Context);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
NewInChannelPtr->UnlockChannelPointer();
|
|
FreeRTSPacket(D2_A1Context);
|
|
Abort();
|
|
return RpcStatus;
|
|
}
|
|
|
|
if (!UseWinHttp)
|
|
{
|
|
RpcStatus = NewInChannel->Receive(http2ttRaw);
|
|
}
|
|
NewInChannelPtr->UnlockChannelPointer();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
Abort();
|
|
}
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2ClientVirtualConnection::OpenReplacementOutChannel (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Opens a replacement out channel. Used during out channel
|
|
recycling.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
HTTP2ClientOutChannel *NewOutChannel;
|
|
int NonDefaultOutChannelSelector;
|
|
HTTP2ChannelPointer *NewOutChannelPtr;
|
|
HTTP2SendContext *D4_A3Context;
|
|
KEEPALIVE_TIMEOUT KATime;
|
|
|
|
// create a new out channel
|
|
RpcStatus = ClientOpenInternal (&ConnectionHint,
|
|
TRUE, // HintWasInitialize
|
|
ConnectionTimeout,
|
|
DefaultReplacementChannelCallTimeout,
|
|
FALSE, // ClientOpenInChannel,
|
|
TRUE, // ClientOpenOutChannel
|
|
TRUE, // IsReplacementChannel
|
|
TRUE // IsFromUpcall
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
// ClientOpenInternal has already aborted the connection
|
|
return RpcStatus;
|
|
}
|
|
|
|
NonDefaultOutChannelSelector = GetNonDefaultOutChannelSelector();
|
|
|
|
NewOutChannelPtr = &OutChannels[NonDefaultOutChannelSelector];
|
|
NewOutChannel = (HTTP2ClientOutChannel *)NewOutChannelPtr->LockChannelPointer();
|
|
|
|
if (NewOutChannel == NULL)
|
|
{
|
|
AbortChannels(RPC_P_CONNECTION_SHUTDOWN);
|
|
return RPC_P_CONNECTION_SHUTDOWN;
|
|
}
|
|
|
|
InChannelState.Mutex.Request();
|
|
if (OutChannelState.State != http2svOpened)
|
|
{
|
|
InChannelState.Mutex.Clear();
|
|
NewOutChannelPtr->UnlockChannelPointer();
|
|
AbortChannels(RPC_P_CONNECTION_SHUTDOWN);
|
|
return RPC_P_CONNECTION_SHUTDOWN;
|
|
}
|
|
|
|
// move to Opened_A6W in anticipation of the send we will make.
|
|
OutChannelState.State = http2svOpened_A6W;
|
|
InChannelState.Mutex.Clear();
|
|
|
|
D4_A3Context = AllocateAndInitializeD4_A3(ProtocolVersion,
|
|
&EmbeddedConnectionCookie,
|
|
&OutChannelCookies[DefaultOutChannelSelector],
|
|
&OutChannelCookies[NonDefaultOutChannelSelector],
|
|
HTTP2ClientReceiveWindow
|
|
);
|
|
|
|
if (D4_A3Context == NULL)
|
|
{
|
|
NewOutChannelPtr->UnlockChannelPointer();
|
|
AbortChannels(RPC_S_OUT_OF_MEMORY);
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
RpcStatus = NewOutChannel->Send(D4_A3Context);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
NewOutChannelPtr->UnlockChannelPointer();
|
|
FreeRTSPacket(D4_A3Context);
|
|
AbortChannels(RpcStatus);
|
|
return RpcStatus;
|
|
}
|
|
|
|
if (CurrentKeepAlive)
|
|
{
|
|
KATime.Milliseconds = 0;
|
|
RpcStatus = NewOutChannel->SetKeepAliveTimeout (
|
|
TRUE, // TurnOn
|
|
FALSE, // bProtectIO
|
|
tuMilliseconds,
|
|
KATime,
|
|
CurrentKeepAlive
|
|
);
|
|
|
|
ASSERT(RpcStatus != RPC_P_CHANNEL_NEEDS_RECYCLING);
|
|
}
|
|
|
|
NewOutChannelPtr->UnlockChannelPointer();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RpcStatus);
|
|
}
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
void HTTP2ClientVirtualConnection::AbortChannels (
|
|
IN RPC_STATUS RpcStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Aborts an HTTP connection but does not disconnect
|
|
the channels. Can be called from above, upcall, or
|
|
neutral context, but not from submit context!
|
|
|
|
Arguments:
|
|
|
|
RpcStatus - the error to abort the channels with
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
// wait for the critical paths to unblock abort. The
|
|
// only critical path that will do that is Open, and
|
|
// it is extremely unlikely that this code will execute
|
|
// together with open (though it is possible, and this
|
|
// is why we need the additional synchronization).
|
|
ChannelsAborted = TRUE;
|
|
|
|
WaitForAbortsToUnblock();
|
|
|
|
HTTP2VirtualConnection::AbortChannels (RpcStatus);
|
|
}
|
|
|
|
HTTP2Channel *HTTP2ClientVirtualConnection::LockDefaultSendChannel (
|
|
OUT HTTP2ChannelPointer **ChannelPtr
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Locks the send channel. For client connections this is the in channel.
|
|
|
|
Arguments:
|
|
|
|
ChannelPtr - on success, the channel pointer to use.
|
|
|
|
Return Value:
|
|
|
|
The locked channel or NULL (same semantics as LockDefaultOutChannel)
|
|
|
|
--*/
|
|
{
|
|
return LockDefaultInChannel(ChannelPtr);
|
|
}
|
|
|
|
HTTP2Channel *HTTP2ClientVirtualConnection::LockDefaultReceiveChannel (
|
|
OUT HTTP2ChannelPointer **ChannelPtr
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Locks the receive channel. For client connections this is the out channel.
|
|
|
|
Arguments:
|
|
|
|
ChannelPtr - on success, the channel pointer to use.
|
|
|
|
Return Value:
|
|
|
|
The locked channel or NULL (same semantics as LockDefaultInChannel)
|
|
|
|
--*/
|
|
{
|
|
return LockDefaultOutChannel(ChannelPtr);
|
|
}
|
|
|
|
const int MaxOutChannelHeader = 300;
|
|
|
|
RPC_STATUS
|
|
RPC_ENTRY
|
|
HTTP2ClientReadChannelHeader (
|
|
IN WS_HTTP2_CONNECTION *Connection,
|
|
IN ULONG BytesRead,
|
|
OUT ULONG *NewBytesRead
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Read a channel HTTP header (usually some string). In success
|
|
case, there is real data in Connection->pReadBuffer. The
|
|
number of bytes there is in NewBytesRead
|
|
|
|
Arguments:
|
|
|
|
Connection - the connection on which the header arrived.
|
|
|
|
BytesRead - the bytes received from the net
|
|
|
|
NewBytesRead - the bytes read from the channel (success only)
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
DWORD message_size;
|
|
RPC_STATUS RpcStatus;
|
|
char *CurrentPosition;
|
|
char *LastPosition; // first position after end
|
|
char *LastPosition2; // first position after end + 4
|
|
// useful for end-of-loop comparison
|
|
char *StartPosition;
|
|
char *HeaderEnd; // first character after header end
|
|
ULONG HTTPResponse;
|
|
BYTE *NewBuffer;
|
|
|
|
BytesRead += Connection->iLastRead;
|
|
|
|
// we have read something. Let's process it now.
|
|
// search for double CR-LF (\r\n\r\n)
|
|
StartPosition = (char *)(Connection->pReadBuffer);
|
|
LastPosition = StartPosition + BytesRead;
|
|
LastPosition2 = LastPosition + 4;
|
|
HeaderEnd = NULL;
|
|
CurrentPosition = (char *)(Connection->pReadBuffer);
|
|
|
|
while (CurrentPosition < LastPosition2)
|
|
{
|
|
if ((*CurrentPosition == '\r')
|
|
&& (*(CurrentPosition + 1) == '\n')
|
|
&& (*(CurrentPosition + 2) == '\r')
|
|
&& (*(CurrentPosition + 3) == '\n')
|
|
)
|
|
{
|
|
// we have a full header
|
|
HeaderEnd = CurrentPosition + 4;
|
|
break;
|
|
}
|
|
|
|
CurrentPosition ++;
|
|
}
|
|
|
|
if (CurrentPosition - StartPosition >= MaxOutChannelHeader)
|
|
{
|
|
// we should have seen the header by now. Abort. Returning
|
|
// failure is enough - we know the caller will abort
|
|
return RPC_S_PROTOCOL_ERROR;
|
|
}
|
|
|
|
if (HeaderEnd == NULL)
|
|
{
|
|
// we didn't find the end of the header. Submit another receive
|
|
// for the rest
|
|
RpcStatus = TransConnectionReallocPacket(Connection,
|
|
&Connection->pReadBuffer,
|
|
BytesRead,
|
|
MaxOutChannelHeader);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
ASSERT(RpcStatus == RPC_S_OUT_OF_MEMORY);
|
|
return(RpcStatus);
|
|
}
|
|
|
|
Connection->iLastRead = BytesRead;
|
|
Connection->maxReadBuffer = MaxOutChannelHeader;
|
|
return RPC_P_PARTIAL_RECEIVE;
|
|
}
|
|
|
|
// we have found the header end. Grab the status code
|
|
HTTPResponse = HttpParseResponse(StartPosition);
|
|
if ((HTTPResponse >= RPC_S_INVALID_STRING_BINDING) && (HTTPResponse <= RPC_X_BAD_STUB_DATA))
|
|
{
|
|
// if it is an RPC error code, just return it.
|
|
return HTTPResponse;
|
|
}
|
|
|
|
if (HTTPResponse != 200)
|
|
return RPC_S_PROTOCOL_ERROR;
|
|
|
|
// check whether we have something else besides the HTTP header
|
|
if (HeaderEnd < LastPosition)
|
|
{
|
|
NewBuffer = TransConnectionAllocatePacket(Connection,
|
|
LastPosition - HeaderEnd);
|
|
|
|
if (0 == NewBuffer)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
|
|
RpcpMemoryCopy(NewBuffer, HeaderEnd, LastPosition - HeaderEnd);
|
|
*NewBytesRead = LastPosition - HeaderEnd;
|
|
|
|
RpcFreeBuffer(Connection->pReadBuffer);
|
|
Connection->pReadBuffer = NewBuffer;
|
|
Connection->maxReadBuffer = LastPosition - HeaderEnd;
|
|
Connection->iLastRead = 0;
|
|
Connection->HeaderRead = TRUE;
|
|
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
// reset the pointer. By doing so we forget all we have
|
|
// read so far (which is only the HTTP header anyway)
|
|
Connection->iLastRead = 0;
|
|
Connection->HeaderRead = TRUE;
|
|
return RPC_P_PARTIAL_RECEIVE;
|
|
}
|
|
|
|
// this is a bit mask. For any particular scheme, AND it with the constant
|
|
// Non-zero means it is multillegged. Schemes are:
|
|
// Scheme Value Multilegged
|
|
// RPC_C_HTTP_AUTHN_SCHEME_BASIC 0x00000001 0
|
|
// RPC_C_HTTP_AUTHN_SCHEME_NTLM 0x00000002 1
|
|
// RPC_C_HTTP_AUTHN_SCHEME_PASSPORT 0x00000004 1
|
|
// RPC_C_HTTP_AUTHN_SCHEME_DIGEST 0x00000008 1
|
|
// RPC_C_HTTP_AUTHN_SCHEME_NEGOTIATE 0x00000010 1
|
|
|
|
const ULONG MultiLeggedSchemeMap =
|
|
RPC_C_HTTP_AUTHN_SCHEME_NTLM
|
|
| RPC_C_HTTP_AUTHN_SCHEME_PASSPORT
|
|
| RPC_C_HTTP_AUTHN_SCHEME_DIGEST
|
|
| RPC_C_HTTP_AUTHN_SCHEME_NEGOTIATE;
|
|
|
|
/*
|
|
All client opened types are valid initial states. The transitions are:
|
|
|
|
cotSearchProxy ------+----> cotUknownAuth
|
|
|
|
|
+----------------+---------------+
|
|
| | |
|
|
cotMLAuth cotSLAuth cotNoAuth
|
|
|
|
|
cotMLAuth2
|
|
|
|
*/
|
|
|
|
typedef enum tagClientOpenTypes
|
|
{
|
|
cotSearchProxy,
|
|
cotNoAuth,
|
|
cotMLAuth,
|
|
cotMLAuth2,
|
|
cotSLAuth,
|
|
cotUnknownAuth,
|
|
cotInvalid
|
|
} ClientOpenTypes;
|
|
|
|
const char *InHeaderVerb = "RPC_IN_DATA";
|
|
const int InHeaderVerbLength = 11; // length of RPC_IN_DATA
|
|
|
|
const char *OutHeaderVerb = "RPC_OUT_DATA";
|
|
const int OutHeaderVerbLength = 12; // length of RPC_OUT_DATA
|
|
|
|
const BYTE EchoData[4] = {0xF8, 0xE8, 0x18, 0x08};
|
|
const ULONG EchoDataLength = sizeof(EchoData);
|
|
|
|
RPC_STATUS HTTP2ClientVirtualConnection::ClientOpenInternal (
|
|
IN HTTPResolverHint *Hint,
|
|
IN BOOL HintWasInitialized,
|
|
IN UINT ConnTimeout,
|
|
IN ULONG CallTimeout,
|
|
IN BOOL ClientOpenInChannel,
|
|
IN BOOL ClientOpenOutChannel,
|
|
IN BOOL IsReplacementChannel,
|
|
IN BOOL IsFromUpcall
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Opens a client side virtual connection.
|
|
|
|
Arguments:
|
|
|
|
Hint - the resolver hint
|
|
|
|
HintWasInitialized - the hint was initialized on input.
|
|
|
|
ConnTimeout - connection timeout
|
|
|
|
CallTimeout - operation timeout
|
|
|
|
ClientOpenInChannel - non-zero if the in channel is to be opened.
|
|
|
|
ClientOpenOutChannel - non-zero if the out channel is to be
|
|
opened.
|
|
|
|
IsReplacementChannel - non-zero if this is channel recycling
|
|
|
|
IsFromUpcall - non-zero if this is called from an upcall. Zero otherwise.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
HTTP2ClientInChannel *NewInChannel;
|
|
HTTP2ClientOutChannel *NewOutChannel;
|
|
BOOL InChannelLocked = FALSE;
|
|
BOOL OutChannelLocked = FALSE;
|
|
HTTP2SendContext *OutChannelSendContext = NULL;
|
|
HTTP2SendContext *InChannelSendContext = NULL;
|
|
ULONG WaitResult;
|
|
HANDLE LocalClientOpenEvent;
|
|
BOOL UseWinHttp;
|
|
KEEPALIVE_TIMEOUT KATimeout;
|
|
BOOL RebuildInChannel;
|
|
BOOL RebuildOutChannel;
|
|
BOOL NukeInChannel;
|
|
BOOL NukeOutChannel;
|
|
BOOL ResetInChannel;
|
|
BOOL ResetOutChannel;
|
|
BOOL SendInChannel;
|
|
BOOL SendOutChannel;
|
|
BOOL OpenInChannel;
|
|
BOOL OpenOutChannel;
|
|
BOOL ReceiveInChannel;
|
|
BOOL ReceiveOutChannel;
|
|
RPCProxyAccessType StoredAccessType;
|
|
const char *VerbToUse;
|
|
int VerbLengthToUse;
|
|
const BYTE *AdditionalDataToUse;
|
|
ULONG AdditionalDataLengthToUse;
|
|
HTTP2StateValues NewConnectionState = http2svInvalid;
|
|
BOOL SetNewConnectionState;
|
|
RPC_STATUS LocalInOpenStatus;
|
|
RPC_STATUS LocalOutOpenStatus;
|
|
ULONG InChosenAuthScheme;
|
|
ULONG OutChosenAuthScheme;
|
|
BOOL IsKeepAlive;
|
|
BOOL IsDone;
|
|
int NonDefaultInChannelSelector;
|
|
int NonDefaultOutChannelSelector;
|
|
HTTP2ChannelPointer *InChannelPtr;
|
|
HTTP2ChannelPointer *OutChannelPtr;
|
|
ClientOpenTypes InOpenType;
|
|
ClientOpenTypes OutOpenType;
|
|
ClientOpenTypes OldOpenType;
|
|
ClientOpenTypes OldInOpenType;
|
|
ClientOpenTypes OldOutOpenType;
|
|
int CurrentCase;
|
|
#if DBG
|
|
int NumberOfRetries = 0;
|
|
#endif
|
|
BOOL AbortsBlocked = FALSE; // if non-zero, the aborts have been blocked and need
|
|
// unblocking before we exit
|
|
|
|
if (IsReplacementChannel == FALSE)
|
|
{
|
|
if (ConnTimeout != RPC_C_BINDING_INFINITE_TIMEOUT)
|
|
{
|
|
ASSERT( ((long)ConnTimeout >= RPC_C_BINDING_MIN_TIMEOUT)
|
|
&& (ConnTimeout <= RPC_C_BINDING_MAX_TIMEOUT));
|
|
|
|
// convert the timeout from runtime scale to transport scale
|
|
ConnectionTimeout = ConvertRuntimeTimeoutToWSTimeout(ConnTimeout);
|
|
}
|
|
else
|
|
{
|
|
ConnectionTimeout = INFINITE;
|
|
}
|
|
}
|
|
|
|
UseWinHttp = ShouldUseWinHttp(HttpCredentials);
|
|
|
|
if (UseWinHttp)
|
|
{
|
|
RpcStatus = InitWinHttpIfNecessary();
|
|
if (RpcStatus != RPC_S_OK)
|
|
return RpcStatus;
|
|
}
|
|
|
|
InOpenType = OutOpenType = cotInvalid;
|
|
|
|
InChosenAuthScheme = 0;
|
|
OutChosenAuthScheme = 0;
|
|
|
|
IsDone = FALSE;
|
|
|
|
if (HttpCredentials)
|
|
{
|
|
// WinHttp5.x does not support pre-auth for digest. This means that even if
|
|
// you know that digest is your scheme, you have to pretend that you don't
|
|
// and wait for the challenge before you choose it. Otherwise WinHttp5.x will
|
|
// complain and fail.
|
|
if ((HttpCredentials->Flags & RPC_C_HTTP_FLAG_USE_FIRST_AUTH_SCHEME)
|
|
&& (*(HttpCredentials->AuthnSchemes) != RPC_C_HTTP_AUTHN_SCHEME_DIGEST))
|
|
{
|
|
OutChosenAuthScheme = InChosenAuthScheme = *(HttpCredentials->AuthnSchemes);
|
|
if (ClientOpenInChannel)
|
|
{
|
|
if (InChosenAuthScheme & MultiLeggedSchemeMap)
|
|
InOpenType = cotMLAuth;
|
|
else
|
|
InOpenType = cotSLAuth;
|
|
}
|
|
|
|
if (ClientOpenOutChannel)
|
|
{
|
|
if (OutChosenAuthScheme & MultiLeggedSchemeMap)
|
|
OutOpenType = cotMLAuth;
|
|
else
|
|
OutOpenType = cotSLAuth;
|
|
}
|
|
}
|
|
}
|
|
else if (IsReplacementChannel == FALSE)
|
|
{
|
|
ASSERT(OutOpenType == cotInvalid);
|
|
|
|
InOpenType = OutOpenType = cotNoAuth;
|
|
}
|
|
else if (ClientOpenInChannel)
|
|
{
|
|
InOpenType = cotNoAuth;
|
|
IsDone = TRUE;
|
|
}
|
|
else
|
|
{
|
|
OutOpenType = cotNoAuth;
|
|
IsDone = TRUE;
|
|
}
|
|
|
|
LocalClientOpenEvent = CreateEvent(NULL, // lpEventAttributes
|
|
FALSE, // bManualReset
|
|
FALSE, // bInitialState
|
|
NULL // lpName
|
|
);
|
|
|
|
if (LocalClientOpenEvent == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
|
|
if (IsReplacementChannel == FALSE)
|
|
{
|
|
RpcStatus = EmbeddedConnectionCookie.Create();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
goto AbortAndExit;
|
|
}
|
|
else
|
|
{
|
|
if (ClientOpenInChannel)
|
|
NonDefaultInChannelSelector = GetNonDefaultInChannelSelector();
|
|
else
|
|
{
|
|
ASSERT(ClientOpenOutChannel);
|
|
NonDefaultOutChannelSelector = GetNonDefaultOutChannelSelector();
|
|
}
|
|
}
|
|
|
|
if (ClientOpenInChannel)
|
|
{
|
|
if (IsReplacementChannel)
|
|
RpcStatus = InChannelCookies[NonDefaultInChannelSelector].Create();
|
|
else
|
|
RpcStatus = InChannelCookies[0].Create();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
if (ClientOpenOutChannel)
|
|
{
|
|
if (IsReplacementChannel)
|
|
RpcStatus = OutChannelCookies[NonDefaultOutChannelSelector].Create();
|
|
else
|
|
RpcStatus = OutChannelCookies[0].Create();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
if (ClientOpenInChannel)
|
|
{
|
|
RebuildInChannel = TRUE;
|
|
NukeInChannel = FALSE;
|
|
ResetInChannel = FALSE;
|
|
OpenInChannel = TRUE;
|
|
InOpenStatus = ERROR_IO_PENDING;
|
|
}
|
|
else
|
|
{
|
|
RebuildInChannel = FALSE;
|
|
NukeInChannel = FALSE;
|
|
ResetInChannel = FALSE;
|
|
OpenInChannel = FALSE;
|
|
ReceiveInChannel = FALSE;
|
|
}
|
|
|
|
if (ClientOpenOutChannel)
|
|
{
|
|
RebuildOutChannel = TRUE;
|
|
NukeOutChannel = FALSE;
|
|
ResetOutChannel = FALSE;
|
|
OpenOutChannel = TRUE;
|
|
// receive is done below
|
|
OutOpenStatus = ERROR_IO_PENDING;
|
|
}
|
|
else
|
|
{
|
|
RebuildOutChannel = FALSE;
|
|
NukeOutChannel = FALSE;
|
|
ResetOutChannel = FALSE;
|
|
OpenOutChannel = FALSE;
|
|
ReceiveOutChannel = FALSE;
|
|
}
|
|
|
|
SetNewConnectionState = FALSE;
|
|
|
|
ClientOpenInEvent = ClientOpenOutEvent = LocalClientOpenEvent;
|
|
|
|
ASSERT(InChosenAuthScheme == OutChosenAuthScheme);
|
|
|
|
// do we know whether to use a proxy?
|
|
StoredAccessType = Hint->AccessType;
|
|
if (StoredAccessType == rpcpatUnknown)
|
|
{
|
|
// this should never happen for replacement channels
|
|
ASSERT(IsReplacementChannel == FALSE);
|
|
|
|
// we don't.
|
|
InOpenType = OutOpenType = cotSearchProxy;
|
|
|
|
// move to http2svSearchProxy
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, IN_CHANNEL_STATE, http2svSearchProxy, 1, 0);
|
|
InChannelState.State = http2svSearchProxy;
|
|
|
|
SendInChannel = FALSE;
|
|
SendOutChannel = FALSE;
|
|
|
|
ReceiveInChannel = TRUE;
|
|
ReceiveOutChannel = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// we know. Do we know what authentication to use?
|
|
if (InChosenAuthScheme)
|
|
{
|
|
ASSERT(InChosenAuthScheme == OutChosenAuthScheme);
|
|
if (InChosenAuthScheme & MultiLeggedSchemeMap)
|
|
{
|
|
if (ClientOpenInChannel)
|
|
InOpenType = cotMLAuth;
|
|
else
|
|
InOpenType = cotInvalid;
|
|
|
|
if (ClientOpenOutChannel)
|
|
{
|
|
OutOpenType = cotMLAuth;
|
|
ReceiveOutChannel = TRUE;
|
|
}
|
|
else
|
|
{
|
|
OutOpenType = cotInvalid;
|
|
ReceiveOutChannel = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ClientOpenInChannel)
|
|
{
|
|
InOpenType = cotSLAuth;
|
|
if (IsReplacementChannel)
|
|
IsDone = TRUE;
|
|
}
|
|
else
|
|
InOpenType = cotInvalid;
|
|
|
|
if (ClientOpenOutChannel)
|
|
{
|
|
OutOpenType = cotSLAuth;
|
|
if (IsReplacementChannel)
|
|
{
|
|
ReceiveOutChannel = FALSE;
|
|
IsDone = TRUE;
|
|
}
|
|
else
|
|
ReceiveOutChannel = TRUE;
|
|
}
|
|
else
|
|
{
|
|
OutOpenType = cotInvalid;
|
|
ReceiveOutChannel = FALSE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ClientOpenInChannel)
|
|
{
|
|
if (InOpenType != cotNoAuth)
|
|
InOpenType = cotUnknownAuth;
|
|
}
|
|
else
|
|
InOpenType = cotInvalid;
|
|
|
|
if (ClientOpenOutChannel)
|
|
{
|
|
if (OutOpenType != cotNoAuth)
|
|
{
|
|
OutOpenType = cotUnknownAuth;
|
|
ReceiveOutChannel = TRUE;
|
|
}
|
|
else if (IsReplacementChannel)
|
|
ReceiveOutChannel = FALSE;
|
|
else
|
|
ReceiveOutChannel = TRUE;
|
|
}
|
|
else
|
|
{
|
|
OutOpenType = cotInvalid;
|
|
ReceiveOutChannel = FALSE;
|
|
}
|
|
}
|
|
|
|
if (IsReplacementChannel == FALSE)
|
|
{
|
|
// move to http2svA3W
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, IN_CHANNEL_STATE, http2svA3W, 1, 0);
|
|
InChannelState.State = http2svA3W;
|
|
}
|
|
|
|
if (
|
|
(
|
|
(InOpenType == cotSLAuth)
|
|
||
|
|
(InOpenType == cotNoAuth)
|
|
)
|
|
&&
|
|
(IsReplacementChannel == FALSE)
|
|
)
|
|
{
|
|
SendInChannel = TRUE;
|
|
}
|
|
else
|
|
SendInChannel = FALSE;
|
|
|
|
if (
|
|
(
|
|
(OutOpenType == cotSLAuth)
|
|
||
|
|
(OutOpenType == cotNoAuth)
|
|
)
|
|
&&
|
|
(IsReplacementChannel == FALSE)
|
|
)
|
|
{
|
|
SendOutChannel = TRUE;
|
|
}
|
|
else
|
|
SendOutChannel = FALSE;
|
|
|
|
// 3 cases for the receive on the in channel
|
|
// 1. If we don't use WinHttp and this is the first open or is in channel replacement,
|
|
// we post a receive on this channel
|
|
// 2. If this is a single legged operation, or this is a replacement channel we don't
|
|
// post a receive.
|
|
// 3. All other cases (we use WinHttp and this is MLAuth/UnknownAuth) we post a receive
|
|
if (ClientOpenInChannel)
|
|
{
|
|
if (!UseWinHttp)
|
|
ReceiveInChannel = TRUE;
|
|
else if ((InOpenType == cotNoAuth) || (InOpenType == cotSLAuth))
|
|
ReceiveInChannel = FALSE;
|
|
else
|
|
ReceiveInChannel = TRUE;
|
|
}
|
|
else
|
|
ReceiveInChannel = FALSE;
|
|
}
|
|
|
|
while (TRUE)
|
|
{
|
|
#if DBG_ERROR
|
|
DbgPrint("Starting loop iteration ....\n");
|
|
NumberOfRetries ++;
|
|
ASSERT (NumberOfRetries < 10);
|
|
#endif
|
|
if (ClientOpenInChannel == FALSE)
|
|
{
|
|
// if we were told not to touch the in channel, make
|
|
// sure we don't
|
|
ASSERT(RebuildInChannel == FALSE
|
|
&& NukeInChannel == FALSE
|
|
&& ResetInChannel == FALSE
|
|
&& SendInChannel == FALSE
|
|
&& OpenInChannel == FALSE
|
|
&& ReceiveInChannel == FALSE);
|
|
}
|
|
|
|
if (ClientOpenOutChannel == FALSE)
|
|
{
|
|
// if we were told not to touch the out channel, make
|
|
// sure we don't
|
|
ASSERT(RebuildOutChannel == FALSE
|
|
&& NukeOutChannel == FALSE
|
|
&& ResetOutChannel == FALSE
|
|
&& SendOutChannel == FALSE
|
|
&& OpenOutChannel == FALSE
|
|
&& ReceiveOutChannel == FALSE);
|
|
}
|
|
|
|
if (NukeInChannel)
|
|
{
|
|
if (IsReplacementChannel)
|
|
InChannelPtr = &InChannels[NonDefaultInChannelSelector];
|
|
else
|
|
InChannelPtr = &InChannels[0];
|
|
|
|
InChannelPtr->FreeChannelPointer(TRUE, // DrainUpCalls
|
|
IsReplacementChannel, // CalledFromUpcallContext
|
|
TRUE, // Abort
|
|
RPC_P_CONNECTION_SHUTDOWN // AbortStatus
|
|
);
|
|
|
|
InOpenStatus = ERROR_IO_PENDING;
|
|
}
|
|
|
|
if (NukeOutChannel)
|
|
{
|
|
if (IsReplacementChannel)
|
|
OutChannelPtr = &OutChannels[NonDefaultOutChannelSelector];
|
|
else
|
|
OutChannelPtr = &OutChannels[0];
|
|
|
|
OutChannelPtr->FreeChannelPointer(TRUE, // DrainUpCalls
|
|
IsReplacementChannel, // CalledFromUpcallContext
|
|
TRUE, // Abort
|
|
RPC_P_CONNECTION_SHUTDOWN // AbortStatus
|
|
);
|
|
|
|
OutOpenStatus = ERROR_IO_PENDING;
|
|
}
|
|
|
|
// after both channels are nuked, see whether we need to change the
|
|
// connection state. We have to do this after nuking the channels
|
|
// to avoid a race in ReceiveComplete where late receives may
|
|
// see a different state
|
|
if (SetNewConnectionState)
|
|
{
|
|
ASSERT(NewConnectionState != http2svInvalid);
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, IN_CHANNEL_STATE, NewConnectionState, 1, 0);
|
|
InChannelState.State = NewConnectionState;
|
|
SetNewConnectionState = FALSE;
|
|
}
|
|
|
|
// we're entering the critical path of opening - the creating of the channel
|
|
// and the opening itself. Block aborts until we fully open both channels
|
|
// (or fail to do so)
|
|
RpcStatus = BlockAborts();
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
goto AbortAndExit;
|
|
}
|
|
AbortsBlocked = TRUE;
|
|
|
|
if (RebuildInChannel)
|
|
{
|
|
if (StoredAccessType == rpcpatUnknown)
|
|
{
|
|
ASSERT((InOpenType == cotSearchProxy)
|
|
|| (InOpenType == cotMLAuth)
|
|
|| (InOpenType == cotSLAuth)
|
|
);
|
|
// if we don't know the type yet, try
|
|
// direct for the in, proxy for the out.
|
|
// One of them will work.
|
|
Hint->AccessType = rpcpatDirect;
|
|
}
|
|
|
|
// initialize in channel
|
|
RpcStatus = AllocateAndInitializeInChannel(Hint,
|
|
HintWasInitialized,
|
|
CallTimeout,
|
|
UseWinHttp,
|
|
&NewInChannel
|
|
);
|
|
|
|
// restore the access type
|
|
if (StoredAccessType == rpcpatUnknown)
|
|
{
|
|
Hint->AccessType = StoredAccessType;
|
|
}
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
if (IsReplacementChannel)
|
|
SetNonDefaultInChannel(NewInChannel);
|
|
else
|
|
SetFirstInChannel(NewInChannel);
|
|
}
|
|
|
|
if (RebuildOutChannel)
|
|
{
|
|
if (StoredAccessType == rpcpatUnknown)
|
|
{
|
|
ASSERT((OutOpenType == cotSearchProxy)
|
|
|| (OutOpenType == cotMLAuth)
|
|
|| (OutOpenType == cotSLAuth)
|
|
);
|
|
// if we don't know the type yet, try
|
|
// direct for the in, proxy for the out.
|
|
// One of them will work.
|
|
Hint->AccessType = rpcpatHTTPProxy;
|
|
}
|
|
|
|
// initialize out channel
|
|
RpcStatus = AllocateAndInitializeOutChannel(Hint,
|
|
TRUE, // HintWasInitialized
|
|
CallTimeout,
|
|
UseWinHttp,
|
|
&NewOutChannel
|
|
);
|
|
|
|
// restore the access type
|
|
if (StoredAccessType == rpcpatUnknown)
|
|
{
|
|
Hint->AccessType = StoredAccessType;
|
|
}
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
if (IsReplacementChannel)
|
|
SetNonDefaultOutChannel(NewOutChannel);
|
|
else
|
|
SetFirstOutChannel(NewOutChannel);
|
|
}
|
|
|
|
// at least one channel must wait for something to happen
|
|
if (IsReplacementChannel == FALSE)
|
|
{
|
|
ASSERT((InOpenStatus == ERROR_IO_PENDING)
|
|
|| (OutOpenStatus == ERROR_IO_PENDING));
|
|
}
|
|
else if (ClientOpenInChannel)
|
|
{
|
|
ASSERT(InOpenStatus == ERROR_IO_PENDING);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(ClientOpenOutChannel);
|
|
ASSERT(OutOpenStatus == ERROR_IO_PENDING);
|
|
}
|
|
|
|
if (ResetInChannel || OpenInChannel || SendInChannel)
|
|
{
|
|
// Lock channel
|
|
|
|
// after calling ClientOpen, we may be aborted asynchronously at any moment.
|
|
// we will have pending async operations soon. Do the channel access by the
|
|
// book.
|
|
if (IsReplacementChannel)
|
|
InChannelPtr = &InChannels[NonDefaultInChannelSelector];
|
|
else
|
|
InChannelPtr = &InChannels[0];
|
|
|
|
NewInChannel = (HTTP2ClientInChannel *)InChannelPtr->LockChannelPointer();
|
|
|
|
if (NewInChannel == NULL)
|
|
{
|
|
RpcStatus = RPC_P_CONNECTION_SHUTDOWN;
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
InChannelLocked = TRUE;
|
|
}
|
|
|
|
// Nuke and rebuild are mutually exclusive with Reset
|
|
if (ResetInChannel)
|
|
{
|
|
ASSERT(NukeInChannel == FALSE);
|
|
ASSERT(RebuildInChannel == FALSE);
|
|
|
|
NewInChannel->Reset();
|
|
}
|
|
|
|
if (OpenInChannel)
|
|
{
|
|
// do we do in_data or echo's?
|
|
if ((InOpenType == cotSearchProxy)
|
|
|| (InOpenType == cotMLAuth)
|
|
|| (InOpenType == cotUnknownAuth)
|
|
)
|
|
{
|
|
AdditionalDataToUse = EchoData;
|
|
AdditionalDataLengthToUse = EchoDataLength;
|
|
if (StoredAccessType == rpcpatUnknown)
|
|
{
|
|
// if we don't know the type yet, try
|
|
// direct for the in, proxy for the out.
|
|
// One of them will work.
|
|
Hint->AccessType = rpcpatDirect;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ASSERT((InOpenType == cotSLAuth)
|
|
|| (InOpenType == cotNoAuth)
|
|
|| (InOpenType == cotMLAuth2)
|
|
);
|
|
ASSERT(StoredAccessType != rpcpatUnknown);
|
|
if (IsReplacementChannel == FALSE)
|
|
{
|
|
ASSERT(SendInChannel);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(SendInChannel == FALSE);
|
|
}
|
|
AdditionalDataToUse = NULL;
|
|
AdditionalDataLengthToUse = 0;
|
|
}
|
|
|
|
if (IsReplacementChannel == FALSE)
|
|
{
|
|
RpcStatus = NewInChannel->Unplug();
|
|
// since no sends have been done yet, unplugging cannot fail here
|
|
ASSERT(RpcStatus == RPC_S_OK);
|
|
}
|
|
|
|
RpcStatus = NewInChannel->ClientOpen(Hint,
|
|
InHeaderVerb,
|
|
InHeaderVerbLength,
|
|
UseWinHttp,
|
|
HttpCredentials,
|
|
InChosenAuthScheme,
|
|
CallTimeout,
|
|
AdditionalDataToUse,
|
|
AdditionalDataLengthToUse
|
|
);
|
|
|
|
// restore the access type
|
|
if (StoredAccessType == rpcpatUnknown)
|
|
{
|
|
Hint->AccessType = StoredAccessType;
|
|
}
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
// if we are searching for the proxy, a failure here is not
|
|
// fatal. We'll just prevent further operations on the channel,
|
|
// and see what the other channel has to say
|
|
if (InChannelState.State == http2svSearchProxy)
|
|
{
|
|
InOpenStatus = RpcStatus;
|
|
SendInChannel = FALSE;
|
|
ReceiveInChannel = FALSE;
|
|
// we don't need to unlock the channel - the code
|
|
// below will do.
|
|
}
|
|
else
|
|
goto AbortAndExit;
|
|
}
|
|
}
|
|
|
|
if (ResetOutChannel || OpenOutChannel || SendOutChannel)
|
|
{
|
|
if (IsReplacementChannel)
|
|
OutChannelPtr = &OutChannels[NonDefaultOutChannelSelector];
|
|
else
|
|
OutChannelPtr = &OutChannels[0];
|
|
|
|
NewOutChannel = (HTTP2ClientOutChannel *)OutChannelPtr->LockChannelPointer();
|
|
|
|
if (NewOutChannel == NULL)
|
|
{
|
|
RpcStatus = RPC_P_CONNECTION_SHUTDOWN;
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
OutChannelLocked = TRUE;
|
|
}
|
|
|
|
// Nuke and rebuild are mutually exclusive with Reset
|
|
if (ResetOutChannel)
|
|
{
|
|
ASSERT(NukeOutChannel == FALSE);
|
|
ASSERT(RebuildOutChannel == FALSE);
|
|
|
|
NewOutChannel->Reset();
|
|
}
|
|
|
|
if (OpenOutChannel)
|
|
{
|
|
// do we do out_data or echo's?
|
|
if ((OutOpenType == cotSearchProxy)
|
|
|| (OutOpenType == cotMLAuth)
|
|
|| (OutOpenType == cotUnknownAuth)
|
|
)
|
|
{
|
|
AdditionalDataToUse = EchoData;
|
|
AdditionalDataLengthToUse = 0;
|
|
AdditionalDataLengthToUse = EchoDataLength;
|
|
if (StoredAccessType == rpcpatUnknown)
|
|
{
|
|
// if we don't know the type yet, try
|
|
// direct for the in, proxy for the out.
|
|
// One of them will work.
|
|
Hint->AccessType = rpcpatHTTPProxy;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ASSERT((OutOpenType == cotNoAuth)
|
|
|| (OutOpenType == cotSLAuth)
|
|
|| (OutOpenType == cotMLAuth2)
|
|
);
|
|
ASSERT(StoredAccessType != rpcpatUnknown);
|
|
if (IsReplacementChannel == FALSE)
|
|
{
|
|
ASSERT(SendOutChannel);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(SendOutChannel == FALSE);
|
|
}
|
|
AdditionalDataToUse = NULL;
|
|
AdditionalDataLengthToUse = 0;
|
|
}
|
|
|
|
RpcStatus = NewOutChannel->ClientOpen(Hint,
|
|
OutHeaderVerb,
|
|
OutHeaderVerbLength,
|
|
IsReplacementChannel, // ReplacementChannel
|
|
UseWinHttp,
|
|
HttpCredentials,
|
|
OutChosenAuthScheme,
|
|
CallTimeout,
|
|
AdditionalDataToUse,
|
|
AdditionalDataLengthToUse
|
|
);
|
|
|
|
// restore the access type
|
|
if (StoredAccessType == rpcpatUnknown)
|
|
{
|
|
Hint->AccessType = StoredAccessType;
|
|
}
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
// if this is the initial open and we are searching for the proxy, and
|
|
// the other channel opened,
|
|
// a failure here is not fatal. We'll just prevent further
|
|
// operations on the channel, and see what the other channel has
|
|
// to say. Note that during initial opening all the state is on the
|
|
// in channel - that's why we check InChannelState here, not OutChannelState
|
|
if ((IsReplacementChannel == FALSE)
|
|
&& (InChannelState.State == http2svSearchProxy)
|
|
&& (InOpenStatus == ERROR_IO_PENDING))
|
|
{
|
|
OutOpenStatus = RpcStatus;
|
|
SendOutChannel = FALSE;
|
|
ReceiveOutChannel = FALSE;
|
|
// we don't need to unlock the channel - the code
|
|
// below will do.
|
|
}
|
|
else
|
|
{
|
|
goto AbortAndExit;
|
|
}
|
|
}
|
|
}
|
|
|
|
// we're done with the opening itself. Unblock aborts
|
|
ASSERT(AbortsBlocked);
|
|
UnblockAborts();
|
|
AbortsBlocked = FALSE;
|
|
|
|
if (SendInChannel)
|
|
{
|
|
// should not happen during replacement
|
|
ASSERT(IsReplacementChannel == FALSE);
|
|
|
|
InChannelSendContext = AllocateAndInitializeD1_B1(HTTP2ProtocolVersion,
|
|
&EmbeddedConnectionCookie,
|
|
&InChannelCookies[0],
|
|
DefaultChannelLifetime,
|
|
DefaultClientKeepAliveInterval,
|
|
&Hint->AssociationGroupId
|
|
);
|
|
|
|
if (InChannelSendContext == NULL)
|
|
{
|
|
RpcStatus = RPC_S_OUT_OF_MEMORY;
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
RpcStatus = NewInChannel->Send(InChannelSendContext);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
goto AbortAndExit;
|
|
|
|
// we don't own this buffer now
|
|
InChannelSendContext = NULL;
|
|
}
|
|
|
|
if (SendOutChannel)
|
|
{
|
|
// should not happen during replacement
|
|
ASSERT(IsReplacementChannel == FALSE);
|
|
|
|
OutChannelSendContext = AllocateAndInitializeD1_A1(HTTP2ProtocolVersion,
|
|
&EmbeddedConnectionCookie,
|
|
&OutChannelCookies[0],
|
|
HTTP2ClientReceiveWindow
|
|
);
|
|
|
|
if (OutChannelSendContext == NULL)
|
|
{
|
|
RpcStatus = RPC_S_OUT_OF_MEMORY;
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
RpcStatus = NewOutChannel->Send(OutChannelSendContext);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
goto AbortAndExit;
|
|
|
|
// we don't own this buffer anymore
|
|
OutChannelSendContext = NULL;
|
|
}
|
|
|
|
// post receives on both channels
|
|
if (ReceiveOutChannel)
|
|
{
|
|
RpcStatus = NewOutChannel->Receive(http2ttRTS);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
goto AbortAndExit;
|
|
}
|
|
}
|
|
|
|
if (ReceiveInChannel)
|
|
{
|
|
RpcStatus = NewInChannel->Receive(http2ttRaw);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
if (ResetInChannel || OpenInChannel || SendInChannel)
|
|
{
|
|
ASSERT(InChannelLocked);
|
|
|
|
InChannelPtr->UnlockChannelPointer();
|
|
|
|
InChannelLocked = FALSE;
|
|
// channel is unlocked. Can't touch it
|
|
NewInChannel = NULL;
|
|
}
|
|
|
|
if (ResetOutChannel || OpenOutChannel || SendOutChannel)
|
|
{
|
|
ASSERT(OutChannelLocked);
|
|
|
|
OutChannelPtr->UnlockChannelPointer();
|
|
|
|
OutChannelLocked = FALSE;
|
|
// channel is unlocked. Can't touch it
|
|
NewOutChannel = NULL;
|
|
}
|
|
|
|
if (IsDone)
|
|
{
|
|
RpcStatus = RPC_S_OK;
|
|
break;
|
|
}
|
|
|
|
// no authentication and single leg authentication are
|
|
// completed in one leg. Make sure we don't loop around
|
|
// with them for replacement case
|
|
if (ClientOpenInChannel && IsReplacementChannel)
|
|
{
|
|
ASSERT(InOpenType != cotNoAuth);
|
|
ASSERT(InOpenType != cotSLAuth);
|
|
ASSERT(InOpenType != cotMLAuth2);
|
|
}
|
|
|
|
if (ClientOpenOutChannel && IsReplacementChannel)
|
|
{
|
|
ASSERT(OutOpenType != cotNoAuth);
|
|
ASSERT(OutOpenType != cotSLAuth);
|
|
ASSERT(OutOpenType != cotMLAuth2);
|
|
}
|
|
WaitAgain:
|
|
// wait for something to happen
|
|
WaitResult = WaitForSingleObject(LocalClientOpenEvent, CallTimeout);
|
|
|
|
if (WaitResult == WAIT_TIMEOUT)
|
|
{
|
|
RpcStatus = RPC_S_CALL_CANCELLED;
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
ASSERT(WaitResult == WAIT_OBJECT_0);
|
|
|
|
// there is race where we could have picked up a channel's event
|
|
// after we waited (e.g. two channels completed immediately after each
|
|
// other). In such case, there wouldn't be anything on any channel - wait
|
|
// again. This race exists only if we do initial connect.
|
|
if (IsReplacementChannel == FALSE)
|
|
{
|
|
if ((InOpenStatus == ERROR_IO_PENDING)
|
|
&& (OutOpenStatus == ERROR_IO_PENDING))
|
|
{
|
|
goto WaitAgain;
|
|
}
|
|
}
|
|
|
|
OldInOpenType = InOpenType;
|
|
OldOutOpenType = OutOpenType;
|
|
|
|
// analyze what happened
|
|
|
|
// If we are in a non-terminal state, check what transitions we
|
|
// need to make to a terminal state
|
|
if (ClientOpenOutChannel
|
|
&&
|
|
(
|
|
(OutOpenType == cotSearchProxy)
|
|
||
|
|
(OutOpenType == cotUnknownAuth)
|
|
)
|
|
)
|
|
{
|
|
OldOpenType = OutOpenType;
|
|
|
|
// We can be here in 3 cases:
|
|
// 1. We're searching for a proxy during initial open
|
|
// 2. We don't know the auth type during initial open
|
|
// 3. We recycle the out channel with unknown auth type
|
|
|
|
// The events of interest are:
|
|
// 1. If we are in case 2, and the channel is still pending,
|
|
// skip the channel.
|
|
// 2. If we're in the remainder of case 2 or we're in 3, or
|
|
// (we're in 1 and the in channel is not positive yet and we
|
|
// have given it enough time to come in, and we have a positive
|
|
// response on this channel), the result is final.
|
|
// 3. In case 1, if this channel has a negative response, fall through
|
|
// to both channel check
|
|
// 4. In case 1, if the other channel has come in, fall through
|
|
|
|
// capture the out open status to get a consistent view of it in
|
|
// the ifs below
|
|
LocalOutOpenStatus = OutOpenStatus;
|
|
|
|
if (OutOpenType == cotSearchProxy)
|
|
{
|
|
ASSERT(IsReplacementChannel == FALSE);
|
|
CurrentCase = 1;
|
|
}
|
|
else if (IsReplacementChannel == FALSE)
|
|
{
|
|
ASSERT(OutOpenType == cotUnknownAuth);
|
|
CurrentCase = 2;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(IsReplacementChannel);
|
|
ASSERT(OutOpenType == cotUnknownAuth);
|
|
CurrentCase = 3;
|
|
}
|
|
|
|
if ((CurrentCase == 2)
|
|
&& (LocalOutOpenStatus == ERROR_IO_PENDING))
|
|
{
|
|
NukeOutChannel = FALSE;
|
|
RebuildOutChannel = FALSE;
|
|
ResetOutChannel = FALSE;
|
|
OpenOutChannel = FALSE;
|
|
ReceiveOutChannel = FALSE;
|
|
SendOutChannel = FALSE;
|
|
}
|
|
else if
|
|
(
|
|
(CurrentCase == 2)
|
|
||
|
|
(CurrentCase == 3)
|
|
||
|
|
(
|
|
// positive response on case 1
|
|
(CurrentCase == 1)
|
|
&&
|
|
(!IsInChannelPositiveWithWait())
|
|
&&
|
|
(
|
|
(LocalOutOpenStatus == RPC_S_OK)
|
|
||
|
|
(LocalOutOpenStatus == RPC_P_AUTH_NEEDED)
|
|
)
|
|
)
|
|
)
|
|
{
|
|
// We'll be here in 3 cases
|
|
// 1. We don't know the auth type during initial open and we have
|
|
// a response on the channel
|
|
// 2. We recycle the out channel with unknown auth type
|
|
// 3. We search for a proxy and this channel will be chosen
|
|
|
|
// the status is final
|
|
if ((LocalOutOpenStatus != RPC_S_OK)
|
|
&& (LocalOutOpenStatus != RPC_P_AUTH_NEEDED))
|
|
{
|
|
RpcStatus = LocalOutOpenStatus;
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
// In all cases the auth scheme is final for the new channel and we
|
|
// need to continue authentication
|
|
|
|
// if we haven't chosen a scheme yet, choose it now
|
|
if (OutChosenAuthScheme == 0)
|
|
{
|
|
OutChosenAuthScheme = GetOutChannelChosenScheme(IsReplacementChannel);
|
|
}
|
|
|
|
if (OutChosenAuthScheme & MultiLeggedSchemeMap)
|
|
{
|
|
// milti legged authentication implies keep alives
|
|
IsKeepAlive = TRUE;
|
|
OutOpenType = cotMLAuth;
|
|
|
|
// we need only reset, open, send and receive
|
|
NukeOutChannel = FALSE;
|
|
RebuildOutChannel = FALSE;
|
|
ResetOutChannel = TRUE;
|
|
OpenOutChannel = TRUE;
|
|
SendOutChannel = FALSE;
|
|
ReceiveOutChannel = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// SSL always supports keep alives
|
|
if (HttpCredentials && HttpCredentials->Flags & RPC_C_HTTP_FLAG_USE_SSL)
|
|
IsKeepAlive = TRUE;
|
|
else
|
|
{
|
|
IsKeepAlive = IsOutChannelKeepAlive(IsReplacementChannel);
|
|
}
|
|
|
|
if (OutChosenAuthScheme)
|
|
OutOpenType = cotSLAuth;
|
|
else
|
|
OutOpenType = cotNoAuth;
|
|
if (IsKeepAlive)
|
|
{
|
|
// we need nuke, rebuild, open, send, receive
|
|
NukeOutChannel = FALSE;
|
|
RebuildOutChannel = FALSE;
|
|
ResetOutChannel = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// we need nuke, rebuild, open, send, receive
|
|
NukeOutChannel = TRUE;
|
|
RebuildOutChannel = TRUE;
|
|
ResetOutChannel = FALSE;
|
|
}
|
|
|
|
OpenOutChannel = TRUE;
|
|
if (IsReplacementChannel)
|
|
{
|
|
ReceiveOutChannel = FALSE;
|
|
|
|
// should be done on next iteration
|
|
IsDone = TRUE;
|
|
}
|
|
else
|
|
{
|
|
SendOutChannel = TRUE;
|
|
ReceiveOutChannel = TRUE;
|
|
}
|
|
|
|
}
|
|
OutOpenStatus = ERROR_IO_PENDING;
|
|
|
|
if (InOpenType == cotSearchProxy)
|
|
{
|
|
// we need to nuke, rebuild, open, send, possibly receive in channel
|
|
NukeInChannel = TRUE;
|
|
RebuildInChannel = TRUE;
|
|
ResetInChannel = FALSE;
|
|
OpenInChannel = TRUE;
|
|
SendInChannel = FALSE;
|
|
|
|
// if we have already chosen an auth scheme, presumably
|
|
// because of RPC_C_HTTP_FLAG_USE_FIRST_AUTH_SCHEME, set it
|
|
if (InChosenAuthScheme)
|
|
{
|
|
ASSERT(IsReplacementChannel == FALSE);
|
|
ASSERT(HttpCredentials);
|
|
ASSERT(HttpCredentials->Flags & RPC_C_HTTP_FLAG_USE_FIRST_AUTH_SCHEME);
|
|
|
|
if (InChosenAuthScheme & MultiLeggedSchemeMap)
|
|
InOpenType = cotMLAuth;
|
|
else
|
|
{
|
|
InOpenType = cotSLAuth;
|
|
SendInChannel = TRUE;
|
|
}
|
|
}
|
|
else
|
|
InOpenType = cotUnknownAuth;
|
|
|
|
// multilegged schemes will still need to do some pinging.
|
|
// single legged schemes are done and only need to open
|
|
// the connection.
|
|
if (OutChosenAuthScheme & MultiLeggedSchemeMap)
|
|
ReceiveInChannel = TRUE;
|
|
else if (InOpenType == cotUnknownAuth)
|
|
{
|
|
ReceiveInChannel = TRUE;
|
|
}
|
|
else
|
|
{
|
|
ReceiveInChannel = FALSE;
|
|
}
|
|
|
|
StoredAccessType = rpcpatHTTPProxy;
|
|
Hint->AccessType = rpcpatHTTPProxy;
|
|
}
|
|
|
|
if (IsReplacementChannel == FALSE)
|
|
{
|
|
// change the connection and channel state
|
|
SetNewConnectionState = TRUE;
|
|
NewConnectionState = http2svA3W;
|
|
}
|
|
|
|
if ((OldOpenType == cotSearchProxy) || IsReplacementChannel)
|
|
continue;
|
|
else
|
|
{
|
|
// we were doing initial open with cotUnknownAuth
|
|
// fall through to the in channel handling code to see
|
|
// what is it up to
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// events 3 and 4. We're searching for the proxy and
|
|
// either this channel came with a negative response or
|
|
// the other channel came in
|
|
ASSERT(CurrentCase == 1);
|
|
ASSERT(IsReplacementChannel == FALSE);
|
|
ASSERT(OutOpenType == cotSearchProxy);
|
|
ASSERT(
|
|
(
|
|
(LocalOutOpenStatus != RPC_S_OK)
|
|
&&
|
|
(LocalOutOpenStatus != RPC_P_AUTH_NEEDED)
|
|
)
|
|
||
|
|
(
|
|
(InOpenStatus == RPC_S_OK)
|
|
||
|
|
(InOpenStatus == RPC_P_AUTH_NEEDED)
|
|
)
|
|
);
|
|
|
|
// fall through to the in channel check
|
|
}
|
|
}
|
|
|
|
if (
|
|
ClientOpenInChannel
|
|
&&
|
|
(
|
|
(InOpenType == cotSearchProxy)
|
|
||
|
|
(InOpenType == cotUnknownAuth)
|
|
)
|
|
)
|
|
{
|
|
OldOpenType = InOpenType;
|
|
|
|
// We can be here in 3 cases:
|
|
// 1. We do initial open and we search for proxy
|
|
// 2. We do initial open with unknown auth.
|
|
// 3. We do in channel recycling with unknown auth
|
|
|
|
// The events of interest are:
|
|
// 1. If the channel is still pending and we are in case 2,
|
|
// skip the channel.
|
|
// 2. If we're in case 3, or the remainder of 2, or (we're in 1 and
|
|
// the result is positive), the result is final.
|
|
// 3. If we're in case 1, and the result is negative, fall
|
|
// through below to both channels checks
|
|
|
|
if (InOpenType == cotSearchProxy)
|
|
{
|
|
ASSERT(IsReplacementChannel == FALSE);
|
|
CurrentCase = 1;
|
|
}
|
|
else if (IsReplacementChannel == FALSE)
|
|
{
|
|
ASSERT(InOpenType == cotUnknownAuth);
|
|
CurrentCase = 2;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(IsReplacementChannel);
|
|
ASSERT(InOpenType == cotUnknownAuth);
|
|
CurrentCase = 3;
|
|
}
|
|
|
|
// capture the InOpenStatus to get a consistent view
|
|
LocalInOpenStatus = InOpenStatus;
|
|
if ((CurrentCase == 2) && (LocalInOpenStatus == ERROR_IO_PENDING))
|
|
{
|
|
NukeInChannel = FALSE;
|
|
RebuildInChannel = FALSE;
|
|
ResetInChannel = FALSE;
|
|
OpenInChannel = FALSE;
|
|
ReceiveInChannel = FALSE;
|
|
SendInChannel = FALSE;
|
|
// the wait must have been woken by the out channel. Loop around
|
|
continue;
|
|
}
|
|
else if
|
|
(
|
|
(CurrentCase == 2)
|
|
||
|
|
(CurrentCase == 3)
|
|
||
|
|
(
|
|
(CurrentCase == 1)
|
|
&&
|
|
(
|
|
(LocalInOpenStatus == RPC_S_OK)
|
|
||
|
|
(LocalInOpenStatus == RPC_P_AUTH_NEEDED)
|
|
)
|
|
)
|
|
)
|
|
{
|
|
if ((LocalInOpenStatus != RPC_S_OK)
|
|
&& (LocalInOpenStatus != RPC_P_AUTH_NEEDED))
|
|
{
|
|
RpcStatus = LocalInOpenStatus;
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
// if we haven't chosen a scheme yet, choose it now
|
|
if (InChosenAuthScheme == 0)
|
|
{
|
|
InChosenAuthScheme = GetInChannelChosenScheme(IsReplacementChannel);
|
|
}
|
|
|
|
if (InChosenAuthScheme & MultiLeggedSchemeMap)
|
|
{
|
|
// milti legged authentication implies keep alives
|
|
IsKeepAlive = TRUE;
|
|
InOpenType = cotMLAuth;
|
|
|
|
// we need only reset, open, send and receive
|
|
NukeInChannel = FALSE;
|
|
RebuildInChannel = FALSE;
|
|
ResetInChannel = TRUE;
|
|
OpenInChannel = TRUE;
|
|
SendInChannel = FALSE;
|
|
ReceiveInChannel = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// SSL always supports keep alives
|
|
if (HttpCredentials && HttpCredentials->Flags & RPC_C_HTTP_FLAG_USE_SSL)
|
|
IsKeepAlive = TRUE;
|
|
else
|
|
{
|
|
IsKeepAlive = IsInChannelKeepAlive(IsReplacementChannel);
|
|
}
|
|
if (InChosenAuthScheme)
|
|
InOpenType = cotSLAuth;
|
|
else
|
|
InOpenType = cotNoAuth;
|
|
if (IsKeepAlive)
|
|
{
|
|
// we need nuke, rebuild, open, send, receive
|
|
NukeInChannel = FALSE;
|
|
RebuildInChannel = FALSE;
|
|
ResetInChannel = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// we need nuke, rebuild, open, send, receive
|
|
NukeInChannel = TRUE;
|
|
RebuildInChannel = TRUE;
|
|
ResetInChannel = FALSE;
|
|
}
|
|
|
|
OpenInChannel = TRUE;
|
|
if (IsReplacementChannel)
|
|
IsDone = TRUE;
|
|
else
|
|
SendInChannel = TRUE;
|
|
|
|
if (UseWinHttp || (IsReplacementChannel == FALSE))
|
|
ReceiveInChannel = FALSE;
|
|
else
|
|
ReceiveInChannel = TRUE;
|
|
}
|
|
|
|
InOpenStatus = ERROR_IO_PENDING;
|
|
|
|
if (OutOpenType == cotSearchProxy)
|
|
{
|
|
// we need to nuke, rebuild, open, send, possibly receive in channel
|
|
NukeOutChannel = TRUE;
|
|
RebuildOutChannel = TRUE;
|
|
ResetOutChannel = FALSE;
|
|
OpenOutChannel = TRUE;
|
|
SendOutChannel = FALSE;
|
|
ReceiveOutChannel = TRUE;
|
|
|
|
// if we have already chosen an auth scheme, presumably
|
|
// because of RPC_C_HTTP_FLAG_USE_FIRST_AUTH_SCHEME, set it
|
|
if (OutChosenAuthScheme)
|
|
{
|
|
ASSERT(IsReplacementChannel == FALSE);
|
|
ASSERT(HttpCredentials);
|
|
ASSERT(HttpCredentials->Flags & RPC_C_HTTP_FLAG_USE_FIRST_AUTH_SCHEME);
|
|
|
|
if (OutChosenAuthScheme & MultiLeggedSchemeMap)
|
|
OutOpenType = cotMLAuth;
|
|
else
|
|
{
|
|
OutOpenType = cotSLAuth;
|
|
|
|
SendOutChannel = TRUE;
|
|
}
|
|
}
|
|
else
|
|
OutOpenType = cotUnknownAuth;
|
|
|
|
StoredAccessType = rpcpatDirect;
|
|
Hint->AccessType = rpcpatDirect;
|
|
}
|
|
|
|
if (IsReplacementChannel == FALSE)
|
|
{
|
|
// change the connection and channel state
|
|
SetNewConnectionState = TRUE;
|
|
NewConnectionState = http2svA3W;
|
|
}
|
|
|
|
if ((OldOpenType == cotSearchProxy) || IsReplacementChannel)
|
|
continue;
|
|
else
|
|
{
|
|
// fall through to code that handles both channels
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ASSERT(CurrentCase == 1);
|
|
ASSERT((LocalInOpenStatus != RPC_S_OK)
|
|
&& (LocalInOpenStatus != RPC_P_AUTH_NEEDED) );
|
|
// fall through below
|
|
}
|
|
}
|
|
|
|
// did we get to an opened state? This should be checked only
|
|
// when we open both (initial open).
|
|
if ((IsReplacementChannel == FALSE)
|
|
&& (InChannelState.State == http2svOpened))
|
|
{
|
|
|
|
RpcStatus = RPC_S_OK;
|
|
|
|
ASSERT(InChannelState.State == http2svOpened);
|
|
ASSERT(OutChannelState.State == http2svOpened);
|
|
|
|
RpcStatus = HTTP_CopyResolverHint(&ConnectionHint,
|
|
Hint,
|
|
FALSE // SourceWillBeAbandoned
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
goto AbortAndExit;
|
|
|
|
break;
|
|
}
|
|
|
|
// if we are in a non-transitional state and we're doing an
|
|
// initial open, this means one of the channels didn't come in.
|
|
// Loop around
|
|
if ((IsReplacementChannel == FALSE)
|
|
&& (
|
|
(InOpenType == cotUnknownAuth)
|
|
||
|
|
(OutOpenType == cotUnknownAuth)
|
|
)
|
|
)
|
|
{
|
|
ASSERT((LocalInOpenStatus == ERROR_IO_PENDING)
|
|
|| (LocalOutOpenStatus == ERROR_IO_PENDING));
|
|
continue;
|
|
}
|
|
|
|
// we're probably authenticating the individual channels
|
|
// see which channel is actionable and what to do with it
|
|
if (ClientOpenInChannel && (OldInOpenType == cotMLAuth))
|
|
{
|
|
// capture the InOpenStatus in a local variable for consistent
|
|
// view
|
|
LocalInOpenStatus = InOpenStatus;
|
|
if (LocalInOpenStatus != ERROR_IO_PENDING)
|
|
{
|
|
// something happened on the in channel. Process it
|
|
if (LocalInOpenStatus == RPC_S_OK)
|
|
{
|
|
// we have successfully completed
|
|
// authentication. Open the connection on the RTS
|
|
// level
|
|
InOpenStatus = ERROR_IO_PENDING;
|
|
// we need to reset, open, send, receive out channel
|
|
NukeInChannel = FALSE;
|
|
RebuildInChannel = FALSE;
|
|
ResetInChannel = TRUE;
|
|
OpenInChannel = TRUE;
|
|
|
|
ReceiveInChannel = FALSE;
|
|
|
|
if (IsReplacementChannel)
|
|
{
|
|
SendInChannel = FALSE;
|
|
IsDone = TRUE;
|
|
}
|
|
else
|
|
SendInChannel = TRUE;
|
|
|
|
InOpenType = cotMLAuth2;
|
|
}
|
|
else
|
|
{
|
|
// if after all the auth we still get a challenge, this means
|
|
// we couldn't auth and this is access denied.
|
|
if (LocalInOpenStatus == RPC_P_AUTH_NEEDED)
|
|
RpcStatus = RPC_S_ACCESS_DENIED;
|
|
else
|
|
RpcStatus = LocalInOpenStatus;
|
|
goto AbortAndExit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// fall through. Below we will detect the state
|
|
// hasn't changed and we won't do any operations
|
|
// on this channel
|
|
}
|
|
}
|
|
|
|
if (ClientOpenOutChannel && (OldOutOpenType == cotMLAuth))
|
|
{
|
|
// capture the OutOpenStatus in a local variable for consistent
|
|
// view
|
|
LocalOutOpenStatus = OutOpenStatus;
|
|
if (LocalOutOpenStatus != ERROR_IO_PENDING)
|
|
{
|
|
// something happened on the in channel. Process it
|
|
if (LocalOutOpenStatus == RPC_S_OK)
|
|
{
|
|
// we have successfully completed multi-legged
|
|
// authentication. Open the connection on the RTS
|
|
// level
|
|
OutOpenStatus = ERROR_IO_PENDING;
|
|
// we need to reset, open, send, receive out channel
|
|
NukeOutChannel = FALSE;
|
|
RebuildOutChannel = FALSE;
|
|
ResetOutChannel = TRUE;
|
|
OpenOutChannel = TRUE;
|
|
if (IsReplacementChannel)
|
|
{
|
|
ReceiveOutChannel = FALSE;
|
|
SendOutChannel = FALSE;
|
|
IsDone = TRUE;
|
|
}
|
|
else
|
|
{
|
|
ReceiveOutChannel = TRUE;
|
|
SendOutChannel = TRUE;
|
|
}
|
|
|
|
OutOpenType = cotMLAuth2;
|
|
}
|
|
else
|
|
{
|
|
// if after all the auth we still get a challenge, this means
|
|
// we couldn't auth and this is access denied.
|
|
if (LocalOutOpenStatus == RPC_P_AUTH_NEEDED)
|
|
RpcStatus = RPC_S_ACCESS_DENIED;
|
|
else
|
|
RpcStatus = LocalOutOpenStatus;
|
|
goto AbortAndExit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// fall through. Below we will detect the state
|
|
// hasn't changed and we won't do any operations
|
|
// on this channel
|
|
}
|
|
}
|
|
|
|
if ((IsReplacementChannel == FALSE) && (InOpenType == cotSearchProxy))
|
|
{
|
|
// none of the channels came in positive so far. If at least one
|
|
// is still pending, wait for it
|
|
if ((LocalInOpenStatus == ERROR_IO_PENDING)
|
|
|| (LocalOutOpenStatus == ERROR_IO_PENDING))
|
|
{
|
|
#if DBG_ERROR
|
|
DbgPrint("Waiting again ....\n");
|
|
#endif
|
|
goto WaitAgain;
|
|
}
|
|
|
|
// both channels came in negative. The server is not
|
|
// available
|
|
if ((LocalInOpenStatus == RPC_S_ACCESS_DENIED)
|
|
|| (LocalOutOpenStatus == RPC_S_ACCESS_DENIED))
|
|
RpcStatus = RPC_S_ACCESS_DENIED;
|
|
else
|
|
RpcStatus = RPC_S_SERVER_UNAVAILABLE;
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
// if we came in with a terminal state and the server responded
|
|
// with an error, bail out
|
|
// first, capture the InOpenStatus in a local variable for consistent
|
|
// view
|
|
LocalInOpenStatus = InOpenStatus;
|
|
if (ClientOpenInChannel
|
|
&&
|
|
(
|
|
(OldInOpenType == cotSLAuth)
|
|
||
|
|
(OldInOpenType == cotNoAuth)
|
|
||
|
|
(OldInOpenType == cotMLAuth2)
|
|
)
|
|
&&
|
|
(LocalInOpenStatus != RPC_S_OK)
|
|
&&
|
|
(LocalInOpenStatus != ERROR_IO_PENDING)
|
|
)
|
|
{
|
|
if (LocalInOpenStatus == RPC_P_AUTH_NEEDED)
|
|
RpcStatus = RPC_S_ACCESS_DENIED;
|
|
else
|
|
RpcStatus = LocalInOpenStatus;
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
// capture the OutOpenStatus in a local variable for consistent
|
|
// view
|
|
LocalOutOpenStatus = OutOpenStatus;
|
|
if (ClientOpenOutChannel
|
|
&&
|
|
(
|
|
(OldOutOpenType == cotSLAuth)
|
|
||
|
|
(OldOutOpenType == cotNoAuth)
|
|
||
|
|
(OldOutOpenType == cotMLAuth2)
|
|
)
|
|
&&
|
|
(LocalOutOpenStatus != RPC_S_OK)
|
|
&&
|
|
(LocalOutOpenStatus != ERROR_IO_PENDING)
|
|
)
|
|
{
|
|
if (LocalOutOpenStatus == RPC_P_AUTH_NEEDED)
|
|
RpcStatus = RPC_S_ACCESS_DENIED;
|
|
else
|
|
RpcStatus = LocalOutOpenStatus;
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
// if state hasn't changed, don't do anything on this channel
|
|
if (OldInOpenType == InOpenType)
|
|
{
|
|
NukeInChannel = FALSE;
|
|
RebuildInChannel = FALSE;
|
|
ResetInChannel = FALSE;
|
|
OpenInChannel = FALSE;
|
|
ReceiveInChannel = FALSE;
|
|
SendInChannel = FALSE;
|
|
}
|
|
|
|
if (OldOutOpenType == OutOpenType)
|
|
{
|
|
NukeOutChannel = FALSE;
|
|
RebuildOutChannel = FALSE;
|
|
ResetOutChannel = FALSE;
|
|
OpenOutChannel = FALSE;
|
|
ReceiveOutChannel = FALSE;
|
|
SendOutChannel = FALSE;
|
|
}
|
|
// loop around for further processing
|
|
}
|
|
|
|
if (AbortsBlocked)
|
|
UnblockAborts();
|
|
|
|
ASSERT(RpcStatus == RPC_S_OK);
|
|
|
|
if (IsReplacementChannel == FALSE)
|
|
{
|
|
ASSERT(InChannelState.State == http2svOpened);
|
|
}
|
|
|
|
ASSERT(LocalClientOpenEvent);
|
|
InChannelState.Mutex.Request();
|
|
ClientOpenInEvent = NULL;
|
|
ClientOpenOutEvent = NULL;
|
|
InChannelState.Mutex.Clear();
|
|
CloseHandle(LocalClientOpenEvent);
|
|
|
|
return RpcStatus;
|
|
|
|
AbortAndExit:
|
|
if (InChannelLocked)
|
|
{
|
|
InChannelPtr->UnlockChannelPointer();
|
|
}
|
|
|
|
if (OutChannelLocked)
|
|
{
|
|
OutChannelPtr->UnlockChannelPointer();
|
|
}
|
|
|
|
if (InChannelSendContext)
|
|
{
|
|
FreeRTSPacket(InChannelSendContext);
|
|
}
|
|
|
|
if (OutChannelSendContext)
|
|
{
|
|
FreeRTSPacket(OutChannelSendContext);
|
|
}
|
|
|
|
if ((RpcStatus == RPC_P_CONNECTION_SHUTDOWN)
|
|
|| (RpcStatus == RPC_P_CONNECTION_CLOSED)
|
|
|| (RpcStatus == RPC_P_SEND_FAILED))
|
|
RpcStatus = RPC_S_SERVER_UNAVAILABLE;
|
|
else if ((RpcStatus == RPC_P_RECEIVE_FAILED) && IsReplacementChannel)
|
|
{
|
|
// RPC_P_RECEIVE_FAILED is also mapped to server unavailable, but only
|
|
// during channel recycling. The reason is that the old crappy RPC Proxy
|
|
// will just close the connection if we directly access the RPC Proxy,
|
|
// so all that we will get will be RPC_P_RECEIVE_FAILED. Not mapping it
|
|
// during initial conneciton establishment allows upper layers to try the old
|
|
// HTTP thus preserving interop
|
|
RpcStatus = RPC_S_SERVER_UNAVAILABLE;
|
|
}
|
|
else if (RpcStatus == ERROR_NOT_ENOUGH_QUOTA
|
|
|| RpcStatus == ERROR_MAX_THRDS_REACHED)
|
|
{
|
|
RpcStatus = RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
ASSERT(LocalClientOpenEvent);
|
|
InChannelState.Mutex.Request();
|
|
ClientOpenInEvent = NULL;
|
|
ClientOpenOutEvent = NULL;
|
|
InChannelState.Mutex.Clear();
|
|
CloseHandle(LocalClientOpenEvent);
|
|
|
|
VALIDATE (RpcStatus)
|
|
{
|
|
RPC_S_PROTSEQ_NOT_SUPPORTED,
|
|
RPC_S_SERVER_UNAVAILABLE,
|
|
RPC_S_OUT_OF_MEMORY,
|
|
RPC_S_OUT_OF_RESOURCES,
|
|
RPC_S_SERVER_TOO_BUSY,
|
|
RPC_S_INVALID_NETWORK_OPTIONS,
|
|
RPC_S_INVALID_ENDPOINT_FORMAT,
|
|
RPC_S_INVALID_NET_ADDR,
|
|
RPC_S_ACCESS_DENIED,
|
|
RPC_S_INTERNAL_ERROR,
|
|
RPC_S_SERVER_OUT_OF_MEMORY,
|
|
RPC_S_CALL_CANCELLED,
|
|
RPC_S_PROTOCOL_ERROR,
|
|
RPC_P_RECEIVE_FAILED
|
|
} END_VALIDATE;
|
|
|
|
if (AbortsBlocked)
|
|
UnblockAborts();
|
|
|
|
// If we are not from upcall, abort. Else, caller will
|
|
// abort
|
|
if (IsFromUpcall == FALSE)
|
|
Abort();
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2ClientVirtualConnection::AllocateAndInitializeInChannel (
|
|
IN HTTPResolverHint *Hint,
|
|
IN BOOL HintWasInitialized,
|
|
IN ULONG CallTimeout,
|
|
IN BOOL UseWinHttp,
|
|
OUT HTTP2ClientInChannel **ReturnInChannel
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Allocate and initialize the in channel stack
|
|
|
|
Arguments:
|
|
|
|
Hint - the resolver hint
|
|
|
|
HintWasInitialized - true if the hint was initialized on input
|
|
|
|
CallTimeout - the timeout for the operation
|
|
|
|
ReturnInChannel - on success the pointer to the allocated in channel.
|
|
Undefined on failure.
|
|
|
|
UseWinHttp - non-zero if WinHttp should be used for the bottom channel.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
ULONG MemorySize;
|
|
BYTE *MemoryBlock, *CurrentBlock;
|
|
HTTP2ClientInChannel *InChannel;
|
|
HTTP2PlugChannel *PlugChannel;
|
|
HTTP2FlowControlSender *FlowControlSender;
|
|
HTTP2PingOriginator *PingOriginator;
|
|
HTTP2ChannelDataOriginator *ChannelDataOriginator;
|
|
HTTP2SocketTransportChannel *RawChannel;
|
|
WS_HTTP2_CONNECTION *RawConnection;
|
|
HTTP2WinHttpTransportChannel *WinHttpConnection;
|
|
BOOL PlugChannelNeedsCleanup;
|
|
BOOL FlowControlSenderNeedsCleanup;
|
|
BOOL PingOriginatorNeedsCleanup;
|
|
BOOL ChannelDataOriginatorNeedsCleanup;
|
|
BOOL RawChannelNeedsCleanup;
|
|
BOOL RawConnectionNeedsCleanup;
|
|
BOOL WinHttpConnectionNeedsCleanup;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
// alocate the in channel
|
|
MemorySize = SIZE_OF_OBJECT_AND_PADDING(HTTP2ClientInChannel)
|
|
+ SIZE_OF_OBJECT_AND_PADDING(HTTP2PlugChannel)
|
|
+ SIZE_OF_OBJECT_AND_PADDING(HTTP2FlowControlSender)
|
|
+ SIZE_OF_OBJECT_AND_PADDING(HTTP2PingOriginator)
|
|
+ SIZE_OF_OBJECT_AND_PADDING(HTTP2ChannelDataOriginator)
|
|
;
|
|
|
|
if (UseWinHttp)
|
|
MemorySize += sizeof(HTTP2WinHttpTransportChannel);
|
|
else
|
|
{
|
|
MemorySize += SIZE_OF_OBJECT_AND_PADDING(HTTP2SocketTransportChannel)
|
|
+ sizeof(WS_HTTP2_CONNECTION);
|
|
}
|
|
|
|
CurrentBlock = MemoryBlock = (BYTE *) new char [MemorySize];
|
|
if (CurrentBlock == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
|
|
InChannel = (HTTP2ClientInChannel *) MemoryBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2ClientInChannel);
|
|
|
|
PlugChannel = (HTTP2PlugChannel *) CurrentBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2PlugChannel);
|
|
|
|
FlowControlSender = (HTTP2FlowControlSender *) CurrentBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2FlowControlSender);
|
|
|
|
PingOriginator = (HTTP2PingOriginator *)CurrentBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2PingOriginator);
|
|
|
|
ChannelDataOriginator = (HTTP2ChannelDataOriginator *)CurrentBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2ChannelDataOriginator);
|
|
|
|
if (UseWinHttp)
|
|
{
|
|
WinHttpConnection = (HTTP2WinHttpTransportChannel *)CurrentBlock;
|
|
}
|
|
else
|
|
{
|
|
RawChannel = (HTTP2SocketTransportChannel *)CurrentBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2SocketTransportChannel);
|
|
|
|
RawConnection = (WS_HTTP2_CONNECTION *)CurrentBlock;
|
|
RawConnection->HeaderRead = FALSE;
|
|
RawConnection->ReadHeaderFn = HTTP2ClientReadChannelHeader;
|
|
}
|
|
|
|
// all memory blocks are allocated. Go and initialize them. Use explicit
|
|
// placement
|
|
PlugChannelNeedsCleanup = FALSE;
|
|
FlowControlSenderNeedsCleanup = FALSE;
|
|
PingOriginatorNeedsCleanup = FALSE;
|
|
ChannelDataOriginatorNeedsCleanup = FALSE;
|
|
RawChannelNeedsCleanup = FALSE;
|
|
RawConnectionNeedsCleanup = FALSE;
|
|
WinHttpConnectionNeedsCleanup = FALSE;
|
|
|
|
if (UseWinHttp)
|
|
{
|
|
RpcStatus = RPC_S_OK;
|
|
WinHttpConnection = new (WinHttpConnection) HTTP2WinHttpTransportChannel (&RpcStatus);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
WinHttpConnection->HTTP2WinHttpTransportChannel::~HTTP2WinHttpTransportChannel();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
WinHttpConnectionNeedsCleanup = TRUE;
|
|
}
|
|
else
|
|
{
|
|
RpcStatus = InitializeRawConnection (RawConnection,
|
|
Hint,
|
|
HintWasInitialized,
|
|
CallTimeout
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
goto AbortAndExit;
|
|
|
|
RawConnection->RuntimeConnectionPtr = this;
|
|
RawConnectionNeedsCleanup = TRUE;
|
|
|
|
RawChannel = new (RawChannel) HTTP2SocketTransportChannel (RawConnection, &RpcStatus);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
RawChannel->HTTP2SocketTransportChannel::~HTTP2SocketTransportChannel();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
RawConnection->Channel = RawChannel;
|
|
|
|
RawChannelNeedsCleanup = TRUE;
|
|
}
|
|
|
|
ChannelDataOriginator = new (ChannelDataOriginator) HTTP2ChannelDataOriginator (DefaultChannelLifetime,
|
|
FALSE, // IsServer
|
|
&RpcStatus);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
ChannelDataOriginator->HTTP2ChannelDataOriginator::~HTTP2ChannelDataOriginator();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
if (UseWinHttp)
|
|
{
|
|
WinHttpConnection->SetUpperChannel(ChannelDataOriginator);
|
|
ChannelDataOriginator->SetLowerChannel(WinHttpConnection);
|
|
}
|
|
else
|
|
{
|
|
RawChannel->SetUpperChannel(ChannelDataOriginator);
|
|
ChannelDataOriginator->SetLowerChannel(RawChannel);
|
|
}
|
|
|
|
ChannelDataOriginatorNeedsCleanup = TRUE;
|
|
|
|
PingOriginator = new (PingOriginator) HTTP2PingOriginator (
|
|
FALSE // NotifyTopChannelForPings
|
|
);
|
|
|
|
ChannelDataOriginator->SetUpperChannel(PingOriginator);
|
|
PingOriginator->SetLowerChannel(ChannelDataOriginator);
|
|
|
|
PingOriginatorNeedsCleanup = TRUE;
|
|
|
|
FlowControlSender = new (FlowControlSender) HTTP2FlowControlSender (FALSE, // IsServer
|
|
TRUE, // SendToRuntime
|
|
&RpcStatus
|
|
);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
FlowControlSender->HTTP2FlowControlSender::~HTTP2FlowControlSender();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
PingOriginator->SetUpperChannel(FlowControlSender);
|
|
FlowControlSender->SetLowerChannel(PingOriginator);
|
|
|
|
FlowControlSenderNeedsCleanup = TRUE;
|
|
|
|
PlugChannel = new (PlugChannel) HTTP2PlugChannel (&RpcStatus);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
PlugChannel->HTTP2PlugChannel::~HTTP2PlugChannel();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
FlowControlSender->SetUpperChannel(PlugChannel);
|
|
PlugChannel->SetLowerChannel(FlowControlSender);
|
|
|
|
PlugChannelNeedsCleanup = TRUE;
|
|
|
|
InChannel = new (InChannel) HTTP2ClientInChannel (this, &RpcStatus);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
InChannel->HTTP2ClientInChannel::~HTTP2ClientInChannel();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
if (UseWinHttp)
|
|
WinHttpConnection->SetTopChannel(InChannel);
|
|
else
|
|
RawChannel->SetTopChannel(InChannel);
|
|
ChannelDataOriginator->SetTopChannel(InChannel);
|
|
PingOriginator->SetTopChannel(InChannel);
|
|
FlowControlSender->SetTopChannel(InChannel);
|
|
PlugChannel->SetTopChannel(InChannel);
|
|
|
|
PlugChannel->SetUpperChannel(InChannel);
|
|
InChannel->SetLowerChannel(PlugChannel);
|
|
|
|
ASSERT(RpcStatus == RPC_S_OK);
|
|
|
|
*ReturnInChannel = InChannel;
|
|
|
|
goto CleanupAndExit;
|
|
|
|
AbortAndExit:
|
|
if (PlugChannelNeedsCleanup)
|
|
{
|
|
PlugChannel->Abort(RpcStatus);
|
|
PlugChannel->FreeObject();
|
|
}
|
|
else if (FlowControlSenderNeedsCleanup)
|
|
{
|
|
FlowControlSender->Abort(RpcStatus);
|
|
FlowControlSender->FreeObject();
|
|
}
|
|
else if (PingOriginatorNeedsCleanup)
|
|
{
|
|
PingOriginator->Abort(RpcStatus);
|
|
PingOriginator->FreeObject();
|
|
}
|
|
else if (ChannelDataOriginatorNeedsCleanup)
|
|
{
|
|
ChannelDataOriginator->Abort(RpcStatus);
|
|
ChannelDataOriginator->FreeObject();
|
|
}
|
|
else if (UseWinHttp)
|
|
{
|
|
if (WinHttpConnectionNeedsCleanup)
|
|
{
|
|
WinHttpConnection->Abort(RpcStatus);
|
|
WinHttpConnection->FreeObject();
|
|
}
|
|
}
|
|
else if (RawChannelNeedsCleanup)
|
|
{
|
|
RawChannel->Abort(RpcStatus);
|
|
RawChannel->FreeObject();
|
|
}
|
|
else if (RawConnectionNeedsCleanup)
|
|
{
|
|
RawConnection->RealAbort();
|
|
}
|
|
|
|
if (MemoryBlock)
|
|
delete [] MemoryBlock;
|
|
|
|
CleanupAndExit:
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS
|
|
RPC_ENTRY
|
|
HTTP2ReadHttpLegacyResponse (
|
|
IN WS_HTTP2_CONNECTION *Connection,
|
|
IN ULONG BytesRead,
|
|
OUT ULONG *NewBytesRead
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Read a channel HTTP header (usually some string). In success
|
|
case, there is real data in Connection->pReadBuffer. The
|
|
number of bytes there is in NewBytesRead
|
|
|
|
Arguments:
|
|
|
|
Connection - the connection on which the header arrived.
|
|
|
|
BytesRead - the bytes received from the net
|
|
|
|
NewBytesRead - the bytes read from the channel (success only)
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
RPC_P_PARTIAL_RECEIVE will cause another loop.
|
|
Any other error will cause processing of NewBuffer
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
BYTE *NewBuffer;
|
|
|
|
BytesRead += Connection->iLastRead;
|
|
|
|
// check whether what we have is a legacy response
|
|
// legacy response is ncacn_http/1.0
|
|
if (*(ULONG *)Connection->pReadBuffer == (ULONG)'cacn')
|
|
{
|
|
// Let's process it now.
|
|
// see if we have sufficient length
|
|
if (BytesRead < HTTP_SERVER_ID_STR_LEN)
|
|
{
|
|
Connection->iLastRead = BytesRead;
|
|
return RPC_P_PARTIAL_RECEIVE;
|
|
}
|
|
else if (BytesRead == HTTP_SERVER_ID_STR_LEN)
|
|
{
|
|
// reset the pointer. By doing so we forget all we have
|
|
// read so far
|
|
Connection->iLastRead = 0;
|
|
Connection->HeaderRead = TRUE;
|
|
return RPC_P_PARTIAL_RECEIVE;
|
|
}
|
|
else
|
|
{
|
|
// we have what we expect, and something more (coalesced read)
|
|
// Process it. First make sure it is what we expect
|
|
if (RpcpMemoryCompare(Connection->pReadBuffer, HTTP_SERVER_ID_STR, HTTP_SERVER_ID_STR_LEN) != 0)
|
|
{
|
|
return RPC_S_PROTOCOL_ERROR;
|
|
}
|
|
|
|
NewBuffer = TransConnectionAllocatePacket(Connection,
|
|
max(Connection->iPostSize, BytesRead - HTTP_SERVER_ID_STR_LEN));
|
|
|
|
if (0 == NewBuffer)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
|
|
RpcpMemoryCopy(NewBuffer,
|
|
((BYTE *)Connection->pReadBuffer) + HTTP_SERVER_ID_STR_LEN,
|
|
BytesRead - HTTP_SERVER_ID_STR_LEN);
|
|
*NewBytesRead = BytesRead - HTTP_SERVER_ID_STR_LEN;
|
|
|
|
RpcFreeBuffer(Connection->pReadBuffer);
|
|
Connection->pReadBuffer = NewBuffer;
|
|
Connection->iLastRead = 0;
|
|
Connection->HeaderRead = TRUE;
|
|
|
|
return RPC_S_OK;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*NewBytesRead = BytesRead;
|
|
Connection->iLastRead = 0;
|
|
Connection->HeaderRead = TRUE;
|
|
return RPC_S_OK;
|
|
}
|
|
}
|
|
|
|
RPC_STATUS HTTP2ClientVirtualConnection::AllocateAndInitializeOutChannel (
|
|
IN HTTPResolverHint *Hint,
|
|
IN BOOL HintWasInitialized,
|
|
IN ULONG CallTimeout,
|
|
IN BOOL UseWinHttp,
|
|
OUT HTTP2ClientOutChannel **ReturnOutChannel
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Allocate and initialize the out channel stack
|
|
|
|
Arguments:
|
|
|
|
Hint - the resolver hint
|
|
|
|
HintWasInitialized - true if the hint was initialized on input
|
|
|
|
CallTimeout - the timeout for the operation
|
|
|
|
ReturnInChannel - on success the pointer to the allocated in channel.
|
|
Undefined on failure.
|
|
|
|
UseWinHttp - non-zero if WinHttp should be used for the bottom channel.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
ULONG MemorySize;
|
|
BYTE *MemoryBlock, *CurrentBlock;
|
|
HTTP2ClientOutChannel *OutChannel;
|
|
HTTP2EndpointReceiver *EndpointReceiver;
|
|
HTTP2PingReceiver *PingReceiver;
|
|
HTTP2SocketTransportChannel *RawChannel;
|
|
WS_HTTP2_CONNECTION *RawConnection;
|
|
HTTP2WinHttpTransportChannel *WinHttpConnection;
|
|
BOOL EndpointReceiverNeedsCleanup;
|
|
BOOL PingReceiverNeedsCleanup;
|
|
BOOL RawChannelNeedsCleanup;
|
|
BOOL RawConnectionNeedsCleanup;
|
|
BOOL WinHttpConnectionNeedsCleanup;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
// alocate the out channel
|
|
MemorySize = SIZE_OF_OBJECT_AND_PADDING(HTTP2ClientOutChannel)
|
|
+ SIZE_OF_OBJECT_AND_PADDING(HTTP2EndpointReceiver)
|
|
+ SIZE_OF_OBJECT_AND_PADDING(HTTP2PingReceiver)
|
|
;
|
|
|
|
if (UseWinHttp)
|
|
MemorySize += sizeof(HTTP2WinHttpTransportChannel);
|
|
else
|
|
{
|
|
MemorySize += SIZE_OF_OBJECT_AND_PADDING(HTTP2SocketTransportChannel)
|
|
+ sizeof(WS_HTTP2_CONNECTION);
|
|
}
|
|
|
|
CurrentBlock = MemoryBlock = (BYTE *) new char [MemorySize];
|
|
if (CurrentBlock == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
|
|
OutChannel = (HTTP2ClientOutChannel *) MemoryBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2ClientOutChannel);
|
|
|
|
EndpointReceiver = (HTTP2EndpointReceiver *)CurrentBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2EndpointReceiver);
|
|
|
|
PingReceiver = (HTTP2PingReceiver *)CurrentBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2PingReceiver);
|
|
|
|
if (UseWinHttp)
|
|
{
|
|
WinHttpConnection = (HTTP2WinHttpTransportChannel *)CurrentBlock;
|
|
}
|
|
else
|
|
{
|
|
RawChannel = (HTTP2SocketTransportChannel *)CurrentBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2SocketTransportChannel);
|
|
|
|
RawConnection = (WS_HTTP2_CONNECTION *)CurrentBlock;
|
|
RawConnection->HeaderRead = FALSE;
|
|
RawConnection->ReadHeaderFn = HTTP2ClientReadChannelHeader;
|
|
}
|
|
|
|
// all memory blocks are allocated. Go and initialize them. Use explicit
|
|
// placement
|
|
EndpointReceiverNeedsCleanup = FALSE;
|
|
PingReceiverNeedsCleanup = FALSE;
|
|
RawChannelNeedsCleanup = FALSE;
|
|
RawConnectionNeedsCleanup = FALSE;
|
|
WinHttpConnectionNeedsCleanup = FALSE;
|
|
|
|
if (UseWinHttp)
|
|
{
|
|
RpcStatus = RPC_S_OK;
|
|
WinHttpConnection = new (WinHttpConnection) HTTP2WinHttpTransportChannel (&RpcStatus);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
WinHttpConnection->HTTP2WinHttpTransportChannel::~HTTP2WinHttpTransportChannel();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
WinHttpConnectionNeedsCleanup = TRUE;
|
|
}
|
|
else
|
|
{
|
|
RpcStatus = InitializeRawConnection (RawConnection,
|
|
Hint,
|
|
HintWasInitialized,
|
|
CallTimeout
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
goto AbortAndExit;
|
|
|
|
RawConnection->RuntimeConnectionPtr = this;
|
|
RawConnectionNeedsCleanup = TRUE;
|
|
|
|
RawChannel = new (RawChannel) HTTP2SocketTransportChannel (RawConnection, &RpcStatus);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
RawChannel->HTTP2SocketTransportChannel::~HTTP2SocketTransportChannel();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
RawConnection->Channel = RawChannel;
|
|
|
|
RawChannelNeedsCleanup = TRUE;
|
|
}
|
|
|
|
PingReceiver = new (PingReceiver) HTTP2PingReceiver(TRUE);
|
|
|
|
if (UseWinHttp)
|
|
{
|
|
WinHttpConnection->SetUpperChannel(PingReceiver);
|
|
PingReceiver->SetLowerChannel(WinHttpConnection);
|
|
}
|
|
else
|
|
{
|
|
RawChannel->SetUpperChannel(PingReceiver);
|
|
PingReceiver->SetLowerChannel(RawChannel);
|
|
}
|
|
|
|
PingReceiverNeedsCleanup = TRUE;
|
|
|
|
EndpointReceiver = new (EndpointReceiver) HTTP2EndpointReceiver(HTTP2ClientReceiveWindow,
|
|
FALSE, // IsServer
|
|
&RpcStatus);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
EndpointReceiver->HTTP2EndpointReceiver::~HTTP2EndpointReceiver();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
PingReceiver->SetUpperChannel(EndpointReceiver);
|
|
EndpointReceiver->SetLowerChannel(PingReceiver);
|
|
|
|
EndpointReceiverNeedsCleanup = TRUE;
|
|
|
|
OutChannel = new (OutChannel) HTTP2ClientOutChannel (this, &RpcStatus);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
OutChannel->HTTP2ClientOutChannel::~HTTP2ClientOutChannel();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
EndpointReceiver->SetUpperChannel(OutChannel);
|
|
OutChannel->SetLowerChannel(EndpointReceiver);
|
|
|
|
if (UseWinHttp)
|
|
WinHttpConnection->SetTopChannel(OutChannel);
|
|
else
|
|
RawChannel->SetTopChannel(OutChannel);
|
|
PingReceiver->SetTopChannel(OutChannel);
|
|
EndpointReceiver->SetTopChannel(OutChannel);
|
|
|
|
ASSERT(RpcStatus == RPC_S_OK);
|
|
|
|
*ReturnOutChannel = OutChannel;
|
|
|
|
goto CleanupAndExit;
|
|
|
|
AbortAndExit:
|
|
if (EndpointReceiverNeedsCleanup)
|
|
{
|
|
EndpointReceiver->Abort(RpcStatus);
|
|
EndpointReceiver->FreeObject();
|
|
}
|
|
else if (PingReceiverNeedsCleanup)
|
|
{
|
|
PingReceiver->Abort(RpcStatus);
|
|
PingReceiver->FreeObject();
|
|
}
|
|
else if (UseWinHttp)
|
|
{
|
|
if (WinHttpConnectionNeedsCleanup)
|
|
{
|
|
WinHttpConnection->Abort(RpcStatus);
|
|
WinHttpConnection->FreeObject();
|
|
}
|
|
}
|
|
else if (RawChannelNeedsCleanup)
|
|
{
|
|
RawChannel->Abort(RpcStatus);
|
|
RawChannel->FreeObject();
|
|
}
|
|
else if (RawConnectionNeedsCleanup)
|
|
{
|
|
RawConnection->RealAbort();
|
|
}
|
|
|
|
if (MemoryBlock)
|
|
delete [] MemoryBlock;
|
|
|
|
CleanupAndExit:
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2ClientVirtualConnection::InitializeRawConnection (
|
|
IN OUT WS_HTTP2_CONNECTION *RawConnection,
|
|
IN HTTPResolverHint *Hint,
|
|
IN BOOL HintWasInitialized,
|
|
IN ULONG CallTimeout
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialize a raw client connection
|
|
|
|
Arguments:
|
|
|
|
RawConnection - memory for the connection. It is uninitialized
|
|
|
|
Hint - the resolver hint
|
|
|
|
HintWasInitialized - true if the hint was initialized on input
|
|
|
|
CallTimeout - the timeout for the operation
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
RPC_CHAR *ConnectionTargetName;
|
|
USHORT PortToUse;
|
|
|
|
RawConnection->Initialize();
|
|
RawConnection->type = COMPLEX_T | CONNECTION | CLIENT;
|
|
|
|
// initialize raw connection
|
|
if (Hint->AccessType == rpcpatHTTPProxy)
|
|
{
|
|
ConnectionTargetName = new RPC_CHAR [RpcpStringLengthA(Hint->HTTPProxy) + 2];
|
|
if (ConnectionTargetName == NULL)
|
|
{
|
|
RpcStatus = RPC_S_OUT_OF_MEMORY;
|
|
return RpcStatus;
|
|
}
|
|
|
|
FullAnsiToUnicode(Hint->HTTPProxy, ConnectionTargetName);
|
|
|
|
PortToUse = Hint->HTTPProxyPort;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(Hint->AccessType == rpcpatDirect);
|
|
|
|
ConnectionTargetName = new RPC_CHAR [RpcpStringLengthA(Hint->RpcProxy) + 1];
|
|
if (ConnectionTargetName == NULL)
|
|
{
|
|
RpcStatus = RPC_S_OUT_OF_MEMORY;
|
|
return RpcStatus;
|
|
}
|
|
|
|
FullAnsiToUnicode(Hint->RpcProxy, ConnectionTargetName);
|
|
|
|
PortToUse = Hint->RpcProxyPort;
|
|
}
|
|
|
|
RpcStatus = TCPOrHTTP_Open(RawConnection,
|
|
ConnectionTargetName,
|
|
PortToUse,
|
|
ConnectionTimeout,
|
|
0, // SendBufferSize
|
|
0, // RecvBufferSize
|
|
Hint,
|
|
HintWasInitialized,
|
|
CallTimeout,
|
|
TRUE, // fHTTP2Open
|
|
NULL // IsValidMachineFn
|
|
);
|
|
|
|
delete [] ConnectionTargetName;
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
BOOL HTTP2ClientVirtualConnection::IsInChannelKeepAlive (
|
|
IN BOOL IsReplacementChannel
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Checks whether the an in channel supports keep alives.
|
|
Which channel depends on the arguments - see below.
|
|
|
|
Arguments:
|
|
|
|
IsReplacementInChannel - if non-zero, this is a replacement
|
|
channel and the non-default channel will be checked. If 0,
|
|
this is a first channel, and the zero'th channel will be
|
|
checked.
|
|
|
|
Return Value:
|
|
|
|
non-zero - the channel supports keep alives
|
|
0 - the channel doesn't support keep alives
|
|
|
|
--*/
|
|
{
|
|
HTTP2ClientInChannel *InChannel;
|
|
BOOL IsKeepAlive;
|
|
HTTP2ChannelPointer *InChannelPtr;
|
|
|
|
if (IsReplacementChannel)
|
|
InChannelPtr = &InChannels[GetNonDefaultInChannelSelector()];
|
|
else
|
|
InChannelPtr = &InChannels[0];
|
|
|
|
InChannel = (HTTP2ClientInChannel *)InChannelPtr->LockChannelPointer();
|
|
|
|
// nobody can abort the connection here
|
|
ASSERT (InChannel != NULL);
|
|
|
|
IsKeepAlive = InChannel->IsKeepAlive();
|
|
|
|
InChannelPtr->UnlockChannelPointer();
|
|
|
|
return IsKeepAlive;
|
|
}
|
|
|
|
BOOL HTTP2ClientVirtualConnection::IsOutChannelKeepAlive (
|
|
IN BOOL IsReplacementChannel
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Checks whether an out channel supports keep alives
|
|
Which channel depends on the arguments - see below.
|
|
|
|
Arguments:
|
|
|
|
IsReplacementInChannel - if non-zero, this is a replacement
|
|
channel and the non-default channel will be checked. If 0,
|
|
this is a first channel, and the zero'th channel will be
|
|
checked.
|
|
|
|
Return Value:
|
|
|
|
non-zero - the channel supports keep alives
|
|
0 - the channel doesn't support keep alives
|
|
|
|
--*/
|
|
{
|
|
HTTP2ClientOutChannel *OutChannel;
|
|
BOOL IsKeepAlive;
|
|
HTTP2ChannelPointer *OutChannelPtr;
|
|
|
|
if (IsReplacementChannel)
|
|
OutChannelPtr = &OutChannels[GetNonDefaultOutChannelSelector()];
|
|
else
|
|
OutChannelPtr = &OutChannels[0];
|
|
|
|
OutChannel = (HTTP2ClientOutChannel *)OutChannelPtr->LockChannelPointer();
|
|
// nobody can abort the connection here
|
|
ASSERT (OutChannel != NULL);
|
|
|
|
IsKeepAlive = OutChannel->IsKeepAlive();
|
|
|
|
OutChannelPtr->UnlockChannelPointer();
|
|
|
|
return IsKeepAlive;
|
|
}
|
|
|
|
ULONG HTTP2ClientVirtualConnection::GetInChannelChosenScheme (
|
|
IN BOOL IsReplacementChannel
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Gets the chosen scheme for the an in channel
|
|
Which channel depends on the arguments - see below.
|
|
|
|
Arguments:
|
|
|
|
IsReplacementInChannel - if non-zero, this is a replacement
|
|
channel and the non-default channel will be checked. If 0,
|
|
this is a first channel, and the zero'th channel will be
|
|
checked.
|
|
|
|
Return Value:
|
|
|
|
Chosen scheme
|
|
|
|
--*/
|
|
{
|
|
HTTP2ClientInChannel *InChannel;
|
|
ULONG ChosenScheme;
|
|
HTTP2ChannelPointer *InChannelPtr;
|
|
|
|
if (IsReplacementChannel)
|
|
InChannelPtr = &InChannels[GetNonDefaultInChannelSelector()];
|
|
else
|
|
InChannelPtr = &InChannels[0];
|
|
|
|
InChannel = (HTTP2ClientInChannel *)InChannelPtr->LockChannelPointer();
|
|
// nobody can abort the connection here
|
|
ASSERT (InChannel != NULL);
|
|
|
|
ChosenScheme = InChannel->GetChosenAuthScheme();
|
|
|
|
InChannelPtr->UnlockChannelPointer();
|
|
|
|
return ChosenScheme;
|
|
}
|
|
|
|
ULONG HTTP2ClientVirtualConnection::GetOutChannelChosenScheme (
|
|
IN BOOL IsReplacementChannel
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Gets the chosen scheme for an out channel
|
|
Which channel depends on the arguments - see below.
|
|
|
|
Arguments:
|
|
|
|
IsReplacementInChannel - if non-zero, this is a replacement
|
|
channel and the non-default channel will be checked. If 0,
|
|
this is a first channel, and the zero'th channel will be
|
|
checked.
|
|
|
|
Return Value:
|
|
|
|
Chosen scheme
|
|
|
|
--*/
|
|
{
|
|
HTTP2ClientOutChannel *OutChannel;
|
|
ULONG ChosenScheme;
|
|
HTTP2ChannelPointer *OutChannelPtr;
|
|
|
|
if (IsReplacementChannel)
|
|
OutChannelPtr = &OutChannels[GetNonDefaultOutChannelSelector()];
|
|
else
|
|
OutChannelPtr = &OutChannels[0];
|
|
|
|
OutChannel = (HTTP2ClientOutChannel *)OutChannelPtr->LockChannelPointer();
|
|
// nobody can abort the connection here
|
|
ASSERT (OutChannel != NULL);
|
|
|
|
ChosenScheme = OutChannel->GetChosenAuthScheme();
|
|
|
|
OutChannelPtr->UnlockChannelPointer();
|
|
|
|
return ChosenScheme;
|
|
}
|
|
|
|
BOOL HTTP2ClientVirtualConnection::IsInChannelPositiveWithWait (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Checks if the in channel has come in with positive result. If
|
|
not, it waits a bit and tries again. If not again, return FALSE.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
non-zero if the in channel came in positive.
|
|
0 otherwise.
|
|
|
|
--*/
|
|
{
|
|
if ((InOpenStatus == RPC_S_OK)
|
|
|| (InOpenStatus == RPC_P_AUTH_NEEDED))
|
|
return TRUE;
|
|
|
|
Sleep(100);
|
|
|
|
return ((InOpenStatus == RPC_S_OK)
|
|
|| (InOpenStatus == RPC_P_AUTH_NEEDED));
|
|
}
|
|
|
|
/*********************************************************************
|
|
Switching Layer
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS
|
|
RPC_ENTRY
|
|
HTTP_SyncRecv(
|
|
IN RPC_TRANSPORT_CONNECTION ThisConnection,
|
|
OUT BUFFER *pBuffer,
|
|
OUT PUINT pBufferLength,
|
|
IN DWORD dwTimeout
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Perform a sync recv on an HTTP connection. Part of the HTTP switching
|
|
layer between old mode and new mode.
|
|
|
|
Arguments:
|
|
|
|
ThisConnection - transport connection
|
|
Buffer - if successful, points to a buffer containing the next PDU.
|
|
BufferLength - if successful, contains the length of the message.
|
|
Timeout - the amount of time to wait for the receive. If -1, we wait
|
|
infinitely.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
BASE_ASYNC_OBJECT *BaseObject = (BASE_ASYNC_OBJECT *) ThisConnection;
|
|
HTTP2VirtualConnection *VirtualConnection;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
if (BaseObject->id == HTTP)
|
|
{
|
|
return WS_SyncRecv(ThisConnection,
|
|
pBuffer,
|
|
pBufferLength,
|
|
dwTimeout
|
|
);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(BaseObject->id == HTTPv2);
|
|
VirtualConnection = (HTTP2VirtualConnection *)ThisConnection;
|
|
|
|
RpcStatus = VirtualConnection->SyncRecv((BYTE **)pBuffer,
|
|
(ULONG *)pBufferLength,
|
|
dwTimeout);
|
|
|
|
VALIDATE(RpcStatus)
|
|
{
|
|
RPC_S_OK,
|
|
RPC_S_OUT_OF_MEMORY,
|
|
RPC_S_OUT_OF_RESOURCES,
|
|
RPC_P_RECEIVE_FAILED,
|
|
RPC_S_CALL_CANCELLED,
|
|
RPC_P_SEND_FAILED,
|
|
RPC_P_CONNECTION_SHUTDOWN,
|
|
RPC_P_TIMEOUT
|
|
}
|
|
CORRUPTION_VALIDATE
|
|
{
|
|
RPC_S_PROTOCOL_ERROR
|
|
} CORRUPTION_END_VALIDATE;
|
|
|
|
return RpcStatus;
|
|
}
|
|
}
|
|
|
|
RPC_STATUS
|
|
RPC_ENTRY
|
|
HTTP_Abort (
|
|
IN RPC_TRANSPORT_CONNECTION Connection
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Aborts an HTTP connection. Part of the HTTP switching
|
|
layer between old mode and new mode.
|
|
|
|
Arguments:
|
|
|
|
Connection - transport connection
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK
|
|
|
|
--*/
|
|
{
|
|
BASE_ASYNC_OBJECT *BaseObject = (BASE_ASYNC_OBJECT *) Connection;
|
|
HTTP2VirtualConnection *VirtualConnection;
|
|
|
|
if (BaseObject->id == HTTP)
|
|
{
|
|
return WS_Abort(Connection);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(BaseObject->id == HTTPv2);
|
|
VirtualConnection = (HTTP2VirtualConnection *)Connection;
|
|
|
|
if ((VirtualConnection->type & TYPE_MASK) == CLIENT)
|
|
{
|
|
((HTTP2ClientVirtualConnection *)VirtualConnection)->HTTP2ClientVirtualConnection::Abort();
|
|
}
|
|
else
|
|
{
|
|
ASSERT((VirtualConnection->type & TYPE_MASK) == SERVER);
|
|
((HTTP2ServerVirtualConnection *)VirtualConnection)->HTTP2ServerVirtualConnection::Abort();
|
|
}
|
|
return RPC_S_OK;
|
|
}
|
|
}
|
|
|
|
RPC_STATUS
|
|
RPC_ENTRY
|
|
HTTP_Close (
|
|
IN RPC_TRANSPORT_CONNECTION ThisConnection,
|
|
IN BOOL fDontFlush
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Aborts an HTTP connection. Part of the HTTP switching
|
|
layer between old mode and new mode.
|
|
|
|
Arguments:
|
|
|
|
ThisConnection - transport connection
|
|
DontFlush - non-zero if all buffers need to be flushed
|
|
before closing the connection. Zero otherwise.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK
|
|
|
|
--*/
|
|
{
|
|
BASE_ASYNC_OBJECT *BaseObject = (BASE_ASYNC_OBJECT *) ThisConnection;
|
|
HTTP2VirtualConnection *VirtualConnection;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
LOG_FN_OPERATION_ENTRY(HTTP2LOG_OPERATION_CLOSE, HTTP2LOG_OT_CALLBACK, (ULONG_PTR)ThisConnection);
|
|
|
|
#if DBG
|
|
// verify the list is still ok
|
|
TransportProtocol::AssertProtocolListIntegrity(BaseObject);
|
|
#endif // DBG
|
|
|
|
if (BaseObject->id == HTTP)
|
|
{
|
|
RpcStatus = WS_Close(ThisConnection,
|
|
fDontFlush
|
|
);
|
|
}
|
|
else if (BaseObject->id == INVALID_PROTOCOL_ID)
|
|
{
|
|
// object was never completely initialized - just return
|
|
RpcStatus = RPC_S_OK;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(BaseObject->id == HTTPv2);
|
|
// the object may have been destroyed by now - cannot use
|
|
// virtual functions. Determine statically the type
|
|
// and call the function to cleanup (it knows how to
|
|
// deal with destroyed objects)
|
|
VirtualConnection = (HTTP2VirtualConnection *)ThisConnection;
|
|
|
|
if ((VirtualConnection->type & TYPE_MASK) == CLIENT)
|
|
{
|
|
((HTTP2ClientVirtualConnection *)VirtualConnection)->HTTP2ClientVirtualConnection::Close(fDontFlush);
|
|
}
|
|
else
|
|
{
|
|
ASSERT((VirtualConnection->type & TYPE_MASK) == SERVER);
|
|
((HTTP2ServerVirtualConnection *)VirtualConnection)->HTTP2ServerVirtualConnection::Close(fDontFlush);
|
|
}
|
|
RpcStatus = RPC_S_OK;
|
|
}
|
|
|
|
LOG_FN_OPERATION_EXIT(HTTP2LOG_OPERATION_CLOSE, HTTP2LOG_OT_CALLBACK, (ULONG_PTR)BaseObject->id);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
|
|
RPC_STATUS
|
|
RPC_ENTRY
|
|
HTTP_Send(
|
|
RPC_TRANSPORT_CONNECTION ThisConnection,
|
|
UINT Length,
|
|
BUFFER Buffer,
|
|
PVOID SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Does a send on an HTTP connection. Part of the HTTP switching
|
|
layer between old mode and new mode.
|
|
|
|
Arguments:
|
|
|
|
ThisConnection - The connection to send the data on.
|
|
Length - The length of the data to send.
|
|
Buffer - The data to send.
|
|
SendContext - A buffer to use as the HTTP2SendContext for
|
|
this operation.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
BASE_ASYNC_OBJECT *BaseObject = (BASE_ASYNC_OBJECT *) ThisConnection;
|
|
HTTP2VirtualConnection *VirtualConnection;
|
|
|
|
if (BaseObject->id == HTTP)
|
|
{
|
|
return CO_Send(ThisConnection,
|
|
Length,
|
|
Buffer,
|
|
SendContext
|
|
);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(BaseObject->id == HTTPv2);
|
|
VirtualConnection = (HTTP2VirtualConnection *)ThisConnection;
|
|
|
|
return VirtualConnection->Send(Length,
|
|
Buffer,
|
|
SendContext
|
|
);
|
|
}
|
|
}
|
|
|
|
RPC_STATUS
|
|
RPC_ENTRY
|
|
HTTP_Recv(
|
|
RPC_TRANSPORT_CONNECTION ThisConnection
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Does a receive on an HTTP connection. Part of the HTTP switching
|
|
layer between old mode and new mode.
|
|
|
|
Arguments:
|
|
|
|
ThisConnection - A connection without a read pending on it.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
BASE_ASYNC_OBJECT *BaseObject = (BASE_ASYNC_OBJECT *) ThisConnection;
|
|
HTTP2VirtualConnection *VirtualConnection;
|
|
|
|
if (BaseObject->id == HTTP)
|
|
{
|
|
return CO_Recv(ThisConnection);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(BaseObject->id == HTTPv2);
|
|
VirtualConnection = (HTTP2VirtualConnection *)ThisConnection;
|
|
|
|
return VirtualConnection->Receive();
|
|
}
|
|
}
|
|
|
|
RPC_STATUS
|
|
RPC_ENTRY
|
|
HTTP_SyncSend(
|
|
IN RPC_TRANSPORT_CONNECTION Connection,
|
|
IN UINT BufferLength,
|
|
IN BUFFER Buffer,
|
|
IN BOOL fDisableShutdownCheck,
|
|
IN BOOL fDisableCancelCheck,
|
|
ULONG Timeout
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Does a sync send on an HTTP connection. Part of the HTTP switching
|
|
layer between old mode and new mode.
|
|
|
|
Arguments:
|
|
|
|
Connection - The connection to send on.
|
|
BufferLength - The size of the buffer.
|
|
Buffer - The data to sent.
|
|
fDisableShutdownCheck - Normally FALSE, when true this disables
|
|
the transport check for async shutdown PDUs.
|
|
fDisableCancelCheck - runtime indicates no cancel
|
|
will be attempted on this send. Can be used
|
|
as optimization hint by the transport
|
|
Timeout - send timeout (call timeout)
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
BASE_ASYNC_OBJECT *BaseObject = (BASE_ASYNC_OBJECT *) Connection;
|
|
HTTP2VirtualConnection *VirtualConnection;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
if (BaseObject->id == HTTP)
|
|
{
|
|
RpcStatus = WS_SyncSend(Connection,
|
|
BufferLength,
|
|
Buffer,
|
|
fDisableShutdownCheck,
|
|
fDisableCancelCheck,
|
|
Timeout
|
|
);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(BaseObject->id == HTTPv2);
|
|
VirtualConnection = (HTTP2VirtualConnection *)Connection;
|
|
|
|
RpcStatus = VirtualConnection->SyncSend(BufferLength,
|
|
Buffer,
|
|
fDisableShutdownCheck,
|
|
fDisableCancelCheck,
|
|
Timeout
|
|
);
|
|
}
|
|
|
|
VALIDATE(RpcStatus)
|
|
{
|
|
RPC_S_OK,
|
|
RPC_S_OUT_OF_MEMORY,
|
|
RPC_S_OUT_OF_RESOURCES,
|
|
RPC_P_RECEIVE_FAILED,
|
|
RPC_S_CALL_CANCELLED,
|
|
RPC_P_SEND_FAILED,
|
|
RPC_P_CONNECTION_SHUTDOWN,
|
|
RPC_P_TIMEOUT
|
|
} END_VALIDATE;
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS
|
|
RPC_ENTRY
|
|
HTTP_TurnOnOffKeepAlives (
|
|
IN RPC_TRANSPORT_CONNECTION ThisConnection,
|
|
IN BOOL TurnOn,
|
|
IN BOOL bProtectIO,
|
|
IN KEEPALIVE_TIMEOUT_UNITS Units,
|
|
IN OUT KEEPALIVE_TIMEOUT KATime,
|
|
IN ULONG KAInterval OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Turns on keep alives for an HTTP connection. Part of the HTTP switching
|
|
layer between old mode and new mode.
|
|
|
|
Arguments:
|
|
|
|
ThisConnection - The connection to turn keep alives on on.
|
|
TurnOn - if non-zero, keep alives are turned on. If zero, keep alives
|
|
are turned off.
|
|
bProtectIO - non-zero if IO needs to be protected against async close
|
|
of the connection.
|
|
Units - in what units is KATime
|
|
KATime - how much to wait before turning on keep alives
|
|
KAInterval - the interval between keep alives
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* / Win32 errors on failure
|
|
|
|
Note:
|
|
|
|
If we use it on the server, we must protect
|
|
the connection against async aborts.
|
|
|
|
--*/
|
|
{
|
|
BASE_ASYNC_OBJECT *BaseObject = (BASE_ASYNC_OBJECT *) ThisConnection;
|
|
HTTP2VirtualConnection *VirtualConnection;
|
|
|
|
if (BaseObject->id == HTTP)
|
|
{
|
|
return WS_TurnOnOffKeepAlives(ThisConnection,
|
|
TurnOn,
|
|
bProtectIO,
|
|
Units,
|
|
KATime,
|
|
KAInterval
|
|
);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(BaseObject->id == HTTPv2);
|
|
VirtualConnection = (HTTP2VirtualConnection *)ThisConnection;
|
|
|
|
// don't bother if the connection is aborted. Besides shorter
|
|
// execution, this avoids trouble where we try to perform the
|
|
// operation on an aborted connection and we run into all
|
|
// sorts of problems.
|
|
if (VirtualConnection->IsAborted())
|
|
return RPC_S_OK;
|
|
|
|
return VirtualConnection->TurnOnOffKeepAlives(TurnOn,
|
|
bProtectIO,
|
|
FALSE, // IsFromUpcall
|
|
Units,
|
|
KATime,
|
|
KAInterval
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
RPC_STATUS
|
|
RPC_ENTRY
|
|
HTTP_QueryClientAddress (
|
|
IN RPC_TRANSPORT_CONNECTION ThisConnection,
|
|
OUT RPC_CHAR **pNetworkAddress
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Returns the IP address of the client on a connection as a string.
|
|
|
|
Arguments:
|
|
|
|
NetworkAddress - Will contain string on success.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
BASE_ASYNC_OBJECT *BaseObject = (BASE_ASYNC_OBJECT *) ThisConnection;
|
|
HTTP2VirtualConnection *VirtualConnection;
|
|
|
|
if (BaseObject->id == HTTP)
|
|
{
|
|
return TCP_QueryClientAddress(ThisConnection,
|
|
pNetworkAddress
|
|
);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(BaseObject->id == HTTPv2);
|
|
VirtualConnection = (HTTP2VirtualConnection *)ThisConnection;
|
|
|
|
return VirtualConnection->QueryClientAddress(pNetworkAddress);
|
|
}
|
|
}
|
|
|
|
|
|
RPC_STATUS
|
|
RPC_ENTRY
|
|
HTTP_QueryLocalAddress (
|
|
IN RPC_TRANSPORT_CONNECTION ThisConnection,
|
|
IN OUT void *Buffer,
|
|
IN OUT unsigned long *BufferSize,
|
|
OUT unsigned long *AddressFormat
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Returns the local IP address of a connection.
|
|
|
|
Arguments:
|
|
|
|
Buffer - The buffer that will receive the output address
|
|
|
|
BufferSize - the size of the supplied Buffer on input. On output the
|
|
number of bytes written to the buffer. If the buffer is too small
|
|
to receive all the output data, ERROR_MORE_DATA is returned,
|
|
nothing is written to the buffer, and BufferSize is set to
|
|
the size of the buffer needed to return all the data.
|
|
|
|
AddressFormat - a constant indicating the format of the returned address.
|
|
Currently supported are RPC_P_ADDR_FORMAT_TCP_IPV4 and
|
|
RPC_P_ADDR_FORMAT_TCP_IPV6. Undefined on failure.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
BASE_ASYNC_OBJECT *BaseObject = (BASE_ASYNC_OBJECT *) ThisConnection;
|
|
HTTP2VirtualConnection *VirtualConnection;
|
|
|
|
if (BaseObject->id == HTTP)
|
|
{
|
|
return TCP_QueryLocalAddress(ThisConnection,
|
|
Buffer,
|
|
BufferSize,
|
|
AddressFormat
|
|
);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(BaseObject->id == HTTPv2);
|
|
VirtualConnection = (HTTP2VirtualConnection *)ThisConnection;
|
|
|
|
return VirtualConnection->QueryLocalAddress(Buffer,
|
|
BufferSize,
|
|
AddressFormat
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
RPC_STATUS
|
|
RPC_ENTRY
|
|
HTTP_QueryClientId(
|
|
IN RPC_TRANSPORT_CONNECTION ThisConnection,
|
|
OUT RPC_CLIENT_PROCESS_IDENTIFIER *ClientProcess
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
For secure protocols (which TCP/IP is not) this is supposed to
|
|
give an ID which will be shared by all clients from the same
|
|
process. This prevents one user from grabbing another users
|
|
association group and using their context handles.
|
|
|
|
Since TCP/IP is not secure we return the IP address of the
|
|
client machine. This limits the attacks to other processes
|
|
running on the client machine which is better than nothing.
|
|
|
|
Arguments:
|
|
|
|
ClientProcess - Transport identification of the "client".
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
BASE_ASYNC_OBJECT *BaseObject = (BASE_ASYNC_OBJECT *) ThisConnection;
|
|
HTTP2VirtualConnection *VirtualConnection;
|
|
|
|
if (BaseObject->id == HTTP)
|
|
{
|
|
return TCP_QueryClientId(ThisConnection,
|
|
ClientProcess
|
|
);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(BaseObject->id == HTTPv2);
|
|
VirtualConnection = (HTTP2VirtualConnection *)ThisConnection;
|
|
|
|
return VirtualConnection->QueryClientId(ClientProcess);
|
|
}
|
|
}
|
|
|
|
RPC_STATUS
|
|
RPC_ENTRY
|
|
HTTP_QueryClientIpAddress (
|
|
IN RPC_TRANSPORT_CONNECTION ThisConnection,
|
|
IN OUT RPC_CLIENT_IP_ADDRESS *ClientIpAddress
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Returns the IP address of the client on a connection as a SOCKADDR.
|
|
|
|
Arguments:
|
|
|
|
ThisConnection - The server connection of interest.
|
|
|
|
ClientIpAddress - the buffer to store the address to.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
BASE_ASYNC_OBJECT *BaseObject = (BASE_ASYNC_OBJECT *) ThisConnection;
|
|
HTTP2VirtualConnection *VirtualConnection;
|
|
|
|
if (BaseObject->id == HTTP)
|
|
{
|
|
return TCP_QueryClientIpAddress(ThisConnection,
|
|
ClientIpAddress
|
|
);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(BaseObject->id == HTTPv2);
|
|
VirtualConnection = (HTTP2VirtualConnection *)ThisConnection;
|
|
|
|
return VirtualConnection->QueryClientIpAddress(ClientIpAddress);
|
|
}
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP Transport Interface
|
|
*********************************************************************/
|
|
|
|
const int HTTPClientConnectionSize = max(sizeof(WS_CLIENT_CONNECTION), sizeof(WS_SAN_CLIENT_CONNECTION));
|
|
const int HTTP2ClientConnectionSize = sizeof(HTTP2ClientVirtualConnection);
|
|
|
|
const int HTTPServerConnectionSize = max(sizeof(WS_CONNECTION), sizeof(WS_SAN_CONNECTION));
|
|
const int HTTP2ServerConnectionSize = max(sizeof(HTTP2ServerVirtualConnection), sizeof(WS_HTTP2_INITIAL_CONNECTION));
|
|
|
|
const RPC_CONNECTION_TRANSPORT
|
|
HTTP_TransportInterface =
|
|
{
|
|
RPC_TRANSPORT_INTERFACE_VERSION,
|
|
HTTP_TOWER_ID,
|
|
HTTP_ADDRESS_ID,
|
|
RPC_STRING_LITERAL("ncacn_http"),
|
|
"593",
|
|
COMMON_ProcessCalls,
|
|
|
|
COMMON_StartPnpNotifications,
|
|
COMMON_ListenForPNPNotifications,
|
|
|
|
COMMON_TowerConstruct,
|
|
COMMON_TowerExplode,
|
|
COMMON_PostRuntimeEvent,
|
|
FALSE,
|
|
WS_GetNetworkAddressVector,
|
|
sizeof(WS_ADDRESS),
|
|
max(HTTPClientConnectionSize, HTTP2ClientConnectionSize),
|
|
max(HTTPServerConnectionSize, HTTP2ServerConnectionSize),
|
|
sizeof(HTTP2SendContext),
|
|
sizeof(HTTPResolverHint),
|
|
HTTP_MAX_SEND,
|
|
HTTP_Initialize,
|
|
0, // InitComplete,
|
|
HTTP_Open,
|
|
0, // No SendRecv on winsock
|
|
HTTP_SyncRecv,
|
|
HTTP_Abort,
|
|
HTTP_Close,
|
|
HTTP_Send,
|
|
HTTP_Recv,
|
|
HTTP_SyncSend,
|
|
HTTP_TurnOnOffKeepAlives,
|
|
HTTP_ServerListen,
|
|
WS_ServerAbortListen,
|
|
COMMON_ServerCompleteListen,
|
|
HTTP_QueryClientAddress,
|
|
HTTP_QueryLocalAddress,
|
|
HTTP_QueryClientId,
|
|
HTTP_QueryClientIpAddress,
|
|
0, // Impersonate
|
|
0, // Revert
|
|
HTTP_FreeResolverHint,
|
|
HTTP_CopyResolverHint,
|
|
HTTP_CompareResolverHint,
|
|
HTTP_SetLastBufferToFree
|
|
};
|
|
|
|
|
|
/*********************************************************************
|
|
HTTP2ProxyServerSideChannel
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS HTTP2ProxyServerSideChannel::InitializeRawConnection (
|
|
IN RPC_CHAR *ServerName,
|
|
IN USHORT ServerPort,
|
|
IN ULONG ConnectionTimeout,
|
|
IN I_RpcProxyIsValidMachineFn IsValidMachineFn
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initializes a raw connection.
|
|
|
|
Arguments:
|
|
|
|
ServerName - the server to connect to.
|
|
|
|
ServerPort - which port on that server to use
|
|
|
|
ConnectionTimeout - the connection timeout to use.
|
|
|
|
IsValidMachineFn - a callback function to verify if the
|
|
target machine/port are not blocked
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* for error
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
HTTPResolverHint DummyHint;
|
|
|
|
RpcStatus = TCPOrHTTP_Open(RawConnection,
|
|
ServerName,
|
|
ServerPort,
|
|
ConnectionTimeout,
|
|
0, // SendBufferSize
|
|
0, // RecvBufferSize
|
|
&DummyHint,
|
|
FALSE, // HintWasInitialized
|
|
10 * 60 * 60, // 10 minutes
|
|
TRUE, // fHTTP2Open
|
|
IsValidMachineFn
|
|
);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2TimeoutTargetConnection
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS HTTP2TimeoutTargetConnection::SetTimeout (
|
|
IN ULONG Timeout,
|
|
IN TimerContext *pTimer
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Called to setup a one time timer. Caller must make
|
|
sure this function is synchronized with CancelTimeout and
|
|
must make sure we don't schedule a timer behind an aborting
|
|
thread (i.e. a timer fires after the object goes away).
|
|
|
|
Arguments:
|
|
|
|
Timeout - interval before the timer fires
|
|
|
|
pTimer - which timer do we refer to.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
BOOL Result;
|
|
|
|
VerifyValidTimer(pTimer);
|
|
|
|
VerifyTimerNotSet(pTimer);
|
|
|
|
Result = CreateTimerQueueTimer(&(pTimer->Handle),
|
|
NULL,
|
|
HTTP2TimeoutTimerCallback,
|
|
pTimer,
|
|
Timeout, // time to first fire
|
|
0, // periodic interval
|
|
WT_EXECUTELONGFUNCTION
|
|
);
|
|
|
|
if (Result == FALSE)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
void HTTP2TimeoutTargetConnection::CancelTimeout (
|
|
IN TimerContext *pTimer
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Called to cancel a timer. The function will not return until
|
|
the timer callbacks have been drained. Caller must ensure
|
|
that this method is synchronized with SetTimeout
|
|
|
|
Arguments:
|
|
|
|
pTimer - which timer do we refer to.
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
HANDLE LocalTimerHandle;
|
|
BOOL Result;
|
|
unsigned int Retries = 0;
|
|
|
|
VerifyValidTimer(pTimer);
|
|
|
|
LocalTimerHandle = InterlockedExchangePointer(&(pTimer->Handle), NULL);
|
|
|
|
// if the timer already fired there is nothing to do here.
|
|
if (LocalTimerHandle == NULL)
|
|
return;
|
|
|
|
do
|
|
{
|
|
Result = DeleteTimerQueueTimer(NULL,
|
|
LocalTimerHandle,
|
|
INVALID_HANDLE_VALUE // tell the timer function to wait for all callbacks
|
|
// to complete before returning
|
|
);
|
|
|
|
//
|
|
// This cannot fail unless we give it invalid parameters or
|
|
// we are out of memory. Retry on out of memory since there is nothing we can do.
|
|
// If after a few attempts we still fail, we need to halt to avoid a later AV.
|
|
//
|
|
// From Rob Earhart:
|
|
// 485863 covers one of the allocs... the entire module is pretty bad code;
|
|
// I'm planning to just replace it (and the API, too).
|
|
//
|
|
|
|
if (!Result)
|
|
{
|
|
ASSERT(GetLastError() == ERROR_NOT_ENOUGH_MEMORY);
|
|
DbgPrint("RPC: DeleteTimerQueueTimer failed: %d\n", GetLastError());
|
|
Sleep(10);
|
|
}
|
|
Retries++;
|
|
}
|
|
while (!Result && Retries < 10);
|
|
|
|
if (!Result)
|
|
{
|
|
DbgPrint("RPC: DeleteTimerQueueTimer repeatedly failed - execution can't proceed.\n");
|
|
DbgBreakPoint();
|
|
}
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2ProxyVirtualConnection
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS HTTP2ProxyVirtualConnection::SendComplete (
|
|
IN RPC_STATUS EventStatus,
|
|
IN OUT HTTP2SendContext *SendContext,
|
|
IN int ChannelId
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Called by lower layers to indicate send complete.
|
|
|
|
Arguments:
|
|
|
|
EventStatus - status of the operation
|
|
|
|
SendContext - the context for the send complete
|
|
|
|
ChannelId - which channel completed the operation
|
|
|
|
Return Value:
|
|
|
|
RPC_P_PACKET_CONSUMED if the packet was consumed and should
|
|
be hidden from the runtime.
|
|
RPC_S_OK if the packet was processed successfully.
|
|
RPC_S_* error if there was an error while processing the
|
|
packet.
|
|
|
|
--*/
|
|
{
|
|
HTTP2ChannelPointer *ChannelPtr;
|
|
HTTP2InProxyInChannel *InProxyInChannel;
|
|
HTTP2OutProxyInChannel *OutProxyInChannel;
|
|
BOOL LocalIsInProxy;
|
|
BOOL IssueAck;
|
|
ULONG BytesReceivedForAck;
|
|
ULONG WindowForAck;
|
|
BOOL UnlockPointer;
|
|
|
|
VerifyValidChannelId(ChannelId);
|
|
|
|
if ((EventStatus == RPC_S_OK)
|
|
&& (SendContext->TrafficType == http2ttData))
|
|
{
|
|
ChannelPtr = MapSendContextUserDataToChannelPtr(SendContext->UserData);
|
|
if (ChannelPtr)
|
|
{
|
|
UnlockPointer = FALSE;
|
|
IssueAck = FALSE;
|
|
LocalIsInProxy = IsInProxy();
|
|
if (LocalIsInProxy)
|
|
{
|
|
InProxyInChannel = (HTTP2InProxyInChannel *)ChannelPtr->LockChannelPointer();
|
|
if (InProxyInChannel)
|
|
{
|
|
UnlockPointer = TRUE;
|
|
InProxyInChannel->BytesConsumedNotification (SendContext->maxWriteBuffer,
|
|
FALSE, // OwnsMutex
|
|
&IssueAck,
|
|
&BytesReceivedForAck,
|
|
&WindowForAck
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OutProxyInChannel = (HTTP2OutProxyInChannel *)ChannelPtr->LockChannelPointer();
|
|
if (OutProxyInChannel)
|
|
{
|
|
UnlockPointer = TRUE;
|
|
OutProxyInChannel->BytesConsumedNotification (SendContext->maxWriteBuffer,
|
|
FALSE, // OwnsMutex
|
|
&IssueAck,
|
|
&BytesReceivedForAck,
|
|
&WindowForAck
|
|
);
|
|
}
|
|
}
|
|
|
|
if (IssueAck)
|
|
{
|
|
// we need to issue a flow control ack to the peer. InProxy uses
|
|
// forwarding, out proxy sends ack directly
|
|
if (LocalIsInProxy)
|
|
{
|
|
EventStatus = InProxyInChannel->ForwardFlowControlAck(
|
|
BytesReceivedForAck,
|
|
WindowForAck
|
|
);
|
|
}
|
|
else
|
|
{
|
|
EventStatus = OutProxyInChannel->ForwardFlowControlAck(
|
|
BytesReceivedForAck,
|
|
WindowForAck
|
|
);
|
|
}
|
|
|
|
#if DBG_ERROR
|
|
DbgPrint("%s proxy issuing flow control ack: %d, %d\n",
|
|
LocalIsInProxy ? "IN" : "OUT",
|
|
BytesReceivedForAck, WindowForAck);
|
|
#endif
|
|
}
|
|
|
|
if (UnlockPointer)
|
|
ChannelPtr->UnlockChannelPointer();
|
|
}
|
|
else
|
|
{
|
|
#if DBG
|
|
DbgPrint("RPCRT4: %d: Channel for User Data %d on connection %p not found\n",
|
|
GetCurrentProcessId(),
|
|
SendContext->UserData,
|
|
this
|
|
);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// successful sends are no-op. Failed sends cause abort. Just
|
|
// turn around the operation status after freeing the packet
|
|
FreeSendContextAndPossiblyData(SendContext);
|
|
|
|
// ok to return any error code. See Rule 12
|
|
return EventStatus;
|
|
}
|
|
|
|
void HTTP2ProxyVirtualConnection::Abort (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Aborts an HTTP connection and disconnects the channels.
|
|
Must only come from neutral context.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_ABORT, HTTP2LOG_OT_PROXY_VC, 0);
|
|
|
|
// abort the channels themselves
|
|
AbortChannels(RPC_P_CONNECTION_SHUTDOWN);
|
|
|
|
// we got to the destructive phase of the abort
|
|
// guard against double aborts
|
|
if (Aborted.Increment() > 1)
|
|
return;
|
|
|
|
DisconnectChannels(FALSE, 0);
|
|
}
|
|
|
|
void HTTP2ProxyVirtualConnection::DisconnectChannels (
|
|
IN BOOL ExemptChannel,
|
|
IN int ExemptChannelId
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Disconnects all channels. Must be called from runtime
|
|
or neutral context. Cannot be called from upcall or
|
|
submit context unless an exempt channel is given
|
|
Note that call must synchronize to ensure we're the only
|
|
thread doing the disconnect
|
|
|
|
Arguments:
|
|
|
|
ExemptChannel - non-zero if ExemptChannelId contains a
|
|
valid exempt channel id. FALSE otherwise.
|
|
|
|
ExemptChannelId - if ExemptChannel is non-zero, this argument
|
|
is the id of a channel that will be disconnected, but not
|
|
synchronized with up calls.
|
|
If ExampleChannel is FALSE, this argument is undefined
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
while (RundownBlock.GetInteger() > 0)
|
|
{
|
|
Sleep(2);
|
|
}
|
|
|
|
RemoveConnectionFromCookieCollection();
|
|
|
|
HTTP2VirtualConnection::DisconnectChannels(ExemptChannel,
|
|
ExemptChannelId
|
|
);
|
|
|
|
// cancel the timeouts after we have disconnected the channels.
|
|
// Since all timeouts are setup within upcalls
|
|
CancelAllTimeouts();
|
|
}
|
|
|
|
RPC_STATUS HTTP2ProxyVirtualConnection::AddConnectionToCookieCollection (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Adds this virtual connection to the cookie collection
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
CookieCollection *ProxyCookieCollection;
|
|
HTTP2VirtualConnection *ExistingConnection;
|
|
|
|
if (IsInProxy())
|
|
ProxyCookieCollection = GetInProxyCookieCollection();
|
|
else
|
|
ProxyCookieCollection = GetOutProxyCookieCollection();
|
|
|
|
ProxyConnectionCookie = new HTTP2ServerCookie(EmbeddedConnectionCookie);
|
|
if (ProxyConnectionCookie == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
|
|
ProxyConnectionCookie->SetConnection(this);
|
|
IsConnectionInCollection = TRUE;
|
|
|
|
ProxyCookieCollection->LockCollection();
|
|
ExistingConnection = ProxyCookieCollection->FindElement(&EmbeddedConnectionCookie);
|
|
|
|
if (ExistingConnection == NULL)
|
|
ProxyCookieCollection->AddElement(ProxyConnectionCookie);
|
|
|
|
ProxyCookieCollection->UnlockCollection();
|
|
|
|
if (ExistingConnection)
|
|
return RPC_S_PROTOCOL_ERROR;
|
|
else
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
void HTTP2ProxyVirtualConnection::RemoveConnectionFromCookieCollection (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Removes this virtual connection from the cookie collection
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
Note:
|
|
|
|
This function must be called exactly once and is not thread safe.
|
|
|
|
--*/
|
|
{
|
|
CookieCollection *ProxyCookieCollection;
|
|
BOOL DeleteProxyConnectionCookie;
|
|
|
|
if (IsConnectionInCollection)
|
|
{
|
|
ASSERT(ProxyConnectionCookie);
|
|
DeleteProxyConnectionCookie = FALSE;
|
|
|
|
if (IsInProxy())
|
|
ProxyCookieCollection = GetInProxyCookieCollection();
|
|
else
|
|
ProxyCookieCollection = GetOutProxyCookieCollection();
|
|
|
|
ProxyCookieCollection->LockCollection();
|
|
|
|
if (ProxyConnectionCookie->RemoveRefCount())
|
|
{
|
|
ProxyCookieCollection->RemoveElement(ProxyConnectionCookie);
|
|
DeleteProxyConnectionCookie = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// the only we we can have a non-zero refcount is if the same
|
|
// machine fakes a web farm.
|
|
ASSERT(ActAsSeparateMachinesOnWebFarm);
|
|
}
|
|
|
|
ProxyCookieCollection->UnlockCollection();
|
|
|
|
if (DeleteProxyConnectionCookie)
|
|
{
|
|
// delete is outside the lock
|
|
delete ProxyConnectionCookie;
|
|
}
|
|
}
|
|
}
|
|
|
|
void HTTP2ProxyVirtualConnection::TimeoutExpired (
|
|
IN TimerContext *pTimer
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
A timeout expired before we cancelled the timer. Abort the connection.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
BOOL Result;
|
|
|
|
Result = AbortAndDestroy(FALSE, // IsFromChannel
|
|
0, // CallingChannelId
|
|
RPC_P_TIMEOUT
|
|
);
|
|
|
|
// if somebody is already destroying it, just return
|
|
if (Result == FALSE)
|
|
return;
|
|
|
|
// now 'this' is a pointer disconnected from everybody
|
|
// that we can destroy at our leisure
|
|
delete this;
|
|
|
|
// Once we mark the timer as expired, we are no longer protected from
|
|
// the virtual connection being freed during the timer callback.
|
|
// We can't touch the virtual connection after this call.
|
|
TimerExpiredNotify(pTimer);
|
|
}
|
|
|
|
RPC_STATUS HTTP2ProxyVirtualConnection::ProxyForwardDataTrafficToDefaultChannel (
|
|
IN BOOL IsInChannel,
|
|
IN BYTE *Packet,
|
|
IN ULONG PacketLength,
|
|
IN ULONG ChannelId
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Forwards the given packet on the given channel. Overloaded
|
|
version for the proxy as we need to do special things for
|
|
the proxy. It can be used for data traffic only. RTS packets
|
|
cannot use this method, as the TrafficType on the outgoing
|
|
packet will be initialized to http2ttData.
|
|
|
|
Arguments:
|
|
|
|
IsInChannel - if non-zero, forward to default in channel.
|
|
If 0, forward to default out channel
|
|
|
|
Packet - the packet to forward. Ownership of the packet
|
|
passes to this function regardless of success or
|
|
failure.
|
|
|
|
PacketLength - the length of the packet to forward
|
|
|
|
ChannelId - the id of the channel on which we received the
|
|
packet.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* error
|
|
|
|
--*/
|
|
{
|
|
HTTP2SendContext *SendContext;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
SendContext = AllocateAndInitializeContextFromPacket(Packet,
|
|
PacketLength
|
|
);
|
|
|
|
if (SendContext == NULL)
|
|
{
|
|
RpcFreeBuffer(Packet);
|
|
RpcStatus = RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(SendContext->Flags == 0);
|
|
SendContext->Flags = SendContextFlagProxySend;
|
|
SendContext->UserData = ConvertChannelIdToSendContextUserData(ChannelId);
|
|
// make sure we can find it after that
|
|
ASSERT(MapSendContextUserDataToChannelPtr(SendContext->UserData) != NULL);
|
|
RpcStatus = SendTrafficOnDefaultChannel(IsInChannel, // IsInChannel
|
|
SendContext
|
|
);
|
|
|
|
// this is a proxy channel - it should not recycle
|
|
ASSERT(RpcStatus != RPC_P_CHANNEL_NEEDS_RECYCLING);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
FreeSendContextAndPossiblyData(SendContext);
|
|
}
|
|
}
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2InProxyInChannel
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS HTTP2InProxyInChannel::ForwardFlowControlAck (
|
|
IN ULONG BytesReceivedForAck,
|
|
IN ULONG WindowForAck
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Forwards a flow control ack to the client through the server
|
|
|
|
Arguments:
|
|
|
|
BytesReceivedForAck - the bytes received when the ACK was issued
|
|
|
|
WindowForAck - the free window when the ACK was issued.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_*
|
|
|
|
--*/
|
|
{
|
|
return ForwardFlowControlAckOnDefaultChannel(FALSE, // IsInChannel
|
|
fdClient,
|
|
BytesReceivedForAck,
|
|
WindowForAck
|
|
);
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2InProxyOutChannel
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS HTTP2InProxyOutChannel::LastPacketSentNotification (
|
|
IN HTTP2SendContext *LastSendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
When a lower channel wants to notify the top
|
|
channel that the last packet has been sent,
|
|
they call this function. Must be called from
|
|
an upcall/neutral context. Only flow control
|
|
senders support last packet notifications
|
|
|
|
Arguments:
|
|
|
|
LastSendContext - the context for the last send
|
|
|
|
Return Value:
|
|
|
|
The value to return to the bottom channel
|
|
|
|
--*/
|
|
{
|
|
// just return ok to caller. When the server aborts the connection,
|
|
// we will abort too.
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
RPC_STATUS HTTP2InProxyOutChannel::SetRawConnectionKeepAlive (
|
|
IN ULONG KeepAliveInterval // in milliseconds
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sets the raw connection keep alive. On abort connections
|
|
failures are ignored.
|
|
|
|
Arguments:
|
|
|
|
KeepAliveInterval - keep alive interval in milliseconds
|
|
|
|
Return Value:
|
|
|
|
The value to return to the bottom channel
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
WS_HTTP2_CONNECTION *RawConnection;
|
|
KEEPALIVE_TIMEOUT KATimeout;
|
|
|
|
RpcStatus = BeginSimpleSubmitAsync();
|
|
if (RpcStatus != RPC_S_OK)
|
|
return RPC_S_OK;
|
|
|
|
RawConnection = GetRawConnection();
|
|
KATimeout.Milliseconds = KeepAliveInterval;
|
|
|
|
// turn off the old interval
|
|
RpcStatus = WS_TurnOnOffKeepAlives(RawConnection,
|
|
FALSE, // TurnOn
|
|
FALSE, // bProtectIO
|
|
tuMilliseconds,
|
|
KATimeout,
|
|
DefaultClientNoResponseKeepAliveInterval
|
|
);
|
|
|
|
// turning off should always succeed
|
|
ASSERT(RpcStatus == RPC_S_OK);
|
|
|
|
// turn on the new interval
|
|
// start with the keep alives immediately and proceed until the specified interval
|
|
RpcStatus = WS_TurnOnOffKeepAlives(RawConnection,
|
|
TRUE, // TurnOn
|
|
FALSE, // bProtectIO
|
|
tuMilliseconds,
|
|
KATimeout,
|
|
KeepAliveInterval
|
|
);
|
|
|
|
FinishSubmitAsync();
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2InProxyVirtualConnection
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS HTTP2InProxyVirtualConnection::InitializeProxyFirstLeg (
|
|
IN USHORT *ServerAddress,
|
|
IN USHORT *ServerPort,
|
|
IN void *ConnectionParameter,
|
|
IN I_RpcProxyCallbackInterface *ProxyCallbackInterface,
|
|
void **IISContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialize the proxy (first leg - second leg will happen when we
|
|
receive first RTS packet).
|
|
|
|
Arguments:
|
|
|
|
ServerAddress - unicode pointer string to the server network address.
|
|
|
|
ServerPort - unicode pointer string to the server port
|
|
|
|
ConnectionParameter - the extension control block in this case
|
|
|
|
ProxyCallbackInterface - a callback interface to the proxy to perform
|
|
various proxy specific functions
|
|
|
|
IISContext - on output (success only) it must be initialized to
|
|
the bottom IISChannel for the InProxy.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
HTTP2InProxyInChannel *NewInChannel;
|
|
int InChannelId;
|
|
int ServerAddressLength; // in characters + terminating 0
|
|
|
|
// initialize in channel
|
|
RpcStatus = AllocateAndInitializeInChannel(ConnectionParameter,
|
|
&NewInChannel,
|
|
IISContext
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
return RpcStatus;
|
|
|
|
SetFirstInChannel(NewInChannel);
|
|
|
|
this->ProxyCallbackInterface = ProxyCallbackInterface;
|
|
this->ConnectionParameter = ConnectionParameter;
|
|
|
|
ServerAddressLength = RpcpStringLength(ServerAddress) + 1;
|
|
ServerName = new RPC_CHAR [ServerAddressLength];
|
|
|
|
if (ServerName == NULL)
|
|
{
|
|
Abort();
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
RpcpMemoryCopy(ServerName, ServerAddress, ServerAddressLength * 2);
|
|
|
|
RpcStatus = EndpointToPortNumber(ServerPort, this->ServerPort);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
Abort();
|
|
// fall through with error
|
|
}
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2InProxyVirtualConnection::StartProxy (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Kicks off listening on the proxy
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* for error
|
|
|
|
--*/
|
|
{
|
|
HTTP2InProxyInChannel *Channel;
|
|
HTTP2ChannelPointer *ChannelPtr;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, IN_CHANNEL_STATE, http2svClosed, 1, 0);
|
|
State.State = http2svClosed; // move to closed state expecting the opening RTS packet
|
|
|
|
Channel = LockDefaultInChannel(&ChannelPtr);
|
|
|
|
ASSERT(Channel != NULL); // we cannot be disconnected now
|
|
|
|
RpcStatus = Channel->Receive(http2ttRaw);
|
|
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
C_ASSERT(sizeof(SOCKADDR_IN) == MAX_IPv4_ADDRESS_SIZE);
|
|
C_ASSERT(sizeof(SOCKADDR_IN6) == MAX_IPv6_ADDRESS_SIZE);
|
|
|
|
RPC_STATUS HTTP2InProxyVirtualConnection::InitializeProxySecondLeg (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialize the proxy (second leg - first leg has happened when we
|
|
received the HTTP establishment header).
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
HTTP2InProxyOutChannel *NewOutChannel;
|
|
int OutChannelId;
|
|
int ServerAddressLength; // in characters + terminating 0
|
|
char ClientIpAddress[16];
|
|
ULONG ClientIpAddressSize;
|
|
ADDRINFO DnsHint;
|
|
ADDRINFO *AddrInfo;
|
|
int err;
|
|
|
|
// initialize out channel
|
|
RpcStatus = AllocateAndInitializeOutChannel(
|
|
&NewOutChannel
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
// this will always come from an upcall. Just return failure
|
|
return RpcStatus;
|
|
}
|
|
|
|
SetFirstOutChannel(NewOutChannel);
|
|
|
|
RpcStatus = ProxyCallbackInterface->GetConnectionTimeoutFn(&IISConnectionTimeout);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
// this will always come from an upcall. Just return failure
|
|
return RpcStatus;
|
|
}
|
|
|
|
// convert it from seconds to milliseconds
|
|
IISConnectionTimeout *= 1000;
|
|
|
|
ClientIpAddressSize = sizeof(ClientIpAddress);
|
|
RpcStatus = ProxyCallbackInterface->GetClientAddressFn(
|
|
ConnectionParameter,
|
|
ClientIpAddress,
|
|
&ClientIpAddressSize
|
|
);
|
|
|
|
ASSERT(RpcStatus != ERROR_INSUFFICIENT_BUFFER);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
// this will always come from an upcall. Just return failure
|
|
return RpcStatus;
|
|
}
|
|
|
|
RpcpMemorySet(&DnsHint, 0, sizeof(ADDRINFO));
|
|
|
|
DnsHint.ai_flags = AI_NUMERICHOST;
|
|
DnsHint.ai_family = PF_UNSPEC;
|
|
|
|
err = getaddrinfo(ClientIpAddress,
|
|
NULL,
|
|
&DnsHint,
|
|
&AddrInfo);
|
|
|
|
// make sure IIS doesn't feed us garbage IP address
|
|
if (err != ERROR_SUCCESS)
|
|
{
|
|
VALIDATE (GetLastError())
|
|
{
|
|
ERROR_OUTOFMEMORY,
|
|
ERROR_NOT_ENOUGH_MEMORY
|
|
} END_VALIDATE;
|
|
// this will always come from an upcall. Just return failure
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
ASSERT(AddrInfo->ai_family == AF_INET);
|
|
|
|
ClientAddress.AddressType = catIPv4;
|
|
RpcpCopyIPv4Address((SOCKADDR_IN *)AddrInfo->ai_addr, (SOCKADDR_IN *)&ClientAddress.u);
|
|
|
|
freeaddrinfo(AddrInfo);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2InProxyVirtualConnection::ReceiveComplete (
|
|
IN RPC_STATUS EventStatus,
|
|
IN BYTE *Buffer,
|
|
IN UINT BufferLength,
|
|
IN int ChannelId
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Called by lower layers to indicate receive complete
|
|
|
|
Arguments:
|
|
|
|
EventStatus - RPC_S_OK for success or RPC_S_* for error
|
|
|
|
Buffer - buffer received
|
|
|
|
BufferLength - length of buffer received
|
|
|
|
ChannelId - which channel completed the operation
|
|
|
|
Return Value:
|
|
|
|
RPC_P_PACKET_CONSUMED if the packet was consumed and should
|
|
be hidden from the runtime.
|
|
RPC_S_OK if the packet was processed successfully.
|
|
RPC_S_* error if there was an error while processing the
|
|
packet.
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
BOOL BufferFreed = FALSE;
|
|
BOOL MutexCleared;
|
|
HTTP2ChannelPointer *ChannelPtr;
|
|
HTTP2ChannelPointer *ChannelPtr2;
|
|
HTTP2InProxyOutChannel *OutChannel;
|
|
HTTP2InProxyInChannel *InChannel;
|
|
HTTP2InProxyInChannel *InChannel2;
|
|
BYTE *CurrentPosition;
|
|
rpcconn_tunnel_settings *RTS;
|
|
CookieCollection *InProxyCookieCollection;
|
|
HTTP2InProxyVirtualConnection *ExistingConnection;
|
|
HTTP2Cookie NewChannelCookie;
|
|
HTTP2SendContext *EmptyRTS;
|
|
int NonDefaultSelector;
|
|
int DefaultSelector;
|
|
HTTP2SendContext *D3_A2Context;
|
|
HTTP2OtherCmdPacketType PacketType;
|
|
int i;
|
|
ULONG BytesReceivedForAck;
|
|
ULONG WindowForAck;
|
|
HTTP2Cookie CookieForChannel;
|
|
ULONG ServerReceiveWindowSize;
|
|
HTTP2SendContext *SendContext;
|
|
ULONG DefaultInChannelId;
|
|
ULONG NewProtocolVersion;
|
|
|
|
|
|
VerifyValidChannelId(ChannelId);
|
|
|
|
if (EventStatus == RPC_S_OK)
|
|
{
|
|
// N.B. All recieve packets are guaranteed to be
|
|
// validated up to the common conn packet size
|
|
if (IsRTSPacket(Buffer))
|
|
{
|
|
// Intermediate versions of .NET Server 2003 and
|
|
// all versions of Windows XPSP1 send D4/A7 without
|
|
// version information (old D4/A7). .NET Server 2003
|
|
// RTM and later send D4/A7 with version information
|
|
// (new D4/A7). Check for new D4/A7 and convert it
|
|
// to D4/A8 if necessary
|
|
if (IsNewD4_A7Packet (Buffer,
|
|
BufferLength,
|
|
fdServer,
|
|
&NewProtocolVersion))
|
|
{
|
|
// We know that the new protocol version cannot
|
|
// be higher than ours because the new guy in D4 (the
|
|
// new out proxy) can only dumb it down.
|
|
CORRUPTION_ASSERT (NewProtocolVersion <= ProtocolVersion);
|
|
if (NewProtocolVersion > ProtocolVersion)
|
|
{
|
|
RpcFreeBuffer(Buffer);
|
|
return RPC_S_PROTOCOL_ERROR;
|
|
}
|
|
ProtocolVersion = NewProtocolVersion;
|
|
ConvertNewD4_A7ToD4_A8 (Buffer,
|
|
(ULONG *)&BufferLength);
|
|
RpcStatus = RPC_P_PACKET_NEEDS_FORWARDING;
|
|
}
|
|
else
|
|
{
|
|
RpcStatus = CheckPacketForForwarding(Buffer,
|
|
BufferLength,
|
|
fdInProxy
|
|
);
|
|
}
|
|
}
|
|
|
|
if (IsRTSPacket(Buffer) && (RpcStatus != RPC_P_PACKET_NEEDS_FORWARDING))
|
|
{
|
|
// RTS packet - check what we need to do with it
|
|
if (IsOtherCmdPacket(Buffer, BufferLength))
|
|
{
|
|
if (IsOutChannel(ChannelId))
|
|
{
|
|
// the only other cmd we expect on the out channel in the proxy are
|
|
// flow control acks
|
|
RpcStatus = ParseAndFreeFlowControlAckPacket (Buffer,
|
|
BufferLength,
|
|
&BytesReceivedForAck,
|
|
&WindowForAck,
|
|
&CookieForChannel
|
|
);
|
|
|
|
BufferFreed = TRUE;
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
return RpcStatus;
|
|
|
|
// notify the channel
|
|
ChannelPtr = GetChannelPointerFromId(ChannelId);
|
|
OutChannel = (HTTP2InProxyOutChannel *)ChannelPtr->LockChannelPointer();
|
|
if (OutChannel == NULL)
|
|
return RPC_P_CONNECTION_SHUTDOWN;
|
|
|
|
RpcStatus = OutChannel->FlowControlAckNotify(BytesReceivedForAck,
|
|
WindowForAck
|
|
);
|
|
|
|
ASSERT(RpcStatus != RPC_P_CHANNEL_NEEDS_RECYCLING);
|
|
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
return RpcStatus;
|
|
|
|
// post another receive
|
|
RpcStatus = PostReceiveOnChannel(GetChannelPointerFromId(ChannelId),
|
|
http2ttRaw
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
return RpcStatus;
|
|
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
RpcStatus = GetOtherCmdPacketType(Buffer,
|
|
BufferLength,
|
|
&PacketType
|
|
);
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
switch (PacketType)
|
|
{
|
|
case http2ocptKeepAliveChange:
|
|
RpcStatus = ParseAndFreeKeepAliveChangePacket(Buffer,
|
|
BufferLength,
|
|
&CurrentClientKeepAliveInterval
|
|
);
|
|
|
|
BufferFreed = TRUE;
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
if (CurrentClientKeepAliveInterval == 0)
|
|
CurrentClientKeepAliveInterval = DefaultClientKeepAliveInterval;
|
|
|
|
// by now the keep alive interval has been set on the connection.
|
|
// Any new channels will be taken care of by the virtual connection
|
|
// We need to go in and effect this change on the existing channels
|
|
for (i = 0; i < 2; i ++)
|
|
{
|
|
OutChannel = (HTTP2InProxyOutChannel *)OutChannels[0].LockChannelPointer();
|
|
if (OutChannel)
|
|
{
|
|
RpcStatus = OutChannel->SetRawConnectionKeepAlive(CurrentClientKeepAliveInterval);
|
|
OutChannels[0].UnlockChannelPointer();
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
// post another receive
|
|
RpcStatus = PostReceiveOnChannel(GetChannelPointerFromId(ChannelId),
|
|
http2ttRaw
|
|
);
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
}
|
|
// return the status code - success or error, both get
|
|
// handled below us
|
|
return RpcStatus;
|
|
break;
|
|
|
|
default:
|
|
ASSERT(0);
|
|
return RPC_S_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
MutexCleared = FALSE;
|
|
State.Mutex.Request();
|
|
switch (State.State)
|
|
{
|
|
case http2svClosed:
|
|
// for closed states, we must receive
|
|
// stuff only on the default in channel
|
|
ASSERT(IsDefaultInChannel(ChannelId));
|
|
|
|
CurrentPosition = ValidateRTSPacketCommon(Buffer,
|
|
BufferLength
|
|
);
|
|
|
|
if (CurrentPosition == NULL)
|
|
{
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
break;
|
|
}
|
|
|
|
RTS = (rpcconn_tunnel_settings *)Buffer;
|
|
if ((RTS->Flags & RTS_FLAG_RECYCLE_CHANNEL) == 0)
|
|
{
|
|
RpcStatus = ParseAndFreeD1_B1(Buffer,
|
|
BufferLength,
|
|
&ProtocolVersion,
|
|
&EmbeddedConnectionCookie,
|
|
&InChannelCookies[0],
|
|
&ChannelLifetime,
|
|
&AssociationGroupId,
|
|
&CurrentClientKeepAliveInterval
|
|
);
|
|
|
|
ProtocolVersion = min(ProtocolVersion, HTTP2ProtocolVersion);
|
|
|
|
BufferFreed = TRUE;
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
|
|
if (CurrentClientKeepAliveInterval < MinimumClientKeepAliveInterval)
|
|
{
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
break;
|
|
}
|
|
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, IN_CHANNEL_STATE, http2svB3W, 1, 0);
|
|
State.State = http2svB3W;
|
|
State.Mutex.Clear();
|
|
MutexCleared = TRUE;
|
|
|
|
RpcStatus = InitializeProxySecondLeg();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
|
|
RpcStatus = ConnectToServer();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
|
|
RpcStatus = SendD1_B2ToServer();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
|
|
RpcStatus = PostReceiveOnChannel(&InChannels[0], http2ttRaw);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
|
|
DefaultClientKeepAliveInterval = CurrentClientKeepAliveInterval;
|
|
|
|
OutChannel = LockDefaultOutChannel(&ChannelPtr);
|
|
if (OutChannel == NULL)
|
|
{
|
|
RpcStatus = RPC_P_CONNECTION_SHUTDOWN;
|
|
break;
|
|
}
|
|
|
|
RpcStatus = OutChannel->SetRawConnectionKeepAlive(CurrentClientKeepAliveInterval);
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
// fall through with the error code
|
|
}
|
|
else
|
|
{
|
|
RpcStatus = ParseAndFreeD2_A1 (Buffer,
|
|
BufferLength,
|
|
&ProtocolVersion,
|
|
&EmbeddedConnectionCookie,
|
|
&InChannelCookies[1], // Old cookie - use InChannelCookies[1]
|
|
// as temporary storage only
|
|
&InChannelCookies[0] // New cookie
|
|
);
|
|
|
|
ProtocolVersion = min(ProtocolVersion, HTTP2ProtocolVersion);
|
|
|
|
BufferFreed = TRUE;
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
|
|
// caller claims this is recycling for an already existing connection
|
|
// find out this connection
|
|
InProxyCookieCollection = GetInProxyCookieCollection();
|
|
InProxyCookieCollection->LockCollection();
|
|
ExistingConnection = (HTTP2InProxyVirtualConnection *)
|
|
InProxyCookieCollection->FindElement(&EmbeddedConnectionCookie);
|
|
if (ExistingConnection == NULL || ActAsSeparateMachinesOnWebFarm)
|
|
{
|
|
// no dice. Probably we executed on a different machine on the web farm
|
|
// proceed as a standalone connection
|
|
InProxyCookieCollection->UnlockCollection();
|
|
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, IN_CHANNEL_STATE, http2svB2W, 1, 0);
|
|
State.State = http2svB2W;
|
|
State.Mutex.Clear();
|
|
MutexCleared = TRUE;
|
|
|
|
RpcStatus = InitializeProxySecondLeg();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
|
|
RpcStatus = ConnectToServer();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
|
|
// posts receive on the server channel as
|
|
// well
|
|
RpcStatus = SendD2_A2ToServer();
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
|
|
OutChannel = LockDefaultOutChannel(&ChannelPtr);
|
|
if (OutChannel == NULL)
|
|
{
|
|
RpcStatus = RPC_P_CONNECTION_SHUTDOWN;
|
|
break;
|
|
}
|
|
|
|
RpcStatus = OutChannel->SetRawConnectionKeepAlive(CurrentClientKeepAliveInterval);
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
|
|
RpcStatus = PostReceiveOnChannel(&InChannels[0], http2ttRaw);
|
|
}
|
|
else
|
|
{
|
|
// detach the in channel from this connection and attach
|
|
// it to the found connection. Grab a reference to it
|
|
// to prevent the case where it goes away underneath us
|
|
// we know that in its current state the connection is single
|
|
// threaded because we are in the completion path of the
|
|
// only async operation
|
|
|
|
RpcStatus = ExistingConnection->SetTimeout(DefaultNoResponseTimeout,
|
|
ExistingConnection->GetInChannelTimer());
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
|
|
ChannelPtr = GetChannelPointerFromId(ChannelId);
|
|
InChannel = (HTTP2InProxyInChannel *)ChannelPtr->LockChannelPointer();
|
|
// there is no way that somebody detached the channel here
|
|
ASSERT(InChannel);
|
|
|
|
// add a reference to keep the channel alive while we disconnect it
|
|
InChannel->AddReference();
|
|
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
// no need to drain the upcalls - we know we are the only
|
|
// upcall
|
|
ChannelPtr->FreeChannelPointer(
|
|
FALSE, // DrainUpCalls
|
|
FALSE, // CalledFromUpcallContext
|
|
FALSE, // Abort
|
|
RPC_S_OK
|
|
);
|
|
|
|
DefaultSelector = ExistingConnection->DefaultInChannelSelector;
|
|
NonDefaultSelector = ExistingConnection->GetNonDefaultInChannelSelector();
|
|
if (ExistingConnection->InChannelCookies[DefaultSelector].Compare (&InChannelCookies[1]))
|
|
{
|
|
// nice try - cookies are different. Ditch the newly established channel
|
|
InProxyCookieCollection->UnlockCollection();
|
|
InChannel->RemoveReference();
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
break;
|
|
}
|
|
|
|
InChannel->SetParent(ExistingConnection);
|
|
ExistingConnection->InChannels[NonDefaultSelector].SetChannel(InChannel);
|
|
ExistingConnection->InChannelCookies[NonDefaultSelector].SetCookie(InChannelCookies[0].GetCookie());
|
|
ExistingConnection->InChannelIds[NonDefaultSelector] = ChannelId;
|
|
|
|
// check if connection is aborted
|
|
if (ExistingConnection->Aborted.GetInteger() > 0)
|
|
{
|
|
InChannel->Abort(RPC_P_CONNECTION_SHUTDOWN);
|
|
}
|
|
|
|
// the extra reference that we added above passes to the existing connection
|
|
// However, below we party on the existing connection and we need to keep it alive
|
|
ExistingConnection->BlockConnectionFromRundown();
|
|
InProxyCookieCollection->UnlockCollection();
|
|
|
|
State.Mutex.Clear();
|
|
MutexCleared = TRUE;
|
|
|
|
// nuke the rest of the old connection
|
|
|
|
// we got to the destructive phase of the abort
|
|
// guard against double aborts
|
|
if (Aborted.Increment() > 1)
|
|
return FALSE;
|
|
|
|
// abort the channels
|
|
AbortChannels(RPC_P_CONNECTION_SHUTDOWN);
|
|
|
|
DisconnectChannels(FALSE, // ExemptChannel
|
|
0 // ExemptChannel id
|
|
);
|
|
|
|
delete this;
|
|
// N.B. don't touch the this pointer after here (Duh!)
|
|
|
|
ExistingConnection->State.Mutex.Request();
|
|
LogEvent(SU_HTTPv2, EV_STATE, ExistingConnection, IN_CHANNEL_STATE, http2svOpened_A5W, 1, 0);
|
|
ExistingConnection->State.State = http2svOpened_A5W;
|
|
ExistingConnection->State.Mutex.Clear();
|
|
|
|
// send D3/A2 to server
|
|
D3_A2Context = AllocateAndInitializeD3_A2(&ExistingConnection->InChannelCookies[NonDefaultSelector]);
|
|
if (D3_A2Context == NULL)
|
|
{
|
|
ExistingConnection->UnblockConnectionFromRundown();
|
|
RpcStatus = RPC_S_OUT_OF_MEMORY;
|
|
break;
|
|
}
|
|
|
|
RpcStatus = ExistingConnection->SendTrafficOnDefaultChannel (
|
|
FALSE, // IsInChannel
|
|
D3_A2Context
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
FreeRTSPacket(D3_A2Context);
|
|
break;
|
|
}
|
|
|
|
RpcStatus = ExistingConnection->PostReceiveOnChannel(
|
|
&ExistingConnection->InChannels[NonDefaultSelector],
|
|
http2ttRaw
|
|
);
|
|
ExistingConnection->UnblockConnectionFromRundown();
|
|
|
|
// fall through with the obtained RpcStatus
|
|
}
|
|
}
|
|
break;
|
|
|
|
case http2svOpened:
|
|
State.Mutex.Clear();
|
|
MutexCleared = TRUE;
|
|
|
|
// the only RTS packets we expect in opened state is D2/A5
|
|
RpcStatus = ParseD2_A5 (Buffer,
|
|
BufferLength,
|
|
&NewChannelCookie
|
|
);
|
|
|
|
if (RpcStatus == RPC_S_PROTOCOL_ERROR)
|
|
{
|
|
RpcFreeBuffer(Buffer);
|
|
break;
|
|
}
|
|
|
|
// send D2/A6 immediately, and queue D2/B1 for sending
|
|
// Since D2/A6 is the same as D2/A5 (the packet we just
|
|
// received), we can just forward it
|
|
RpcStatus = ForwardTrafficToDefaultChannel (
|
|
FALSE, // IsInChannel
|
|
Buffer,
|
|
BufferLength
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
|
|
// we don't own the buffer after a successful send
|
|
BufferFreed = TRUE;
|
|
|
|
OutChannel = LockDefaultOutChannel (&ChannelPtr);
|
|
if (OutChannel == NULL)
|
|
{
|
|
RpcStatus = RPC_P_CONNECTION_SHUTDOWN;
|
|
break;
|
|
}
|
|
|
|
// Allocate D1/B1
|
|
EmptyRTS = AllocateAndInitializeEmptyRTS ();
|
|
if (EmptyRTS == NULL)
|
|
{
|
|
ChannelPtr->UnlockChannelPointer();
|
|
RpcStatus = RPC_S_OUT_OF_MEMORY;
|
|
break;
|
|
}
|
|
|
|
EmptyRTS->Flags = SendContextFlagSendLast;
|
|
RpcStatus = OutChannel->Send(EmptyRTS);
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
// we're done. There were no queued buffers and D1/B1
|
|
// was sent immediately. When the server aborts its
|
|
// end of the connection, we will close down
|
|
break;
|
|
}
|
|
else if (RpcStatus == ERROR_IO_PENDING)
|
|
{
|
|
// D1/B1 was not sent immediately. When it is sent,
|
|
// the LastPacketSentNotification mechanism will
|
|
// destroy the connection. Return success for know
|
|
RpcStatus = RPC_S_OK;
|
|
}
|
|
else
|
|
{
|
|
// an error occurred during sending. Free the packet and
|
|
// return it back to the caller
|
|
FreeRTSPacket(EmptyRTS);
|
|
}
|
|
break;
|
|
|
|
case http2svOpened_A5W:
|
|
|
|
// the only RTS packets we expect in opened_A5W state is D3/A5
|
|
RpcStatus = ParseAndFreeD3_A5 (Buffer,
|
|
BufferLength,
|
|
&NewChannelCookie
|
|
);
|
|
|
|
BufferFreed = TRUE;
|
|
|
|
CancelTimeout(GetInChannelTimer());
|
|
|
|
if (RpcStatus == RPC_S_PROTOCOL_ERROR)
|
|
break;
|
|
|
|
ASSERT(InChannels[0].IsChannelSet() && InChannels[1].IsChannelSet());
|
|
|
|
if (InChannelCookies[GetNonDefaultInChannelSelector()].Compare(&NewChannelCookie))
|
|
{
|
|
// abort
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
break;
|
|
}
|
|
|
|
// switch channels and get rid of the old channel
|
|
SwitchDefaultInChannelSelector();
|
|
|
|
// we received all those packets on the default in channel by
|
|
// definition
|
|
DefaultInChannelId = GetDefaultInChannelId();
|
|
|
|
// drain the queue of buffers received on the non-default
|
|
// channel (new channel) and actually send them on the new default channel
|
|
while ((Buffer = (BYTE *) NonDefaultChannelBufferQueue.TakeOffQueue(&BufferLength)) != NULL)
|
|
{
|
|
RpcStatus = ProxyForwardDataTrafficToDefaultChannel (FALSE, // IsInChannel
|
|
Buffer,
|
|
BufferLength,
|
|
DefaultInChannelId
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
// just exit. During abort the rest of the buffers
|
|
// will be freed
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if we finished the loop with an error, bail out
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, IN_CHANNEL_STATE, http2svOpened, 1, 0);
|
|
State.State = http2svOpened;
|
|
|
|
State.Mutex.Clear();
|
|
MutexCleared = TRUE;
|
|
|
|
ChannelPtr = GetChannelPointerFromId(ChannelId);
|
|
|
|
InChannel2 = LockDefaultInChannel(&ChannelPtr2);
|
|
if (InChannel2 == NULL)
|
|
{
|
|
RpcStatus = RPC_P_CONNECTION_SHUTDOWN;
|
|
break;
|
|
}
|
|
|
|
InChannel = (HTTP2InProxyInChannel *)ChannelPtr->LockChannelPointer();
|
|
if (InChannel == NULL)
|
|
{
|
|
ChannelPtr2->UnlockChannelPointer();
|
|
RpcStatus = RPC_P_CONNECTION_SHUTDOWN;
|
|
break;
|
|
}
|
|
|
|
InChannel->TransferReceiveStateToNewChannel(InChannel2);
|
|
|
|
ChannelPtr2->UnlockChannelPointer();
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
// detach, abort, and release lifetime reference
|
|
ChannelPtr->FreeChannelPointer(TRUE, // DrainUpCalls
|
|
TRUE, // CalledFromUpcallContext
|
|
TRUE, // Abort
|
|
RPC_P_CONNECTION_SHUTDOWN // AbortStatus
|
|
);
|
|
|
|
// return success. When the reference for this receive
|
|
// is removed, the channel will go away
|
|
RpcStatus = RPC_S_OK;
|
|
break;
|
|
|
|
case http2svB2W:
|
|
if (IsOutChannel(ChannelId) == FALSE)
|
|
{
|
|
ASSERT(0);
|
|
// make sure client doesn't rush things
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
break;
|
|
}
|
|
|
|
RpcStatus = ParseAndFreeD2_B2(Buffer,
|
|
BufferLength,
|
|
&ServerReceiveWindowSize
|
|
);
|
|
|
|
BufferFreed = TRUE;
|
|
|
|
if (RpcStatus == RPC_S_PROTOCOL_ERROR)
|
|
break;
|
|
|
|
// we know the connection is legitimate - add ourselves
|
|
// to the collection
|
|
InProxyCookieCollection = GetInProxyCookieCollection();
|
|
InProxyCookieCollection->LockCollection();
|
|
ExistingConnection = (HTTP2InProxyVirtualConnection *)
|
|
InProxyCookieCollection->FindElement(&EmbeddedConnectionCookie);
|
|
|
|
if (ExistingConnection != NULL)
|
|
{
|
|
// the only way we will be in this protocol is if
|
|
// we were faking a web farm
|
|
ASSERT (ActAsSeparateMachinesOnWebFarm);
|
|
ProxyConnectionCookie = ExistingConnection->GetCookie();
|
|
ProxyConnectionCookie->AddRefCount();
|
|
ProxyConnectionCookie->SetConnection(this);
|
|
|
|
// remember that we are part of the cookie collection now
|
|
IsConnectionInCollection = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// we truly didn't find anything - add ourselves.
|
|
RpcStatus = AddConnectionToCookieCollection ();
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
InProxyCookieCollection->UnlockCollection();
|
|
break;
|
|
}
|
|
}
|
|
|
|
InProxyCookieCollection->UnlockCollection();
|
|
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, IN_CHANNEL_STATE, http2svOpened, 1, 0);
|
|
State.State = http2svOpened;
|
|
State.Mutex.Clear();
|
|
MutexCleared = TRUE;
|
|
|
|
// unplug the out channel to get the flow going
|
|
OutChannel = LockDefaultOutChannel (&ChannelPtr);
|
|
if (OutChannel == NULL)
|
|
{
|
|
RpcStatus = RPC_P_CONNECTION_SHUTDOWN;
|
|
break;
|
|
}
|
|
|
|
OutChannel->SetPeerReceiveWindow(ServerReceiveWindowSize);
|
|
RpcStatus = OutChannel->Unplug();
|
|
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
|
|
RpcStatus = PostReceiveOnDefaultChannel(FALSE, // IsInChannel
|
|
http2ttRaw
|
|
);
|
|
break;
|
|
|
|
case http2svB3W:
|
|
ASSERT(IsDefaultOutChannel(ChannelId));
|
|
RpcStatus = ParseAndFreeD1_B3(Buffer,
|
|
BufferLength,
|
|
&ServerReceiveWindowSize,
|
|
&ProtocolVersion
|
|
);
|
|
|
|
ProtocolVersion = min(ProtocolVersion, HTTP2ProtocolVersion);
|
|
|
|
BufferFreed = TRUE;
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
RpcStatus = PostReceiveOnChannel(&OutChannels[0], http2ttRaw);
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
RpcStatus = AddConnectionToCookieCollection();
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, IN_CHANNEL_STATE, http2svOpened, 1, 0);
|
|
State.State = http2svOpened;
|
|
State.Mutex.Clear();
|
|
MutexCleared = TRUE;
|
|
|
|
OutChannel = (HTTP2InProxyOutChannel *)OutChannels[0].LockChannelPointer();
|
|
if (OutChannel)
|
|
{
|
|
OutChannel->SetPeerReceiveWindow(ServerReceiveWindowSize);
|
|
RpcStatus = OutChannel->Unplug();
|
|
OutChannels[0].UnlockChannelPointer();
|
|
}
|
|
else
|
|
{
|
|
RpcStatus = RPC_P_CONNECTION_CLOSED;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
ASSERT(0);
|
|
}
|
|
if (MutexCleared == FALSE)
|
|
State.Mutex.Clear();
|
|
}
|
|
else
|
|
{
|
|
// data packet or RTS packet that needs forwarding. Just forward it
|
|
if (IsDefaultOutChannel(ChannelId))
|
|
{
|
|
// non-RTS packet in any state from out channel
|
|
// is a protocol error
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
}
|
|
else
|
|
{
|
|
if ((State.State == http2svOpened_A5W)
|
|
&& (!IsRTSPacket(Buffer)))
|
|
{
|
|
// this thread is racing with a thread that received D3/A5 on the default
|
|
// channel and is trying to switch the default channel and the state. Make
|
|
// the check within the mutex
|
|
State.Mutex.Request();
|
|
if (IsDefaultInChannel(ChannelId) == FALSE)
|
|
{
|
|
// sends on non-default channel in Opened_A5W state get queued until we
|
|
// receive D3/A5
|
|
if (State.State == http2svOpened_A5W)
|
|
{
|
|
if (NonDefaultChannelBufferQueue.PutOnQueue(Buffer, BufferLength))
|
|
{
|
|
State.Mutex.Clear();
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
State.Mutex.Clear();
|
|
|
|
// post a receive for the next buffer
|
|
ChannelPtr = GetChannelPointerFromId(ChannelId);
|
|
|
|
RpcStatus = PostReceiveOnChannel(ChannelPtr, http2ttRaw);
|
|
|
|
return RPC_S_OK;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// the channel is default - fall through to forwarding the data
|
|
}
|
|
State.Mutex.Clear();
|
|
}
|
|
|
|
RpcStatus = ProxyForwardDataTrafficToDefaultChannel (FALSE, // IsInChannel
|
|
Buffer,
|
|
BufferLength,
|
|
ChannelId
|
|
);
|
|
|
|
// ownership of the buffer passed to ProxyForwardDataTrafficToDefaultChannel
|
|
// regardless of success or failure. Make sure we remember that.
|
|
BufferFreed = TRUE;
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
ChannelPtr = GetChannelPointerFromId(ChannelId);
|
|
|
|
RpcStatus = PostReceiveOnChannel(ChannelPtr, http2ttRaw);
|
|
}
|
|
else
|
|
{
|
|
// fall through with the error
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (IsInChannel(ChannelId) && !IsDefaultInChannel(ChannelId))
|
|
{
|
|
// ignore errors on non-default in channels. They can go
|
|
// away while we are still sending data to the server.
|
|
// We will destroy the connection when the server is done
|
|
RpcStatus = RPC_S_OK;
|
|
}
|
|
else
|
|
{
|
|
// just turn around the error code
|
|
RpcStatus = EventStatus;
|
|
}
|
|
// in failure cases we don't own the buffer
|
|
BufferFreed = TRUE;
|
|
}
|
|
|
|
if (BufferFreed == FALSE)
|
|
RpcFreeBuffer(Buffer);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
void HTTP2InProxyVirtualConnection::EnableIISSessionClose (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Enables close of the IIS session at the IISTransportChannel
|
|
level. Simply delegates to the appropriate channel.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
// on the in proxy, the in channel has the IISTransport channel
|
|
HTTP2InProxyInChannel *InChannel;
|
|
HTTP2ChannelPointer *ChannelPtr;
|
|
|
|
InChannel = LockDefaultInChannel(&ChannelPtr);
|
|
if (InChannel == NULL)
|
|
{
|
|
// somebody aborted the connection - nothing to do
|
|
return;
|
|
}
|
|
|
|
InChannel->EnableIISSessionClose();
|
|
|
|
ChannelPtr->UnlockChannelPointer();
|
|
}
|
|
|
|
void HTTP2InProxyVirtualConnection::DisconnectChannels (
|
|
IN BOOL ExemptChannel,
|
|
IN int ExemptChannelId
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Disconnects all channels. Must be called from runtime
|
|
or neutral context. Cannot be called from upcall or
|
|
submit context unless an exempt channel is given
|
|
Note that call must synchronize to ensure we're the only
|
|
thread doing the disconnect
|
|
|
|
Arguments:
|
|
|
|
ExemptChannel - non-zero if ExemptChannelId contains a
|
|
valid exempt channel id. FALSE otherwise.
|
|
|
|
ExemptChannelId - if ExemptChannel is non-zero, this argument
|
|
is the id of a channel that will be disconnected, but not
|
|
synchronized with up calls.
|
|
If ExampleChannel is FALSE, this argument is undefined
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
BYTE *Buffer;
|
|
UINT BufferLength;
|
|
|
|
State.Mutex.Request();
|
|
if (State.State == http2svOpened_A5W)
|
|
{
|
|
while ((Buffer = (BYTE *) NonDefaultChannelBufferQueue.TakeOffQueue(&BufferLength)) != NULL)
|
|
{
|
|
RpcFreeBuffer(Buffer);
|
|
}
|
|
}
|
|
State.Mutex.Clear();
|
|
|
|
HTTP2ProxyVirtualConnection::DisconnectChannels(ExemptChannel,
|
|
ExemptChannelId
|
|
);
|
|
}
|
|
|
|
RPC_STATUS HTTP2InProxyVirtualConnection::AllocateAndInitializeInChannel (
|
|
IN void *ConnectionParameter,
|
|
OUT HTTP2InProxyInChannel **ReturnInChannel,
|
|
OUT void **IISContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Allocates and initializes the in proxy in channel.
|
|
|
|
Arguments:
|
|
|
|
ConnectionParameter - really an EXTENSION_CONTROL_BLOCK
|
|
|
|
ReturnInChannel - on success the created in channel.
|
|
|
|
IISContext - on output, the IISChannel pointer used as
|
|
connection context with IIS.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* for error
|
|
|
|
--*/
|
|
{
|
|
ULONG MemorySize;
|
|
BYTE *MemoryBlock, *CurrentBlock;
|
|
HTTP2InProxyInChannel *InChannel;
|
|
HTTP2ProxyReceiver *ProxyReceiver;
|
|
HTTP2PingReceiver *PingReceiver;
|
|
HTTP2IISTransportChannel *IISChannel;
|
|
BOOL ProxyReceiverNeedsCleanup;
|
|
BOOL PingReceiverNeedsCleanup;
|
|
BOOL IISChannelNeedsCleanup;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
// alocate the in channel
|
|
MemorySize = SIZE_OF_OBJECT_AND_PADDING(HTTP2InProxyInChannel)
|
|
+ SIZE_OF_OBJECT_AND_PADDING(HTTP2ProxyReceiver)
|
|
+ SIZE_OF_OBJECT_AND_PADDING(HTTP2PingReceiver)
|
|
+ SIZE_OF_OBJECT_AND_PADDING(HTTP2IISTransportChannel);
|
|
|
|
MemoryBlock = (BYTE *) new char [MemorySize];
|
|
CurrentBlock = MemoryBlock;
|
|
if (CurrentBlock == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
|
|
InChannel = (HTTP2InProxyInChannel *) MemoryBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2InProxyInChannel);
|
|
|
|
ProxyReceiver = (HTTP2ProxyReceiver *) CurrentBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2ProxyReceiver);
|
|
|
|
PingReceiver = (HTTP2PingReceiver *)CurrentBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2PingReceiver);
|
|
|
|
IISChannel = (HTTP2IISTransportChannel *)CurrentBlock;
|
|
|
|
// all memory blocks are allocated. Go and initialize them. Use explicit
|
|
// placement
|
|
ProxyReceiverNeedsCleanup = FALSE;
|
|
PingReceiverNeedsCleanup = FALSE;
|
|
IISChannelNeedsCleanup = FALSE;
|
|
RpcStatus = RPC_S_OK;
|
|
|
|
IISChannel = new (IISChannel) HTTP2IISTransportChannel (ConnectionParameter);
|
|
|
|
IISChannelNeedsCleanup = TRUE;
|
|
|
|
ProxyReceiver = new (ProxyReceiver) HTTP2ProxyReceiver (HTTP2InProxyReceiveWindow,
|
|
&RpcStatus);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
ProxyReceiver->HTTP2ProxyReceiver::~HTTP2ProxyReceiver();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
IISChannel->SetUpperChannel(ProxyReceiver);
|
|
ProxyReceiver->SetLowerChannel(IISChannel);
|
|
|
|
ProxyReceiverNeedsCleanup = TRUE;
|
|
|
|
PingReceiver = new (PingReceiver) HTTP2PingReceiver(TRUE);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
PingReceiver->HTTP2PingReceiver::~HTTP2PingReceiver();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
ProxyReceiver->SetUpperChannel(PingReceiver);
|
|
PingReceiver->SetLowerChannel(ProxyReceiver);
|
|
|
|
PingReceiverNeedsCleanup = TRUE;
|
|
|
|
InChannel = new (InChannel) HTTP2InProxyInChannel (this, &RpcStatus);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
InChannel->HTTP2InProxyInChannel::~HTTP2InProxyInChannel();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
PingReceiver->SetUpperChannel(InChannel);
|
|
InChannel->SetLowerChannel(PingReceiver);
|
|
|
|
IISChannel->SetTopChannel(InChannel);
|
|
ProxyReceiver->SetTopChannel(InChannel);
|
|
PingReceiver->SetTopChannel(InChannel);
|
|
|
|
ASSERT(RpcStatus == RPC_S_OK);
|
|
|
|
*ReturnInChannel = InChannel;
|
|
*IISContext = IISChannel;
|
|
|
|
goto CleanupAndExit;
|
|
|
|
AbortAndExit:
|
|
if (PingReceiverNeedsCleanup)
|
|
{
|
|
PingReceiver->Abort(RpcStatus);
|
|
PingReceiver->FreeObject();
|
|
}
|
|
else if (ProxyReceiverNeedsCleanup)
|
|
{
|
|
ProxyReceiver->Abort(RpcStatus);
|
|
ProxyReceiver->FreeObject();
|
|
}
|
|
else if (IISChannelNeedsCleanup)
|
|
{
|
|
IISChannel->Abort(RpcStatus);
|
|
IISChannel->FreeObject();
|
|
}
|
|
|
|
if (MemoryBlock)
|
|
delete [] MemoryBlock;
|
|
|
|
CleanupAndExit:
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2InProxyVirtualConnection::AllocateAndInitializeOutChannel (
|
|
OUT HTTP2InProxyOutChannel **ReturnOutChannel
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Allocates and initializes the in proxy out channel.
|
|
|
|
Arguments:
|
|
|
|
ReturnInChannel - on success the created in channel.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* for error
|
|
|
|
--*/
|
|
{
|
|
ULONG MemorySize;
|
|
BYTE *MemoryBlock, *CurrentBlock;
|
|
HTTP2InProxyOutChannel *OutChannel;
|
|
HTTP2ProxyPlugChannel *PlugChannel;
|
|
HTTP2FlowControlSender *FlowControlSender;
|
|
HTTP2ProxySocketTransportChannel *RawChannel;
|
|
WS_HTTP2_CONNECTION *RawConnection;
|
|
BOOL PlugChannelNeedsCleanup;
|
|
BOOL FlowControlSenderNeedsCleanup;
|
|
BOOL RawChannelNeedsCleanup;
|
|
BOOL RawConnectionNeedsCleanup;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
// alocate the in channel
|
|
MemorySize = SIZE_OF_OBJECT_AND_PADDING(HTTP2InProxyOutChannel)
|
|
+ SIZE_OF_OBJECT_AND_PADDING(HTTP2ProxyPlugChannel)
|
|
+ SIZE_OF_OBJECT_AND_PADDING(HTTP2FlowControlSender)
|
|
+ SIZE_OF_OBJECT_AND_PADDING(HTTP2ProxySocketTransportChannel)
|
|
+ sizeof(WS_HTTP2_CONNECTION);
|
|
|
|
MemoryBlock = (BYTE *) new char [MemorySize];
|
|
CurrentBlock = MemoryBlock;
|
|
if (CurrentBlock == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
|
|
OutChannel = (HTTP2InProxyOutChannel *) MemoryBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2InProxyOutChannel);
|
|
|
|
PlugChannel = (HTTP2ProxyPlugChannel *) CurrentBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2ProxyPlugChannel);
|
|
|
|
FlowControlSender = (HTTP2FlowControlSender *) CurrentBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2FlowControlSender);
|
|
|
|
RawChannel = (HTTP2ProxySocketTransportChannel *)CurrentBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2ProxySocketTransportChannel);
|
|
|
|
RawConnection = (WS_HTTP2_CONNECTION *)CurrentBlock;
|
|
RawConnection->HeaderRead = FALSE;
|
|
RawConnection->ReadHeaderFn = HTTP2ReadHttpLegacyResponse;
|
|
|
|
// all memory blocks are allocated. Go and initialize them. Use explicit
|
|
// placement
|
|
PlugChannelNeedsCleanup = FALSE;
|
|
FlowControlSenderNeedsCleanup = FALSE;
|
|
RawChannelNeedsCleanup = FALSE;
|
|
RawConnectionNeedsCleanup = FALSE;
|
|
|
|
RawConnection->id = INVALID_PROTOCOL_ID;
|
|
RawConnection->Initialize();
|
|
RawConnection->type = COMPLEX_T | CONNECTION | CLIENT;
|
|
|
|
RawConnectionNeedsCleanup = TRUE;
|
|
|
|
RpcStatus = RPC_S_OK;
|
|
RawChannel = new (RawChannel) HTTP2ProxySocketTransportChannel (RawConnection, &RpcStatus);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
RawChannel->HTTP2ProxySocketTransportChannel::~HTTP2ProxySocketTransportChannel();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
RawConnection->Channel = RawChannel;
|
|
|
|
RawChannelNeedsCleanup = TRUE;
|
|
|
|
FlowControlSender = new (FlowControlSender) HTTP2FlowControlSender (FALSE, // IsServer
|
|
FALSE, // SendToRuntime
|
|
&RpcStatus
|
|
);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
FlowControlSender->HTTP2FlowControlSender::~HTTP2FlowControlSender();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
RawChannel->SetUpperChannel(FlowControlSender);
|
|
FlowControlSender->SetLowerChannel(RawChannel);
|
|
|
|
FlowControlSenderNeedsCleanup = TRUE;
|
|
|
|
PlugChannel = new (PlugChannel) HTTP2ProxyPlugChannel (&RpcStatus);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
PlugChannel->HTTP2ProxyPlugChannel::~HTTP2ProxyPlugChannel();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
FlowControlSender->SetUpperChannel(PlugChannel);
|
|
PlugChannel->SetLowerChannel(FlowControlSender);
|
|
|
|
PlugChannelNeedsCleanup = TRUE;
|
|
|
|
OutChannel = new (OutChannel) HTTP2InProxyOutChannel (this,
|
|
RawConnection,
|
|
&RpcStatus);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
OutChannel->HTTP2InProxyOutChannel::~HTTP2InProxyOutChannel();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
PlugChannel->SetUpperChannel(OutChannel);
|
|
OutChannel->SetLowerChannel(PlugChannel);
|
|
|
|
RawChannel->SetTopChannel(OutChannel);
|
|
FlowControlSender->SetTopChannel(OutChannel);
|
|
PlugChannel->SetTopChannel(OutChannel);
|
|
|
|
ASSERT(RpcStatus == RPC_S_OK);
|
|
|
|
*ReturnOutChannel = OutChannel;
|
|
|
|
goto CleanupAndExit;
|
|
|
|
AbortAndExit:
|
|
|
|
RawConnection->fIgnoreFree = TRUE;
|
|
|
|
if (PlugChannelNeedsCleanup)
|
|
{
|
|
PlugChannel->Abort(RpcStatus);
|
|
PlugChannel->FreeObject();
|
|
}
|
|
else if (FlowControlSenderNeedsCleanup)
|
|
{
|
|
FlowControlSender->Abort(RpcStatus);
|
|
FlowControlSender->FreeObject();
|
|
}
|
|
else if (RawChannelNeedsCleanup)
|
|
{
|
|
RawChannel->Abort(RpcStatus);
|
|
RawChannel->FreeObject();
|
|
}
|
|
else if (RawConnectionNeedsCleanup)
|
|
{
|
|
RawConnection->RealAbort();
|
|
}
|
|
|
|
RawConnection->fIgnoreFree = FALSE;
|
|
|
|
if (MemoryBlock)
|
|
delete [] MemoryBlock;
|
|
|
|
CleanupAndExit:
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2InProxyVirtualConnection::ConnectToServer (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Connects to the server
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* for error
|
|
|
|
--*/
|
|
{
|
|
HTTP2ChannelPointer *ChannelPtr;
|
|
HTTP2InProxyOutChannel *OutChannel;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
OutChannel = LockDefaultOutChannel(&ChannelPtr);
|
|
|
|
// we don't have any async operations to abort the connection
|
|
// yet - the out channel must be there
|
|
if (OutChannel == NULL)
|
|
{
|
|
ASSERT(0);
|
|
return RPC_S_INTERNAL_ERROR;
|
|
}
|
|
|
|
RpcStatus = OutChannel->InitializeRawConnection(ServerName,
|
|
ServerPort,
|
|
ConnectionTimeout,
|
|
ProxyCallbackInterface->IsValidMachineFn
|
|
);
|
|
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2InProxyVirtualConnection::SendD1_B2ToServer (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sends D1/B2 to server
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* for error
|
|
|
|
--*/
|
|
{
|
|
HTTP2ChannelPointer *ChannelPtr;
|
|
HTTP2InProxyOutChannel *OutChannel;
|
|
RPC_STATUS RpcStatus;
|
|
HTTP2SendContext *SendContext;
|
|
BOOL SendSucceeded = FALSE;
|
|
|
|
SendContext = AllocateAndInitializeD1_B2(ProtocolVersion,
|
|
&EmbeddedConnectionCookie,
|
|
&InChannelCookies[0],
|
|
HTTP2InProxyReceiveWindow,
|
|
IISConnectionTimeout,
|
|
&AssociationGroupId,
|
|
&ClientAddress
|
|
);
|
|
|
|
if (SendContext == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
|
|
OutChannel = LockDefaultOutChannel(&ChannelPtr);
|
|
// we don't have any async operations to abort the connection
|
|
// yet - the out channel must be there
|
|
ASSERT(OutChannel);
|
|
RpcStatus = OutChannel->Send(SendContext);
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
SendSucceeded = TRUE;
|
|
RpcStatus = OutChannel->Receive(http2ttRaw);
|
|
}
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
if (SendSucceeded == FALSE)
|
|
{
|
|
FreeRTSPacket(SendContext);
|
|
}
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2InProxyVirtualConnection::SendD2_A2ToServer (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sends D2/A2 to server
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* for error
|
|
|
|
--*/
|
|
{
|
|
HTTP2ChannelPointer *ChannelPtr;
|
|
HTTP2InProxyOutChannel *OutChannel;
|
|
RPC_STATUS RpcStatus;
|
|
HTTP2SendContext *SendContext;
|
|
BOOL SendSucceeded = FALSE;
|
|
|
|
SendContext = AllocateAndInitializeD2_A2(ProtocolVersion,
|
|
&EmbeddedConnectionCookie,
|
|
&InChannelCookies[1],
|
|
&InChannelCookies[0],
|
|
HTTP2InProxyReceiveWindow,
|
|
IISConnectionTimeout
|
|
);
|
|
|
|
if (SendContext == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
|
|
OutChannel = LockDefaultOutChannel(&ChannelPtr);
|
|
// we don't have any async operations to abort the connection
|
|
// yet - the out channel must be there
|
|
ASSERT(OutChannel);
|
|
RpcStatus = OutChannel->Send(SendContext);
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
SendSucceeded = TRUE;
|
|
RpcStatus = OutChannel->Receive(http2ttRaw);
|
|
}
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
if (SendSucceeded == FALSE)
|
|
{
|
|
FreeRTSPacket(SendContext);
|
|
}
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2OutProxyInChannel
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS HTTP2OutProxyInChannel::ForwardFlowControlAck (
|
|
IN ULONG BytesReceivedForAck,
|
|
IN ULONG WindowForAck
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Forwards a flow control ack back to the server
|
|
|
|
Arguments:
|
|
|
|
BytesReceivedForAck - the bytes received when the ACK was issued
|
|
|
|
WindowForAck - the free window when the ACK was issued.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_*
|
|
|
|
--*/
|
|
{
|
|
return ForwardFlowControlAckOnThisChannel(BytesReceivedForAck,
|
|
WindowForAck,
|
|
FALSE // NonChannelData
|
|
);
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2OutProxyOutChannel
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS HTTP2OutProxyOutChannel::LastPacketSentNotification (
|
|
IN HTTP2SendContext *LastSendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
When a lower channel wants to notify the top
|
|
channel that the last packet has been sent,
|
|
they call this function. Must be called from
|
|
an upcall/neutral context. Only flow control
|
|
senders support last packet notifications
|
|
|
|
Arguments:
|
|
|
|
LastSendContext - the context for the last send
|
|
|
|
Return Value:
|
|
|
|
The value to return to the bottom channel
|
|
|
|
--*/
|
|
{
|
|
HTTP2OutProxyVirtualConnection *VirtualConnection;
|
|
|
|
ASSERT(LastSendContext->Flags & SendContextFlagSendLast);
|
|
ASSERT((LastSendContext->UserData == oplptD4_A10)
|
|
|| (LastSendContext->UserData == oplptD5_B3));
|
|
|
|
VirtualConnection = (HTTP2OutProxyVirtualConnection *)LockParentPointer();
|
|
// if the connection was already aborted, nothing to do
|
|
if (VirtualConnection == NULL)
|
|
return RPC_P_PACKET_CONSUMED;
|
|
|
|
// we know the parent will disconnect from us in their
|
|
// notification
|
|
VirtualConnection->LastPacketSentNotification(ChannelId,
|
|
LastSendContext);
|
|
|
|
UnlockParentPointer();
|
|
|
|
if (LastSendContext->UserData == oplptD5_B3)
|
|
{
|
|
// if we are about to send D5_B3, this is the last packet
|
|
// on the channel. Detach from the parent and return an
|
|
// error
|
|
DrainUpcallsAndFreeParent();
|
|
}
|
|
|
|
// just shutdown the connection or what has remained of it
|
|
// (only this channel in the D5_B3 case)
|
|
return RPC_P_CONNECTION_SHUTDOWN;
|
|
}
|
|
|
|
void HTTP2OutProxyOutChannel::PingTrafficSentNotify (
|
|
IN ULONG PingTrafficSize
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Notifies a channel that ping traffic has been sent.
|
|
|
|
Arguments:
|
|
|
|
PingTrafficSize - the size of the ping traffic sent.
|
|
|
|
--*/
|
|
{
|
|
BOOL Result;
|
|
|
|
AccumulatedPingTraffic += PingTrafficSize;
|
|
if (AccumulatedPingTraffic >= AccumulatedPingTrafficNotifyThreshold)
|
|
{
|
|
Result = PingTrafficSentNotifyServer (AccumulatedPingTraffic);
|
|
if (Result)
|
|
AccumulatedPingTraffic = 0;
|
|
}
|
|
}
|
|
|
|
BOOL HTTP2OutProxyOutChannel::PingTrafficSentNotifyServer (
|
|
IN ULONG PingTrafficSize
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sends a notification to the server that ping traffic originated
|
|
at the out proxy has been sent. This allows to server to do
|
|
proper accounting for when to recycle the out channel.
|
|
|
|
Arguments:
|
|
|
|
PingTrafficSize - the size of the ping traffic to notify the
|
|
server about.
|
|
|
|
Return Value:
|
|
|
|
Non-zero if the notification was sent successfully.
|
|
0 otherwise.
|
|
|
|
--*/
|
|
{
|
|
HTTP2OutProxyVirtualConnection *VirtualConnection;
|
|
BOOL Result;
|
|
|
|
VirtualConnection = (HTTP2OutProxyVirtualConnection *)LockParentPointer();
|
|
// if the connection was already aborted, nothing to do
|
|
if (VirtualConnection == NULL)
|
|
return TRUE;
|
|
|
|
Result = VirtualConnection->PingTrafficSentNotifyServer(PingTrafficSize);
|
|
|
|
UnlockParentPointer();
|
|
|
|
return Result;
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2OutProxyVirtualConnection
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS HTTP2OutProxyVirtualConnection::InitializeProxyFirstLeg (
|
|
IN USHORT *ServerAddress,
|
|
IN USHORT *ServerPort,
|
|
IN void *ConnectionParameter,
|
|
IN I_RpcProxyCallbackInterface *ProxyCallbackInterface,
|
|
void **IISContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialize the proxy.
|
|
|
|
Arguments:
|
|
|
|
ServerAddress - unicode pointer string to the server network address.
|
|
|
|
ServerPort - unicode pointer string to the server port
|
|
|
|
ConnectionParameter - the extension control block in this case
|
|
|
|
ProxyCallbackInterface - a callback interface to the proxy to perform
|
|
various proxy specific functions.
|
|
|
|
IISContext - on output (success only) it must be initialized to
|
|
the bottom IISChannel for the InProxy.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
HTTP2OutProxyOutChannel *NewOutChannel;
|
|
int OutChannelId;
|
|
int ServerAddressLength; // in characters + terminating 0
|
|
|
|
// initialize out channel
|
|
RpcStatus = AllocateAndInitializeOutChannel(ConnectionParameter,
|
|
&NewOutChannel,
|
|
IISContext
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
return RpcStatus;
|
|
|
|
SetFirstOutChannel(NewOutChannel);
|
|
|
|
this->ProxyCallbackInterface = ProxyCallbackInterface;
|
|
this->ConnectionParameter = ConnectionParameter;
|
|
|
|
ServerAddressLength = RpcpStringLength(ServerAddress) + 1;
|
|
ServerName = new RPC_CHAR [ServerAddressLength];
|
|
|
|
if (ServerName == NULL)
|
|
{
|
|
Abort();
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
RpcpMemoryCopy(ServerName, ServerAddress, ServerAddressLength * 2);
|
|
|
|
RpcStatus = EndpointToPortNumber(ServerPort, this->ServerPort);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
Abort();
|
|
// fall through with error
|
|
}
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2OutProxyVirtualConnection::StartProxy (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Kicks off listening on the proxy
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* for error
|
|
|
|
--*/
|
|
{
|
|
HTTP2OutProxyOutChannel *Channel;
|
|
HTTP2ChannelPointer *ChannelPtr;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, IN_CHANNEL_STATE, http2svClosed, 1, 0);
|
|
State.State = http2svClosed; // move to closed state expecting the opening RTS packet
|
|
|
|
Channel = LockDefaultOutChannel(&ChannelPtr);
|
|
|
|
ASSERT(Channel != NULL); // we cannot be disconnected now
|
|
|
|
RpcStatus = Channel->Receive(http2ttRaw);
|
|
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2OutProxyVirtualConnection::InitializeProxySecondLeg (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialize the proxy.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
HTTP2OutProxyInChannel *NewInChannel;
|
|
int InChannelId;
|
|
|
|
// initialize in channel
|
|
RpcStatus = AllocateAndInitializeInChannel(
|
|
&NewInChannel
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
// this will always come from an upcall. Just return failure
|
|
return RpcStatus;
|
|
}
|
|
|
|
SetFirstInChannel(NewInChannel);
|
|
|
|
RpcStatus = ProxyCallbackInterface->GetConnectionTimeoutFn(&IISConnectionTimeout);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
// this will always come from an upcall. Just return failure
|
|
return RpcStatus;
|
|
}
|
|
|
|
IISConnectionTimeout *= 1000;
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2OutProxyVirtualConnection::ReceiveComplete (
|
|
IN RPC_STATUS EventStatus,
|
|
IN BYTE *Buffer,
|
|
IN UINT BufferLength,
|
|
IN int ChannelId
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Called by lower layers to indicate receive complete
|
|
|
|
Arguments:
|
|
|
|
EventStatus - RPC_S_OK for success or RPC_S_* for error
|
|
Buffer - buffer received
|
|
BufferLength - length of buffer received
|
|
ChannelId - which channel completed the operation
|
|
|
|
Return Value:
|
|
|
|
RPC_P_PACKET_CONSUMED if the packet was consumed and should
|
|
be hidden from the runtime.
|
|
RPC_S_OK if the packet was processed successfully.
|
|
RPC_S_* error if there was an error while processing the
|
|
packet.
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
BOOL BufferFreed = FALSE;
|
|
BOOL MutexCleared;
|
|
ULONG ChannelLifetime;
|
|
ULONG Ignored;
|
|
HTTP2ChannelPointer *ChannelPtr;
|
|
HTTP2ChannelPointer *ChannelPtr2;
|
|
HTTP2OutProxyOutChannel *OutChannel;
|
|
HTTP2OutProxyOutChannel *OutChannel2;
|
|
CookieCollection *OutProxyCookieCollection;
|
|
BYTE *CurrentPosition;
|
|
rpcconn_tunnel_settings *RTS;
|
|
HTTP2SendContext *D5_A4Context;
|
|
HTTP2OutProxyVirtualConnection *ExistingConnection;
|
|
int NonDefaultSelector;
|
|
int DefaultSelector;
|
|
HTTP2SendContext *D4_A10Context;
|
|
BOOL IsAckOrNak;
|
|
HTTP2SendContext *D5_B3Context;
|
|
ULONG BytesReceivedForAck;
|
|
ULONG WindowForAck;
|
|
HTTP2Cookie CookieForChannel;
|
|
ULONG ClientReceiveWindowSize;
|
|
HTTP2SendContext *SendContext;
|
|
ULONG LocalProtocolVersion;
|
|
|
|
VerifyValidChannelId(ChannelId);
|
|
|
|
if (EventStatus == RPC_S_OK)
|
|
{
|
|
// N.B. All recieve packets are guaranteed to be
|
|
// validated up to the common conn packet size
|
|
if (IsRTSPacket(Buffer))
|
|
{
|
|
RpcStatus = HTTPTransInfo->CreateThread();
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
RpcFreeBuffer(Buffer);
|
|
return RpcStatus;
|
|
}
|
|
|
|
if (IsD2_A3Packet (Buffer,
|
|
BufferLength,
|
|
&LocalProtocolVersion))
|
|
{
|
|
// the newly acquired version can only be lower than the
|
|
// old version
|
|
CORRUPTION_ASSERT (LocalProtocolVersion <= ProtocolVersion);
|
|
if (LocalProtocolVersion > ProtocolVersion)
|
|
{
|
|
RpcFreeBuffer(Buffer);
|
|
return RPC_S_PROTOCOL_ERROR;
|
|
}
|
|
|
|
ProtocolVersion = LocalProtocolVersion;
|
|
RpcStatus = RPC_P_PACKET_NEEDS_FORWARDING;
|
|
}
|
|
else
|
|
{
|
|
RpcStatus = CheckPacketForForwarding(Buffer,
|
|
BufferLength,
|
|
fdOutProxy
|
|
);
|
|
}
|
|
}
|
|
|
|
if (IsRTSPacket(Buffer) && (RpcStatus != RPC_P_PACKET_NEEDS_FORWARDING))
|
|
{
|
|
// RTS packet - check what we need to do with it
|
|
if (IsOtherCmdPacket(Buffer, BufferLength))
|
|
{
|
|
// the only other cmd packets we expect in the proxy are
|
|
// flow control acks
|
|
RpcStatus = ParseAndFreeFlowControlAckPacketWithDestination (Buffer,
|
|
BufferLength,
|
|
fdOutProxy,
|
|
&BytesReceivedForAck,
|
|
&WindowForAck,
|
|
&CookieForChannel
|
|
);
|
|
|
|
BufferFreed = TRUE;
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
return RpcStatus;
|
|
|
|
// notify the flow control sender about the ack
|
|
OutChannel = (HTTP2OutProxyOutChannel *)MapCookieToAnyChannelPointer(
|
|
&CookieForChannel,
|
|
&ChannelPtr
|
|
);
|
|
|
|
if (OutChannel && !IsOutChannel(ChannelPtr))
|
|
{
|
|
CORRUPTION_ASSERT(0);
|
|
ChannelPtr->UnlockChannelPointer();
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
OutChannel = NULL;
|
|
// fall through with the error
|
|
}
|
|
|
|
if (OutChannel)
|
|
{
|
|
RpcStatus = OutChannel->FlowControlAckNotify(BytesReceivedForAck,
|
|
WindowForAck
|
|
);
|
|
|
|
ASSERT(RpcStatus != RPC_P_CHANNEL_NEEDS_RECYCLING);
|
|
|
|
ChannelPtr->UnlockChannelPointer();
|
|
}
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
return RpcStatus;
|
|
|
|
// post another receive
|
|
RpcStatus = PostReceiveOnChannel(GetChannelPointerFromId(ChannelId),
|
|
http2ttRaw
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
return RpcStatus;
|
|
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
MutexCleared = FALSE;
|
|
State.Mutex.Request();
|
|
switch (State.State)
|
|
{
|
|
case http2svClosed:
|
|
// for closed states, we must receive
|
|
// stuff only on the default out (client) channel
|
|
ASSERT(IsDefaultOutChannel(ChannelId));
|
|
|
|
CurrentPosition = ValidateRTSPacketCommon(Buffer,
|
|
BufferLength
|
|
);
|
|
|
|
if (CurrentPosition == NULL)
|
|
{
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
break;
|
|
}
|
|
|
|
RTS = (rpcconn_tunnel_settings *)Buffer;
|
|
if ((RTS->Flags & RTS_FLAG_RECYCLE_CHANNEL) == 0)
|
|
{
|
|
RpcStatus = ParseAndFreeD1_A1(Buffer,
|
|
BufferLength,
|
|
&ProtocolVersion,
|
|
&EmbeddedConnectionCookie,
|
|
&OutChannelCookies[0],
|
|
&ClientReceiveWindowSize
|
|
);
|
|
|
|
ProtocolVersion = min(ProtocolVersion, HTTP2ProtocolVersion);
|
|
|
|
BufferFreed = TRUE;
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, IN_CHANNEL_STATE, http2svC1W, 1, 0);
|
|
State.State = http2svC1W;
|
|
State.Mutex.Clear();
|
|
MutexCleared = TRUE;
|
|
|
|
RpcStatus = InitializeProxySecondLeg();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
|
|
RpcStatus = ConnectToServer();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
|
|
RpcStatus = SendHeaderToClient();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
|
|
OutChannel = LockDefaultOutChannel(&ChannelPtr);
|
|
if (OutChannel == NULL)
|
|
{
|
|
RpcStatus = RPC_P_CONNECTION_SHUTDOWN;
|
|
break;
|
|
}
|
|
|
|
OutChannel->SetPeerReceiveWindow(ClientReceiveWindowSize);
|
|
RpcStatus = OutChannel->SetConnectionTimeout(IISConnectionTimeout);
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
// zero out the in channel cookie
|
|
InChannelCookies[0].ZeroOut();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
|
|
RpcStatus = SendD1_A3ToClient();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
|
|
RpcStatus = SendD1_A2ToServer(DefaultChannelLifetime);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RpcStatus = ParseAndFreeD4_A3 (Buffer,
|
|
BufferLength,
|
|
&ProtocolVersion,
|
|
&EmbeddedConnectionCookie,
|
|
&OutChannelCookies[1], // Old cookie - use OutChannelCookies[1]
|
|
// as temporary storage only
|
|
&OutChannelCookies[0], // New cookie
|
|
&ClientReceiveWindowSize
|
|
);
|
|
|
|
ProtocolVersion = min(ProtocolVersion, HTTP2ProtocolVersion);
|
|
|
|
BufferFreed = TRUE;
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
|
|
// caller claims this is recycling for an already existing connection
|
|
// find out this connection
|
|
OutProxyCookieCollection = GetOutProxyCookieCollection();
|
|
OutProxyCookieCollection->LockCollection();
|
|
ExistingConnection = (HTTP2OutProxyVirtualConnection *)
|
|
OutProxyCookieCollection->FindElement(&EmbeddedConnectionCookie);
|
|
if (ExistingConnection == NULL || ActAsSeparateMachinesOnWebFarm)
|
|
{
|
|
// no dice. Probably we executed on a different machine on the web farm
|
|
// proceed as a standalone connection
|
|
OutProxyCookieCollection->UnlockCollection();
|
|
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, OUT_CHANNEL_STATE, http2svA11W, 1, 0);
|
|
State.State = http2svA11W;
|
|
State.Mutex.Clear();
|
|
MutexCleared = TRUE;
|
|
|
|
RpcStatus = InitializeProxySecondLeg();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
|
|
OutChannel = LockDefaultOutChannel(&ChannelPtr);
|
|
// we cannot be aborted here
|
|
ASSERT(OutChannel);
|
|
OutChannel->SetPeerReceiveWindow(ClientReceiveWindowSize);
|
|
// make sure no packets (RTS or other) go out until
|
|
// we get out D4/A11 and send out the header response
|
|
OutChannel->SetStrongPlug();
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
// zero out the in channel cookie
|
|
InChannelCookies[0].ZeroOut();
|
|
|
|
RpcStatus = ConnectToServer();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
|
|
RpcStatus = SendD4_A4ToServer(DefaultChannelLifetime);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
|
|
RpcStatus = PostReceiveOnDefaultChannel(FALSE, // IsInChannel
|
|
http2ttRaw
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// detach the out channel from this connection and attach
|
|
// it to the found connection. Grab a reference to it
|
|
// to prevent the case where it goes away underneath us
|
|
// we know that in its current state the connection is single
|
|
// threaded because we are in the completion path of the
|
|
// only async operation
|
|
ChannelPtr = GetChannelPointerFromId(ChannelId);
|
|
OutChannel = (HTTP2OutProxyOutChannel *)ChannelPtr->LockChannelPointer();
|
|
// there is no way that somebody detached the channel here
|
|
ASSERT(OutChannel);
|
|
|
|
// add a reference to keep the channel alive while we disconnect it
|
|
OutChannel->AddReference();
|
|
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
// no need to drain the upcalls - we know we are the only
|
|
// upcall
|
|
ChannelPtr->FreeChannelPointer(
|
|
FALSE, // DrainUpCalls
|
|
FALSE, // CalledFromUpcallContext
|
|
FALSE, // Abort
|
|
RPC_S_OK
|
|
);
|
|
|
|
DefaultSelector = ExistingConnection->DefaultOutChannelSelector;
|
|
NonDefaultSelector = ExistingConnection->GetNonDefaultOutChannelSelector();
|
|
if (ExistingConnection->OutChannelCookies[DefaultSelector].Compare (&OutChannelCookies[1]))
|
|
{
|
|
// nice try - cookies are different. Ditch the newly established channel
|
|
OutProxyCookieCollection->UnlockCollection();
|
|
OutChannel->RemoveReference();
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
break;
|
|
}
|
|
|
|
OutChannel2 = ExistingConnection->LockDefaultOutChannel(&ChannelPtr2);
|
|
if (OutChannel2 == NULL)
|
|
{
|
|
OutProxyCookieCollection->UnlockCollection();
|
|
OutChannel->RemoveReference();
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
break;
|
|
}
|
|
|
|
ClientReceiveWindowSize = OutChannel2->GetPeerReceiveWindow();
|
|
ChannelPtr2->UnlockChannelPointer();
|
|
|
|
OutChannel->SetPeerReceiveWindow(ClientReceiveWindowSize);
|
|
|
|
OutChannel->SetParent(ExistingConnection);
|
|
ExistingConnection->OutChannels[NonDefaultSelector].SetChannel(OutChannel);
|
|
ExistingConnection->OutChannelCookies[NonDefaultSelector].SetCookie(OutChannelCookies[0].GetCookie());
|
|
ExistingConnection->OutChannelIds[NonDefaultSelector] = ChannelId;
|
|
|
|
// check if connection is aborted
|
|
if (ExistingConnection->Aborted.GetInteger() > 0)
|
|
{
|
|
OutChannel->Abort(RPC_P_CONNECTION_SHUTDOWN);
|
|
}
|
|
|
|
// the extra reference that we added above passes to the existing connection
|
|
// However, below we party on the existing connection and we need to keep it alive
|
|
ExistingConnection->BlockConnectionFromRundown();
|
|
OutProxyCookieCollection->UnlockCollection();
|
|
|
|
State.Mutex.Clear();
|
|
MutexCleared = TRUE;
|
|
|
|
// nuke the rest of the old connection
|
|
|
|
// we got to the destructive phase of the abort
|
|
// guard against double aborts
|
|
if (Aborted.Increment() <= 1)
|
|
{
|
|
// abort the channels
|
|
AbortChannels(RPC_P_CONNECTION_SHUTDOWN);
|
|
|
|
DisconnectChannels(FALSE, // ExemptChannel
|
|
0 // ExemptChannel id
|
|
);
|
|
|
|
delete this;
|
|
// N.B. don't touch the this pointer after here
|
|
}
|
|
|
|
// post another receive on the new channel
|
|
RpcStatus = PostReceiveOnChannel (&(ExistingConnection->OutChannels[NonDefaultSelector]),
|
|
http2ttRaw);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
ExistingConnection->UnblockConnectionFromRundown();
|
|
break;
|
|
}
|
|
|
|
ExistingConnection->State.Mutex.Request();
|
|
LogEvent(SU_HTTPv2, EV_STATE, ExistingConnection, OUT_CHANNEL_STATE, http2svOpened_B1W, 1, 0);
|
|
ExistingConnection->State.State = http2svOpened_B1W;
|
|
ExistingConnection->State.Mutex.Clear();
|
|
|
|
// send D5/A4 to server
|
|
D5_A4Context = AllocateAndInitializeD5_A4(&ExistingConnection->OutChannelCookies[NonDefaultSelector]);
|
|
if (D5_A4Context == NULL)
|
|
{
|
|
ExistingConnection->UnblockConnectionFromRundown();
|
|
RpcStatus = RPC_S_OUT_OF_MEMORY;
|
|
break;
|
|
}
|
|
|
|
RpcStatus = ExistingConnection->SendTrafficOnDefaultChannel (
|
|
TRUE, // IsInChannel
|
|
D5_A4Context
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
FreeRTSPacket(D5_A4Context);
|
|
|
|
ExistingConnection->UnblockConnectionFromRundown();
|
|
|
|
// fall through with the obtained RpcStatus
|
|
}
|
|
}
|
|
break;
|
|
|
|
case http2svOpened:
|
|
State.Mutex.Clear();
|
|
MutexCleared = TRUE;
|
|
|
|
// the only RTS packets we expect in opened state is D4/A9
|
|
RpcStatus = ParseAndFreeD4_A9 (Buffer,
|
|
BufferLength
|
|
);
|
|
|
|
BufferFreed = TRUE;
|
|
|
|
if (RpcStatus == RPC_S_PROTOCOL_ERROR)
|
|
break;
|
|
|
|
// queue D4/A10 for sending
|
|
// First, allocate D4/A10
|
|
D4_A10Context = AllocateAndInitializeD4_A10 ();
|
|
if (D4_A10Context == NULL)
|
|
{
|
|
RpcStatus = RPC_S_OUT_OF_MEMORY;
|
|
break;
|
|
}
|
|
|
|
D4_A10Context->Flags = SendContextFlagSendLast;
|
|
D4_A10Context->UserData = oplptD4_A10;
|
|
RpcStatus = SendTrafficOnDefaultChannel (FALSE, // IsInChannel
|
|
D4_A10Context);
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
// we're done. There were no queued buffers and D4/A10
|
|
// was sent immediately. Close down
|
|
RpcStatus = RPC_P_CONNECTION_SHUTDOWN;
|
|
break;
|
|
}
|
|
else if (RpcStatus == ERROR_IO_PENDING)
|
|
{
|
|
// D4/A10 was not sent immediately. When it is sent,
|
|
// the LastPacketSentNotification mechanism will
|
|
// destroy the connection. Return success for know
|
|
RpcStatus = RPC_S_OK;
|
|
}
|
|
else
|
|
{
|
|
// an error occurred during sending. Free the packet and
|
|
// return error back to the caller
|
|
FreeRTSPacket(D4_A10Context);
|
|
}
|
|
|
|
// on success, post another receive so that we can get flow control
|
|
// acks and we can keep sending to the client in case there was
|
|
// a queue on the out channel
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
RpcStatus = PostReceiveOnChannel(&InChannels[0], http2ttRaw);
|
|
// fall through with any errors
|
|
}
|
|
|
|
break;
|
|
|
|
case http2svC1W:
|
|
ASSERT(IsDefaultInChannel(ChannelId));
|
|
RpcStatus = ParseD1_C1(Buffer,
|
|
BufferLength,
|
|
&ProtocolVersion,
|
|
&Ignored, // InProxyReceiveWindowSize
|
|
&Ignored // InProxyConnectionTimeout
|
|
);
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
ProtocolVersion = min(ProtocolVersion, HTTP2ProtocolVersion);
|
|
|
|
RpcStatus = AddConnectionToCookieCollection();
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, IN_CHANNEL_STATE, http2svOpened, 1, 0);
|
|
State.State = http2svOpened;
|
|
State.Mutex.Clear();
|
|
MutexCleared = TRUE;
|
|
|
|
OutChannel = LockDefaultOutChannel(&ChannelPtr);
|
|
if (OutChannel != NULL)
|
|
{
|
|
RpcStatus = OutChannel->ForwardTraffic(Buffer,
|
|
BufferLength
|
|
);
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
BufferFreed = TRUE;
|
|
RpcStatus = PostReceiveOnChannel(&InChannels[0], http2ttRaw);
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
RpcStatus = OutChannel->Unplug();
|
|
}
|
|
}
|
|
|
|
ChannelPtr->UnlockChannelPointer();
|
|
}
|
|
else
|
|
RpcStatus = RPC_P_CONNECTION_CLOSED;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case http2svOpened_CliW:
|
|
break;
|
|
|
|
case http2svOpened_B1W:
|
|
State.Mutex.Clear();
|
|
MutexCleared = TRUE;
|
|
|
|
// the only RTS packets we expect in opened state is D5/B1 or D2/B2
|
|
RpcStatus = ParseAndFreeD5_B1orB2 (Buffer,
|
|
BufferLength,
|
|
&IsAckOrNak
|
|
);
|
|
|
|
BufferFreed = TRUE;
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
|
|
OutChannel = LockDefaultOutChannel(&ChannelPtr);
|
|
if (OutChannel == NULL)
|
|
{
|
|
RpcStatus = RPC_P_CONNECTION_SHUTDOWN;
|
|
break;
|
|
}
|
|
|
|
// keep an extra reference for after we detach the
|
|
// channel
|
|
OutChannel->AddReference();
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
if (IsAckOrNak == FALSE)
|
|
{
|
|
// Nak - nuke the non-default channel
|
|
// and move to state opened
|
|
ChannelPtr->FreeChannelPointer(TRUE, // DrainUpCalls
|
|
FALSE, // CalledFromUpcallContext
|
|
TRUE, // Abort
|
|
RPC_S_PROTOCOL_ERROR
|
|
);
|
|
|
|
// switch to state opened
|
|
State.Mutex.Request();
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, OUT_CHANNEL_STATE, http2svOpened, 1, 0);
|
|
State.State = http2svOpened;
|
|
State.Mutex.Clear();
|
|
|
|
break;
|
|
}
|
|
|
|
SwitchDefaultOutChannelSelector();
|
|
|
|
// Send D5/B3 to client
|
|
D5_B3Context = AllocateAndInitializeD5_B3();
|
|
|
|
if (D5_B3Context == NULL)
|
|
{
|
|
OutChannel->RemoveReference();
|
|
RpcStatus = RPC_S_OUT_OF_MEMORY;
|
|
break;
|
|
}
|
|
|
|
D5_B3Context->Flags = SendContextFlagSendLast;
|
|
D5_B3Context->UserData = oplptD5_B3;
|
|
RpcStatus = OutChannel->Send(D5_B3Context);
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
// synchronous send. Abort and detach the old channel
|
|
ChannelPtr->FreeChannelPointer(TRUE, // DrainUpCalls
|
|
FALSE, // CalledFromUpcallContext
|
|
TRUE, // Abort
|
|
RPC_P_CONNECTION_SHUTDOWN
|
|
);
|
|
}
|
|
else if (RpcStatus == ERROR_IO_PENDING)
|
|
{
|
|
// async send. Just release our reference
|
|
// and return success
|
|
RpcStatus = RPC_S_OK;
|
|
}
|
|
else
|
|
{
|
|
// failed to send. Abort all
|
|
FreeRTSPacket(D5_B3Context);
|
|
OutChannel->Abort(RpcStatus);
|
|
OutChannel->RemoveReference();
|
|
break;
|
|
}
|
|
|
|
// release the extra reference
|
|
OutChannel->RemoveReference();
|
|
|
|
// switch to state opened
|
|
State.Mutex.Request();
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, OUT_CHANNEL_STATE, http2svOpened, 1, 0);
|
|
State.State = http2svOpened;
|
|
State.Mutex.Clear();
|
|
|
|
// unplug the newly created channel
|
|
OutChannel = LockDefaultOutChannel(&ChannelPtr);
|
|
if (OutChannel == NULL)
|
|
{
|
|
RpcStatus = RPC_P_CONNECTION_SHUTDOWN;
|
|
break;
|
|
}
|
|
|
|
RpcStatus = OutChannel->Unplug();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
ChannelPtr->UnlockChannelPointer();
|
|
break;
|
|
}
|
|
|
|
// send the header response to the client
|
|
RpcStatus = SendHeaderToClient();
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
ChannelPtr->UnlockChannelPointer();
|
|
break;
|
|
}
|
|
|
|
// set the connection timeout
|
|
|
|
RpcStatus = OutChannel->SetConnectionTimeout(IISConnectionTimeout);
|
|
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
|
|
// post another receive on the channel
|
|
RpcStatus = PostReceiveOnDefaultChannel(TRUE, // IsInChannel
|
|
http2ttRaw);
|
|
// fall through with the new error code
|
|
break;
|
|
|
|
case http2svA11W:
|
|
if (IsOutChannel(ChannelId) == FALSE)
|
|
{
|
|
ASSERT(0);
|
|
// make sure client doesn't rush things
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
break;
|
|
}
|
|
|
|
RpcStatus = ParseAndFreeD4_A11(Buffer,
|
|
BufferLength
|
|
);
|
|
|
|
BufferFreed = TRUE;
|
|
|
|
if (RpcStatus == RPC_S_PROTOCOL_ERROR)
|
|
break;
|
|
|
|
// now that we know the new channel is legit, add it to the
|
|
// collection
|
|
OutProxyCookieCollection = GetOutProxyCookieCollection();
|
|
OutProxyCookieCollection->LockCollection();
|
|
ExistingConnection = (HTTP2OutProxyVirtualConnection *)
|
|
OutProxyCookieCollection->FindElement(&EmbeddedConnectionCookie);
|
|
if (ExistingConnection != NULL)
|
|
{
|
|
// the only way we will be in this protocol is if
|
|
// we were faking a web farm
|
|
ASSERT (ActAsSeparateMachinesOnWebFarm);
|
|
ProxyConnectionCookie = ExistingConnection->GetCookie();
|
|
ProxyConnectionCookie->AddRefCount();
|
|
ProxyConnectionCookie->SetConnection(this);
|
|
|
|
// remember that we are part of the cookie collection now
|
|
IsConnectionInCollection = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// we truly didn't find anything - add ourselves.
|
|
RpcStatus = AddConnectionToCookieCollection ();
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
OutProxyCookieCollection->UnlockCollection();
|
|
break;
|
|
}
|
|
}
|
|
OutProxyCookieCollection->UnlockCollection();
|
|
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, IN_CHANNEL_STATE, http2svOpened, 1, 0);
|
|
State.State = http2svOpened;
|
|
State.Mutex.Clear();
|
|
MutexCleared = TRUE;
|
|
|
|
// unplug the out channel to get the flow going
|
|
OutChannel = LockDefaultOutChannel (&ChannelPtr);
|
|
if (OutChannel == NULL)
|
|
{
|
|
RpcStatus = RPC_P_CONNECTION_SHUTDOWN;
|
|
break;
|
|
}
|
|
|
|
RpcStatus = SendHeaderToClient();
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
ChannelPtr->UnlockChannelPointer();
|
|
break;
|
|
}
|
|
|
|
RpcStatus = OutChannel->SetConnectionTimeout(IISConnectionTimeout);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
ChannelPtr->UnlockChannelPointer();
|
|
break;
|
|
}
|
|
|
|
RpcStatus = OutChannel->Unplug();
|
|
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
break;
|
|
|
|
case http2svOpened_A5W:
|
|
break;
|
|
|
|
case http2svB2W:
|
|
break;
|
|
|
|
default:
|
|
ASSERT(0);
|
|
}
|
|
if (MutexCleared == FALSE)
|
|
State.Mutex.Clear();
|
|
}
|
|
else
|
|
{
|
|
// data packet or RTS packet that needs forwarding. Just forward it
|
|
ASSERT (IsDefaultInChannel(ChannelId));
|
|
|
|
if (State.State == http2svC1W)
|
|
{
|
|
// non-RTS packet or forward RTS packet in C1W state from out channel
|
|
// is a protocol error
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
}
|
|
else
|
|
{
|
|
SendContext = AllocateAndInitializeContextFromPacket(Buffer,
|
|
BufferLength
|
|
);
|
|
|
|
// the buffer is converted to send context. We can't free
|
|
// it directly - we must make sure we free it on failure before exit.
|
|
BufferFreed = TRUE;
|
|
|
|
if (SendContext == NULL)
|
|
{
|
|
RpcStatus = RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(SendContext->Flags == 0);
|
|
SendContext->Flags = SendContextFlagProxySend;
|
|
SendContext->UserData = ConvertChannelIdToSendContextUserData(ChannelId);
|
|
RpcStatus = SendTrafficOnDefaultChannel(FALSE, // IsInChannel
|
|
SendContext
|
|
);
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
ChannelPtr = GetChannelPointerFromId(ChannelId);
|
|
|
|
RpcStatus = PostReceiveOnChannel(ChannelPtr, http2ttRaw);
|
|
}
|
|
else
|
|
{
|
|
FreeSendContextAndPossiblyData(SendContext);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// just turn around the error code
|
|
RpcStatus = EventStatus;
|
|
// in failure cases we don't own the buffer
|
|
BufferFreed = TRUE;
|
|
}
|
|
|
|
if (BufferFreed == FALSE)
|
|
RpcFreeBuffer(Buffer);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
void HTTP2OutProxyVirtualConnection::EnableIISSessionClose (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Enables close of the IIS session at the IISTransportChannel
|
|
level. Simply delegates to the appropriate channel.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
// on the out proxy, the out channel has the IISTransport channel
|
|
HTTP2OutProxyOutChannel *OutChannel;
|
|
HTTP2ChannelPointer *ChannelPtr;
|
|
|
|
OutChannel = LockDefaultOutChannel(&ChannelPtr);
|
|
if (OutChannel == NULL)
|
|
{
|
|
// somebody aborted the connection - nothing to do
|
|
return;
|
|
}
|
|
|
|
OutChannel->EnableIISSessionClose();
|
|
|
|
ChannelPtr->UnlockChannelPointer();
|
|
}
|
|
|
|
BOOL HTTP2OutProxyVirtualConnection::PingTrafficSentNotifyServer (
|
|
IN ULONG PingTrafficSize
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sends a notification to the server that ping traffic originated
|
|
at the out proxy has been sent. This allows to server to do
|
|
proper accounting for when to recycle the out channel.
|
|
The function is called from neutral upcall context. It can't
|
|
return an error, and it can't abort.
|
|
|
|
Arguments:
|
|
|
|
PingTrafficSize - the size of the ping traffic to notify the
|
|
server about.
|
|
|
|
Return Value:
|
|
|
|
Non-zero if the notification was sent successfully.
|
|
0 otherwise.
|
|
|
|
--*/
|
|
{
|
|
HTTP2SendContext *PingTrafficSentContext;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
PingTrafficSentContext = AllocateAndInitializePingTrafficSentNotifyPacket (PingTrafficSize);
|
|
if (PingTrafficSentContext == NULL)
|
|
return FALSE;
|
|
|
|
RpcStatus = SendTrafficOnDefaultChannel (TRUE, // IsInChannel
|
|
PingTrafficSentContext
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
FreeRTSPacket(PingTrafficSentContext);
|
|
return FALSE;
|
|
}
|
|
else
|
|
return TRUE;
|
|
}
|
|
|
|
RPC_STATUS HTTP2OutProxyVirtualConnection::AllocateAndInitializeInChannel (
|
|
OUT HTTP2OutProxyInChannel **ReturnInChannel
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Allocates and initializes the out proxy in channel.
|
|
|
|
Arguments:
|
|
|
|
ReturnInChannel - on success the created in channel.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* for error
|
|
|
|
--*/
|
|
{
|
|
ULONG MemorySize;
|
|
BYTE *MemoryBlock, *CurrentBlock;
|
|
HTTP2OutProxyInChannel *InChannel;
|
|
HTTP2ProxyReceiver *ProxyReceiver;
|
|
HTTP2ProxySocketTransportChannel *RawChannel;
|
|
WS_HTTP2_CONNECTION *RawConnection;
|
|
BOOL ProxyReceiverNeedsCleanup;
|
|
BOOL RawChannelNeedsCleanup;
|
|
BOOL RawConnectionNeedsCleanup;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
// alocate the in channel
|
|
MemorySize = SIZE_OF_OBJECT_AND_PADDING(HTTP2OutProxyInChannel)
|
|
+ SIZE_OF_OBJECT_AND_PADDING(HTTP2ProxyReceiver)
|
|
+ SIZE_OF_OBJECT_AND_PADDING(HTTP2ProxySocketTransportChannel)
|
|
+ sizeof(WS_HTTP2_CONNECTION);
|
|
|
|
MemoryBlock = (BYTE *) new char [MemorySize];
|
|
CurrentBlock = MemoryBlock;
|
|
if (CurrentBlock == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
|
|
InChannel = (HTTP2OutProxyInChannel *) MemoryBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2OutProxyInChannel);
|
|
|
|
ProxyReceiver = (HTTP2ProxyReceiver *) CurrentBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2ProxyReceiver);
|
|
|
|
RawChannel = (HTTP2ProxySocketTransportChannel *)CurrentBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2ProxySocketTransportChannel);
|
|
|
|
RawConnection = (WS_HTTP2_CONNECTION *)CurrentBlock;
|
|
RawConnection->HeaderRead = FALSE;
|
|
RawConnection->ReadHeaderFn = HTTP2ReadHttpLegacyResponse;
|
|
|
|
// all memory blocks are allocated. Go and initialize them. Use explicit
|
|
// placement
|
|
ProxyReceiverNeedsCleanup = FALSE;
|
|
RawChannelNeedsCleanup = FALSE;
|
|
RawConnectionNeedsCleanup = FALSE;
|
|
|
|
RawConnection->id = INVALID_PROTOCOL_ID;
|
|
RawConnection->Initialize();
|
|
RawConnection->type = COMPLEX_T | CONNECTION | CLIENT;
|
|
|
|
RawConnectionNeedsCleanup = TRUE;
|
|
|
|
RpcStatus = RPC_S_OK;
|
|
RawChannel = new (RawChannel) HTTP2ProxySocketTransportChannel (RawConnection, &RpcStatus);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
RawChannel->HTTP2ProxySocketTransportChannel::~HTTP2ProxySocketTransportChannel();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
RawConnection->Channel = RawChannel;
|
|
|
|
RawChannelNeedsCleanup = TRUE;
|
|
|
|
ProxyReceiver = new (ProxyReceiver) HTTP2ProxyReceiver (HTTP2OutProxyReceiveWindow,
|
|
&RpcStatus);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
ProxyReceiver->HTTP2ProxyReceiver::~HTTP2ProxyReceiver();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
RawChannel->SetUpperChannel(ProxyReceiver);
|
|
ProxyReceiver->SetLowerChannel(RawChannel);
|
|
|
|
ProxyReceiverNeedsCleanup = TRUE;
|
|
|
|
InChannel = new (InChannel) HTTP2OutProxyInChannel (this,
|
|
RawConnection,
|
|
&RpcStatus);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
InChannel->HTTP2OutProxyInChannel::~HTTP2OutProxyInChannel();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
ProxyReceiver->SetUpperChannel(InChannel);
|
|
InChannel->SetLowerChannel(ProxyReceiver);
|
|
|
|
RawChannel->SetTopChannel(InChannel);
|
|
ProxyReceiver->SetTopChannel(InChannel);
|
|
|
|
ASSERT(RpcStatus == RPC_S_OK);
|
|
|
|
*ReturnInChannel = InChannel;
|
|
|
|
goto CleanupAndExit;
|
|
|
|
AbortAndExit:
|
|
if (ProxyReceiverNeedsCleanup)
|
|
{
|
|
ProxyReceiver->Abort(RpcStatus);
|
|
ProxyReceiver->FreeObject();
|
|
}
|
|
else if (RawChannelNeedsCleanup)
|
|
{
|
|
RawChannel->Abort(RpcStatus);
|
|
RawChannel->FreeObject();
|
|
}
|
|
else if (RawConnectionNeedsCleanup)
|
|
{
|
|
RawConnection->RealAbort();
|
|
}
|
|
|
|
if (MemoryBlock)
|
|
delete [] MemoryBlock;
|
|
|
|
CleanupAndExit:
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2OutProxyVirtualConnection::AllocateAndInitializeOutChannel (
|
|
IN void *ConnectionParameter,
|
|
OUT HTTP2OutProxyOutChannel **ReturnOutChannel,
|
|
OUT void **IISContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Allocates and initializes the out proxy out channel.
|
|
|
|
Arguments:
|
|
|
|
ConnectionParameter - really an EXTENSION_CONTROL_BLOCK
|
|
|
|
ReturnOutChannel - on success the created out channel.
|
|
|
|
IISContext - on output, the IISChannel pointer used as
|
|
connection context with IIS.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* for error
|
|
|
|
--*/
|
|
{
|
|
ULONG MemorySize;
|
|
BYTE *MemoryBlock, *CurrentBlock;
|
|
HTTP2OutProxyOutChannel *OutChannel;
|
|
HTTP2ProxyPlugChannel *PlugChannel;
|
|
HTTP2FlowControlSender *FlowControlSender;
|
|
HTTP2PingOriginator *PingOriginator;
|
|
HTTP2PingReceiver *PingReceiver;
|
|
HTTP2IISSenderTransportChannel *IISChannel;
|
|
BOOL PlugChannelNeedsCleanup;
|
|
BOOL FlowControlSenderNeedsCleanup;
|
|
BOOL PingOriginatorNeedsCleanup;
|
|
BOOL PingReceiverNeedsCleanup;
|
|
BOOL IISChannelNeedsCleanup;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
// alocate the in channel
|
|
MemorySize = SIZE_OF_OBJECT_AND_PADDING(HTTP2OutProxyOutChannel)
|
|
+ SIZE_OF_OBJECT_AND_PADDING(HTTP2ProxyPlugChannel)
|
|
+ SIZE_OF_OBJECT_AND_PADDING(HTTP2FlowControlSender)
|
|
+ SIZE_OF_OBJECT_AND_PADDING(HTTP2PingOriginator)
|
|
+ SIZE_OF_OBJECT_AND_PADDING(HTTP2PingReceiver)
|
|
+ SIZE_OF_OBJECT_AND_PADDING(HTTP2IISSenderTransportChannel)
|
|
;
|
|
|
|
MemoryBlock = (BYTE *) new char [MemorySize];
|
|
CurrentBlock = MemoryBlock;
|
|
if (CurrentBlock == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
|
|
OutChannel = (HTTP2OutProxyOutChannel *) MemoryBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2OutProxyOutChannel);
|
|
|
|
PlugChannel = (HTTP2ProxyPlugChannel *) CurrentBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2ProxyPlugChannel);
|
|
|
|
FlowControlSender = (HTTP2FlowControlSender *) CurrentBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2FlowControlSender);
|
|
|
|
PingOriginator = (HTTP2PingOriginator *)CurrentBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2PingOriginator);
|
|
|
|
PingReceiver = (HTTP2PingReceiver *)CurrentBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2PingReceiver);
|
|
|
|
IISChannel = (HTTP2IISSenderTransportChannel *)CurrentBlock;
|
|
|
|
// all memory blocks are allocated. Go and initialize them. Use explicit
|
|
// placement
|
|
PlugChannelNeedsCleanup = FALSE;
|
|
FlowControlSenderNeedsCleanup = FALSE;
|
|
PingOriginatorNeedsCleanup = FALSE;
|
|
PingReceiverNeedsCleanup = FALSE;
|
|
IISChannelNeedsCleanup = FALSE;
|
|
RpcStatus = RPC_S_OK;
|
|
|
|
IISChannel = new (IISChannel) HTTP2IISSenderTransportChannel (ConnectionParameter, &RpcStatus);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
IISChannel->HTTP2IISSenderTransportChannel::~HTTP2IISSenderTransportChannel();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
IISChannelNeedsCleanup = TRUE;
|
|
|
|
PingReceiver = new (PingReceiver) HTTP2PingReceiver (FALSE);
|
|
|
|
IISChannel->SetUpperChannel(PingReceiver);
|
|
PingReceiver->SetLowerChannel(IISChannel);
|
|
|
|
PingReceiverNeedsCleanup = TRUE;
|
|
|
|
PingOriginator = new (PingOriginator) HTTP2PingOriginator (
|
|
TRUE // NotifyTopChannelForPings
|
|
);
|
|
|
|
PingReceiver->SetUpperChannel(PingOriginator);
|
|
PingOriginator->SetLowerChannel(PingReceiver);
|
|
|
|
PingOriginatorNeedsCleanup = TRUE;
|
|
|
|
FlowControlSender = new (FlowControlSender) HTTP2FlowControlSender (FALSE, // IsServer
|
|
FALSE, // SendToRuntime
|
|
&RpcStatus
|
|
);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
FlowControlSender->HTTP2FlowControlSender::~HTTP2FlowControlSender();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
PingOriginator->SetUpperChannel(FlowControlSender);
|
|
FlowControlSender->SetLowerChannel(PingOriginator);
|
|
|
|
FlowControlSenderNeedsCleanup = TRUE;
|
|
|
|
PlugChannel = new (PlugChannel) HTTP2ProxyPlugChannel (&RpcStatus);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
PlugChannel->HTTP2ProxyPlugChannel::~HTTP2ProxyPlugChannel();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
FlowControlSender->SetUpperChannel(PlugChannel);
|
|
PlugChannel->SetLowerChannel(FlowControlSender);
|
|
|
|
PlugChannelNeedsCleanup = TRUE;
|
|
|
|
OutChannel = new (OutChannel) HTTP2OutProxyOutChannel (this, &RpcStatus);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
OutChannel->HTTP2OutProxyOutChannel::~HTTP2OutProxyOutChannel();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
PlugChannel->SetUpperChannel(OutChannel);
|
|
OutChannel->SetLowerChannel(PlugChannel);
|
|
|
|
IISChannel->SetTopChannel(OutChannel);
|
|
PingOriginator->SetTopChannel(OutChannel);
|
|
PingReceiver->SetTopChannel(OutChannel);
|
|
FlowControlSender->SetTopChannel(OutChannel);
|
|
PlugChannel->SetTopChannel(OutChannel);
|
|
|
|
ASSERT(RpcStatus == RPC_S_OK);
|
|
|
|
*ReturnOutChannel = OutChannel;
|
|
*IISContext = IISChannel;
|
|
|
|
goto CleanupAndExit;
|
|
|
|
AbortAndExit:
|
|
if (PlugChannelNeedsCleanup)
|
|
{
|
|
PlugChannel->Abort(RpcStatus);
|
|
PlugChannel->FreeObject();
|
|
}
|
|
else if (FlowControlSenderNeedsCleanup)
|
|
{
|
|
FlowControlSender->Abort(RpcStatus);
|
|
FlowControlSender->FreeObject();
|
|
}
|
|
else if (PingOriginatorNeedsCleanup)
|
|
{
|
|
PingOriginator->Abort(RpcStatus);
|
|
PingOriginator->FreeObject();
|
|
}
|
|
else if (PingReceiverNeedsCleanup)
|
|
{
|
|
PingReceiver->Abort(RpcStatus);
|
|
PingReceiver->FreeObject();
|
|
}
|
|
else if (IISChannelNeedsCleanup)
|
|
{
|
|
IISChannel->Abort(RpcStatus);
|
|
IISChannel->FreeObject();
|
|
}
|
|
|
|
if (MemoryBlock)
|
|
delete [] MemoryBlock;
|
|
|
|
CleanupAndExit:
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2OutProxyVirtualConnection::ConnectToServer (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Connects to the server and sends D1/A2
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* for error
|
|
|
|
--*/
|
|
{
|
|
HTTP2ChannelPointer *ChannelPtr;
|
|
HTTP2OutProxyInChannel *InChannel;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
InChannel = LockDefaultInChannel(&ChannelPtr);
|
|
|
|
// we cannot be aborted right now
|
|
if (InChannel == NULL)
|
|
{
|
|
ASSERT(0);
|
|
return RPC_S_INTERNAL_ERROR;
|
|
}
|
|
|
|
RpcStatus = InChannel->InitializeRawConnection(ServerName,
|
|
ServerPort,
|
|
ConnectionTimeout,
|
|
ProxyCallbackInterface->IsValidMachineFn
|
|
);
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
RpcStatus = InChannel->Receive(http2ttRaw);
|
|
}
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2OutProxyVirtualConnection::SendHeaderToClient (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sends response header to client
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* for error
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
HTTP2SendContext *SendContext;
|
|
|
|
SendContext = AllocateAndInitializeResponseHeader();
|
|
if (SendContext == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
|
|
RpcStatus = SendTrafficOnDefaultChannel(FALSE, // IsInChannel
|
|
SendContext
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
RpcFreeBuffer(SendContext);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
|
|
RPC_STATUS HTTP2OutProxyVirtualConnection::SendD1_A3ToClient (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sends D1/A3 to client
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* for error
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
HTTP2SendContext *SendContext;
|
|
|
|
SendContext = AllocateAndInitializeD1_A3(IISConnectionTimeout);
|
|
if (SendContext == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
|
|
RpcStatus = SendTrafficOnDefaultChannel(FALSE, // IsInChannel
|
|
SendContext
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
FreeRTSPacket(SendContext);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2OutProxyVirtualConnection::SendD1_A2ToServer (
|
|
IN ULONG ChannelLifetime
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sends D1/A3 to client
|
|
|
|
Arguments:
|
|
|
|
ChannelLifetime - the lifetime of the channel as established by
|
|
the client.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* for error
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
HTTP2SendContext *SendContext;
|
|
|
|
SendContext = AllocateAndInitializeD1_A2 (ProtocolVersion,
|
|
&EmbeddedConnectionCookie,
|
|
&OutChannelCookies[0],
|
|
ChannelLifetime,
|
|
ProxyReceiveWindowSize
|
|
);
|
|
|
|
if (SendContext == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
|
|
RpcStatus = SendTrafficOnDefaultChannel(TRUE, // IsInChannel
|
|
SendContext
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
FreeRTSPacket(SendContext);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2OutProxyVirtualConnection::SendD4_A4ToServer (
|
|
IN ULONG ChannelLifetime
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sends D1/A3 to client
|
|
|
|
Arguments:
|
|
|
|
ChannelLifetime - the lifetime of the channel as established by
|
|
the client.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* for error
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
HTTP2SendContext *SendContext;
|
|
|
|
SendContext = AllocateAndInitializeD4_A4 (ProtocolVersion,
|
|
&EmbeddedConnectionCookie,
|
|
&OutChannelCookies[1],
|
|
&OutChannelCookies[0],
|
|
ChannelLifetime,
|
|
ProxyReceiveWindowSize,
|
|
IISConnectionTimeout
|
|
);
|
|
|
|
if (SendContext == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
|
|
RpcStatus = SendTrafficOnDefaultChannel(TRUE, // IsInChannel
|
|
SendContext
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
FreeRTSPacket(SendContext);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
void HTTP2OutProxyVirtualConnection::LastPacketSentNotification (
|
|
IN int ChannelId,
|
|
IN HTTP2SendContext *LastSendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
When a channel wants to notify the virtual connection
|
|
that the last packet has been sent, they call this function.
|
|
Must be called from an upcall/neutral context. Only flow control
|
|
senders generated past packet notifications
|
|
|
|
Arguments:
|
|
|
|
ChannelId - the channelfor which this notification is.
|
|
|
|
LastSendContext - the send context for the last send
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
HTTP2ChannelPointer *ChannelPtr;
|
|
|
|
ASSERT(LastSendContext->Flags & SendContextFlagSendLast);
|
|
ASSERT((LastSendContext->UserData == oplptD4_A10)
|
|
|| (LastSendContext->UserData == oplptD5_B3));
|
|
|
|
if (LastSendContext->UserData == oplptD5_B3)
|
|
{
|
|
ChannelPtr = GetChannelPointerFromId(ChannelId);
|
|
|
|
if (ChannelPtr == NULL)
|
|
{
|
|
// This should never happen.
|
|
ASSERT(0);
|
|
return;
|
|
}
|
|
|
|
// Detach the old channel
|
|
ChannelPtr->FreeChannelPointer(TRUE, // DrainUpCalls
|
|
TRUE, // CalledFromUpcallContext
|
|
FALSE, // Abort
|
|
RPC_S_OK
|
|
);
|
|
}
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2ServerChannel
|
|
*********************************************************************/
|
|
|
|
void HTTP2ServerChannel::AbortConnection (
|
|
IN RPC_STATUS AbortReason
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Aborts the virtual connection.
|
|
|
|
Arguments:
|
|
|
|
RpcStatus - the error to abort with
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
HTTP2VirtualConnection *VirtualConnection;
|
|
|
|
// Per Rule 40:
|
|
// We need to syncronize with the channels' being added in InitializeServerConnection().
|
|
// It is possible that another thread is calling SetChannel on the channel pointer
|
|
// and we do not want to ensure mutual exclusion with that thread.
|
|
// The thread in InitializeServerConnection() will be holding CookieCollection mutex.
|
|
// We must take this look before LockParentPointer() to avoid a deadlock with a thread
|
|
// in InitializeServerConnection();
|
|
GetServerCookieCollection()->LockCollection();
|
|
|
|
// abort the parent connection
|
|
VirtualConnection = LockParentPointer();
|
|
if (VirtualConnection)
|
|
{
|
|
VirtualConnection->AbortChannels(AbortReason);
|
|
UnlockParentPointer();
|
|
}
|
|
else
|
|
{
|
|
// abort this channel at least
|
|
Abort(AbortReason);
|
|
}
|
|
|
|
GetServerCookieCollection()->UnlockCollection();
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2ServerInChannel
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS HTTP2ServerInChannel::QueryLocalAddress (
|
|
IN OUT void *Buffer,
|
|
IN OUT unsigned long *BufferSize,
|
|
OUT unsigned long *AddressFormat
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Returns the local IP address of a channel.
|
|
|
|
Arguments:
|
|
|
|
Buffer - The buffer that will receive the output address
|
|
|
|
BufferSize - the size of the supplied Buffer on input. On output the
|
|
number of bytes written to the buffer. If the buffer is too small
|
|
to receive all the output data, ERROR_MORE_DATA is returned,
|
|
nothing is written to the buffer, and BufferSize is set to
|
|
the size of the buffer needed to return all the data.
|
|
|
|
AddressFormat - a constant indicating the format of the returned address.
|
|
Currently supported are RPC_P_ADDR_FORMAT_TCP_IPV4 and
|
|
RPC_P_ADDR_FORMAT_TCP_IPV6. Undefined on failure.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
WS_HTTP2_CONNECTION *RawConnection;
|
|
|
|
RpcStatus = BeginSimpleSubmitAsync();
|
|
if (RpcStatus != RPC_S_OK)
|
|
return RpcStatus;
|
|
|
|
RawConnection = GetRawConnection();
|
|
RpcStatus = TCP_QueryLocalAddress(RawConnection,
|
|
Buffer,
|
|
BufferSize,
|
|
AddressFormat
|
|
);
|
|
|
|
FinishSubmitAsync();
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2ServerInChannel::ForwardFlowControlAck (
|
|
IN ULONG BytesReceivedForAck,
|
|
IN ULONG WindowForAck
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Forwards a flow control ack back to the in proxy
|
|
|
|
Arguments:
|
|
|
|
BytesReceivedForAck - the bytes received when the ACK was issued
|
|
|
|
WindowForAck - the free window when the ACK was issued.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_*
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
RpcStatus = ForwardFlowControlAckOnThisChannel(BytesReceivedForAck,
|
|
WindowForAck,
|
|
TRUE // NonChannelData
|
|
);
|
|
|
|
// we're sending non-channel data. This cannot lead to channel recycle
|
|
// indication
|
|
ASSERT(RpcStatus != RPC_P_CHANNEL_NEEDS_RECYCLING);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2ServerOutChannel
|
|
*********************************************************************/
|
|
|
|
RPC_STATUS HTTP2ServerOutChannel::Send (
|
|
IN OUT HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send request
|
|
|
|
Arguments:
|
|
|
|
SendContext - the send context
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
BOOL IsDataSend;
|
|
|
|
IsDataSend = (SendContext->TrafficType == http2ttData);
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_SEND, HTTP2LOG_OT_SERVER_CHANNEL, PtrToUlong(SendContext));
|
|
|
|
if (IsDataSend)
|
|
DataSendsPending.Increment();
|
|
|
|
RpcStatus = HTTP2ServerChannel::Send(SendContext);
|
|
|
|
if (IsDataSend && (RpcStatus != RPC_S_OK) && (RpcStatus != RPC_P_CHANNEL_NEEDS_RECYCLING))
|
|
DataSendsPending.Decrement();
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_SEND, HTTP2LOG_OT_SERVER_CHANNEL, (IsDataSend << 24) | DataSendsPending.GetInteger());
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2ServerOutChannel::SendComplete (
|
|
IN RPC_STATUS EventStatus,
|
|
IN OUT HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send complete notification
|
|
|
|
Arguments:
|
|
|
|
EventStatus - the status of the send
|
|
|
|
SendContext - send context
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
BOOL IsDataSend;
|
|
|
|
IsDataSend = (SendContext->TrafficType == http2ttData);
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_SEND_COMPLETE, HTTP2LOG_OT_SERVER_CHANNEL, PtrToUlong(SendContext));
|
|
|
|
if (SendContext->Flags & SendContextFlagAbandonedSend)
|
|
{
|
|
// abandoned send. Complete it silently and return back
|
|
ASSERT(SendContext->TrafficType == http2ttData);
|
|
|
|
RpcFreeBuffer(SendContext->u.BufferToFree);
|
|
FreeLastSendContext(SendContext);
|
|
|
|
if (IsDataSend)
|
|
DataSendsPending.Decrement();
|
|
ASSERT(DataSendsPending.GetInteger()>=0);
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_SEND_COMPLETE, HTTP2LOG_OT_SERVER_CHANNEL, (IsDataSend << 24) | DataSendsPending.GetInteger());
|
|
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
RpcStatus = HTTP2Channel::SendComplete (EventStatus, SendContext);
|
|
|
|
if (IsDataSend)
|
|
DataSendsPending.Decrement();
|
|
ASSERT(DataSendsPending.GetInteger()>=0);
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_SEND_COMPLETE, HTTP2LOG_OT_SERVER_CHANNEL, (IsDataSend << 24) | DataSendsPending.GetInteger());
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2ServerOutChannel::SyncSend (
|
|
IN HTTP2TrafficType TrafficType,
|
|
IN ULONG BufferLength,
|
|
IN BYTE *Buffer,
|
|
IN BOOL fDisableCancelCheck,
|
|
IN ULONG Timeout,
|
|
IN BASE_ASYNC_OBJECT *Connection,
|
|
IN HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Overwrites HTTP2Channel::SyncSend.
|
|
In the case of HTTP2ServerOutChannel we need to make sure that its
|
|
Send method is called and DataSendsPending counter is incremented.
|
|
|
|
Arguments:
|
|
|
|
TrafficType - the type of traffic
|
|
|
|
BufferLength - the length of the buffer
|
|
|
|
Buffer - the buffer to send
|
|
|
|
fDisableCancelCheck - don't do checks for cancels. Can be
|
|
used as optimization
|
|
|
|
Timeout - the call timeout
|
|
|
|
Connection - the transport connection object. Used for cancelling.
|
|
|
|
SendContext - a memory block of sufficient size to initialize a send context
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_SEND, HTTP2LOG_OT_SERVER_CHANNEL, 0);
|
|
|
|
PrepareForSyncSend (BufferLength,
|
|
Buffer,
|
|
SendContext);
|
|
|
|
// In the case of HTTP2ServerOutChannel we need to make sure that its
|
|
// Send method is called and DataSendsPending counter is incremented.
|
|
RpcStatus = Send(SendContext);
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_SEND, HTTP2LOG_OT_SERVER_CHANNEL, RpcStatus);
|
|
return RpcStatus;
|
|
}
|
|
|
|
void HTTP2ServerOutChannel::SendCancelled (
|
|
IN HTTP2SendContext *SendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
A lower channel cancelled a send already passed through this channel.
|
|
|
|
Arguments:
|
|
|
|
SendContext - the send context of the send that was cancelled
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_SEND_CANCELLED, HTTP2LOG_OT_SERVER_CHANNEL, PtrToUlong(SendContext));
|
|
|
|
HTTP2Channel::SendCancelled(SendContext);
|
|
|
|
if(SendContext->TrafficType == http2ttData)
|
|
DataSendsPending.Decrement();
|
|
|
|
ASSERT(DataSendsPending.GetInteger()>=0);
|
|
|
|
LOG_OPERATION_EXIT(HTTP2LOG_OPERATION_SEND_CANCELLED, HTTP2LOG_OT_SERVER_CHANNEL, DataSendsPending.GetInteger());
|
|
}
|
|
|
|
RPC_STATUS HTTP2ServerOutChannel::SetKeepAliveTimeout (
|
|
IN BOOL TurnOn,
|
|
IN BOOL bProtectIO,
|
|
IN KEEPALIVE_TIMEOUT_UNITS Units,
|
|
IN OUT KEEPALIVE_TIMEOUT KATime,
|
|
IN ULONG KAInterval OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Change the keep alive value on the channel
|
|
|
|
Arguments:
|
|
|
|
TurnOn - if non-zero, keep alives are turned on. If zero, keep alives
|
|
are turned off.
|
|
|
|
bProtectIO - non-zero if IO needs to be protected against async close
|
|
of the connection.
|
|
|
|
Units - in what units is KATime
|
|
|
|
KATime - how much to wait before turning on keep alives
|
|
|
|
KAInterval - the interval between keep alives
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
// The server channel does not support this for Whistler
|
|
return RPC_S_CANNOT_SUPPORT;
|
|
}
|
|
|
|
RPC_STATUS HTTP2ServerOutChannel::LastPacketSentNotification (
|
|
IN HTTP2SendContext *LastSendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
When a lower channel wants to notify the top
|
|
channel that the last packet has been sent,
|
|
they call this function. Must be called from
|
|
an upcall/neutral context. Only flow control
|
|
senders support past packet notifications
|
|
|
|
Arguments:
|
|
|
|
LastSendContext - the context for the last send
|
|
|
|
Return Value:
|
|
|
|
The value to return to the runtime
|
|
|
|
--*/
|
|
{
|
|
HTTP2ServerVirtualConnection *VirtualConnection;
|
|
|
|
VirtualConnection = LockParentPointer();
|
|
// if the connection was already aborted, nothing to do
|
|
if (VirtualConnection == NULL)
|
|
return RPC_P_PACKET_CONSUMED;
|
|
|
|
// we know the parent will disconnect from us in their
|
|
// notification
|
|
VirtualConnection->LastPacketSentNotification(ChannelId,
|
|
LastSendContext);
|
|
|
|
UnlockParentPointer();
|
|
|
|
DrainUpcallsAndFreeParent();
|
|
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
RPC_STATUS HTTP2ServerOutChannel::GetChannelOriginatorBufferQueue (
|
|
OUT LIST_ENTRY *NewBufferHead
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Gets the buffer queue of the channel data originator and if
|
|
any send contexts are done on the cached send context, they are
|
|
moved to an allocated send context.
|
|
|
|
Arguments:
|
|
|
|
NewBufferHead - the linked list head to add the buffers on.
|
|
The head must be empty.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_OUT_OF_MEMORY
|
|
|
|
--*/
|
|
{
|
|
ASSERT(RpcpIsListEmpty(NewBufferHead));
|
|
|
|
GetDataOriginatorChannel()->GetBufferQueue(NewBufferHead);
|
|
|
|
return UnaffinitizeSendContextList(NewBufferHead);
|
|
}
|
|
|
|
RPC_STATUS HTTP2ServerOutChannel::GetFlowControlSenderBufferQueue (
|
|
OUT LIST_ENTRY *NewBufferHead
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Gets the buffer queue of the flow control sender channel and if
|
|
any send contexts are done on the cached send context, they are
|
|
moved to an allocated send context.
|
|
|
|
Arguments:
|
|
|
|
NewBufferHead - the linked list head to add the buffers on.
|
|
The head must be empty.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_OUT_OF_MEMORY
|
|
|
|
--*/
|
|
{
|
|
ASSERT(RpcpIsListEmpty(NewBufferHead));
|
|
|
|
GetFlowControlSenderChannel()->GetBufferQueue(NewBufferHead);
|
|
|
|
return UnaffinitizeSendContextList(NewBufferHead);
|
|
}
|
|
|
|
HTTP2SendContext *HTTP2ServerOutChannel::GetLastSendContext (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Gets (creates if necessary) a last send context.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
The last context created or NULL if there is not enough memory
|
|
|
|
Notes:
|
|
|
|
Since each connection will submit one last send at a time, this
|
|
method can be single threaded.
|
|
|
|
--*/
|
|
{
|
|
if (CachedLastSendContextUsed == FALSE)
|
|
{
|
|
CachedLastSendContextUsed = TRUE;
|
|
return GetCachedLastSendContext();
|
|
}
|
|
else
|
|
{
|
|
return (new HTTP2SendContext);
|
|
}
|
|
}
|
|
|
|
RPC_STATUS HTTP2ServerOutChannel::UnaffinitizeSendContextList (
|
|
IN LIST_ENTRY *ListHead
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Walks through the supplied list of send contexts and if any
|
|
one is the channel cached context, it allocates a new send
|
|
context and frees the cached context send (i.e. unaffinitizes
|
|
it).
|
|
|
|
Arguments:
|
|
|
|
ListHead - the list of send contexts. The list may be empty.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_OUT_OF_MEMORY
|
|
|
|
--*/
|
|
{
|
|
LIST_ENTRY *CurrentEntry;
|
|
HTTP2SendContext *CachedSendContext;
|
|
LIST_ENTRY *CachedSendContextListEntry;
|
|
HTTP2SendContext *NewSendContext;
|
|
BOOL CachedSendContextFound;
|
|
LIST_ENTRY *NextListEntry;
|
|
|
|
CachedSendContext = GetCachedLastSendContext();
|
|
CachedSendContextListEntry = &CachedSendContext->ListEntry;
|
|
|
|
CachedSendContextFound = FALSE;
|
|
CurrentEntry = ListHead->Flink;
|
|
|
|
while (CurrentEntry != ListHead)
|
|
{
|
|
if (CurrentEntry == CachedSendContextListEntry)
|
|
{
|
|
ASSERT(CachedLastSendContextUsed);
|
|
// the cached context can be found only once in the list
|
|
ASSERT(CachedSendContextFound == FALSE);
|
|
CachedSendContextFound = TRUE;
|
|
NewSendContext = new HTTP2SendContext;
|
|
if (NewSendContext == NULL)
|
|
{
|
|
// free the whole list and exit
|
|
CurrentEntry = ListHead;
|
|
|
|
while (CurrentEntry != ListHead)
|
|
{
|
|
// save the next element before we delete
|
|
NextListEntry = CurrentEntry->Flink;
|
|
|
|
FreeLastSendContext(CONTAINING_RECORD(CurrentEntry, HTTP2SendContext, ListEntry));
|
|
|
|
// move on to the next
|
|
CurrentEntry = NextListEntry;
|
|
}
|
|
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
// copy the context and relink the new entry to the list
|
|
RpcpMemoryCopy(NewSendContext, CachedSendContext, sizeof(HTTP2SendContext));
|
|
|
|
NewSendContext->ListEntry.Blink->Flink = &NewSendContext->ListEntry;
|
|
NewSendContext->ListEntry.Flink->Blink = &NewSendContext->ListEntry;
|
|
CurrentEntry = &NewSendContext->ListEntry;
|
|
|
|
FreeLastSendContext(CachedSendContext);
|
|
}
|
|
|
|
CurrentEntry = CurrentEntry->Flink;
|
|
}
|
|
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
/*********************************************************************
|
|
HTTP2ServerVirtualConnection
|
|
*********************************************************************/
|
|
|
|
void HTTP2ServerVirtualConnection::Abort (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Aborts an HTTP connection and disconnects the channels.
|
|
Must only come from the runtime.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
LOG_OPERATION_ENTRY(HTTP2LOG_OPERATION_ABORT, HTTP2LOG_OT_SERVER_VC, 0);
|
|
|
|
// abort the channels themselves
|
|
HTTP2VirtualConnection::AbortChannels(RPC_P_CONNECTION_CLOSED);
|
|
|
|
// we got to the destructive phase of the abort
|
|
// guard against double aborts
|
|
if (Aborted.Increment() > 1)
|
|
return;
|
|
|
|
// rule 38 - drain the sends before disconnecting
|
|
DrainOutChannelPendingSends ();
|
|
|
|
HTTP2VirtualConnection::DisconnectChannels(FALSE, 0);
|
|
|
|
CancelAllTimeouts();
|
|
}
|
|
|
|
void HTTP2ServerVirtualConnection::Close (
|
|
IN BOOL DontFlush
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Closes an HTTP connection. Connection may have already been aborted.
|
|
|
|
Arguments:
|
|
|
|
DontFlush - non-zero if all buffers need to be flushed
|
|
before closing the connection. Zero otherwise.
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
CookieCollection *ServerCookieCollection = GetServerCookieCollection();
|
|
|
|
ServerCookieCollection->LockCollection();
|
|
ServerCookieCollection->RemoveElement(&EmbeddedConnectionCookie);
|
|
ServerCookieCollection->UnlockCollection();
|
|
|
|
HTTP2ServerVirtualConnection::Abort();
|
|
|
|
// call destructor without freeing memory
|
|
HTTP2ServerVirtualConnection::~HTTP2ServerVirtualConnection();
|
|
}
|
|
|
|
RPC_STATUS HTTP2ServerVirtualConnection::QueryClientAddress (
|
|
OUT RPC_CHAR **pNetworkAddress
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Returns the IP address of the client on a connection as a string.
|
|
|
|
This is a server side function. Assert on the client. Proxies don't
|
|
override that. Other virtual connections may override it.
|
|
|
|
Arguments:
|
|
|
|
NetworkAddress - Will contain string on success.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
ULONG ClientAddressType;
|
|
|
|
if (ClientAddress.AddressType == catIPv4)
|
|
{
|
|
ClientAddressType = TCP;
|
|
((SOCKADDR_IN *)&ClientAddress.u)->sin_family = AF_INET;
|
|
}
|
|
else
|
|
{
|
|
ClientAddressType = TCP_IPv6;
|
|
((SOCKADDR_IN6 *)&ClientAddress.u)->sin6_family = AF_INET6;
|
|
}
|
|
|
|
return WS_ConvertClientAddress((const SOCKADDR *)&ClientAddress.u,
|
|
ClientAddressType,
|
|
pNetworkAddress
|
|
);
|
|
}
|
|
|
|
RPC_STATUS HTTP2ServerVirtualConnection::QueryLocalAddress (
|
|
IN OUT void *Buffer,
|
|
IN OUT unsigned long *BufferSize,
|
|
OUT unsigned long *AddressFormat
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Returns the local IP address of a connection.
|
|
|
|
This is a server side function. Assert on the client. Proxies don't
|
|
override that. Other virtual connections may override it.
|
|
|
|
Arguments:
|
|
|
|
Buffer - The buffer that will receive the output address
|
|
|
|
BufferSize - the size of the supplied Buffer on input. On output the
|
|
number of bytes written to the buffer. If the buffer is too small
|
|
to receive all the output data, ERROR_MORE_DATA is returned,
|
|
nothing is written to the buffer, and BufferSize is set to
|
|
the size of the buffer needed to return all the data.
|
|
|
|
AddressFormat - a constant indicating the format of the returned address.
|
|
Currently supported are RPC_P_ADDR_FORMAT_TCP_IPV4 and
|
|
RPC_P_ADDR_FORMAT_TCP_IPV6. Undefined on failure.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
HTTP2ServerInChannel *ServerInChannel;
|
|
HTTP2ChannelPointer *ChannelPtr;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
ServerInChannel = LockDefaultInChannel(&ChannelPtr);
|
|
if (ServerInChannel == NULL)
|
|
return RPC_S_NO_CONTEXT_AVAILABLE;
|
|
|
|
RpcStatus = ServerInChannel->QueryLocalAddress(Buffer,
|
|
BufferSize,
|
|
AddressFormat
|
|
);
|
|
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2ServerVirtualConnection::QueryClientId(
|
|
OUT RPC_CLIENT_PROCESS_IDENTIFIER *ClientProcess
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
For secure protocols (which TCP/IP is not) this is supposed to
|
|
give an ID which will be shared by all clients from the same
|
|
process. This prevents one user from grabbing another users
|
|
association group and using their context handles.
|
|
|
|
Since TCP/IP is not secure we return the IP address of the
|
|
client machine. This limits the attacks to other processes
|
|
running on the client machine which is better than nothing.
|
|
|
|
This is a server side function. Assert on the client. Proxies don't
|
|
override that. Other virtual connections may override it.
|
|
|
|
Arguments:
|
|
|
|
ClientProcess - Transport identification of the "client".
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
ClientProcess->SetHTTP2ClientIdentifier(AssociationGroupId.GetCookie(),
|
|
COOKIE_SIZE_IN_BYTES,
|
|
FALSE // fLocal
|
|
);
|
|
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
RPC_STATUS HTTP2ServerVirtualConnection::QueryClientIpAddress (
|
|
IN OUT RPC_CLIENT_IP_ADDRESS *ClientIpAddress
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Returns the IP address of the client on a connection.
|
|
|
|
This is a server side function. Assert on the client. Proxies don't
|
|
override that. Other virtual connections may override it.
|
|
|
|
Arguments:
|
|
|
|
ClientIpAddress - Will contain the ip address on success.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or other RPC_S_* errors for error
|
|
|
|
--*/
|
|
{
|
|
ULONG BufferLength = max(sizeof(SOCKADDR_IN), sizeof(SOCKADDR_IN6));
|
|
|
|
if (ClientAddress.AddressType == catIPv4)
|
|
{
|
|
((SOCKADDR_IN *)&ClientAddress.u)->sin_family = AF_INET;
|
|
BufferLength = sizeof(SOCKADDR_IN);
|
|
}
|
|
else
|
|
{
|
|
((SOCKADDR_IN6 *)&ClientAddress.u)->sin6_family = AF_INET6;
|
|
BufferLength = sizeof(SOCKADDR_IN6);
|
|
}
|
|
|
|
ASSERT(BufferLength <= sizeof(*ClientIpAddress));
|
|
|
|
ClientIpAddress->DataSize = BufferLength;
|
|
|
|
RpcpMemoryCopy (ClientIpAddress, &ClientAddress.u, BufferLength);
|
|
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
void HTTP2ServerVirtualConnection::LastPacketSentNotification (
|
|
IN int ChannelId,
|
|
IN HTTP2SendContext *LastSendContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
When a channel wants to notify the virtual connection
|
|
that the last packet has been sent, they call this function.
|
|
Must be called from an upcall/neutral context. Only flow control
|
|
senders generates last packet notifications
|
|
|
|
Arguments:
|
|
|
|
ChannelId - the channelfor which this notification is.
|
|
|
|
LastSendContext - the context for the last send
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
// this must not be on the default in channel
|
|
ASSERT(IsOutChannel(ChannelId));
|
|
ASSERT(!IsDefaultOutChannel(ChannelId));
|
|
|
|
// detach the channel that notified us. Since we're in upcall, we know
|
|
// we hold at least one reference
|
|
OutChannels[GetNonDefaultOutChannelSelector()].FreeChannelPointer(FALSE, // DrainUpCalls
|
|
FALSE, // CalledFromUpcallContext
|
|
FALSE, // Abort
|
|
RPC_S_OK // AbortStatus
|
|
);
|
|
}
|
|
|
|
RPC_STATUS HTTP2ServerVirtualConnection::RecycleChannel (
|
|
IN BOOL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initiates channel recycling on the server.
|
|
|
|
Arguments:
|
|
|
|
IsFromUpcall - non-zero if it comes from upcall. Zero otherwise.
|
|
Ignored for server side recycling.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK of the recycling operation started successfully.
|
|
RPC_S_* error for errors.
|
|
|
|
--*/
|
|
{
|
|
HTTP2SendContext *D4_A1Context;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
#if DBG
|
|
DbgPrint("RPCRT4: %d: Recycling OUT channel\n", GetCurrentProcessId());
|
|
#endif
|
|
|
|
InChannelState.Mutex.Request();
|
|
// we shouldn't get recycle unless we're in an opened state
|
|
ASSERT(OutChannelState.State == http2svOpened);
|
|
|
|
// we shouldn't recycle the default channel unless the non-default
|
|
// is discarded. If we were to do this, this raises a race condition
|
|
// where a third channel can arrive while the first is still not
|
|
// discarded. To prevent this, we hold the ball until the first
|
|
// channel is discarded, and then we open the flood gates for
|
|
// new channel recycling. See how we handle delayed channel recycling
|
|
// in HTTP2ServerVirtualConnection::ReceiveComplete
|
|
|
|
if (OutChannels[GetNonDefaultOutChannelSelector()].IsChannelSet())
|
|
{
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, OUT_CHANNEL_STATE, http2svNonDefaultChannelCloseWait, 1, 0);
|
|
OutChannelState.State = http2svNonDefaultChannelCloseWait;
|
|
InChannelState.Mutex.Clear();
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
// Send invitation to the client to start channel recycling
|
|
D4_A1Context = AllocateAndInitializeD4_A1 ();
|
|
if (D4_A1Context == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, OUT_CHANNEL_STATE, http2svOpened_A4W, 1, 0);
|
|
OutChannelState.State = http2svOpened_A4W;
|
|
|
|
VerifyTimerNotSet (GetOutChannelTimer());
|
|
|
|
InChannelState.Mutex.Clear();
|
|
|
|
RpcStatus = SendTrafficOnDefaultChannel(FALSE, // IsInChannel
|
|
D4_A1Context
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
FreeRTSPacket(D4_A1Context);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2ServerVirtualConnection::SendComplete (
|
|
IN RPC_STATUS EventStatus,
|
|
IN OUT HTTP2SendContext *SendContext,
|
|
IN int ChannelId
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Called by lower layers to indicate send complete.
|
|
|
|
Arguments:
|
|
|
|
EventStatus - status of the operation
|
|
|
|
SendContext - the context for the send complete
|
|
|
|
ChannelId - which channel completed the operation
|
|
|
|
Return Value:
|
|
|
|
RPC_P_PACKET_CONSUMED if the packet was consumed and should
|
|
be hidden from the runtime.
|
|
RPC_S_OK if the packet was processed successfully.
|
|
RPC_S_* error if there was an error while processing the
|
|
packet.
|
|
|
|
--*/
|
|
{
|
|
VerifyValidChannelId(ChannelId);
|
|
|
|
if (SendContext->TrafficType == http2ttRTS)
|
|
{
|
|
FreeSendContextAndPossiblyData(SendContext);
|
|
if (EventStatus != RPC_S_OK)
|
|
{
|
|
// any send failures on the server are cause for connection abortion
|
|
AbortChannels(EventStatus);
|
|
}
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
else
|
|
return EventStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2ServerVirtualConnection::ReceiveComplete (
|
|
IN RPC_STATUS EventStatus,
|
|
IN BYTE *Buffer,
|
|
IN UINT BufferLength,
|
|
IN int ChannelId
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Called by lower layers to indicate receive complete
|
|
|
|
Arguments:
|
|
|
|
EventStatus - RPC_S_OK for success or RPC_S_* for error
|
|
Buffer - buffer received
|
|
BufferLength - length of buffer received
|
|
ChannelId - which channel completed the operation
|
|
|
|
Return Value:
|
|
|
|
RPC_P_PACKET_CONSUMED if the packet was consumed and should
|
|
be hidden from the runtime.
|
|
RPC_S_OK if the packet was processed successfully.
|
|
RPC_S_* error if there was an error while processing the
|
|
packet.
|
|
|
|
--*/
|
|
{
|
|
HTTP2ServerOutChannel *OutChannel;
|
|
HTTP2ServerOutChannel *NewOutChannel;
|
|
HTTP2ServerInChannel *InChannel;
|
|
HTTP2ServerInChannel *InChannel2;
|
|
HTTP2ChannelPointer *ChannelPtr;
|
|
HTTP2ChannelPointer *NewChannelPtr;
|
|
HTTP2ChannelPointer *DefaultChannelPtr;
|
|
HTTP2Cookie ChannelCookie;
|
|
RPC_STATUS RpcStatus;
|
|
HTTP2SendContext *EmptyRTS;
|
|
BOOL BufferFreed;
|
|
BOOL DataReceivePosted;
|
|
HTTP2ServerOpenedPacketType PacketType;
|
|
LIST_ENTRY NewBufferHead;
|
|
HTTP2SendContext *D4_A9Context;
|
|
HTTP2SendContext *D5_A5Context;
|
|
HTTP2SendContext *D5_B1OrB2Context;
|
|
HTTP2SendContext *D2_B2Context;
|
|
ULONG BytesReceivedForAck;
|
|
ULONG WindowForAck;
|
|
HTTP2ServerOutChannelOtherCmdPacketType OutChannelPacketType;
|
|
BOOL IsOtherCmd;
|
|
ULONG PingTrafficSent;
|
|
BOOL ChannelNotSet;
|
|
BOOL ChannelRecyclingNeeded;
|
|
|
|
VerifyValidChannelId(ChannelId);
|
|
|
|
if (IsInChannel(ChannelId))
|
|
{
|
|
// in channel has an endpoint receiver. Delegate RTS and data failures to it
|
|
if (EventStatus != RPC_S_OK)
|
|
return EventStatus;
|
|
|
|
if (IsRTSPacket(Buffer))
|
|
{
|
|
RpcStatus = HTTPTransInfo->CreateThread();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
RpcFreeBuffer(Buffer);
|
|
AbortChannels(RPC_S_PROTOCOL_ERROR);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
BufferFreed = FALSE;
|
|
|
|
RpcStatus = CheckPacketForForwarding(Buffer,
|
|
BufferLength,
|
|
fdServer
|
|
);
|
|
|
|
if (RpcStatus == RPC_P_PACKET_NEEDS_FORWARDING)
|
|
{
|
|
// flow control acks have some weird routing. Handle
|
|
// them separately. First, test for other cmd, since it's
|
|
// cheaper
|
|
if (IsOtherCmdPacket(Buffer, BufferLength))
|
|
{
|
|
// we know this is a other cmd command. Now check for
|
|
// forwarded flow control ack
|
|
RpcStatus = ParseFlowControlAckPacketWithDestination (Buffer,
|
|
BufferLength,
|
|
fdOutProxy,
|
|
&BytesReceivedForAck,
|
|
&WindowForAck,
|
|
&ChannelCookie
|
|
);
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
ChannelNotSet = FALSE;
|
|
// flow control ack. Route it based on which out channel has
|
|
// a matching cookie. It is possible that none has. That's ok -
|
|
// just drop the packet in these cases
|
|
if (OutChannelCookies[0].Compare(&ChannelCookie) == 0)
|
|
{
|
|
if (OutChannels[0].IsChannelSet())
|
|
{
|
|
RpcStatus = ForwardTrafficToChannel (
|
|
&OutChannels[0],
|
|
Buffer,
|
|
BufferLength
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// see comment below where we check ChannelNotSet
|
|
ChannelNotSet = TRUE;
|
|
ASSERT(DefaultOutChannelSelector == 1);
|
|
}
|
|
}
|
|
else if (OutChannelCookies[1].Compare(&ChannelCookie) == 0)
|
|
{
|
|
if (OutChannels[1].IsChannelSet())
|
|
{
|
|
RpcStatus = ForwardTrafficToChannel (
|
|
&OutChannels[1],
|
|
Buffer,
|
|
BufferLength
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// see comment below where we check ChannelNotSet
|
|
ChannelNotSet = TRUE;
|
|
ASSERT(DefaultOutChannelSelector == 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// fake failure - this will be handled below
|
|
RpcStatus = RPC_P_SEND_FAILED;
|
|
}
|
|
|
|
if (ChannelNotSet)
|
|
{
|
|
// we could have a match on a channel that does not exist
|
|
// if we are in D5 after D5/B1. The old channel still needs
|
|
// flow control, but the new channel cookie is current by
|
|
// now. In these cases the old channel cookie will be moved
|
|
// to the location of the cookie for the non-default channel
|
|
// Make sure this is the case. After that forward to the out proxy
|
|
// on the default channel. The out proxy will again compare cookies
|
|
// and will know which channel to forward the flow control ack to.
|
|
ASSERT(OutChannelState.State == http2svOpened);
|
|
|
|
RpcStatus = ForwardTrafficToDefaultChannel (
|
|
FALSE, // IsInChannel
|
|
Buffer,
|
|
BufferLength
|
|
);
|
|
}
|
|
|
|
// handle a recycling request if necessary
|
|
RpcStatus = StartChannelRecyclingIfNecessary(RpcStatus,
|
|
TRUE // IsFromUpcall
|
|
);
|
|
|
|
// since forwarding may fail if channels are discarded, consume
|
|
// the packet and ignore the failure
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
RpcFreeBuffer(Buffer);
|
|
RpcStatus = RPC_S_OK;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// not a forwarded flow control ack after all. Just
|
|
// forward it using normal methods
|
|
RpcStatus = ForwardTrafficToDefaultChannel(
|
|
FALSE, // IsInChannel
|
|
Buffer,
|
|
BufferLength
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RpcStatus = ForwardTrafficToDefaultChannel(
|
|
FALSE, // IsInChannel
|
|
Buffer,
|
|
BufferLength
|
|
);
|
|
}
|
|
|
|
RpcStatus = StartChannelRecyclingIfNecessary(RpcStatus,
|
|
TRUE // IsFromUpcall
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RPC_P_CONNECTION_CLOSED);
|
|
RpcFreeBuffer(Buffer);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
// we no longer own the buffer
|
|
BufferFreed = TRUE;
|
|
}
|
|
else if (RpcStatus == RPC_S_PROTOCOL_ERROR)
|
|
{
|
|
// RTS packet is for us but is garbled
|
|
AbortChannels(RPC_P_CONNECTION_CLOSED);
|
|
RpcFreeBuffer(Buffer);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
else
|
|
{
|
|
RpcStatus = GetServerOpenedPacketType (Buffer,
|
|
BufferLength,
|
|
&PacketType
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RPC_P_CONNECTION_CLOSED);
|
|
RpcFreeBuffer(Buffer);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
if ((PacketType == http2soptD4_A8orD5_A8) || (PacketType == http2soptD2_A6orD3_A2))
|
|
{
|
|
InChannelState.Mutex.Request();
|
|
if (PacketType == http2soptD4_A8orD5_A8)
|
|
{
|
|
// determine whether it is D4/A8 or D5/A8 based on the state
|
|
// we are in
|
|
if (OutChannelState.State == http2svOpened_A8W)
|
|
PacketType = http2soptD4_A8;
|
|
else if (OutChannelState.State == http2svOpened_D5A8W)
|
|
PacketType = http2soptD5_A8;
|
|
else
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
}
|
|
else
|
|
{
|
|
// determine whether it is D2/A6 or D3/A2 based on the state
|
|
// we are in
|
|
if (InChannelState.State == http2svOpened_A6W)
|
|
PacketType = http2soptD2_A6;
|
|
else if (InChannelState.State == http2svOpened)
|
|
PacketType = http2soptD3_A2;
|
|
else
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
}
|
|
InChannelState.Mutex.Clear();
|
|
}
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RPC_P_CONNECTION_CLOSED);
|
|
RpcFreeBuffer(Buffer);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
switch (PacketType)
|
|
{
|
|
case http2soptD2_A6:
|
|
// this would better be D2/A6
|
|
RpcStatus = ParseAndFreeD2_A6 (Buffer,
|
|
BufferLength,
|
|
&ChannelCookie
|
|
);
|
|
|
|
BufferFreed = TRUE;
|
|
|
|
// we got D2/A6. Cancel the timeout we setup with D2/A2
|
|
CancelTimeout(GetInChannelTimer());
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RPC_P_CONNECTION_CLOSED);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
if (InChannelCookies[GetNonDefaultInChannelSelector()].Compare(&ChannelCookie))
|
|
{
|
|
// cookies don't match - nuke the channel
|
|
AbortChannels(RPC_P_CONNECTION_CLOSED);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
InChannelState.Mutex.Request();
|
|
// we haven't posted a receive yet - there is no
|
|
// way the state of the channel will change
|
|
ASSERT(InChannelState.State == http2svOpened_A6W);
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, IN_CHANNEL_STATE, http2svOpened_B1W, 1, 0);
|
|
InChannelState.State = http2svOpened_B1W;
|
|
InChannelState.Mutex.Clear();
|
|
break;
|
|
|
|
case http2soptD3_A2:
|
|
if (IsDefaultInChannel(ChannelId) == FALSE)
|
|
{
|
|
ASSERT(0);
|
|
AbortChannels(RPC_S_PROTOCOL_ERROR);
|
|
RpcFreeBuffer(Buffer);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
RpcStatus = ParseAndFreeD3_A2 (Buffer,
|
|
BufferLength,
|
|
&ChannelCookie
|
|
);
|
|
|
|
BufferFreed = TRUE;
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RpcStatus);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
// update the passed in cookie
|
|
InChannelCookies[DefaultInChannelSelector].SetCookie(ChannelCookie.GetCookie());
|
|
|
|
// pass D3/A3 back
|
|
EmptyRTS = AllocateAndInitializeEmptyRTSWithDestination (fdClient);
|
|
if (EmptyRTS == NULL)
|
|
{
|
|
AbortChannels(RpcStatus);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
RpcStatus = SendTrafficOnDefaultChannel (FALSE, // IsInChannel
|
|
EmptyRTS
|
|
);
|
|
|
|
RpcStatus = StartChannelRecyclingIfNecessary(RpcStatus,
|
|
TRUE // IsFromUpcall
|
|
);
|
|
|
|
break;
|
|
|
|
case http2soptD2_B1:
|
|
InChannelState.Mutex.Request();
|
|
if (InChannelState.State != http2svOpened_B1W)
|
|
{
|
|
InChannelState.Mutex.Clear();
|
|
AbortChannels(RPC_S_PROTOCOL_ERROR);
|
|
RpcFreeBuffer(Buffer);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
InChannelState.Mutex.Clear();
|
|
|
|
RpcStatus = ParseAndFreeEmptyRTS(Buffer,
|
|
BufferLength);
|
|
|
|
// we no longer own the buffer
|
|
BufferFreed = TRUE;
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RPC_S_PROTOCOL_ERROR);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
// we're done with this channel. We want to switch
|
|
// channels and destroy
|
|
// and detach the channel.
|
|
SwitchDefaultInChannelSelector();
|
|
|
|
ChannelPtr = GetChannelPointerFromId(ChannelId);
|
|
InChannel = (HTTP2ServerInChannel *)ChannelPtr->LockChannelPointer();
|
|
if (InChannel == NULL)
|
|
{
|
|
AbortChannels(RPC_S_PROTOCOL_ERROR);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
InChannel2 = LockDefaultInChannel(&NewChannelPtr);
|
|
if (InChannel2 == NULL)
|
|
{
|
|
ChannelPtr->UnlockChannelPointer();
|
|
AbortChannels(RPC_S_PROTOCOL_ERROR);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
DataReceivePosted = InChannel->IsDataReceivePosted();
|
|
|
|
RpcStatus = InChannel->TransferReceiveStateToNewChannel(InChannel2);
|
|
|
|
NewChannelPtr->UnlockChannelPointer();
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RPC_S_PROTOCOL_ERROR);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
D2_B2Context = AllocateAndInitializeD2_B2 (HTTP2ServerReceiveWindow);
|
|
if (D2_B2Context == NULL)
|
|
{
|
|
AbortChannels(RPC_S_OUT_OF_MEMORY);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
// now that we have transferred the settings, we can open
|
|
// the pipeline from the new in proxy
|
|
RpcStatus = SendTrafficOnDefaultChannel(TRUE, // IsInChannel
|
|
D2_B2Context
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RPC_S_PROTOCOL_ERROR);
|
|
FreeRTSPacket(D2_B2Context);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
// detach, abort and free lifetime reference
|
|
ChannelPtr->FreeChannelPointer(TRUE, // DrainUpCalls
|
|
TRUE, // CalledFromUpcallContext
|
|
TRUE, // Abort
|
|
RPC_P_CONNECTION_SHUTDOWN
|
|
);
|
|
|
|
InChannelState.Mutex.Request();
|
|
// we haven't posted a receive yet - there is no
|
|
// way the state of the channel will change
|
|
ASSERT(InChannelState.State == http2svOpened_B1W);
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, IN_CHANNEL_STATE, http2svOpened, 1, 0);
|
|
InChannelState.State = http2svOpened;
|
|
InChannelState.Mutex.Clear();
|
|
|
|
if (DataReceivePosted)
|
|
{
|
|
RpcStatus = PostReceiveOnDefaultChannel (
|
|
TRUE, // IsInChannel
|
|
http2ttData
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RPC_S_PROTOCOL_ERROR);
|
|
}
|
|
}
|
|
|
|
return RPC_P_PACKET_CONSUMED;
|
|
break;
|
|
|
|
case http2soptD4_A8:
|
|
// verify the new cookie against the old, and execute
|
|
// the detachment of the old channel after sending D4_A9
|
|
InChannelState.Mutex.Request();
|
|
if (OutChannelState.State != http2svOpened_A8W)
|
|
{
|
|
InChannelState.Mutex.Clear();
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
break;
|
|
}
|
|
|
|
// move back to opened state
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, OUT_CHANNEL_STATE, http2svOpened, 1, 0);
|
|
OutChannelState.State = http2svOpened;
|
|
InChannelState.Mutex.Clear();
|
|
|
|
RpcStatus = ParseAndFreeD4_A8 (Buffer,
|
|
BufferLength,
|
|
fdServer,
|
|
&ChannelCookie
|
|
);
|
|
|
|
BufferFreed = TRUE;
|
|
|
|
// we got D4/A8. Cancel the timeout we setup with D4/A4
|
|
CancelTimeout(GetOutChannelTimer());
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
|
|
if (OutChannelCookies[GetNonDefaultOutChannelSelector()].Compare(&ChannelCookie))
|
|
{
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
break;
|
|
}
|
|
|
|
// lock the old channel
|
|
OutChannel = LockDefaultOutChannel(&ChannelPtr);
|
|
if (OutChannel == NULL)
|
|
{
|
|
RpcStatus = RPC_P_CONNECTION_CLOSED;
|
|
break;
|
|
}
|
|
|
|
// switch channels (new channel is still plugged)
|
|
SwitchDefaultOutChannelSelector();
|
|
|
|
// wait for all submits to get out of old channel
|
|
OutChannel->DrainPendingSubmissions();
|
|
|
|
// leave 1 for our lock
|
|
ChannelPtr->DrainPendingLocks(1);
|
|
|
|
// lock new channel (by now it is default)
|
|
NewOutChannel = LockDefaultOutChannel(&NewChannelPtr);
|
|
if (NewOutChannel == NULL)
|
|
{
|
|
ChannelPtr->UnlockChannelPointer();
|
|
RpcStatus = RPC_P_CONNECTION_CLOSED;
|
|
break;
|
|
}
|
|
|
|
// if flow control sender was queuing, grab all its buffers as well.
|
|
// Note that the flow control sender is higher in the stack and must
|
|
// be done first (to preserve packet ordering)
|
|
RpcpInitializeListHead(&NewBufferHead);
|
|
RpcStatus = OutChannel->GetFlowControlSenderBufferQueue(&NewBufferHead);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
// if we couldn't get the send contexts, they would have been
|
|
// freed in GetFlowControlSenderBufferQueue
|
|
NewChannelPtr->UnlockChannelPointer();
|
|
ChannelPtr->UnlockChannelPointer();
|
|
break;
|
|
}
|
|
|
|
AddBufferQueueToChannel(&NewBufferHead, NewOutChannel);
|
|
|
|
// GetChannelOriginatorBufferQueue must be called in submission
|
|
// context only. Get there
|
|
RpcStatus = OutChannel->BeginSimpleSubmitAsync();
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
NewChannelPtr->UnlockChannelPointer();
|
|
ChannelPtr->UnlockChannelPointer();
|
|
break;
|
|
}
|
|
|
|
// if old channel was queuing, grab all its buffers. Since it is
|
|
// below the flow control sender, we must do it second to make sure
|
|
// they are before the flow control sender's buffers
|
|
RpcpInitializeListHead(&NewBufferHead);
|
|
RpcStatus = OutChannel->GetChannelOriginatorBufferQueue(&NewBufferHead);
|
|
|
|
OutChannel->FinishSubmitAsync();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
// if we couldn't get the send contexts, they would have been
|
|
// freed in GetChannelOriginatorBufferQueue
|
|
NewChannelPtr->UnlockChannelPointer();
|
|
ChannelPtr->UnlockChannelPointer();
|
|
break;
|
|
}
|
|
|
|
AddBufferQueueToChannel(&NewBufferHead, NewOutChannel);
|
|
|
|
// register the last packet to send with the old channel
|
|
D4_A9Context = AllocateAndInitializeD4_A9 ();
|
|
|
|
if (D4_A9Context == NULL)
|
|
{
|
|
ChannelPtr->UnlockChannelPointer();
|
|
NewChannelPtr->UnlockChannelPointer();
|
|
RpcStatus = RPC_S_OUT_OF_MEMORY;
|
|
break;
|
|
}
|
|
|
|
RpcStatus = OutChannel->Send(D4_A9Context);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
ChannelPtr->UnlockChannelPointer();
|
|
NewChannelPtr->UnlockChannelPointer();
|
|
FreeRTSPacket(D4_A9Context);
|
|
break;
|
|
}
|
|
|
|
// D4_A9 was sent. We must switch the
|
|
// default loopback and detach the channel.
|
|
// Note that we don't abort the channel - we
|
|
// just release the lifetime reference
|
|
// When the proxy closes the connection, then
|
|
// we will abort
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
RpcStatus = NewOutChannel->Unplug();
|
|
|
|
NewChannelPtr->UnlockChannelPointer();
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
break;
|
|
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
|
|
break;
|
|
|
|
case http2soptD5_A8:
|
|
// verify the new cookie against the old, and execute
|
|
// the detachment of the old channel after sending D4_A9
|
|
InChannelState.Mutex.Request();
|
|
if (OutChannelState.State != http2svOpened_D5A8W)
|
|
{
|
|
InChannelState.Mutex.Clear();
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
break;
|
|
}
|
|
|
|
RpcStatus = ParseAndFreeD5_A8 (Buffer,
|
|
BufferLength,
|
|
fdServer,
|
|
&ChannelCookie
|
|
);
|
|
|
|
BufferFreed = TRUE;
|
|
|
|
CancelTimeout(GetOutChannelTimer());
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
InChannelState.Mutex.Clear();
|
|
break;
|
|
}
|
|
|
|
// we use the non-default out channel cookie simply as temporary storage
|
|
// b/n D5/A4 and D5/A8
|
|
if (OutChannelCookies[GetNonDefaultOutChannelSelector()].Compare(&ChannelCookie))
|
|
{
|
|
// the new channel is fake. Tell the proxy about it, and it will ditch it
|
|
D5_B1OrB2Context = AllocateAndInitializeD5_B1orB2 (FALSE);
|
|
if (D5_B1OrB2Context == NULL)
|
|
{
|
|
InChannelState.Mutex.Clear();
|
|
RpcStatus = RPC_S_OUT_OF_MEMORY;
|
|
break;
|
|
}
|
|
|
|
RpcStatus = SendTrafficOnDefaultChannel(FALSE, // IsInChannel
|
|
D5_B1OrB2Context);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
InChannelState.Mutex.Clear();
|
|
FreeRTSPacket(D5_B1OrB2Context);
|
|
break;
|
|
}
|
|
|
|
// move back to opened state
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, OUT_CHANNEL_STATE, http2svOpened, 1, 0);
|
|
OutChannelState.State = http2svOpened;
|
|
InChannelState.Mutex.Clear();
|
|
break;
|
|
}
|
|
|
|
// the cookie matches. Move the new channel cookie from temporary to permanent
|
|
// storage and move the old channel cookie to temp storage
|
|
|
|
// move old cookie to temp storage
|
|
ChannelCookie.SetCookie(
|
|
OutChannelCookies[DefaultOutChannelSelector].GetCookie());
|
|
// move new cookie from class temp storage to permanent storage
|
|
OutChannelCookies[DefaultOutChannelSelector].SetCookie (
|
|
OutChannelCookies[GetNonDefaultOutChannelSelector()].GetCookie());
|
|
// move the old cookie from local temporary storage to class temporary storage
|
|
OutChannelCookies[GetNonDefaultOutChannelSelector()].SetCookie (
|
|
ChannelCookie.GetCookie());
|
|
|
|
// move back to opened state
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, OUT_CHANNEL_STATE, http2svOpened, 1, 0);
|
|
OutChannelState.State = http2svOpened;
|
|
InChannelState.Mutex.Clear();
|
|
|
|
OutChannel = LockDefaultOutChannel(&ChannelPtr);
|
|
if (OutChannel == NULL)
|
|
{
|
|
RpcStatus = RPC_P_CONNECTION_SHUTDOWN;
|
|
break;
|
|
}
|
|
|
|
OutChannel->PlugDataOriginatorChannel();
|
|
|
|
// Wait for everybody that was in to get out. This way we know
|
|
// the channel was plugged.
|
|
|
|
// we know that this will complete because eventually the runtime
|
|
// will flow control itself if there is no lull in sent traffic
|
|
OutChannel->DrainPendingSubmissions();
|
|
|
|
D5_B1OrB2Context = AllocateAndInitializeD5_B1orB2 (TRUE);
|
|
if (D5_B1OrB2Context == NULL)
|
|
{
|
|
ChannelPtr->UnlockChannelPointer();
|
|
RpcStatus = RPC_S_OUT_OF_MEMORY;
|
|
break;
|
|
}
|
|
|
|
RpcStatus = OutChannel->Send(D5_B1OrB2Context);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
ChannelPtr->UnlockChannelPointer();
|
|
FreeRTSPacket(D5_B1OrB2Context);
|
|
break;
|
|
}
|
|
|
|
RpcStatus = OutChannel->RestartDataOriginatorChannel();
|
|
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
// fall through the error code
|
|
break;
|
|
|
|
default:
|
|
ASSERT(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
RpcStatus = PostReceiveOnDefaultChannel(
|
|
TRUE, // IsInChannel
|
|
http2ttRTS
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
AbortChannels(RPC_P_CONNECTION_CLOSED);
|
|
|
|
if (BufferFreed == FALSE)
|
|
RpcFreeBuffer(Buffer);
|
|
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
else
|
|
{
|
|
return EventStatus;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (EventStatus != RPC_S_OK)
|
|
{
|
|
if (IsDefaultOutChannel(ChannelId) == FALSE)
|
|
{
|
|
InChannelState.Mutex.Request();
|
|
if ( (OutChannelState.State == http2svOpened)
|
|
|| (OutChannelState.State == http2svNonDefaultChannelCloseWait))
|
|
{
|
|
// were in state where we were delaying recycling of the channel
|
|
// until the non-default channel is closed? See
|
|
// HTTP2ServerVirtualConnection::RecycleChannel for more
|
|
// information
|
|
if (OutChannelState.State == http2svNonDefaultChannelCloseWait)
|
|
{
|
|
ChannelRecyclingNeeded = TRUE;
|
|
OutChannelState.State = http2svOpened;
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, OUT_CHANNEL_STATE, http2svOpened, 1, 0);
|
|
}
|
|
else
|
|
ChannelRecyclingNeeded = FALSE;
|
|
|
|
// close on the non-default channel in open
|
|
// state is not an error. Just discard the channel
|
|
InChannelState.Mutex.Clear();
|
|
|
|
ChannelPtr = GetChannelPointerFromId(ChannelId);
|
|
|
|
if (ChannelPtr == NULL)
|
|
{
|
|
// This should never happen.
|
|
ASSERT(0);
|
|
return RPC_S_INTERNAL_ERROR;
|
|
}
|
|
|
|
if (ChannelRecyclingNeeded)
|
|
{
|
|
// we are currently on the non-default channel. Once we free it,
|
|
// we have nothing to keep the virtual connection alive while
|
|
// we trigger recycling. In order to protect the virtual
|
|
// connection from disappearing, we must get a second lock before
|
|
// we free our channel - Rule 39.
|
|
if (LockDefaultOutChannel(&DefaultChannelPtr) == NULL)
|
|
{
|
|
// the default out channel is gone - the connection is being
|
|
// aborted. Ignore the channel recycling and just bail out after
|
|
// cleanup
|
|
ASSERT(IsAborted());
|
|
ChannelRecyclingNeeded = FALSE;
|
|
// fall through. Next we will abort the channel and since
|
|
// ChannelRecyclingNeeded is FALSE, just bail out
|
|
}
|
|
}
|
|
|
|
OutChannel = (HTTP2ServerOutChannel *)ChannelPtr->LockChannelPointer();
|
|
if (OutChannel)
|
|
{
|
|
// make sure the pending sends are drained. Otherwise
|
|
// a send that is currently completing may violate
|
|
// rule 38
|
|
OutChannel->DrainPendingSends();
|
|
ChannelPtr->UnlockChannelPointer();
|
|
}
|
|
|
|
ChannelPtr->FreeChannelPointer(
|
|
TRUE, // DrainUpcalls
|
|
TRUE, // CalledFromUpcallContext
|
|
TRUE, // Abort
|
|
RPC_P_CONNECTION_SHUTDOWN
|
|
);
|
|
|
|
if (ChannelRecyclingNeeded)
|
|
{
|
|
RpcStatus = RecycleChannel(
|
|
TRUE // IsFromUpcall
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RpcStatus);
|
|
// fall through to consuming the packet and returning.
|
|
// Since the channels are aborted, the pending operaitons
|
|
// will come back and abort the connection.
|
|
}
|
|
DefaultChannelPtr->UnlockChannelPointer();
|
|
}
|
|
|
|
RpcStatus = RPC_P_PACKET_CONSUMED;
|
|
BufferFreed = TRUE;
|
|
return RpcStatus;
|
|
}
|
|
else
|
|
InChannelState.Mutex.Clear();
|
|
}
|
|
else if (InChannelState.State == http2svB2W)
|
|
{
|
|
// if this is a half open connection, treat
|
|
// this as data receive and indicate it to the
|
|
// runtime
|
|
AbortChannels(EventStatus);
|
|
|
|
// if we are still in this state, return error to the
|
|
// runtime. Else, somebody else joined and we can ignore
|
|
// this error
|
|
InChannelState.Mutex.Request();
|
|
if (InChannelState.State == http2svB2W)
|
|
{
|
|
// convert the state to closed. This is necessary so that
|
|
// if the in channel finally comes, it finds a closed
|
|
// connection and doesn't try to open it.
|
|
InChannelState.State = http2svClosed;
|
|
|
|
// Leave EventStatus as it is and fall through
|
|
}
|
|
else
|
|
{
|
|
// consume the receive
|
|
EventStatus = RPC_P_PACKET_CONSUMED;
|
|
}
|
|
InChannelState.Mutex.Clear();
|
|
return EventStatus;
|
|
}
|
|
|
|
AbortChannels(EventStatus);
|
|
|
|
// we expect only RTS traffic on this channel. Nobody would post
|
|
// a data receive on this channel. Consume the receive
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
if (IsRTSPacket(Buffer))
|
|
{
|
|
RpcStatus = HTTPTransInfo->CreateThread();
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
RpcFreeBuffer(Buffer);
|
|
AbortChannels(RPC_S_PROTOCOL_ERROR);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
IsOtherCmd = IsOtherCmdPacket(Buffer,
|
|
BufferLength
|
|
);
|
|
|
|
RpcStatus = GetServerOutChannelOtherCmdPacketType (
|
|
Buffer,
|
|
BufferLength,
|
|
&OutChannelPacketType
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
RpcFreeBuffer(Buffer);
|
|
AbortChannels(RPC_S_PROTOCOL_ERROR);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
if (IsOtherCmd && (OutChannelPacketType == http2sococptFlowControl))
|
|
{
|
|
RpcStatus = ParseAndFreeFlowControlAckPacket (Buffer,
|
|
BufferLength,
|
|
&BytesReceivedForAck,
|
|
&WindowForAck,
|
|
&ChannelCookie
|
|
);
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
// notify the flow control sender
|
|
ChannelPtr = GetChannelPointerFromId(ChannelId);
|
|
OutChannel = (HTTP2ServerOutChannel *)ChannelPtr->LockChannelPointer();
|
|
// forward acks only on default channels. Non-default channels
|
|
// will have all their buffers transfered to the new channel in the
|
|
// immediate future. If we forward to them, we can cause nasty
|
|
// race conditions as another thread tries to get channels out of them
|
|
if (IsDefaultOutChannel(ChannelId))
|
|
{
|
|
if (OutChannel == NULL)
|
|
{
|
|
AbortChannels(RPC_P_CONNECTION_SHUTDOWN);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
RpcStatus = OutChannel->FlowControlAckNotify(BytesReceivedForAck,
|
|
WindowForAck
|
|
);
|
|
|
|
RpcStatus = StartChannelRecyclingIfNecessary(RpcStatus,
|
|
TRUE // IsFromUpcall
|
|
);
|
|
}
|
|
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
// post another receive
|
|
RpcStatus = PostReceiveOnChannel(GetChannelPointerFromId(ChannelId),
|
|
http2ttRaw
|
|
);
|
|
}
|
|
}
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RpcStatus);
|
|
}
|
|
}
|
|
else if (IsOtherCmd && (OutChannelPacketType == http2sococptPingTrafficSentNotify))
|
|
{
|
|
RpcStatus = ParseAndFreePingTrafficSentNotifyPacket (Buffer,
|
|
BufferLength,
|
|
&PingTrafficSent
|
|
);
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
// notify the channel data originator
|
|
ChannelPtr = GetChannelPointerFromId(ChannelId);
|
|
OutChannel = (HTTP2ServerOutChannel *)ChannelPtr->LockChannelPointer();
|
|
if (OutChannel == NULL)
|
|
{
|
|
AbortChannels(RPC_P_CONNECTION_SHUTDOWN);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
// prevent bogus values from the proxy. We allow no more than
|
|
// approximately MaxBytesSentByProxy bytes per BytesSentByProxyTimeInterval.
|
|
// The exact calculation doesn't matter. This requirement is so much below
|
|
// the bar necessary to attack the server, that anything close to it makes
|
|
// us safe
|
|
if (BytesSentByProxyTimeIntervalStart == 0)
|
|
BytesSentByProxyTimeIntervalStart = NtGetTickCount();
|
|
else
|
|
{
|
|
if (NtGetTickCount() - BytesSentByProxyTimeIntervalStart > BytesSentByProxyTimeInterval)
|
|
{
|
|
// start a new interval
|
|
BytesSentByProxyTimeIntervalStart = NtGetTickCount();
|
|
BytesSentByProxyForInterval = PingTrafficSent;
|
|
}
|
|
else
|
|
{
|
|
BytesSentByProxyForInterval += PingTrafficSent;
|
|
}
|
|
|
|
if (BytesSentByProxyForInterval > MaxBytesSentByProxy)
|
|
{
|
|
AbortChannels(RPC_S_PROTOCOL_ERROR);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
}
|
|
|
|
RpcStatus = OutChannel->NotifyDataOriginatorForTrafficSent (PingTrafficSent);
|
|
|
|
RpcStatus = StartChannelRecyclingIfNecessary(RpcStatus,
|
|
TRUE // IsFromUpcall
|
|
);
|
|
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
// post another receive
|
|
RpcStatus = PostReceiveOnChannel(GetChannelPointerFromId(ChannelId),
|
|
http2ttRaw
|
|
);
|
|
}
|
|
}
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RpcStatus);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// the only packet we expect here is D5/A4
|
|
// we must be in Opened_A4W state for it
|
|
InChannelState.Mutex.Request();
|
|
if (OutChannelState.State != http2svOpened_A4W)
|
|
{
|
|
InChannelState.Mutex.Clear();
|
|
RpcFreeBuffer(Buffer);
|
|
AbortChannels(RPC_S_PROTOCOL_ERROR);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
RpcStatus = ParseAndFreeD5_A4 (Buffer,
|
|
BufferLength,
|
|
&OutChannelCookies[GetNonDefaultOutChannelSelector()]
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RPC_S_PROTOCOL_ERROR);
|
|
InChannelState.Mutex.Clear();
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
// move to Opened_A8W state
|
|
LogEvent(SU_HTTPv2, EV_STATE, this, OUT_CHANNEL_STATE, http2svOpened_D5A8W, 1, 0);
|
|
OutChannelState.State = http2svOpened_D5A8W;
|
|
InChannelState.Mutex.Clear();
|
|
|
|
RpcStatus = SetTimeout(DefaultNoResponseTimeout, GetOutChannelTimer());
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RpcStatus);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
// send out D5/A5
|
|
D5_A5Context = AllocateAndInitializeD5_A5 (fdClient);
|
|
|
|
if (D5_A5Context == NULL)
|
|
{
|
|
AbortChannels(RPC_S_OUT_OF_MEMORY);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
RpcStatus = SendTrafficOnDefaultChannel(FALSE, // IsInChannel
|
|
D5_A5Context
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
AbortChannels(RPC_S_OUT_OF_MEMORY);
|
|
FreeRTSPacket(D5_A5Context);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
|
|
RpcStatus = PostReceiveOnDefaultChannel(FALSE, // IsInChannel
|
|
http2ttRaw
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
AbortChannels(RpcStatus);
|
|
}
|
|
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
else
|
|
{
|
|
// we shouldn't receive non-RTS packets on the out channel on the
|
|
// server
|
|
AbortChannels(RPC_S_PROTOCOL_ERROR);
|
|
return RPC_P_PACKET_CONSUMED;
|
|
}
|
|
}
|
|
}
|
|
|
|
RPC_STATUS HTTP2ServerVirtualConnection::SyncSend (
|
|
IN ULONG BufferLength,
|
|
IN BYTE *Buffer,
|
|
IN BOOL fDisableShutdownCheck,
|
|
IN BOOL fDisableCancelCheck,
|
|
IN ULONG Timeout
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Does a sync send on a server HTTP connection.
|
|
|
|
Arguments:
|
|
|
|
BufferLength - the length of the data to send.
|
|
|
|
Buffer - the data to send.
|
|
|
|
fDisableShutdownCheck - ignored
|
|
|
|
fDisableCancelCheck - runtime indicates no cancel
|
|
will be attempted on this send. Can be used
|
|
as optimization hint by the transport
|
|
|
|
Timeout - send timeout (call timeout)
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK for success or RPC_S_* / Win32 error for failure
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
RPC_STATUS RpcStatus2;
|
|
HTTP2SendContext *SendContext;
|
|
HTTP2ServerOutChannel *Channel;
|
|
HTTP2ChannelPointer *ChannelPtr;
|
|
|
|
#if DBG
|
|
InChannelState.Mutex.Request();
|
|
if ((OutChannelState.State == http2svOpened)
|
|
|| (OutChannelState.State == http2svNonDefaultChannelCloseWait) )
|
|
{
|
|
VerifyTimerNotSet(GetOutChannelTimer());
|
|
}
|
|
InChannelState.Mutex.Clear();
|
|
#endif
|
|
|
|
// if the caller did not set a last buffer to free, we can't abandon the send
|
|
// because we can't cleanup. Wait for the send to complete.
|
|
if (IsLastBufferToFreeSet() == FALSE)
|
|
{
|
|
return HTTP2VirtualConnection::SyncSend (BufferLength,
|
|
Buffer,
|
|
fDisableShutdownCheck,
|
|
fDisableCancelCheck,
|
|
Timeout
|
|
);
|
|
}
|
|
|
|
// we will complete this as an async send behind the covers
|
|
// and we will fake success unless the submission itself
|
|
// fails
|
|
Channel = (HTTP2ServerOutChannel *)LockDefaultSendChannel (&ChannelPtr);
|
|
if (Channel == NULL)
|
|
{
|
|
return RPC_P_SEND_FAILED;
|
|
}
|
|
|
|
SendContext = Channel->GetLastSendContext();
|
|
if (SendContext == NULL)
|
|
{
|
|
ChannelPtr->UnlockChannelPointer();
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
SendContext->u.BufferToFree = GetAndResetLastBufferToFree();
|
|
SendContext->SetListEntryUnused();
|
|
SendContext->maxWriteBuffer = BufferLength;
|
|
SendContext->pWriteBuffer = Buffer;
|
|
// SendContext->Write.pAsyncObject = NULL; // this will be initialized in the bottom layer
|
|
SendContext->Write.ol.Internal = STATUS_PENDING;
|
|
SendContext->TrafficType = http2ttData;
|
|
SendContext->Write.ol.OffsetHigh = 0;
|
|
// Clear any stale flags in case the context has been cached.
|
|
SendContext->Flags = SendContextFlagAbandonedSend;
|
|
SendContext->UserData = 0;
|
|
|
|
RpcStatus = Channel->Send(SendContext);
|
|
|
|
if ((RpcStatus != RPC_S_OK) && (RpcStatus != RPC_P_CHANNEL_NEEDS_RECYCLING))
|
|
{
|
|
// synchronous failure - cleanup
|
|
RpcFreeBuffer(SendContext->u.BufferToFree);
|
|
Channel->FreeLastSendContext(SendContext);
|
|
}
|
|
|
|
ChannelPtr->UnlockChannelPointer();
|
|
|
|
//
|
|
// If Send() returns RPC_P_CHANNEL_NEEDS_RECYCLING, then the SendContext
|
|
// has been queued in HTTP2ChannelDataOriginator::Send() in BufferQueue
|
|
// and HTTP2ChannelDataOriginator::Abort() will fail it later by posting
|
|
// CHANNEL_DATA_ORIGINATOR_DIRECT_SEND in the case of communication breakdown.
|
|
//
|
|
// We should make sure the send completes at most once. Therefore, if we know
|
|
// the send context will be taken off the queue during the abort, we will mask this
|
|
// syncronous failure.
|
|
//
|
|
|
|
if (RpcStatus == RPC_P_CHANNEL_NEEDS_RECYCLING)
|
|
{
|
|
// make sure there is a thread to pick up the recycling events
|
|
RpcStatus = HTTPTransInfo->CreateThread();
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
VALIDATE(RpcStatus)
|
|
{
|
|
RPC_S_OK,
|
|
RPC_S_OUT_OF_MEMORY,
|
|
RPC_S_OUT_OF_RESOURCES,
|
|
RPC_P_SEND_FAILED,
|
|
RPC_S_CALL_CANCELLED,
|
|
RPC_P_RECEIVE_COMPLETE,
|
|
RPC_P_TIMEOUT
|
|
} END_VALIDATE;
|
|
|
|
// REVIEW: We may want to think what happens with the queued SendContext.
|
|
// It may or may not get completed in this case. Ideally, we would want
|
|
// make sure that it will not complete if we are going to fail syncronously.
|
|
return RpcStatus;
|
|
}
|
|
|
|
// get the ball rolling with the recycle
|
|
RpcStatus = RecycleChannel(
|
|
FALSE // IsFromUpcall
|
|
);
|
|
|
|
VALIDATE(RpcStatus)
|
|
{
|
|
RPC_S_OK,
|
|
RPC_S_OUT_OF_MEMORY,
|
|
RPC_S_OUT_OF_RESOURCES,
|
|
RPC_S_CALL_CANCELLED,
|
|
RPC_P_SEND_FAILED,
|
|
RPC_P_RECEIVE_FAILED,
|
|
RPC_P_CONNECTION_SHUTDOWN,
|
|
RPC_P_CONNECTION_CLOSED,
|
|
RPC_P_TIMEOUT
|
|
} END_VALIDATE;
|
|
|
|
// Supress the sync failure. The failure will be returned
|
|
// during the abort on the completion of the queued send.
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
RpcStatus = RPC_S_OK;
|
|
}
|
|
}
|
|
|
|
// Note that send can't really fail with protocol error. When
|
|
// it happens it has simply picked the error with which
|
|
// the connection was aborted. This is as good as a failed send.
|
|
if ((RpcStatus == RPC_P_CONNECTION_SHUTDOWN)
|
|
|| (RpcStatus == RPC_P_RECEIVE_FAILED)
|
|
|| (RpcStatus == RPC_P_CONNECTION_CLOSED)
|
|
|| (RpcStatus == RPC_S_PROTOCOL_ERROR))
|
|
RpcStatus = RPC_P_SEND_FAILED;
|
|
|
|
VALIDATE(RpcStatus)
|
|
{
|
|
RPC_S_OK,
|
|
RPC_S_OUT_OF_MEMORY,
|
|
RPC_S_OUT_OF_RESOURCES,
|
|
RPC_P_SEND_FAILED,
|
|
RPC_S_CALL_CANCELLED,
|
|
RPC_P_RECEIVE_COMPLETE,
|
|
RPC_P_TIMEOUT
|
|
} END_VALIDATE;
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2ServerVirtualConnection::InitializeServerConnection (
|
|
IN BYTE *Packet,
|
|
IN ULONG PacketLength,
|
|
IN WS_HTTP2_INITIAL_CONNECTION *Connection,
|
|
OUT HTTP2ServerVirtualConnection **ServerVirtualConnection,
|
|
OUT BOOL *VirtualConnectionCreated
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initializes a server connection. Based on the content of the
|
|
packet (i.e. D1/A2 or D1/B2), it will either initialize the
|
|
out channel or the in channel respectively, and if it is
|
|
the first leg of the connection establishment, establish the
|
|
virtual connection itself and insert it into the cookie table
|
|
|
|
Note: This function must initialize the type member and migrate the
|
|
WS_HTTP2_INITIAL_CONNECTION after morphing it into
|
|
WS_HTTP2_CONNECTION. The VirtualConnectionCreated parameter indicates
|
|
whether this was done.
|
|
|
|
Arguments:
|
|
|
|
Packet - received packet. Guaranteed to be present until PacketLength.
|
|
On second leg, this function must not free the buffer. Ownership
|
|
of the buffer remains with the caller.
|
|
|
|
PacketLength - the lenght of the received packet.
|
|
|
|
Connection - the received connection. It must be migrated
|
|
and morphed into WS_HTTP2_CONNECTION on success. If the virtual
|
|
connection was already created, then returning failure without
|
|
un-migrating is fine. Cleanup paths will check and recognize this
|
|
as HTTP2ServerVirtualConnection.
|
|
|
|
ServerVirtualConnection - on successful return, the created server virtual
|
|
connection
|
|
|
|
VirtualConnectionCreated - if non-zero, the WS_HTTP2_INITIAL_CONNECTION
|
|
was morphed into virtual connection. Else, the WS_HTTP2_INITIAL_CONNECTION
|
|
is still around. Must be set on success and failure.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* for error. If we return RPC_S_OK, the packet will be
|
|
consumed by caller. If we return anything else, it won't be.
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
RPC_STATUS RpcStatus2;
|
|
HTTP2FirstServerPacketType PacketType;
|
|
WS_HTTP2_INITIAL_CONNECTION *OriginalConnection = Connection;
|
|
HTTP2ServerInChannel *InChannel = NULL;
|
|
HTTP2ServerOutChannel *OutChannel = NULL;
|
|
HTTP2ServerCookie ServerCookie;
|
|
HTTP2Cookie ChannelCookie;
|
|
HTTP2Cookie NewChannelCookie;
|
|
ULONG OutProxyReceiveWindow;
|
|
ULONG ProtocolVersion;
|
|
ULONG OutChannelLifetime;
|
|
ULONG InProxyReceiveWindow;
|
|
ULONG InProxyConnectionTimeout;
|
|
ULONG OutProxyConnectionTimeout;
|
|
HTTP2Cookie AssociationGroupId;
|
|
ChannelSettingClientAddress ClientAddress;
|
|
HTTP2ServerVirtualConnection *LocalServerConnection;
|
|
HTTP2ServerVirtualConnection *VCPlaceHolder;
|
|
BOOL FirstLeg;
|
|
BOOL AbortServerConnection = FALSE;
|
|
HTTP2ServerChannel *ThisChannel;
|
|
HTTP2ChannelPointer *OtherChannelPtr;
|
|
HTTP2SendContext *D1_C1Context;
|
|
HTTP2SendContext *D1_B3Context;
|
|
HTTP2SendContext *D2_A3Context;
|
|
HTTP2SendContext *D4_A4Context;
|
|
HTTP2SendContext *D4_A5Context;
|
|
int NonDefaultChannel;
|
|
|
|
*VirtualConnectionCreated = FALSE;
|
|
|
|
// First, do a little bit of parsing to
|
|
// determine if this is the first request for this connection
|
|
// cookie. If not, join the other connection and destroy the
|
|
// runtime stuff for this one. If yes, build a virtual connection
|
|
RpcStatus = GetFirstServerPacketType(Packet,
|
|
PacketLength,
|
|
&PacketType
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
// packet with failure will be propagated to the runtime
|
|
return RpcStatus;
|
|
}
|
|
|
|
if (PacketType == http2fsptD1_A2)
|
|
{
|
|
RpcStatus = ParseD1_A2(Packet,
|
|
PacketLength,
|
|
&ProtocolVersion,
|
|
&ServerCookie,
|
|
&ChannelCookie,
|
|
&OutChannelLifetime,
|
|
&OutProxyReceiveWindow);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
return RpcStatus;
|
|
|
|
if (OutChannelLifetime < MinimumChannelLifetime)
|
|
return RPC_S_PROTOCOL_ERROR;
|
|
|
|
// a request to establish a new out connection
|
|
RpcStatus = AllocateAndInitializeOutChannel (&Connection,
|
|
OutChannelLifetime,
|
|
&OutChannel);
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
// unplug the newly created out channel
|
|
RpcStatus2 = OutChannel->Unplug ();
|
|
// we know we can't fail here since there are no data in the pipe line
|
|
ASSERT(RpcStatus2 == RPC_S_OK);
|
|
|
|
OutChannel->SetPeerReceiveWindow(OutProxyReceiveWindow);
|
|
}
|
|
}
|
|
else if (PacketType == http2fsptD1_B2)
|
|
{
|
|
RpcpMemorySet(&ClientAddress, 0, sizeof(ClientAddress));
|
|
|
|
RpcStatus = ParseD1_B2(Packet,
|
|
PacketLength,
|
|
&ProtocolVersion,
|
|
&ServerCookie,
|
|
&ChannelCookie,
|
|
&InProxyReceiveWindow,
|
|
&InProxyConnectionTimeout,
|
|
&AssociationGroupId,
|
|
&ClientAddress
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
return RpcStatus;
|
|
|
|
// a request to establish a new in connection
|
|
RpcStatus = AllocateAndInitializeInChannel (&Connection,
|
|
&InChannel);
|
|
}
|
|
else if (PacketType == http2fsptD2_A2)
|
|
{
|
|
// in channel replacement
|
|
RpcStatus = ParseD2_A2(Packet,
|
|
PacketLength,
|
|
&ProtocolVersion,
|
|
&ServerCookie,
|
|
&ChannelCookie,
|
|
&NewChannelCookie,
|
|
&InProxyReceiveWindow,
|
|
&InProxyConnectionTimeout
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
return RpcStatus;
|
|
|
|
// a request to establish a replacement in connection
|
|
RpcStatus = AllocateAndInitializeInChannel (&Connection,
|
|
&InChannel);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(PacketType == http2fsptD4_A4);
|
|
|
|
// out channel replacement
|
|
RpcStatus = ParseD4_A4(Packet,
|
|
PacketLength,
|
|
&ProtocolVersion,
|
|
&ServerCookie,
|
|
&ChannelCookie,
|
|
&NewChannelCookie,
|
|
&OutChannelLifetime,
|
|
&OutProxyReceiveWindow,
|
|
&OutProxyConnectionTimeout
|
|
);
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
return RpcStatus;
|
|
|
|
// a request to establish a replacement out connection
|
|
RpcStatus = AllocateAndInitializeOutChannel (&Connection,
|
|
OutChannelLifetime,
|
|
&OutChannel);
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
OutChannel->SetPeerReceiveWindow(OutProxyReceiveWindow);
|
|
}
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
return RpcStatus;
|
|
|
|
// take the lower of our and reported version
|
|
ProtocolVersion = min(ProtocolVersion, HTTP2ProtocolVersion);
|
|
|
|
// add the raw connection to the PnP list
|
|
TransportProtocol::AddObjectToProtocolList(Connection);
|
|
|
|
// figure out whether we arrived first or second
|
|
GetServerCookieCollection()->LockCollection();
|
|
|
|
LocalServerConnection =
|
|
(HTTP2ServerVirtualConnection *)GetServerCookieCollection()->FindElement(&ServerCookie);
|
|
|
|
if (((PacketType == http2fsptD2_A2) || (PacketType == http2fsptD4_A4))
|
|
&& (LocalServerConnection == NULL))
|
|
{
|
|
// we cannot establish a replacement connection if the old one is not around
|
|
OriginalConnection->fAborted = 1;
|
|
OriginalConnection->pReadBuffer = NULL;
|
|
RpcStatus = RPC_P_RECEIVE_FAILED;
|
|
goto AbortFirstLegAndExit;
|
|
}
|
|
|
|
if (LocalServerConnection == NULL)
|
|
{
|
|
// we're first. Initialize the server virtual connection
|
|
// we know the server has reserved space for the larger of
|
|
// WS_HTTP2_INITIAL_CONNECTION and HTTP2ServerVirtualConnection.
|
|
// Use the same space.
|
|
|
|
VCPlaceHolder = (HTTP2ServerVirtualConnection *)OriginalConnection;
|
|
LocalServerConnection = new (VCPlaceHolder) HTTP2ServerVirtualConnection(&ServerCookie,
|
|
ProtocolVersion,
|
|
&RpcStatus);
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
// we use the first timer for connection establishment
|
|
RpcStatus = LocalServerConnection->SetTimeout(DefaultNoResponseTimeout,
|
|
LocalServerConnection->GetInChannelTimer());
|
|
}
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
LocalServerConnection->HTTP2ServerVirtualConnection::~HTTP2ServerVirtualConnection();
|
|
OriginalConnection->fAborted = 1;
|
|
OriginalConnection->pReadBuffer = NULL;
|
|
goto AbortFirstLegAndExit;
|
|
}
|
|
|
|
*VirtualConnectionCreated = TRUE;
|
|
LocalServerConnection->id = HTTPv2;
|
|
LocalServerConnection->type = COMPLEX_T | SERVER;
|
|
FirstLeg = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// the actual transport connection is by now owned by the channel
|
|
// Create a fake connection in the current location that will no-op on
|
|
// close.
|
|
OriginalConnection->fAborted = 1;
|
|
OriginalConnection->pReadBuffer = NULL;
|
|
if (PacketType == http2fsptD2_A2)
|
|
{
|
|
// if this is a replacement channel, check the cookies
|
|
if (LocalServerConnection->CompareCookieWithDefaultInChannelCookie(&ChannelCookie))
|
|
{
|
|
// cookies don't match. Nuke the newly established channel - it is probably
|
|
// fake
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
goto AbortFirstLegAndExit;
|
|
}
|
|
|
|
// we still hold the cookie collection mutex. This synchronizes with
|
|
// aborts
|
|
RpcStatus = LocalServerConnection->SetTimeout(DefaultNoResponseTimeout,
|
|
LocalServerConnection->GetInChannelTimer());
|
|
if (RpcStatus != RPC_S_OK)
|
|
goto AbortFirstLegAndExit;
|
|
}
|
|
else if (PacketType == http2fsptD4_A4)
|
|
{
|
|
// if this is a replacement channel, check the cookies
|
|
if (LocalServerConnection->CompareCookieWithDefaultOutChannelCookie(&ChannelCookie))
|
|
{
|
|
// cookies don't match. Nuke the newly established channel - it is probably
|
|
// fake
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
goto AbortFirstLegAndExit;
|
|
}
|
|
|
|
// we still hold the cookie collection mutex. This synchronizes with
|
|
// aborts
|
|
RpcStatus = LocalServerConnection->SetTimeout(DefaultNoResponseTimeout,
|
|
LocalServerConnection->GetOutChannelTimer());
|
|
if (RpcStatus != RPC_S_OK)
|
|
goto AbortFirstLegAndExit;
|
|
}
|
|
|
|
FirstLeg = FALSE;
|
|
}
|
|
|
|
// set the runtime connection ptr for the raw connection
|
|
Connection->RuntimeConnectionPtr = LocalServerConnection;
|
|
|
|
LocalServerConnection->ProtocolVersion = min (ProtocolVersion, LocalServerConnection->ProtocolVersion);
|
|
|
|
if (PacketType == http2fsptD1_A2)
|
|
{
|
|
if (LocalServerConnection->OutChannels[0].IsChannelSet())
|
|
{
|
|
// if we already have a second channel, then this is a protocol error
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
goto AbortSecondLegAndExit;
|
|
}
|
|
|
|
LocalServerConnection->OutProxySettings[0].ReceiveWindow = OutProxyReceiveWindow;
|
|
LocalServerConnection->OutProxySettings[0].ChannelLifetime = OutChannelLifetime;
|
|
LocalServerConnection->OutChannelCookies[0].SetCookie(ChannelCookie.GetCookie());
|
|
|
|
// attach the newly created stack to the connection
|
|
LocalServerConnection->SetFirstOutChannel(OutChannel);
|
|
OutChannel->SetParent(LocalServerConnection);
|
|
|
|
if (FirstLeg)
|
|
{
|
|
ASSERT(LocalServerConnection->InChannelState.State == http2svClosed);
|
|
LogEvent(SU_HTTPv2, EV_STATE, LocalServerConnection, IN_CHANNEL_STATE, http2svB2W, 1, 0);
|
|
LocalServerConnection->InChannelState.State = http2svB2W;
|
|
GetServerCookieCollection()->AddElement(&LocalServerConnection->EmbeddedConnectionCookie);
|
|
}
|
|
else
|
|
{
|
|
// we got the second leg - cancel the timeout for the second leg
|
|
LocalServerConnection->CancelTimeout(LocalServerConnection->GetInChannelTimer());
|
|
|
|
if (LocalServerConnection->InChannelState.State != http2svA2W)
|
|
{
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
goto AbortSecondLegAndExit;
|
|
}
|
|
|
|
LogEvent(SU_HTTPv2, EV_STATE, LocalServerConnection, IN_CHANNEL_STATE, http2svOpened, 1, 0);
|
|
LocalServerConnection->InChannelState.State = http2svOpened;
|
|
LocalServerConnection->OutChannelState.State = http2svOpened;
|
|
|
|
ASSERT(InChannel == NULL);
|
|
|
|
InChannel = LocalServerConnection->LockDefaultInChannel(&OtherChannelPtr);
|
|
if (InChannel == NULL)
|
|
{
|
|
RpcStatus = RPC_P_RECEIVE_FAILED;
|
|
goto AbortSecondLegAndExit;
|
|
}
|
|
InChannel->AddReference();
|
|
OtherChannelPtr->UnlockChannelPointer();
|
|
|
|
// for second leg, we need to add one reference before we release
|
|
// the lock. Otherwise the pending receive on the first leg may
|
|
// kill the connection and the channel with it
|
|
OutChannel->AddReference();
|
|
}
|
|
}
|
|
else if ((PacketType == http2fsptD1_B2) || (PacketType == http2fsptD2_A2))
|
|
{
|
|
if (PacketType == http2fsptD1_B2)
|
|
{
|
|
if (FirstLeg == FALSE)
|
|
{
|
|
// we got the second leg - cancel the timeout for the second leg
|
|
LocalServerConnection->CancelTimeout(LocalServerConnection->GetInChannelTimer());
|
|
}
|
|
|
|
CopyClientAddress(&LocalServerConnection->ClientAddress,
|
|
&ClientAddress);
|
|
LocalServerConnection->InProxyReceiveWindows[0] = InProxyReceiveWindow;
|
|
LocalServerConnection->InProxyConnectionTimeout = InProxyConnectionTimeout;
|
|
LocalServerConnection->AssociationGroupId.SetCookie(AssociationGroupId.GetCookie());
|
|
LocalServerConnection->InChannelCookies[0].SetCookie(ChannelCookie.GetCookie());
|
|
|
|
// attach the newly created stack to the connection
|
|
LocalServerConnection->SetFirstInChannel(InChannel);
|
|
InChannel->SetParent(LocalServerConnection);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(PacketType == http2fsptD2_A2);
|
|
NonDefaultChannel = LocalServerConnection->GetNonDefaultInChannelSelector();
|
|
LocalServerConnection->InProxyReceiveWindows[NonDefaultChannel] = InProxyReceiveWindow;
|
|
LocalServerConnection->InProxyConnectionTimeout = InProxyConnectionTimeout;
|
|
LocalServerConnection->InChannelCookies[NonDefaultChannel].SetCookie(NewChannelCookie.GetCookie());
|
|
|
|
// attach the newly created stack to the connection
|
|
LocalServerConnection->SetNonDefaultInChannel(InChannel);
|
|
InChannel->SetParent(LocalServerConnection);
|
|
|
|
if (LocalServerConnection->InChannelState.State != http2svOpened)
|
|
{
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
goto AbortSecondLegAndExit;
|
|
}
|
|
LogEvent(SU_HTTPv2, EV_STATE, LocalServerConnection, IN_CHANNEL_STATE, http2svOpened_A6W, 1, 0);
|
|
LocalServerConnection->InChannelState.State = http2svOpened_A6W;
|
|
ASSERT(FirstLeg == FALSE);
|
|
}
|
|
|
|
if (FirstLeg)
|
|
{
|
|
ASSERT(LocalServerConnection->InChannelState.State == http2svClosed);
|
|
LogEvent(SU_HTTPv2, EV_STATE, LocalServerConnection, IN_CHANNEL_STATE, http2svA2W, 1, 0);
|
|
LocalServerConnection->InChannelState.State = http2svA2W;
|
|
GetServerCookieCollection()->AddElement(&LocalServerConnection->EmbeddedConnectionCookie);
|
|
}
|
|
else
|
|
{
|
|
if (PacketType == http2fsptD1_B2)
|
|
{
|
|
if (LocalServerConnection->InChannelState.State != http2svB2W)
|
|
{
|
|
// this can happen if the out channel managed to attach
|
|
// itself and then die
|
|
RpcStatus = RPC_P_RECEIVE_FAILED;
|
|
goto AbortSecondLegAndExit;
|
|
}
|
|
LogEvent(SU_HTTPv2, EV_STATE, LocalServerConnection, IN_CHANNEL_STATE, http2svOpened, 1, 0);
|
|
LocalServerConnection->InChannelState.State = http2svOpened;
|
|
LocalServerConnection->OutChannelState.State = http2svOpened;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(PacketType == http2fsptD2_A2);
|
|
}
|
|
|
|
ASSERT(OutChannel == NULL);
|
|
|
|
OutChannel = LocalServerConnection->LockDefaultOutChannel(&OtherChannelPtr);
|
|
if (OutChannel == NULL)
|
|
{
|
|
RpcStatus = RPC_P_RECEIVE_FAILED;
|
|
goto AbortSecondLegAndExit;
|
|
}
|
|
OutChannel->AddReference();
|
|
OtherChannelPtr->UnlockChannelPointer();
|
|
|
|
// for second leg, we need to add one reference before we release
|
|
// the lock. Otherwise the pending receive on the first leg may
|
|
// kill the connection and the channel with it
|
|
InChannel->AddReference();
|
|
}
|
|
}
|
|
else if (PacketType == http2fsptD4_A4)
|
|
{
|
|
NonDefaultChannel = LocalServerConnection->GetNonDefaultOutChannelSelector();
|
|
LocalServerConnection->OutProxySettings[NonDefaultChannel].ReceiveWindow = OutProxyReceiveWindow;
|
|
LocalServerConnection->OutChannelCookies[NonDefaultChannel].SetCookie(NewChannelCookie.GetCookie());
|
|
|
|
// attach the newly created stack to the connection
|
|
LocalServerConnection->SetNonDefaultOutChannel(OutChannel);
|
|
OutChannel->SetParent(LocalServerConnection);
|
|
|
|
if (LocalServerConnection->OutChannelState.State != http2svOpened_A4W)
|
|
{
|
|
RpcStatus = RPC_S_PROTOCOL_ERROR;
|
|
goto AbortSecondLegAndExit;
|
|
}
|
|
LogEvent(SU_HTTPv2, EV_STATE, LocalServerConnection, OUT_CHANNEL_STATE, http2svOpened_A8W, 1, 0);
|
|
LocalServerConnection->OutChannelState.State = http2svOpened_A8W;
|
|
ASSERT(FirstLeg == FALSE);
|
|
|
|
ASSERT(InChannel == NULL);
|
|
|
|
InChannel = LocalServerConnection->LockDefaultInChannel(&OtherChannelPtr);
|
|
if (InChannel == NULL)
|
|
{
|
|
RpcStatus = RPC_P_RECEIVE_FAILED;
|
|
goto AbortSecondLegAndExit;
|
|
}
|
|
InChannel->AddReference();
|
|
OtherChannelPtr->UnlockChannelPointer();
|
|
|
|
// for second leg, we need to add one reference before we release
|
|
// the lock. Otherwise the pending receive on the first leg may
|
|
// kill the connection and the channel with it
|
|
OutChannel->AddReference();
|
|
}
|
|
else
|
|
{
|
|
ASSERT(0);
|
|
}
|
|
|
|
// we have a virtual connection and at least one of its channels
|
|
// attached to it. We have a no-op connection in OriginalConnection
|
|
// by now. Any failure paths on the second leg must abort the virtual connection
|
|
|
|
if ((PacketType == http2fsptD1_A2) || (PacketType == http2fsptD4_A4))
|
|
{
|
|
// out channel
|
|
RpcStatus = OutChannel->Receive(http2ttRaw);
|
|
ThisChannel = OutChannel;
|
|
}
|
|
else
|
|
{
|
|
ASSERT((PacketType == http2fsptD1_B2)
|
|
|| (PacketType == http2fsptD2_A2) );
|
|
// in channel
|
|
RpcStatus = InChannel->Receive(http2ttRTS);
|
|
if ((PacketType == http2fsptD1_B2) && (RpcStatus == RPC_S_OK))
|
|
{
|
|
// naturally, we're also interested in data receives
|
|
RpcStatus = InChannel->Receive(http2ttData);
|
|
}
|
|
ThisChannel = InChannel;
|
|
}
|
|
|
|
// Make sure HTTP2ServerVirtualConnection didn't forget to
|
|
// initialize its type member
|
|
if (FirstLeg && RpcStatus == RPC_S_OK)
|
|
{
|
|
ASSERT(LocalServerConnection->id == HTTPv2);
|
|
ASSERT(LocalServerConnection->type & COMPLEX_T);
|
|
}
|
|
|
|
// Release the lock after posting a raw receive in order to syncronize the receive with
|
|
// the in channel establishment.
|
|
GetServerCookieCollection()->UnlockCollection();
|
|
|
|
if (FirstLeg)
|
|
{
|
|
// this is the first leg. We know we are the only ones parting on
|
|
// the connection. In case of error just return it back. The runtime
|
|
// will turn around and close the connection
|
|
}
|
|
else
|
|
{
|
|
if ((PacketType == http2fsptD1_B2) || (PacketType == http2fsptD1_A2))
|
|
{
|
|
// the connection is fully fleshed out and both channels are plugged. Some
|
|
// additional activity remains. We need to abort the runtime connection
|
|
// for the second leg and remove the extra refcount
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
// we need to re-obtain the local server connection pointer through a safe mechanism.
|
|
// After we released the collection mutex, it may have been destroyed
|
|
LocalServerConnection = (HTTP2ServerVirtualConnection *) InChannel->LockParentPointer();
|
|
if (LocalServerConnection)
|
|
{
|
|
// we successfully submitted receives. Now send out D1/C1 and D1/B3
|
|
D1_C1Context = AllocateAndInitializeD1_C1(LocalServerConnection->ProtocolVersion,
|
|
LocalServerConnection->InProxyReceiveWindows[0],
|
|
LocalServerConnection->InProxyConnectionTimeout
|
|
);
|
|
|
|
if (D1_C1Context != NULL)
|
|
{
|
|
// we don't need to lock it, because we have a reference to it
|
|
RpcStatus = OutChannel->Send(D1_C1Context);
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
D1_B3Context = AllocateAndInitializeD1_B3(HTTP2ServerReceiveWindow,
|
|
LocalServerConnection->ProtocolVersion
|
|
);
|
|
if (D1_B3Context != NULL)
|
|
{
|
|
RpcStatus = InChannel->Send(D1_B3Context);
|
|
if (RpcStatus != RPC_S_OK)
|
|
FreeRTSPacket(D1_B3Context);
|
|
}
|
|
else
|
|
{
|
|
RpcStatus = RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FreeRTSPacket(D1_C1Context);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RpcStatus = RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
InChannel->UnlockParentPointer();
|
|
}
|
|
else
|
|
RpcStatus = RPC_P_CONNECTION_SHUTDOWN;
|
|
}
|
|
}
|
|
else if (PacketType == http2fsptD2_A2)
|
|
{
|
|
// We have added the second channel to the connection. Keep the ball
|
|
// rolling
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
// After we released the collection mutex, it may have been destroyed
|
|
LocalServerConnection = (HTTP2ServerVirtualConnection *) OutChannel->LockParentPointer();
|
|
if (LocalServerConnection)
|
|
{
|
|
// we successfully submitted receives. Now send out D2/A3
|
|
D2_A3Context = AllocateAndInitializeD2_A3(fdClient,
|
|
LocalServerConnection->ProtocolVersion,
|
|
LocalServerConnection->InProxyReceiveWindows[NonDefaultChannel],
|
|
LocalServerConnection->InProxyConnectionTimeout
|
|
);
|
|
|
|
if (D2_A3Context != NULL)
|
|
{
|
|
// we don't need to lock it, because we have a reference to it.
|
|
RpcStatus = OutChannel->Send(D2_A3Context);
|
|
|
|
if ((RpcStatus != RPC_S_OK)
|
|
&& (RpcStatus != RPC_P_CHANNEL_NEEDS_RECYCLING))
|
|
{
|
|
FreeRTSPacket(D2_A3Context);
|
|
}
|
|
|
|
// handle a recycling request if necessary
|
|
RpcStatus = LocalServerConnection->StartChannelRecyclingIfNecessary(RpcStatus,
|
|
FALSE // IsFromUpcall
|
|
);
|
|
}
|
|
else
|
|
{
|
|
RpcStatus = RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
OutChannel->UnlockParentPointer();
|
|
}
|
|
else
|
|
RpcStatus = RPC_P_CONNECTION_SHUTDOWN;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ASSERT(PacketType == http2fsptD4_A4);
|
|
|
|
// We have added the second channel to the connection. Keep the ball
|
|
// rolling
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
// After we released the collection mutex, it may have been destroyed
|
|
LocalServerConnection = (HTTP2ServerVirtualConnection *) OutChannel->LockParentPointer();
|
|
if (LocalServerConnection)
|
|
{
|
|
// we successfully submitted receives. Now send out D4/A5
|
|
D4_A5Context = AllocateAndInitializeD4_A5(fdClient,
|
|
LocalServerConnection->ProtocolVersion,
|
|
OutProxyConnectionTimeout
|
|
);
|
|
|
|
if (D4_A5Context != NULL)
|
|
{
|
|
// We still need to send on the default channel. Obtain
|
|
// a pointer through the virtual connection
|
|
RpcStatus = LocalServerConnection->SendTrafficOnDefaultChannel(FALSE, // IsInChannel
|
|
D4_A5Context
|
|
);
|
|
if (RpcStatus != RPC_S_OK)
|
|
FreeRTSPacket(D4_A5Context);
|
|
}
|
|
else
|
|
{
|
|
RpcStatus = RPC_S_OUT_OF_MEMORY;
|
|
}
|
|
|
|
OutChannel->UnlockParentPointer();
|
|
}
|
|
else
|
|
RpcStatus = RPC_P_CONNECTION_SHUTDOWN;
|
|
}
|
|
}
|
|
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
// we can't directly access the connection because we don't have
|
|
// a way to prevent it from going away underneath us. We do it through
|
|
// the channels instead (on which we do have a refcount)
|
|
LocalServerConnection = (HTTP2ServerVirtualConnection *)ThisChannel->LockParentPointer();
|
|
if (LocalServerConnection)
|
|
{
|
|
LocalServerConnection->AbortChannels(RpcStatus);
|
|
ThisChannel->UnlockParentPointer();
|
|
}
|
|
}
|
|
|
|
InChannel->RemoveReference();
|
|
OutChannel->RemoveReference();
|
|
|
|
if (RpcStatus == RPC_S_OK)
|
|
{
|
|
// fake failure to the runtime. We have migrated our transport connection
|
|
// to the virtual connection and we don't need this one anymore
|
|
RpcStatus = RPC_P_RECEIVE_FAILED;
|
|
}
|
|
}
|
|
|
|
return RpcStatus;
|
|
|
|
AbortSecondLegAndExit:
|
|
ASSERT(FirstLeg == FALSE);
|
|
|
|
LocalServerConnection->Abort();
|
|
|
|
GetServerCookieCollection()->UnlockCollection();
|
|
|
|
// We do not need to unlink the added connection from the PnP list
|
|
// since this will be done on Free().
|
|
|
|
// the original connection must be WS_HTTP2_INITIAL_CONNECTION
|
|
ASSERT(OriginalConnection->id == HTTP);
|
|
|
|
ASSERT(RpcStatus != RPC_S_OK);
|
|
|
|
return RpcStatus;
|
|
|
|
AbortFirstLegAndExit:
|
|
// we failed to create a server connection. Release all locks and fail
|
|
GetServerCookieCollection()->UnlockCollection();
|
|
|
|
// the original connection must be WS_HTTP2_INITIAL_CONNECTION
|
|
ASSERT(OriginalConnection->id == HTTP);
|
|
|
|
// destroy the channel created during the first leg
|
|
if ((PacketType == http2fsptD1_A2) || (PacketType == http2fsptD4_A4))
|
|
{
|
|
OutChannel->Abort(RpcStatus);
|
|
OutChannel->RemoveReference();
|
|
}
|
|
else
|
|
{
|
|
ASSERT((PacketType == http2fsptD1_B2) || (PacketType == http2fsptD2_A2));
|
|
InChannel->Abort(RpcStatus);
|
|
InChannel->RemoveReference();
|
|
}
|
|
|
|
ASSERT(RpcStatus != RPC_S_OK);
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2ServerVirtualConnection::AllocateAndInitializeInChannel (
|
|
IN OUT WS_HTTP2_INITIAL_CONNECTION **Connection,
|
|
OUT HTTP2ServerInChannel **ReturnInChannel
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initializes a server in channel.
|
|
|
|
Note: This function must migrate the WS_HTTP2_INITIAL_CONNECTION after
|
|
morphing it into WS_HTTP2_CONNECTION
|
|
|
|
Arguments:
|
|
|
|
Connection - on input, the received connection. It must be migrated
|
|
and morphed into WS_HTTP2_CONNECTION on success. All failure paths
|
|
must be sure to move the WS_HTTP2_INITIAL_CONNECTION back to its
|
|
original location.
|
|
|
|
ReturnInChannel - on successful return, the created server in channel
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* for error
|
|
|
|
--*/
|
|
{
|
|
ULONG MemorySize;
|
|
BYTE *MemoryBlock, *CurrentBlock;
|
|
HTTP2ServerInChannel *InChannel;
|
|
HTTP2EndpointReceiver *EndpointReceiver;
|
|
HTTP2SocketTransportChannel *RawChannel;
|
|
WS_HTTP2_CONNECTION *RawConnection;
|
|
BOOL EndpointReceiverNeedsCleanup;
|
|
BOOL RawChannelNeedsCleanup;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
// alocate the in channel
|
|
MemorySize = SIZE_OF_OBJECT_AND_PADDING(HTTP2ServerInChannel)
|
|
+ SIZE_OF_OBJECT_AND_PADDING(HTTP2EndpointReceiver)
|
|
+ SIZE_OF_OBJECT_AND_PADDING(HTTP2SocketTransportChannel)
|
|
+ sizeof(WS_HTTP2_CONNECTION);
|
|
|
|
MemoryBlock = (BYTE *) new char [MemorySize];
|
|
CurrentBlock = MemoryBlock;
|
|
if (CurrentBlock == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
|
|
InChannel = (HTTP2ServerInChannel *) MemoryBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2ServerInChannel);
|
|
|
|
EndpointReceiver = (HTTP2EndpointReceiver *) CurrentBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2EndpointReceiver);
|
|
|
|
RawChannel = (HTTP2SocketTransportChannel *)CurrentBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2SocketTransportChannel);
|
|
|
|
RawConnection = (WS_HTTP2_CONNECTION *)CurrentBlock;
|
|
|
|
// all memory blocks are allocated. Go and initialize them. Use explicit
|
|
// placement
|
|
EndpointReceiverNeedsCleanup = FALSE;
|
|
RawChannelNeedsCleanup = FALSE;
|
|
|
|
// Wait for any pending IO to get out.
|
|
while((*Connection)->IsIoStarting())
|
|
Sleep(1);
|
|
|
|
RpcpMemoryCopy(RawConnection, *Connection, sizeof(WS_HTTP2_CONNECTION));
|
|
|
|
RawConnection->HeaderRead = TRUE;
|
|
RawConnection->ReadHeaderFn = NULL;
|
|
RawConnection->Read.pAsyncObject = RawConnection;
|
|
RawConnection->type = COMPLEX_T | CONNECTION | SERVER;
|
|
RawConnection->fAborted = 1; // this connection must not be aborted
|
|
// unless we successfully initialize
|
|
// the channel. Therefore, artificially
|
|
// abort the connection (preventing real
|
|
// aborts) until we initialize the channel
|
|
|
|
// Since Initialize() is not called, we need to init fIgnoreFree explicitly.
|
|
RawConnection->fIgnoreFree = FALSE;
|
|
|
|
RawConnection = new (RawConnection) WS_HTTP2_CONNECTION;
|
|
|
|
RpcStatus = RPC_S_OK;
|
|
|
|
RawChannel = new (RawChannel) HTTP2SocketTransportChannel (RawConnection, &RpcStatus);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
RawChannel->HTTP2SocketTransportChannel::~HTTP2SocketTransportChannel();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
RawConnection->Channel = RawChannel;
|
|
|
|
RawChannelNeedsCleanup = TRUE;
|
|
|
|
EndpointReceiver = new (EndpointReceiver) HTTP2EndpointReceiver (HTTP2ServerReceiveWindow,
|
|
TRUE, // IsServer
|
|
&RpcStatus);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
EndpointReceiver->HTTP2EndpointReceiver::~HTTP2EndpointReceiver();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
RawChannel->SetUpperChannel(EndpointReceiver);
|
|
EndpointReceiver->SetLowerChannel(RawChannel);
|
|
|
|
EndpointReceiverNeedsCleanup = TRUE;
|
|
|
|
InChannel = new (InChannel) HTTP2ServerInChannel (&RpcStatus);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
InChannel->HTTP2ServerInChannel::~HTTP2ServerInChannel();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
EndpointReceiver->SetUpperChannel(InChannel);
|
|
InChannel->SetLowerChannel(EndpointReceiver);
|
|
|
|
RawChannel->SetTopChannel(InChannel);
|
|
EndpointReceiver->SetTopChannel(InChannel);
|
|
|
|
ASSERT(RpcStatus == RPC_S_OK);
|
|
|
|
RawConnection->fAborted = 0;
|
|
|
|
*ReturnInChannel = InChannel;
|
|
*Connection = (WS_HTTP2_INITIAL_CONNECTION *)RawConnection;
|
|
|
|
goto CleanupAndExit;
|
|
|
|
AbortAndExit:
|
|
|
|
// No need to clean up the raw connection.
|
|
// If we failed, the virtual connection
|
|
// is not created, and the caller will abort
|
|
// the original conneciton.
|
|
//
|
|
// We need to make sure that this failure path
|
|
// will not try freeing the raw connection.
|
|
// For this we set fIgnoreFree to true to ensure that
|
|
// the free will be ignored.
|
|
|
|
ASSERT(RpcStatus != RPC_S_OK);
|
|
|
|
RawConnection->fIgnoreFree = TRUE;
|
|
|
|
if (EndpointReceiverNeedsCleanup)
|
|
{
|
|
EndpointReceiver->Abort(RpcStatus);
|
|
EndpointReceiver->FreeObject();
|
|
}
|
|
else if (RawChannelNeedsCleanup)
|
|
{
|
|
RawChannel->Abort(RpcStatus);
|
|
RawChannel->FreeObject();
|
|
}
|
|
|
|
RawConnection->fIgnoreFree = FALSE;
|
|
|
|
if (MemoryBlock)
|
|
delete [] MemoryBlock;
|
|
|
|
CleanupAndExit:
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
RPC_STATUS HTTP2ServerVirtualConnection::AllocateAndInitializeOutChannel (
|
|
IN OUT WS_HTTP2_INITIAL_CONNECTION **Connection,
|
|
IN ULONG OutChannelLifetime,
|
|
OUT HTTP2ServerOutChannel **ReturnOutChannel
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initializes a server out channel.
|
|
|
|
Note: This function must migrate the WS_HTTP2_INITIAL_CONNECTION after
|
|
morphing it into WS_HTTP2_CONNECTION
|
|
|
|
Arguments:
|
|
|
|
Connection - on input, the received connection. It must be migrated
|
|
and morphed into WS_HTTP2_CONNECTION on success. All failure paths
|
|
must be sure to move the WS_HTTP2_INITIAL_CONNECTION back to its
|
|
original location.
|
|
|
|
OutChannelLifetime - the lifetime on the out channel.
|
|
|
|
ReturnOutChannel - on successful return, the created server out channel
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK or RPC_S_* for error
|
|
|
|
--*/
|
|
{
|
|
ULONG MemorySize;
|
|
BYTE *MemoryBlock, *CurrentBlock;
|
|
HTTP2ServerOutChannel *OutChannel;
|
|
HTTP2PlugChannel *PlugChannel;
|
|
HTTP2FlowControlSender *FlowControlSender;
|
|
HTTP2ChannelDataOriginator *ChannelDataOriginator;
|
|
HTTP2SocketTransportChannel *RawChannel;
|
|
WS_HTTP2_CONNECTION *RawConnection;
|
|
BOOL PlugChannelNeedsCleanup;
|
|
BOOL FlowControlSenderNeedsCleanup;
|
|
BOOL ChannelDataOriginatorNeedsCleanup;
|
|
BOOL RawChannelNeedsCleanup;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
// alocate the out channel
|
|
MemorySize = SIZE_OF_OBJECT_AND_PADDING(HTTP2ServerOutChannel)
|
|
+ SIZE_OF_OBJECT_AND_PADDING(HTTP2PlugChannel)
|
|
+ SIZE_OF_OBJECT_AND_PADDING(HTTP2FlowControlSender)
|
|
+ SIZE_OF_OBJECT_AND_PADDING(HTTP2ChannelDataOriginator)
|
|
+ SIZE_OF_OBJECT_AND_PADDING(HTTP2SocketTransportChannel)
|
|
+ SIZE_OF_OBJECT_AND_PADDING(WS_HTTP2_CONNECTION)
|
|
+ sizeof(HTTP2SendContext) // send context for the last send
|
|
;
|
|
|
|
MemoryBlock = (BYTE *) new char [MemorySize];
|
|
CurrentBlock = MemoryBlock;
|
|
if (CurrentBlock == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
|
|
OutChannel = (HTTP2ServerOutChannel *) MemoryBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2ServerOutChannel);
|
|
|
|
PlugChannel = (HTTP2PlugChannel *) CurrentBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2PlugChannel);
|
|
|
|
FlowControlSender = (HTTP2FlowControlSender *) CurrentBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2FlowControlSender);
|
|
|
|
ChannelDataOriginator = (HTTP2ChannelDataOriginator *)CurrentBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2ChannelDataOriginator);
|
|
|
|
RawChannel = (HTTP2SocketTransportChannel *)CurrentBlock;
|
|
CurrentBlock += SIZE_OF_OBJECT_AND_PADDING(HTTP2SocketTransportChannel);
|
|
|
|
RawConnection = (WS_HTTP2_CONNECTION *)CurrentBlock;
|
|
|
|
// all memory blocks are allocated. Go and initialize them. Use explicit
|
|
// placement
|
|
PlugChannelNeedsCleanup = FALSE;
|
|
FlowControlSenderNeedsCleanup = FALSE;
|
|
ChannelDataOriginatorNeedsCleanup = FALSE;
|
|
RawChannelNeedsCleanup = FALSE;
|
|
|
|
// migrate the connection to its new location. Since nobody points to it (we have
|
|
// been unlinked from the PnP list), copying is sufficient
|
|
|
|
// Wait for any pending IO to get out.
|
|
while((*Connection)->IsIoStarting())
|
|
Sleep(1);
|
|
|
|
RpcpMemoryCopy(RawConnection, *Connection, sizeof(WS_HTTP2_CONNECTION));
|
|
RawConnection->HeaderRead = TRUE;
|
|
RawConnection->Read.pAsyncObject = RawConnection;
|
|
RawConnection->type = COMPLEX_T | CONNECTION | SERVER;
|
|
RawConnection->fAborted = 1; // this connection must not be aborted
|
|
// unless we successfully initialize
|
|
// the channel. Therefore, artificially
|
|
// abort the connection (preventing real
|
|
// aborts) until we initialize the channel
|
|
|
|
// Since Initialize() is not called, we need to init fIgnoreFree explicitly.
|
|
RawConnection->fIgnoreFree = FALSE;
|
|
|
|
RawConnection = new (RawConnection) WS_HTTP2_CONNECTION;
|
|
|
|
RpcStatus = RPC_S_OK;
|
|
|
|
RawChannel = new (RawChannel) HTTP2SocketTransportChannel (RawConnection, &RpcStatus);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
RawChannel->HTTP2SocketTransportChannel::~HTTP2SocketTransportChannel();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
RawConnection->Channel = RawChannel;
|
|
|
|
RawChannelNeedsCleanup = TRUE;
|
|
|
|
ChannelDataOriginator = new (ChannelDataOriginator) HTTP2ChannelDataOriginator (OutChannelLifetime,
|
|
TRUE, // IsServer
|
|
&RpcStatus);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
ChannelDataOriginator->HTTP2ChannelDataOriginator::~HTTP2ChannelDataOriginator();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
RawChannel->SetUpperChannel(ChannelDataOriginator);
|
|
ChannelDataOriginator->SetLowerChannel(RawChannel);
|
|
|
|
ChannelDataOriginatorNeedsCleanup = TRUE;
|
|
|
|
FlowControlSender = new (FlowControlSender) HTTP2FlowControlSender (TRUE, // IsServer
|
|
TRUE, // SendToRuntime
|
|
&RpcStatus
|
|
);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
FlowControlSender->HTTP2FlowControlSender::~HTTP2FlowControlSender();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
ChannelDataOriginator->SetUpperChannel(FlowControlSender);
|
|
FlowControlSender->SetLowerChannel(ChannelDataOriginator);
|
|
|
|
FlowControlSenderNeedsCleanup = TRUE;
|
|
|
|
PlugChannel = new (PlugChannel) HTTP2PlugChannel (&RpcStatus);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
PlugChannel->HTTP2PlugChannel::~HTTP2PlugChannel();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
FlowControlSender->SetUpperChannel(PlugChannel);
|
|
PlugChannel->SetLowerChannel(FlowControlSender);
|
|
|
|
PlugChannelNeedsCleanup = TRUE;
|
|
|
|
OutChannel = new (OutChannel) HTTP2ServerOutChannel (&RpcStatus);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
OutChannel->HTTP2ServerOutChannel::~HTTP2ServerOutChannel();
|
|
goto AbortAndExit;
|
|
}
|
|
|
|
RawChannel->SetTopChannel(OutChannel);
|
|
ChannelDataOriginator->SetTopChannel(OutChannel);
|
|
FlowControlSender->SetTopChannel(OutChannel);
|
|
PlugChannel->SetTopChannel(OutChannel);
|
|
|
|
PlugChannel->SetUpperChannel(OutChannel);
|
|
OutChannel->SetLowerChannel(PlugChannel);
|
|
|
|
ASSERT(RpcStatus == RPC_S_OK);
|
|
|
|
RawConnection->fAborted = 0;
|
|
|
|
*ReturnOutChannel = OutChannel;
|
|
*Connection = (WS_HTTP2_INITIAL_CONNECTION *)RawConnection;
|
|
|
|
goto CleanupAndExit;
|
|
|
|
AbortAndExit:
|
|
|
|
// We need to make sure that this failure path
|
|
// will not try freeing the raw connection.
|
|
// For this we set fIgnoreFree to true to ensure that
|
|
// the free will be ignored.
|
|
|
|
RawConnection->fIgnoreFree = TRUE;
|
|
|
|
if (PlugChannelNeedsCleanup)
|
|
{
|
|
PlugChannel->Abort(RpcStatus);
|
|
PlugChannel->FreeObject();
|
|
}
|
|
else if (FlowControlSenderNeedsCleanup)
|
|
{
|
|
FlowControlSender->Abort(RpcStatus);
|
|
FlowControlSender->FreeObject();
|
|
}
|
|
else if (ChannelDataOriginatorNeedsCleanup)
|
|
{
|
|
ChannelDataOriginator->Abort(RpcStatus);
|
|
ChannelDataOriginator->FreeObject();
|
|
}
|
|
else if (RawChannelNeedsCleanup)
|
|
{
|
|
RawChannel->Abort(RpcStatus);
|
|
RawChannel->FreeObject();
|
|
}
|
|
|
|
RawConnection->fIgnoreFree = FALSE;
|
|
|
|
// no need to clean up the raw connection.
|
|
// If we failed, the virtual connection
|
|
// is not created, and the caller will abort
|
|
// the original conneciton
|
|
|
|
if (MemoryBlock)
|
|
delete [] MemoryBlock;
|
|
|
|
CleanupAndExit:
|
|
|
|
return RpcStatus;
|
|
}
|
|
|
|
void HTTP2ServerVirtualConnection::TimeoutExpired (
|
|
IN TimerContext *pTimer
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
A timeout expired before we cancelled the timer. Abort the connection.
|
|
|
|
Arguments:
|
|
|
|
pTimer - Pointer to the timer context for which the timeour has expired.
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
VerifyValidTimer(pTimer);
|
|
|
|
AbortChannels(RPC_P_TIMEOUT);
|
|
|
|
// Once we mark the timer as expired, we are no longer protected from
|
|
// the virtual connection being freed during the timer callback.
|
|
// We can't touch the virtual connection after this call.
|
|
TimerExpiredNotify(pTimer);
|
|
}
|
|
|
|
void HTTP2ServerVirtualConnection::DrainOutChannelPendingSends (
|
|
void
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Make sure all out channels have their sends drained.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
int i;
|
|
HTTP2ServerOutChannel *OutChannel;
|
|
|
|
for (i = 0; i < 2; i ++)
|
|
{
|
|
OutChannel = (HTTP2ServerOutChannel *)OutChannels[i].LockChannelPointer();
|
|
if (OutChannel)
|
|
{
|
|
// make sure the pending sends are drained. Otherwise
|
|
// a send that is currently completing may violate
|
|
// rule 38
|
|
OutChannel->DrainPendingSends();
|
|
OutChannels[i].UnlockChannelPointer();
|
|
}
|
|
}
|
|
}
|