#include "FlowSshCppIncludes.h"
#include "FlowSshCpp.h"

// CRT
#include <assert.h>


namespace FlowSshCpp
{

#define FLOWSSHCPP_REQUEST(handlerType, handlerObj, mainObj, call)  \
	handlerType::S_TryLock(handlerObj); \
	try { handlerType::S_OnStart(handlerObj, mainObj); call; } \
	catch (...) { handlerType::S_OnStartFailed(handlerObj); throw; }

#if (_MANAGED == 1) || (_M_CEE == 1)
#define BITVISE_FLOWSSHNET 1
#endif


class AutoSysFreeString : public NoCopy
{
public:
	AutoSysFreeString(BSTR bstr) : m_bstr(bstr) {}
	~AutoSysFreeString() { if (m_bstr) SysFreeString(m_bstr); }
	operator BSTR() { return m_bstr; }

private:
	BSTR m_bstr;
};

inline std::wstring Bstr2Ws(BSTR bstr)
{
	if (bstr != NULL)
	{
		AutoSysFreeString autoFree(bstr);
		return std::wstring(bstr);
	}

	return std::wstring();
}


template <class T>
class AutoFreeArrayPtr : public NoCopy
{
public:
	AutoFreeArrayPtr(T* ptr) : m_ptr(ptr) {}
	~AutoFreeArrayPtr() { if (m_ptr) delete [] m_ptr; }
	operator T*() { return m_ptr; }

private:
	T* m_ptr;
};

#define FLOWSSHCPP_EXCEPTION_IN_HANDLER(handlerName) \
	ExceptionInHandler(e, handlerName, handlerName L": [Error formatting exception]")

void ExceptionInHandler(std::exception const& e, wchar_t const* handlerName, wchar_t const* defErrDesc)
{
	char const* whatN = e.what();
	
	int result = MultiByteToWideChar(CP_ACP, 0, whatN, -1, 0, 0);
	if (result > 0)
	{
		AutoFreeArrayPtr<wchar_t> whatW(new (std::nothrow) wchar_t[(unsigned int)result]);
		if ((wchar_t*) whatW)
		{
			result = MultiByteToWideChar(CP_ACP, 0, whatN, -1, whatW, result);
			if (result > 0)
			{
				size_t descLen = wcslen(handlerName) + wcslen(L": ") + wcslen(whatW) + 1;
				AutoFreeArrayPtr<wchar_t> desc(new (std::nothrow) wchar_t[descLen]);
				if ((wchar_t*) desc)
				{
					desc[0] = L'\0';
					wcscat_s(desc, descLen, handlerName);
					wcscat_s(desc, descLen, L": ");
					wcscat_s(desc, descLen, whatW);

					Initializer::FlowSshC(NULL, FlowSshC_ErrorFlags_InHandler, desc);
					return;
				}
			}
		}
	}
	
	Initializer::FlowSshC(NULL, FlowSshC_ErrorFlags_InHandler, defErrDesc);
}


inline wchar_t const* SafeStr(wchar_t const* z)
{
	if (z)
		return z;
	else
		return L"";
}


// Initializer

Initializer::Initializer(RefPtr<ErrorHandler> errHandler)
{
	if (InterlockedIncrement(&m_count) != 1)
	{
		InterlockedDecrement(&m_count);
		throw InitializerExistsException();
	}
	if (!errHandler.Get())
	{
		assert(!"Initializer should be provided with a valid ErrorHandler.");
	}

	m_errHandler = errHandler;
	FlowSshC_Initialize(&FlowSshC, errHandler.Get());
}

Initializer::~Initializer()
{
	FlowSshC_Shutdown();

	InterlockedDecrement(&m_count);
	m_errHandler.Reset();
}

FlowSshC_Version Initializer::GetVersion()
{
	return FlowSshC_GetVersion();
}

void Initializer::SetActCode(wchar_t const* actCodeHex)
{
	FlowSshC_SetActCode(actCodeHex);
}

void Initializer::Shutdown()
{
	FlowSshC_Shutdown();
}

void Initializer::FlowSshC(void* handlerData, unsigned int flags, wchar_t const* desc)
{
	if (m_extHandler)
	{
		m_extHandler(handlerData, flags, desc);
		return;
	}

	if (flags & FlowSshC_ErrorFlags_InCall) 
		throw Exception(desc);
	else
	{
		try 
		{
			RefPtr<ErrorHandler> errHandler(m_errHandler);
			if (errHandler.Get())
				errHandler->OnExceptionInHandler(handlerData != NULL, desc);
		}
		catch (...) 
		{ 
			assert(!"Don't throw exceptions from OnExceptionInHandler.");
			throw;
		}
	}
}

LONG Initializer::m_count = 0;
RefPtr<ErrorHandler> Initializer::m_errHandler = NULL;
FlowSshC_ErrorHandler Initializer::m_extHandler = NULL;

#ifdef BITVISE_FLOWSSHNET
void Initializer::RegisterExtErrHandler(FlowSshC_ErrorHandler extHandler)
{
	m_extHandler = extHandler;
}
#else
void Initializer::RegisterExtErrHandler(FlowSshC_ErrorHandler)
{
	throw Exception(L"Initializer::RegisterExtErrHandler is for internal FlowSshNet usage only.");
}
#endif



// DefaultErrorHandler

#ifndef BITVISE_FLOWSSHNET
class DefaultErrorHandler
{
public:
	DefaultErrorHandler() { FlowSshC_Initialize(&Initializer::FlowSshC, NULL); }
} g_errorHandler;
#endif



// ProgressHandler

ProgressHandler::ProgressHandler()
{
	m_bSuccess = false;
	m_taskState = 0;
	m_taskSpecificStep = 0;
}

bool ProgressHandler::Success() const
{
	CsLocker locker(m_cs);
	return m_bSuccess;
}

unsigned int ProgressHandler::GetTaskState() const
{
	CsLocker locker(m_cs);
	return m_taskState;
}

unsigned int ProgressHandler::GetTaskSpecificStep() const
{
	CsLocker locker(m_cs);
	return m_taskSpecificStep;
}

std::wstring ProgressHandler::GetAuxInfo() const
{
	CsLocker locker(m_cs);
	return m_auxInfo;
}

std::wstring ProgressHandler::DescribeConnectError() const
{
	CsLocker locker(m_cs);
	return DescribeConnectError(GetTaskSpecificStep(), GetAuxInfo().c_str());
}

std::wstring ProgressHandler::DescribeSftpChannelOpenError() const
{
	CsLocker locker(m_cs);
	return DescribeSftpChannelOpenError(GetTaskSpecificStep(), GetAuxInfo().c_str());
}

std::wstring ProgressHandler::DescribeConnectError(unsigned int step, wchar_t const* auxInfo)
{
	std::wstring au = SafeStr(auxInfo);
	if (au.length() < 1)
		au = L"(no additional info)";

	switch (step)
	{
	case FlowSshC_ConnectStep_ConnectToProxy:		return L"Connecting to proxy server failed: " + au;
	case FlowSshC_ConnectStep_ConnectToSshServer:	return L"Connecting to SSH server failed: " + au;
	case FlowSshC_ConnectStep_SshVersionString:		return L"SSH version string failed: " + au;
	case FlowSshC_ConnectStep_SshKeyExchange:		return L"SSH key exchange failed: " + au;
	case FlowSshC_ConnectStep_SshUserAuth:			return L"SSH authentication failed: " + au;
	default:										return L"Connecting failed at unknown step: " + au;
	}
}

std::wstring ProgressHandler::DescribeSftpChannelOpenError(unsigned int step, wchar_t const* auxInfo)
{
	std::wstring au = SafeStr(auxInfo);
	if (au.length() < 1)
		au = L"(no additional info)";

	switch (step)
	{
	case FlowSshC_ClientSftpChannelOpenStep_OpenRequest: return L"Opening SFTP channel failed: " + au;
	case FlowSshC_ClientSftpChannelOpenStep_SftpRequest: return L"Requesting SFTP subsystem failed: " + au;
	case FlowSshC_ClientSftpChannelOpenStep_InitPacket:  return L"Initializing SFTP protocol failed: " + au;
	default:											 return L"Opening SFTP channel failed at unknown step: " + au;
	}
}

void ProgressHandler::OnStart()
{
	CsLocker locker(m_cs);
	m_bSuccess = false;
	m_taskState = 0;
	m_taskSpecificStep = 0;
	m_auxInfo.clear();
}

void ProgressHandler::FlowSshC(void* thisPtr, unsigned int taskState, unsigned int taskSpecificStep, wchar_t const* auxInfo)
{
	ProgressHandler* pHandler = static_cast<ProgressHandler*>(thisPtr);
	if (!pHandler)
		return;

	bool bLastEvent = (taskState != FlowSshC_TaskState_InProgress);

	AutoReleaseRef releaseRef;
	if (bLastEvent)
		releaseRef.Set(pHandler);

	CsLocker locker(pHandler->m_cs);

	pHandler->m_bSuccess = (taskState == FlowSshC_TaskState_Completed);	

	if (bLastEvent)
	{
		if (pHandler->m_bSuccess)
		{
			locker.Unlock();
			try { pHandler->OnSuccess(); }
			catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"ProgressHandler::OnSuccess"); }
			locker.Lock();
		}
		else
		{
			pHandler->m_taskState = taskState;
			pHandler->m_taskSpecificStep = taskSpecificStep;
			pHandler->m_auxInfo = SafeStr(auxInfo);

			locker.Unlock();
			try { pHandler->OnError(taskSpecificStep, pHandler->m_auxInfo); }
			catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"ProgressHandler::OnError"); }
			locker.Lock();
		}

		locker.Unlock();
		try { pHandler->Base_OnDone(); }
		catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"ProgressHandler::OnDone"); }
		locker.Lock();
	}
	else
	{
		locker.Unlock();
		try { pHandler->OnProgress(taskSpecificStep); }
		catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"ProgressHandler::OnProgress"); }
		locker.Lock();
	}
}



// Keypair

Keypair::Keypair(wchar_t const* alg, unsigned int bitCount)
{
	// Never returns a null pointer.
	m_flowSshC = FlowSshC_Keypair_CreateNew(alg, bitCount);
}

Keypair::Keypair(Data const& data, wchar_t const* passphrase)
{	
	// Returns NULL if keypair (private key) cannot be decoded (e.g. bad data or passphrase).
	m_flowSshC = FlowSshC_Keypair_CreateFromData(data.GetPtr(), data.GetSize(), passphrase);
	if (!m_flowSshC)
		throw KeypairLoadException();
}

Keypair::Keypair(FlowSshC_Keypair* flowSshC)
{
	// Never returns a null pointer.
	m_flowSshC = FlowSshC_Keypair_CreateCopy(flowSshC);
}

Keypair::~Keypair()
{
	FlowSshC_Keypair_Release(m_flowSshC);
	m_flowSshC = nullptr;
}

void Keypair::SetPassphrase(wchar_t const* passphrase) 
{ 
	FlowSshC_Keypair_SetPassphrase(m_flowSshC, passphrase); 
}

Data Keypair::GetBitviseData() const
{
	return Data(FlowSshC_Keypair_GetBitviseData(m_flowSshC));
}

Data Keypair::GetOpenSshData() const
{
	// Returns NULL if keypair (private key) cannot be exported
	// (e.g. keypair algorithm is not supported in OpenSsh format).
	BSTR bstr = FlowSshC_Keypair_GetOpenSshData(m_flowSshC);
	if (!bstr)
		throw KeypairExportException();
	return Data(bstr);
}

Data Keypair::GetPuttyData() const
{
	// Returns NULL if keypair (private key) cannot be exported
	// (e.g. keypair algorithm is not supported in Putty format).
	BSTR bstr = FlowSshC_Keypair_GetPuttyData(m_flowSshC);
	if (!bstr)
		throw KeypairExportException();
	return Data(bstr);
}



// PublicKey

PublicKey::PublicKey(RefPtrConst<Keypair> const& keypair)
{
	if (!keypair.Get()) throw ArgumentNullRefPtrException();

	// Never returns a null pointer.	
	m_flowSshC = FlowSshC_PublicKey_CreateFromKeypair(keypair->GetFlowSshC());
}

PublicKey::PublicKey(Data const& data)
{
	// Returns NULL if public key cannot be decoded.
	m_flowSshC = FlowSshC_PublicKey_CreateFromData(data.GetPtr(), data.GetSize());
	if (!m_flowSshC)
		throw PublicKeyLoadException();
}

PublicKey::PublicKey(FlowSshC_PublicKey* flowSshC)
{	
	// Never returns a null pointer.
	m_flowSshC = FlowSshC_PublicKey_CreateCopy(flowSshC);
}

PublicKey::~PublicKey()
{
	FlowSshC_PublicKey_Release(m_flowSshC);
	m_flowSshC = nullptr;
}

unsigned int PublicKey::GetBitCount() const
{
	return FlowSshC_PublicKey_GetBitCount(m_flowSshC);
}

unsigned int PublicKey::GetEffectiveSecurity() const
{
	return FlowSshC_PublicKey_GetEffectiveSecurity(m_flowSshC);
}

bool PublicKey::IsEqual(PublicKey const* other) const
{
	return FlowSshC_PublicKey_IsEqual(m_flowSshC, other->m_flowSshC);
}

std::wstring PublicKey::GetAlg() const
{
	return Bstr2Ws(FlowSshC_PublicKey_GetAlg(m_flowSshC));
}

std::wstring PublicKey::GetMd5() const
{
	return Bstr2Ws(FlowSshC_PublicKey_GetMd5(m_flowSshC));
}

std::wstring PublicKey::GetBubbleBabble() const
{
	return Bstr2Ws(FlowSshC_PublicKey_GetBubbleBabble(m_flowSshC));
}

std::wstring PublicKey::GetSha256() const
{
	return Bstr2Ws(FlowSshC_PublicKey_GetSha256(m_flowSshC));
}

Data PublicKey::GetSsh2Data() const
{
	return Data(FlowSshC_PublicKey_GetSsh2Data(m_flowSshC));
}

Data PublicKey::GetOpenSshData() const
{
	return Data(FlowSshC_PublicKey_GetOpenSshData(m_flowSshC));
}



// ForwardingErr

void ForwardingErr::Reset()
{
	m_errCode = 0;
	m_auxInfo.clear();
	m_desc.clear();
}

ForwardingErr& ForwardingErr::operator= (FlowSshC_ForwardingErr const* x)
{
	if (!x)
		Reset();
	else
	{
		m_errCode = x->m_errCode;
		m_auxInfo = SafeStr(x->m_auxInfo);
		m_desc = SafeStr(x->m_desc);
	}
	return *this;
}



// ForwardingHandler

ForwardingHandler::ForwardingHandler()
{
	m_bSuccess = false;
	m_listPort = 0;
}

bool ForwardingHandler::Success() const
{
	CsLocker locker(m_cs);
	return m_bSuccess;
}

unsigned int ForwardingHandler::GetListPort() const
{
	CsLocker locker(m_cs);
	return m_listPort;
}

ForwardingErr ForwardingHandler::GetError() const
{
	CsLocker locker(m_cs);
	return m_error;
}

void ForwardingHandler::OnStart()
{
	CsLocker locker(m_cs);
	m_bSuccess = false;
	m_listPort = 0;
	m_error.Reset();
}

void ForwardingHandler::FlowSshC(void* thisPtr, unsigned int listPort, FlowSshC_ForwardingErr const* error)
{
	ForwardingHandler* pHandler = static_cast<ForwardingHandler*>(thisPtr);
	if (!pHandler)
		return;

	AutoReleaseRef releaseRef(pHandler);
	CsLocker locker(pHandler->m_cs);

	pHandler->m_bSuccess = !error;
	
	if (!error)
	{
		pHandler->m_listPort = listPort;

		locker.Unlock();
		try { pHandler->OnSuccess(pHandler->m_listPort); }
		catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"ForwardingHandler::OnSuccess"); }
		locker.Lock();
	}
	else
	{
		pHandler->m_error = error;

		locker.Unlock();
		try { pHandler->OnError(pHandler->m_error); }
		catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"ForwardingHandler::OnError"); }
		locker.Lock();
	}

	locker.Unlock();
	try { pHandler->Base_OnDone(); }
	catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"ForwardingHandler::OnDone"); }
	locker.Lock();
}



// ForwardingLog

RefPtr<ForwardingLog> ForwardingLog::Create(FlowSshC_ForwardingLog const* x)
{
	switch (x->m_event)
	{
	default:
		return RefPtr<ForwardingLog>();

	case ForwardingLog::C2SOpenSent:
	case ForwardingLog::C2SOpened:
		return new ForwardingLog_C2S((FlowSshC_ForwardingLog_C2S const*) x);
	case ForwardingLog::C2SOpenFailed:
		return new ForwardingLog_C2SOpenFailed((FlowSshC_ForwardingLog_C2SOpenFailed const*) x);
	case ForwardingLog::C2SClosed:
		return new ForwardingLog_C2SClosed((FlowSshC_ForwardingLog_C2SClosed const*) x);

	case ForwardingLog::S2COpenIgnored:
	case ForwardingLog::S2COpenReceived:
	case ForwardingLog::S2COpened:
		return new ForwardingLog_S2C((FlowSshC_ForwardingLog_S2C const*) x);
	case ForwardingLog::S2COpenFailed:
		return new ForwardingLog_S2COpenFailed((FlowSshC_ForwardingLog_S2COpenFailed const*) x);
	case ForwardingLog::S2CClosed:
		return new ForwardingLog_S2CClosed((FlowSshC_ForwardingLog_S2CClosed const*) x);
			
	case ForwardingLog::ServerSideC2SAdded:
	case ForwardingLog::ServerSideC2SCanceled:
		return new ForwardingLog_ServerSideC2S((FlowSshC_ForwardingLog_ServerSideC2S const*) x);
	case ForwardingLog::ServerSideC2SAddFailed:
		return new ForwardingLog_ServerSideC2SAddFailed((FlowSshC_ForwardingLog_ServerSideC2SAddFailed const*) x);

	case ForwardingLog::ProxyAccepted:
		return new ForwardingLog_Proxy((FlowSshC_ForwardingLog_Proxy const*) x);
	case ForwardingLog::ProxyDecodeFailed:
		return new ForwardingLog_ProxyDecodeFailed((FlowSshC_ForwardingLog_ProxyDecodeFailed const*) x);
	case ForwardingLog::ProxyConnectStarted:
		return new ForwardingLog_ProxyStarted((FlowSshC_ForwardingLog_ProxyStarted const*) x);
	case ForwardingLog::ProxyBindFailed:
		return new ForwardingLog_ProxyBindFailed((FlowSshC_ForwardingLog_ProxyBindFailed const*) x);
	case ForwardingLog::ProxyBindStarted:
		return new ForwardingLog_ProxyBindStarted((FlowSshC_ForwardingLog_ProxyBindStarted const*) x);
	case ForwardingLog::ProxyBindAborted:
		return new ForwardingLog_ProxyBindAborted((FlowSshC_ForwardingLog_ProxyBindAborted const*) x);
	}
}

ForwardingLog::ForwardingLog(FlowSshC_ForwardingLog const* x)
	: m_event(x->m_event)
	, m_desc(x->m_desc) 
{
}

ForwardingLog_X2Y::ForwardingLog_X2Y(FlowSshC_ForwardingLog_C2S const* x)
	: ForwardingLog(&x->m_base)
	, m_type(x->m_type)
	, m_srvSideRuleDesc(x->m_srvSideRuleDesc)
	, m_listInterface(x->m_listInterface)
	, m_listPort(x->m_listPort)
	, m_origAddress(x->m_origAddress)
	, m_origPort(x->m_origPort)
{
	if (m_type == ServerSide)
		m_destPort = 0;
	else
	{
		m_destAddress = x->m_destAddress;
		m_destPort = x->m_destPort;
	}
}

ForwardingLog_X2Y::ForwardingLog_X2Y(FlowSshC_ForwardingLog_S2C const* x)
	: ForwardingLog(&x->m_base)
	, m_type(x->m_type)
	, m_srvSideRuleDesc(x->m_srvSideRuleDesc)
	, m_origAddress(x->m_origAddress)
	, m_origPort(x->m_origPort)
	, m_destAddress(x->m_destAddress)
	, m_destPort(x->m_destPort) 
{
	if (m_type == ServerSide)
		m_listPort = 0;
	else
	{
		m_listInterface = x->m_listInterface;
		m_listPort = x->m_listPort;
	}
}

ForwardingLog_X2YOpenFailed::ForwardingLog_X2YOpenFailed(FlowSshC_ForwardingLog_C2SOpenFailed const* x)
	: ForwardingLog_X2Y(&x->m_base)
{
	if (x->m_auxInfo)
		m_auxInfo = x->m_auxInfo;
}

ForwardingLog_X2YOpenFailed::ForwardingLog_X2YOpenFailed(FlowSshC_ForwardingLog_S2COpenFailed const* x)
	: ForwardingLog_X2Y(&x->m_base)
{
	if (x->m_auxInfo)
		m_auxInfo = x->m_auxInfo;
}

ForwardingLog_X2YClosed::ForwardingLog_X2YClosed(FlowSshC_ForwardingLog_C2SClosed const* x)
	: ForwardingLog_X2Y(&x->m_base)
	, m_bytesSent(x->m_bytesSent)
	, m_bytesReceived(x->m_bytesReceived)
{
}

ForwardingLog_X2YClosed::ForwardingLog_X2YClosed(FlowSshC_ForwardingLog_S2CClosed const* x)
	: ForwardingLog_X2Y(&x->m_base)
	, m_bytesSent(x->m_bytesSent)
	, m_bytesReceived(x->m_bytesReceived)
{
}

ForwardingLog_ServerSideC2S::ForwardingLog_ServerSideC2S(FlowSshC_ForwardingLog_ServerSideC2S const* x)
	: ForwardingLog(&x->m_base)
	, m_listInterface(x->m_listInterface)
	, m_listPort(x->m_listPort)
	, m_ruleDesc(x->m_ruleDesc)
{
}

ForwardingLog_ServerSideC2SAddFailed::ForwardingLog_ServerSideC2SAddFailed(FlowSshC_ForwardingLog_ServerSideC2SAddFailed const* x)
	: ForwardingLog_ServerSideC2S(&x->m_base)
	, m_errCode(x->m_errCode)
{
	if (x->m_auxInfo)
		m_auxInfo = x->m_auxInfo;
}

ForwardingLog_Proxy::ForwardingLog_Proxy(FlowSshC_ForwardingLog_Proxy const* x)
	: ForwardingLog(&x->m_base)
	, m_proxyListInterface(x->m_proxyListInterface)
	, m_proxyListPort(x->m_proxyListPort)
	, m_proxyOrigAddress(x->m_proxyOrigAddress)
	, m_proxyOrigPort(x->m_proxyOrigPort)
{
}

ForwardingLog_ProxyDecodeFailed::ForwardingLog_ProxyDecodeFailed(FlowSshC_ForwardingLog_ProxyDecodeFailed const* x)
	: ForwardingLog_Proxy(&x->m_base)
	, m_errCode(x->m_errCode)
{
	if (x->m_auxInfo)
		m_auxInfo = x->m_auxInfo;
}

ForwardingLog_ProxyStarted::ForwardingLog_ProxyStarted(FlowSshC_ForwardingLog_ProxyStarted const* x)
	: ForwardingLog_Proxy(&x->m_base)
	, m_proxyType(x->m_proxyType)
	, m_proxyReqAddress(x->m_proxyReqAddress)
	, m_proxyReqPort(x->m_proxyReqPort)
{
}

ForwardingLog_ProxyBindFailed::ForwardingLog_ProxyBindFailed(FlowSshC_ForwardingLog_ProxyBindFailed const* x)
	: ForwardingLog_ProxyStarted(&x->m_base)
	, m_errCode(x->m_errCode)
{
	if (x->m_auxInfo)
		m_auxInfo = x->m_auxInfo;
}

ForwardingLog_ProxyBindStarted::ForwardingLog_ProxyBindStarted(FlowSshC_ForwardingLog_ProxyBindStarted const* x)
	: ForwardingLog_ProxyStarted(&x->m_base)
	, m_bindPublicAddress(x->m_bindPublicAddress)
	, m_bindPublicPort(x->m_bindPublicPort)
	, m_bindListInterface(x->m_bindListInterface)
	, m_bindListPort(x->m_bindListPort)
{
}

ForwardingLog_ProxyBindAborted::ForwardingLog_ProxyBindAborted(FlowSshC_ForwardingLog_ProxyBindAborted const* x)
	: ForwardingLog_ProxyBindStarted(&x->m_base)
	, m_abrtCode(x->m_abrtCode)
{
	if (x->m_auxInfo)
		m_auxInfo = x->m_auxInfo;
}



// Client

Client::Client()
{
	// Never returns a null pointer.
	m_flowSshC = FlowSshC_Client_Create();

	try
	{	
		FlowSshC_Client_SetVersionHandler(m_flowSshC, &Client::OnSshVersion, this);
		FlowSshC_Client_SetHostKeyHandler(m_flowSshC, &Client::OnHostKey, this);
		FlowSshC_Client_SetKexDoneHandler(m_flowSshC, &Client::OnKexDone, this);
		FlowSshC_Client_SetUserAuthHandler(m_flowSshC, &Client::OnUserAuth, this);
		FlowSshC_Client_SetBannerHandler(m_flowSshC, &Client::OnBanner, this);
		FlowSshC_Client_SetSessionIdReplyHandler(m_flowSshC, &Client::OnSessionIdReply, this);
		FlowSshC_Client_SetServerNoticeHandler(m_flowSshC, &Client::OnServerNotice, this);
		FlowSshC_Client_SetHostKeySyncHandler(m_flowSshC, &Client::OnHostKeySync, this);
		FlowSshC_Client_SetForwardingLogHandler(m_flowSshC, &Client::OnForwardingLog, this);
		FlowSshC_Client_SetDisconnectHandler(m_flowSshC, &Client::OnDisconnect, this);
	}
	catch (...)
	{
		FlowSshC_Client_Release(m_flowSshC);
		m_flowSshC = nullptr;
		throw;
	}	
}

void Client::OnDestroy() const
{
	// Prevent additional handler calls (including those
	// on derived classes) before destroying the object.

	try { FlowSshC_Client_SetVersionHandler(m_flowSshC, 0, 0); } catch (...) {}
	try { FlowSshC_Client_SetHostKeyHandler(m_flowSshC, 0, 0); } catch (...) {}
	try { FlowSshC_Client_SetKexDoneHandler(m_flowSshC, 0, 0); } catch (...) {}
	try { FlowSshC_Client_SetUserAuthHandler(m_flowSshC, 0, 0); } catch (...) {}
	try { FlowSshC_Client_SetBannerHandler(m_flowSshC, 0, 0); } catch (...) {}
	try { FlowSshC_Client_SetSessionIdReplyHandler(m_flowSshC, 0, 0); } catch (...) {}
	try { FlowSshC_Client_SetServerNoticeHandler(m_flowSshC, 0, 0); } catch (...) {}
	try { FlowSshC_Client_SetHostKeySyncHandler(m_flowSshC, 0, 0); } catch (...) {}
	try { FlowSshC_Client_SetForwardingLogHandler(m_flowSshC, 0, 0); } catch (...) {}
	try { FlowSshC_Client_SetDisconnectHandler(m_flowSshC, 0, 0); } catch (...) {}

	try { FlowSshC_Client_Release(m_flowSshC); } catch (...) {}
	m_flowSshC = nullptr;

	RefCountable::OnDestroy();
}

void Client::SetAppName(wchar_t const* appNameAndVersion)
{
	FlowSshC_Client_SetAppName(m_flowSshC, appNameAndVersion);
}

void Client::SetDebugFile(wchar_t const* debugFile, unsigned int debugEventMask)
{
	FlowSshC_Client_SetDebugFile(m_flowSshC, debugFile, debugEventMask);
}

void Client::SetProxyType(unsigned int type)
{ 
	FlowSshC_Client_SetProxyType(m_flowSshC, type); 
}

void Client::SetProxyHost(wchar_t const* host)
{
	FlowSshC_Client_SetProxyHost(m_flowSshC, host);
}

void Client::SetProxyPort(unsigned int port)
{
	FlowSshC_Client_SetProxyPort(m_flowSshC, port);
}

void Client::SetProxyUserName(wchar_t const* userName)
{
	FlowSshC_Client_SetProxyUserName(m_flowSshC, userName);
}

void Client::SetProxyPassword(wchar_t const* password)
{
	FlowSshC_Client_SetProxyPassword(m_flowSshC, password);
}

void Client::SetProxyOptions(bool resolveLocally)
{
	FlowSshC_Client_SetProxyOptions(m_flowSshC, resolveLocally);
}

void Client::SetHost(wchar_t const* host)
{
	FlowSshC_Client_SetHost(m_flowSshC, host);
}

void Client::SetObfuscation(bool enable, wchar_t const* keyword)
{
	FlowSshC_Client_SetObfuscation(m_flowSshC, enable, keyword);
}

void Client::SetUserName(wchar_t const* userName)
{
	FlowSshC_Client_SetUserName(m_flowSshC, userName);
}

void Client::SetPassword(wchar_t const* password)
{
	FlowSshC_Client_SetPassword(m_flowSshC, password);
}

void Client::SetKeypair(RefPtrConst<Keypair> const& keypair)
{
	if (!keypair.Get()) throw ArgumentNullRefPtrException();
	FlowSshC_Client_SetKeypair(m_flowSshC, keypair->GetFlowSshC());
}

void Client::SetSessionId(wchar_t const* clientSessionId, unsigned int timeoutMs)
{
	FlowSshC_Client_SetSessionId(m_flowSshC, clientSessionId, timeoutMs);
}

void Client::SetKeyExchangeAlgs(KeyExchangeAlgs const& algs)
{
	FlowSshC_Client_SetKeyExchangeAlgs(m_flowSshC, &algs);
}

void Client::SetHostKeyAlgs(HostKeyAlgs const& algs)
{
	FlowSshC_Client_SetHostKeyAlgs(m_flowSshC, &algs);
}

void Client::SetEncryptionAlgs(EncryptionAlgs const& algs)
{
	FlowSshC_Client_SetEncryptionAlgs(m_flowSshC, &algs);
}

void Client::SetMacAlgs(MacAlgs const& algs)
{
	FlowSshC_Client_SetMacAlgs(m_flowSshC, &algs);
}

void Client::SetCompressionAlgs(CompressionAlgs const& algs)
{
	FlowSshC_Client_SetCompressionAlgs(m_flowSshC, &algs);
}

void Client::SetOptions(Options const& opts)
{
	FlowSshC_Client_SetOptions(m_flowSshC, &opts);
	FlowSshC_Client_SetOptions2(m_flowSshC, &opts);
	FlowSshC_Client_SetRequireStrictKex(m_flowSshC, opts.m_requireStrictKex);
}

void Client::SetSocketProvider(unsigned int socketProvider)
{
	FlowSshC_Client_SetSocketProvider(m_flowSshC, socketProvider);
}

void Client::SetPreferredIPVersion(unsigned int version)
{
	FlowSshC_Client_SetPreferredIPVersion(m_flowSshC, version);
}

void Client::SetConnectInterface(wchar_t const* interfaceList)
{
	FlowSshC_Client_SetConnectInterface(m_flowSshC, interfaceList);
}

void Client::Connect(RefPtr<ProgressHandler> const& progress)
{
	FLOWSSHCPP_REQUEST(ProgressHandler, progress, this,
	FlowSshC_Client_Connect(m_flowSshC, &ProgressHandler::FlowSshC, progress.Get()));
}

void Client::Disconnect(RefPtr<ProgressHandler> const& progress)
{
	FLOWSSHCPP_REQUEST(ProgressHandler, progress, this,
	FlowSshC_Client_Disconnect(m_flowSshC, &ProgressHandler::FlowSshC, progress.Get()));
}

void Client::AddForwarding(ForwardingRule const& rule, RefPtr<ForwardingHandler> const& response)
{
	FlowSshC_ForwardingRule flowSshC = { rule.m_clientToServer, rule.m_listInterface.c_str(), rule.m_listPort, rule.m_destHost.c_str(), rule.m_destPort };

	FLOWSSHCPP_REQUEST(ForwardingHandler, response, this,
	FlowSshC_Client_AddForwarding(m_flowSshC, &flowSshC, &ForwardingHandler::FlowSshC, response.Get()));
}

void Client::AddForwarding(ForwardingRuleExt const& rule, RefPtr<ForwardingHandler> const& response)
{
	FlowSshC_ForwardingRuleExt flowSshC = { { rule.m_clientToServer, rule.m_listInterface.c_str(), rule.m_listPort, rule.m_destHost.c_str(), rule.m_destPort }, rule.m_connectInterface.c_str() };
	
	FLOWSSHCPP_REQUEST(ForwardingHandler, response, this,
	FlowSshC_Client_AddForwardingExt(m_flowSshC, &flowSshC, &ForwardingHandler::FlowSshC, response.Get()));
}

void Client::CancelForwarding(ForwardingRuleRef const& ruleRef, RefPtr<ForwardingHandler> const& response)
{
	FlowSshC_ForwardingRule flowSshC = { ruleRef.m_clientToServer, ruleRef.m_listInterface.c_str(), ruleRef.m_listPort, 0, 0 };

	FLOWSSHCPP_REQUEST(ForwardingHandler, response, this,
	FlowSshC_Client_CancelForwarding(m_flowSshC, &flowSshC, &ForwardingHandler::FlowSshC, response.Get()));
}

void Client::InviteForwardings(bool clientToServer, RefPtr<ForwardingHandler> const& response)
{
	FLOWSSHCPP_REQUEST(ForwardingHandler, response, this,
	FlowSshC_Client_InviteForwardings(m_flowSshC, clientToServer, &ForwardingHandler::FlowSshC, response.Get()));
}

void Client::EnableProxyForwarding(ProxyForwarding const& settings, RefPtr<ForwardingHandler> const& response)
{
	FlowSshC_ProxyForwarding flowSshC = { settings.m_listInterface.c_str(), settings.m_listPort, nullptr, nullptr, nullptr, nullptr };
	if (!settings.m_bindPublicAddressIP4.empty())
		flowSshC.m_bindPublicAddressIP4 = settings.m_bindPublicAddressIP4.c_str();
	if (!settings.m_bindPublicAddressIP6.empty())
		flowSshC.m_bindPublicAddressIP6 = settings.m_bindPublicAddressIP6.c_str();
	if (!settings.m_bindListInterfaceIP4.empty())
		flowSshC.m_bindListInterfaceIP4 = settings.m_bindListInterfaceIP4.c_str();
	if (!settings.m_bindListInterfaceIP6.empty())
		flowSshC.m_bindListInterfaceIP6 = settings.m_bindListInterfaceIP6.c_str();
	
	FLOWSSHCPP_REQUEST(ForwardingHandler, response, this,
	FlowSshC_Client_EnableProxyForwarding(m_flowSshC, &flowSshC, &ForwardingHandler::FlowSshC, response.Get()));
}

void Client::DisableProxyForwarding(RefPtr<ForwardingHandler> const& response)
{
	FLOWSSHCPP_REQUEST(ForwardingHandler, response, this,
	FlowSshC_Client_DisableProxyForwarding(m_flowSshC, &ForwardingHandler::FlowSshC, response.Get()));
}


Client::FurtherAuth::FurtherAuth(FlowSshC_FurtherAuth* flowSshC)
{
	m_flowSshC = flowSshC;
}

void Client::FurtherAuth::SetUserName(wchar_t const* userName)
{
	FlowSshC_FurtherAuth_SetUserName(m_flowSshC, userName);
}

void Client::FurtherAuth::SetPassword(wchar_t const* password)
{
	FlowSshC_FurtherAuth_SetPassword(m_flowSshC, password);
}

void Client::FurtherAuth::SetKeypair(RefPtrConst<Keypair> const& keypair)
{
	if (!keypair.Get()) throw ArgumentNullRefPtrException();
	FlowSshC_FurtherAuth_SetKeypair(m_flowSshC, keypair->GetFlowSshC());
}

bool Client::FurtherAuth::HavePartialSuccess() const
{
	return FlowSshC_FurtherAuth_HavePartialSuccess(m_flowSshC);
}

bool Client::FurtherAuth::IsPasswordRemaining() const
{
	return FlowSshC_FurtherAuth_IsPasswordRemaining(m_flowSshC);
}

bool Client::FurtherAuth::IsPublicKeyRemaining() const
{
	return FlowSshC_FurtherAuth_IsPublicKeyRemaining(m_flowSshC);
}


Client::PasswordChange::PasswordChange(FlowSshC_PasswordChange* flowSshC)
{
	m_flowSshC = flowSshC;
}

std::wstring Client::PasswordChange::GetPrompt() const
{	
	return Bstr2Ws(FlowSshC_PasswordChange_GetPrompt(m_flowSshC));
}

void Client::PasswordChange::SetNewPassword(wchar_t const* password)
{ 
	FlowSshC_PasswordChange_SetNewPassword(m_flowSshC, password);
}


void Client::OnSshVersion(void* thisPtr, wchar_t const* version)
{
	Client* pClient = static_cast<Client*>(thisPtr);
	if (!pClient || !version)
		return;
	
	try { pClient->OnSshVersion(std::wstring(version)); }
	catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"Client::OnSshVersion"); }
}

bool Client::OnHostKey(void* thisPtr, FlowSshC_PublicKey* publicKey)
{
	Client* pClient = static_cast<Client*>(thisPtr);
	if (!pClient || !publicKey)
		return false;

	try
	{
		RefPtr<PublicKey> oPublicKey(new PublicKey(publicKey));
		return pClient->OnHostKey(oPublicKey);
	}
	catch (std::exception const& e){ FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"Client::OnHostKey"); }

	return false;
}

void Client::OnKexDone(void* thisPtr, unsigned __int64 kexNr, bool incoming, wchar_t const* kexAlg, wchar_t const* encAlg, wchar_t const* macAlg, wchar_t const* cmprAlg)
{
	Client* pClient = static_cast<Client*>(thisPtr);
	if (!pClient || !encAlg || !macAlg || !cmprAlg)
		return;
	
	try { pClient->OnKexDone(kexNr, incoming, std::wstring(kexAlg), std::wstring(encAlg), std::wstring(macAlg), std::wstring(cmprAlg)); }
	catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"Client::OnKexDone"); }
}

bool Client::OnUserAuth(void* thisPtr, FlowSshC_FurtherAuth* furtherAuth, FlowSshC_PasswordChange* passwordChange)
{	
	Client* pClient = static_cast<Client*>(thisPtr);
	if (!pClient || (!furtherAuth && !passwordChange))
		return false;

	// FurtherAuth and PasswordChange are always exclusive - one will be NULL.
	if (furtherAuth)
	{
		FurtherAuth oAuth(furtherAuth);

		try { return pClient->OnFurtherAuth(oAuth); }
		catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"Client::OnUserAuth"); }
	}
	else if (passwordChange)
	{
		PasswordChange oPwdChange(passwordChange);

		try { return pClient->OnPasswordChange(oPwdChange); }
		catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"Client::OnPasswordChange"); }
	}

	return false;
}

void Client::OnBanner(void* thisPtr, wchar_t const* banner)
{
	Client* pClient = static_cast<Client*>(thisPtr);
	if (!pClient || !banner)
		return;
	
	try { pClient->OnBanner(std::wstring(SafeStr(banner))); }
	catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"Client::OnBanner"); }
}

void Client::OnSessionIdReply(void* thisPtr, unsigned int code)
{
	Client* pClient = static_cast<Client*>(thisPtr);
	if (!pClient)
		return;
	
	try { pClient->OnSessionIdReply(code); }
	catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"Client::OnSessionIdReply"); }
}

void Client::OnServerNotice(void* thisPtr, unsigned int severity, wchar_t const* facility, wchar_t const* status, wchar_t const* description)
{
	Client* pClient = static_cast<Client*>(thisPtr);
	if (!pClient)
		return;

	try { pClient->OnServerNotice(severity, std::wstring(facility), std::wstring(status), std::wstring(description)); }
	catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"Client::OnServerNotice"); }
}

bool Client::OnHostKeySync(void* thisPtr, FlowSshC_PublicKey* publicKey, bool keyVerified)
{
	Client* pClient = static_cast<Client*>(thisPtr);
	if (!pClient || !publicKey)
		return false;

	try
	{
		RefPtr<PublicKey> oPublicKey(new PublicKey(publicKey));
		return pClient->OnHostKeySync(oPublicKey, keyVerified);
	}
	catch (std::exception const& e){ FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"Client::OnHostKeySync"); }

	return false;
}

void Client::OnForwardingLog(void* thisPtr, FlowSshC_ForwardingLog const* log)
{
	Client* pClient = static_cast<Client*>(thisPtr);
	if (!pClient || !log)
		return;

	RefPtrConst<ForwardingLog> logCpp = ForwardingLog::Create(log);
	if (logCpp.Get())
	{
		try { pClient->OnForwardingLog(logCpp); }
		catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"Client::OnForwardingLog"); }
	}
}

void Client::OnDisconnect(void* thisPtr, unsigned int reason, wchar_t const* desc)
{
	Client* pClient = static_cast<Client*>(thisPtr);
	if (!pClient)
		return;
	
	try { pClient->OnDisconnect(reason, std::wstring(SafeStr(desc))); }
	catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"Client::OnDisconnect"); }
}



// ReceiveHandler

ReceiveHandler::ReceiveHandler()
{
	m_bSuccess	= false;
	m_bStdErr	= false;
	m_bEof		= false;
}

bool ReceiveHandler::Success() const
{
	CsLocker locker(m_cs);
	return m_bSuccess;
}

bool ReceiveHandler::StdErr() const
{
	CsLocker locker(m_cs);
	return m_bStdErr;
}

bool ReceiveHandler::Eof() const
{
	CsLocker locker(m_cs);
	return m_bEof;
}

unsigned char const* ReceiveHandler::GetDataPtr() const
{
	CsLocker locker(m_cs);
	if (IsDone()) return m_data.GetPtr();
	else return NULL;
}

unsigned int ReceiveHandler::GetDataSize() const
{
	CsLocker locker(m_cs);
	if (IsDone()) return m_data.GetSize();
	else return 0;
}

void ReceiveHandler::OnStart()
{
	CsLocker locker(m_cs);
	m_bSuccess	= false;
	m_bStdErr	= false;
	m_bEof		= false;
	m_data.Resize(0);
}

void ReceiveHandler::FlowSshC(void* thisPtr, bool stdErr, unsigned char const* dataPtr, unsigned int dataSize, bool eof)
{
	ReceiveHandler* pHandler = static_cast<ReceiveHandler*>(thisPtr);
	if (!pHandler)
		return;

	AutoReleaseRef releaseRef(pHandler);
	CsLocker locker(pHandler->m_cs);

	// Associated channel is or gets closed?
	pHandler->m_bSuccess = !(stdErr == false && dataPtr == NULL && dataSize == 0 && eof == false);	

	if (pHandler->m_bSuccess)
	{
		EnsureAbort(dataSize < INT_MAX);
		EnsureAbort(!dataSize || dataPtr);
		pHandler->m_bStdErr	= stdErr;
		pHandler->m_data	= Data(dataPtr, dataSize);
		pHandler->m_bEof	= eof;

		locker.Unlock();
		try { pHandler->OnReceive(stdErr, dataPtr, dataSize, eof); }
		catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"ReceiveHandler::OnReceive"); }
		locker.Lock();
	}
	else
	{	// channel closed
		locker.Unlock();
		try { pHandler->OnError(); }
		catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"ReceiveHandler::OnError"); }
		locker.Lock();
	}

	locker.Unlock();
	try { pHandler->Base_OnDone(); }
	catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"ReceiveHandler::OnDone"); }
	locker.Lock();
}



// ClientSessionChannel

ClientSessionChannel::ClientSessionChannel(RefPtrConst<Client> const& client)
{
	if (!client.Get()) throw ArgumentNullRefPtrException();

	// Never returns a null pointer.
	m_flowSshC = FlowSshC_ClientSessionChannel_Create(client->GetFlowSshC());

	try
	{
		FlowSshC_ClientSessionChannel_SetExitHandler(m_flowSshC, &ClientSessionChannel::OnExit, this);
		FlowSshC_ClientSessionChannel_SetCloseHandler(m_flowSshC, &ClientSessionChannel::OnChannelClose, this);
	}
	catch (...)
	{
		FlowSshC_ClientSessionChannel_Release(m_flowSshC);
		m_flowSshC = nullptr;
		throw;
	}

	// Channel keeps the associated client alive.
	m_client = client;
}

void ClientSessionChannel::OnDestroy() const
{
	// Prevent additional handler calls (including those
	// on derived classes) before destroying the object.

	try { FlowSshC_ClientSessionChannel_SetExitHandler(m_flowSshC, 0, 0); } catch (...) {}
	try { FlowSshC_ClientSessionChannel_SetCloseHandler(m_flowSshC, 0, 0); } catch (...) {}
	
	try { FlowSshC_ClientSessionChannel_Release(m_flowSshC); } catch (...) {}
	m_flowSshC = nullptr;

	RefCountable::OnDestroy();
}

void ClientSessionChannel::OpenRequest(RefPtr<ProgressHandler> const& progress)							
{
	FLOWSSHCPP_REQUEST(ProgressHandler, progress, this,
	FlowSshC_ClientSessionChannel_OpenRequest(m_flowSshC, &ProgressHandler::FlowSshC, progress.Get()));
}

void ClientSessionChannel::PtyRequest(wchar_t const* term, unsigned int widthCols, unsigned int heightRows, RefPtr<ProgressHandler> const& progress) 
{
	FLOWSSHCPP_REQUEST(ProgressHandler, progress, this,
	FlowSshC_ClientSessionChannel_PtyRequest(m_flowSshC, term, widthCols, heightRows, &ProgressHandler::FlowSshC, progress.Get()));
}

void ClientSessionChannel::ExecRequest(wchar_t const* command, RefPtr<ProgressHandler> const& progress)
{
	FLOWSSHCPP_REQUEST(ProgressHandler, progress, this,
	FlowSshC_ClientSessionChannel_ExecRequest(m_flowSshC, command, &ProgressHandler::FlowSshC, progress.Get()));
}

void ClientSessionChannel::ShellRequest(RefPtr<ProgressHandler> const& progress)
{
	FLOWSSHCPP_REQUEST(ProgressHandler, progress, this,
	FlowSshC_ClientSessionChannel_ShellRequest(m_flowSshC, &ProgressHandler::FlowSshC, progress.Get()));
}

void ClientSessionChannel::Receive(unsigned int maxBytes, RefPtr<ReceiveHandler> const& receive)				
{
	FLOWSSHCPP_REQUEST(ReceiveHandler, receive, this,
	FlowSshC_ClientSessionChannel_Receive(m_flowSshC, maxBytes, &ReceiveHandler::FlowSshC, receive.Get()));
}

void ClientSessionChannel::Send(Data const& data, bool eof, RefPtr<ProgressHandler> const& progress)	
{
	FLOWSSHCPP_REQUEST(ProgressHandler, progress, this,
	FlowSshC_ClientSessionChannel_Send(m_flowSshC, data.GetPtr(), data.GetSize(), eof, &ProgressHandler::FlowSshC, progress.Get()));
}

void ClientSessionChannel::Signal(wchar_t const* signalName, RefPtr<ProgressHandler> const& progress)
{
	FLOWSSHCPP_REQUEST(ProgressHandler, progress, this,
	FlowSshC_ClientSessionChannel_Signal(m_flowSshC, signalName, &ProgressHandler::FlowSshC, progress.Get()));
}

void ClientSessionChannel::Close(RefPtr<ProgressHandler> const& progress)										
{
	FLOWSSHCPP_REQUEST(ProgressHandler, progress, this,
	FlowSshC_ClientSessionChannel_Close(m_flowSshC, &ProgressHandler::FlowSshC, progress.Get()));
}


void ClientSessionChannel::OnExit(void* thisPtr, FlowSshC_ExitStatus const* status, FlowSshC_ExitSignal const* signal)
{
	ClientSessionChannel* pChannel = static_cast<ClientSessionChannel*>(thisPtr);
	if (!pChannel || (!status && !signal))
		return;
	
	// ExitStatus and ExitSignal are always exclusive - one will be NULL.
	if (status)
		try { pChannel->OnExitStatus(*status); }
		catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"ClientSessionChannel::OnExitStatus"); }
	else if (signal)
	{
		ExitSignal oSignal = {signal->m_name, signal->m_coreDumped, signal->m_errMsg};

		try { pChannel->OnExitSignal(oSignal); }
		catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"ClientSessionChannel::OnExitSignal"); }
	}
}

void ClientSessionChannel::OnChannelClose(void* thisPtr)
{
	ClientSessionChannel* pChannel = static_cast<ClientSessionChannel*>(thisPtr);
	if (!pChannel)
		return;

	try { pChannel->OnChannelClose(); }
	catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"ClientSessionChannel::OnChannelClose"); }
}



// FileAttrs

void FileAttrs::Reset()
{
	m_validAttrFlags = 0;
	m_type	= 0;
	m_size = 0;
	m_uid = 0;
	m_gid = 0;
	m_permissions = 0;
	m_accessTime = 0;
	m_accessTimeNs = 0;
	m_createTime = 0;
	m_createTimeNs = 0;
	m_modifyTime = 0;
	m_modifyTimeNs = 0;
	m_owner.clear();
	m_group.clear();
	m_allocSize = 0;
	m_textHint = 0;
}

FileAttrs& FileAttrs::operator= (FlowSshC_FileAttrs const* x)
{
	if (!x)
		Reset();
	else
	{
		m_validAttrFlags	= x->m_validAttrFlags;
		m_type				= x->m_type;
		m_size				= x->m_size;
		m_uid				= x->m_uid;
		m_gid				= x->m_gid;
		m_permissions		= x->m_permissions;
		m_accessTime		= x->m_accessTime;
		m_accessTimeNs		= x->m_accessTimeNs;
		m_createTime		= x->m_createTime;
		m_createTimeNs		= x->m_createTimeNs;
		m_modifyTime		= x->m_modifyTime;
		m_modifyTimeNs		= x->m_modifyTimeNs;
		m_owner				= SafeStr(x->m_owner);
		m_group				= SafeStr(x->m_group);
		m_allocSize			= x->m_allocSize;
		m_textHint			= x->m_textHint;
	}
	return *this;
}

FlowSshC_FileAttrs const FileAttrs_2_FlowSshC_FileAttrs(FileAttrs const& x)
{
	FlowSshC_FileAttrs to;
	::ZeroMemory(&to, sizeof(FlowSshC_FileAttrs));

	to.m_validAttrFlags	= x.m_validAttrFlags;
	to.m_type			= x.m_type;
	to.m_size			= x.m_size;
	to.m_uid			= x.m_uid;
	to.m_gid			= x.m_gid;
	to.m_permissions	= x.m_permissions;
	to.m_accessTime		= x.m_accessTime;
	to.m_accessTimeNs	= x.m_accessTimeNs;
	to.m_createTime		= x.m_createTime;
	to.m_createTimeNs	= x.m_createTimeNs;
	to.m_modifyTime		= x.m_modifyTime;
	to.m_modifyTimeNs	= x.m_modifyTimeNs;
	to.m_owner			= x.m_owner.c_str();
	to.m_group			= x.m_group.c_str();
	to.m_allocSize		= x.m_allocSize;
	to.m_textHint		= x.m_textHint;

	return to;
}


// FileInfo

FileInfo::FileInfo(FlowSshC_FileInfo const* x)
{
	*this = x;
}

FileInfo& FileInfo::operator= (FlowSshC_FileInfo const* x)
{
	if (!x)
	{	// Reset content
		m_name.clear();
		m_attrs.Reset();
	}
	else
	{
		m_name  = SafeStr(x->m_name);
		m_attrs = &(x->m_attrs);
	}
	return *this;
}


// SftpErr

void SftpErr::Reset()
{
	m_errCode = 0;
	m_errMsg.clear();
}

SftpErr& SftpErr::operator= (FlowSshC_SftpErr const* x)
{
	if (!x)
		Reset();
	else
	{
		m_errCode = x->m_errCode;
		m_errMsg  = SafeStr(x->m_errMsg);
	}
	return *this;
}

std::wstring SftpErr::Describe() const
{
	std::wstring msg;
	if (!!m_errMsg.length())
		msg = L", message: " + m_errMsg;

	return L"SFTP error code: " + DescribeSftpErrCode(m_errCode) + msg;
}


// ListErr

void ListErr::Reset()
{
	m_listOp = 0;
	m_errCode = 0;
	m_errMsg.clear();
}

ListErr& ListErr::operator= (FlowSshC_ListErr const* x)
{
	if (!x)
		Reset();
	else
	{
		m_listOp  = x->m_listOp;
		m_errCode = x->m_errCode;
		m_errMsg  = SafeStr(x->m_errMsg);
	}
	return *this;
}

std::wstring ListErr::Describe() const
{
	std::wstring code;
	if (m_listOp > 99)
		code = L", SFTP error code: " + DescribeSftpErrCode(m_errCode);

	std::wstring msg;
	if (!!m_errMsg.length())
		msg = L", message: " + m_errMsg;

	return L"List error, type: " + DescribeListOp(m_listOp) + code + msg;
}


// TransferErr

void TransferErr::Reset()
{
	m_transferOp = 0;
	m_errCode    = 0;
	m_errMsg.clear();
}

TransferErr& TransferErr::operator= (FlowSshC_TransferErr const* x)
{
	if (!x)
		Reset();
	else
	{
		m_transferOp = x->m_transferOp;
		m_errCode    = x->m_errCode;
		m_errMsg     = SafeStr(x->m_errMsg);
	}
	return *this;
}

std::wstring TransferErr::Describe() const
{
	std::wstring code;
	if (m_transferOp <= 99)		  code = L"";
	else if (m_transferOp <= 199) code = L", SFTP error code: " + DescribeSftpErrCode(m_errCode);
	else if (m_transferOp <= 299) code = L", Windows error code: " + std::to_wstring((unsigned __int64)m_errCode);
	else if (m_transferOp <= 399) code = L", resume error code: " + DescribeResumeErrCode(m_errCode);

	std::wstring msg;
	if (!!m_errMsg.length())
		msg = L", message: " + m_errMsg;

	return L"Transfer error, type: " + DescribeTransferOp(m_transferOp) + code + msg;
}


// RealPathHandler

RealPathHandler::RealPathHandler()
{
	m_bSuccess	= false;
}

bool RealPathHandler::Success() const
{
	CsLocker locker(m_cs);
	return m_bSuccess;
}

std::wstring RealPathHandler::GetRealPath() const
{
	CsLocker locker(m_cs);
	return m_sRealPath;
}

SftpErr RealPathHandler::GetError() const
{
	CsLocker locker(m_cs);
	return m_error;
}

void RealPathHandler::OnStart()
{
	CsLocker locker(m_cs);
	m_bSuccess	= false;
	m_sRealPath.clear();
	m_error.Reset();
}

void RealPathHandler::FlowSshC(void* thisPtr, wchar_t const* realPath, FlowSshC_SftpErr const* error)
{
	RealPathHandler* pHandler = static_cast<RealPathHandler*>(thisPtr);
	if (!pHandler)
		return;

	AutoReleaseRef releaseRef(pHandler);
	CsLocker locker(pHandler->m_cs);

	pHandler->m_bSuccess = !error;

	if (!error)
	{
		pHandler->m_sRealPath = SafeStr(realPath);

		locker.Unlock();
		try { pHandler->OnRealPath(pHandler->m_sRealPath); }
		catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"RealPathHandler::OnRealPath"); }
		locker.Lock();
	}
	else
	{
		pHandler->m_error = error;

		locker.Unlock();
		try { pHandler->OnError(pHandler->m_error);	}
		catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"RealPathHandler::OnError"); }
		locker.Lock();
	}

	locker.Unlock();
	try { pHandler->Base_OnDone(); }
	catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"RealPathHandler::OnDone"); }
	locker.Lock();
}



// StatHandler Classes

StatHandler::StatHandler()
{
	m_bSuccess = false;
}

bool StatHandler::Success() const
{
	CsLocker locker(m_cs);
	return m_bSuccess;
}

FileAttrs StatHandler::GetFileAttrs() const
{
	CsLocker locker(m_cs);
	return m_fileAttrs;
}

SftpErr StatHandler::GetError() const
{
	CsLocker locker(m_cs);
	return m_error;
}

void StatHandler::OnStart()
{
	CsLocker locker(m_cs);
	m_bSuccess	= false;
	m_fileAttrs.Reset();
	m_error.Reset();
}

void StatHandler::FlowSshC(void* thisPtr, FlowSshC_FileAttrs const* fileAttrs, FlowSshC_SftpErr const* error)
{
	StatHandler* pHandler = static_cast<StatHandler*>(thisPtr);
	if (!pHandler)
		return;

	AutoReleaseRef releaseRef(pHandler);
	CsLocker locker(pHandler->m_cs);

	pHandler->m_bSuccess = !error;

	if (!error)
	{
		EnsureAbort(fileAttrs);
		pHandler->m_fileAttrs = fileAttrs;

		locker.Unlock();
		try { pHandler->OnStat(pHandler->m_fileAttrs); }
		catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"StatHandler::OnStat"); }
		locker.Lock();
	}
	else
	{		
		pHandler->m_error = error;

		locker.Unlock();
		try { pHandler->OnError(pHandler->m_error); }
		catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"StatHandler::OnError"); }
		locker.Lock();
	}

	locker.Unlock();
	try { pHandler->Base_OnDone(); }
	catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"StatHandler::OnDone"); }
	locker.Lock();
}



// SftpHandler

SftpHandler::SftpHandler()
{
	m_bSuccess = false;
}

bool SftpHandler::Success() const
{
	CsLocker locker(m_cs);
	return m_bSuccess;
}

SftpErr SftpHandler::GetError() const
{
	CsLocker locker(m_cs);
	return m_error;
}

void SftpHandler::OnStart()
{
	CsLocker locker(m_cs);
	m_bSuccess	= false;
	m_error.Reset();
}

void SftpHandler::FlowSshC(void* thisPtr, FlowSshC_SftpErr const* error)
{
	SftpHandler* pHandler = static_cast<SftpHandler*>(thisPtr);
	if (!pHandler)
		return;

	AutoReleaseRef releaseRef(pHandler);
	CsLocker locker(pHandler->m_cs);

	pHandler->m_bSuccess = !error;

	if (!error)
	{
		locker.Unlock();
		try { pHandler->OnSuccess(); }
		catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"SftpHandler::OnSuccess"); }
		locker.Lock();
	}
	else
	{
		pHandler->m_error = error;

		locker.Unlock();
		try { pHandler->OnError(pHandler->m_error); }
		catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"SftpHandler::OnError"); }
		locker.Lock();
	}

	locker.Unlock();
	try { pHandler->Base_OnDone(); }
	catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"SftpHandler::OnDone"); }
	locker.Lock();
}



// ListHandler

std::vector<FileInfo> ListHandler::m_emptyFileInfoVector(0);

ListHandler::ListHandler()
{
	m_bSuccess = false;
}

bool ListHandler::Success() const
{
	CsLocker locker(m_cs);
	return m_bSuccess;
}

ListErr ListHandler::GetError() const
{
	CsLocker locker(m_cs);
	return m_error;
}

std::vector<FileInfo> const& ListHandler::GetFileInfos() const
{
	CsLocker locker(m_cs);
	if (IsDone()) return m_fileInfos;
	else return m_emptyFileInfoVector;
}

void ListHandler::OnStart()
{
	CsLocker locker(m_cs);
	m_bSuccess	= false;
	m_error.Reset();
	m_fileInfos.clear();
}

bool ListHandler::OnList(std::vector<FileInfo> const& fileInfos, bool /*endOfList*/)
{
	m_fileInfos.reserve(m_fileInfos.size() + fileInfos.size());
	m_fileInfos.insert(m_fileInfos.end(), fileInfos.begin(), fileInfos.end());
	return true;
}

bool ListHandler::FlowSshC(void* thisPtr, FlowSshC_FileInfo const* fileInfos, unsigned int nrFileInfos, bool endOfList, FlowSshC_ListErr const* error)
{
	ListHandler* pHandler = static_cast<ListHandler*>(thisPtr);
	if (!pHandler)
		return false;
	
	bool bRet = true;
	bool bLastEvent	= endOfList || error;

	AutoReleaseRef releaseRef;
	if (bLastEvent)
		releaseRef.Set(pHandler);

	CsLocker locker(pHandler->m_cs);

	if (bLastEvent)
		pHandler->m_bSuccess = !error;

	if (!error)
	{
		EnsureAbort(nrFileInfos < INT_MAX);
		EnsureAbort(!nrFileInfos || fileInfos);
		std::vector<FileInfo> fileInfosVec(nrFileInfos);
		for (size_t c=0; c<nrFileInfos; c++)
			fileInfosVec[c] = FileInfo(&fileInfos[c]);

		locker.Unlock();
		try { bRet = pHandler->OnList(fileInfosVec, endOfList); }
		catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"ListHandler::OnList"); }
		locker.Lock();

		if (!bRet && !bLastEvent)	// aborted?
		{
			bLastEvent = true; 
			releaseRef.Set(pHandler);
			pHandler->m_bSuccess = false;
			pHandler->m_error.m_listOp = FlowSshC_ListOp_Exception;
			pHandler->m_error.m_errCode = 0;
			pHandler->m_error.m_errMsg = L"Directory listing aborted.";
		}
	}
	else
	{
		bRet = false;
		pHandler->m_error = error;
		
		locker.Unlock();
		try { pHandler->OnError(pHandler->m_error); }
		catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"ListHandler::OnError"); }
		locker.Lock();
	}

	if (bLastEvent)
	{
		locker.Unlock();
		try { pHandler->Base_OnDone(); }
		catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"ListHandler::OnDone"); }
		locker.Lock();
	}

	return bRet;
}



// TransferHandler

TransferHandler::TransferHandler()
{
	m_bSuccess = false;
	::ZeroMemory(&m_transferStat, sizeof(m_transferStat));
}

bool TransferHandler::Success() const
{
	CsLocker locker(m_cs);
	return m_bSuccess;
}

FlowSshC_TransferStat TransferHandler::GetTransferStat() const
{
	CsLocker locker(m_cs);
	return m_transferStat;
}

TransferErr TransferHandler::GetError() const
{
	CsLocker locker(m_cs);
	return m_error;
}

void TransferHandler::OnStart()
{
	CsLocker locker(m_cs);
	m_bSuccess	= false;
	::ZeroMemory(&m_transferStat, sizeof(m_transferStat));
	m_error.Reset();
}

bool TransferHandler::FlowSshC(void* thisPtr, bool done, FlowSshC_TransferStat const* transferStat, FlowSshC_TransferErr const* error)
{
	TransferHandler* pHandler = static_cast<TransferHandler*>(thisPtr);
	if (!pHandler)
		return false;

	bool bRet = true;
	bool bLastEvent	= done || error;

	AutoReleaseRef releaseRef;
	if (bLastEvent)
		releaseRef.Set(pHandler);

	CsLocker locker(pHandler->m_cs);

	if (bLastEvent)
		pHandler->m_bSuccess = !error;
	
	if (!error)
	{
		pHandler->m_transferStat = *transferStat;

		locker.Unlock();
		try { bRet = pHandler->OnTransfer(done, pHandler->m_transferStat); }
		catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"TransferHandler::OnTransfer"); }
		locker.Lock();
		
		if (!bRet && !bLastEvent)	// aborted?
		{
			bLastEvent = true;
			releaseRef.Set(pHandler);
			pHandler->m_bSuccess = false;
			pHandler->m_error.m_transferOp = FlowSshC_TransferOp_Exception;
			pHandler->m_error.m_errCode = 0;
			pHandler->m_error.m_errMsg = L"File transfer aborted.";
		}
	}
	else
	{
		bRet = false;
		pHandler->m_error = error;

		locker.Unlock();
		try { pHandler->OnError(pHandler->m_error); }
		catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"TransferHandler::OnError"); }
		locker.Lock();
	}
	
	if (bLastEvent)
	{
		locker.Unlock();
		try { pHandler->Base_OnDone(); }
		catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"TransferHandler::OnDone"); }
		locker.Lock();
	}

	return bRet;
}



// ClientSftpChannel

ClientSftpChannel::ClientSftpChannel(RefPtrConst<Client> const& client)
{
	if (!client.Get()) throw ArgumentNullRefPtrException();

	// Never returns a null pointer.
	m_flowSshC = FlowSshC_ClientSftpChannel_Create(client->GetFlowSshC());

	try 
	{
		FlowSshC_ClientSftpChannel_SetVersionHandler(m_flowSshC, &ClientSftpChannel::OnSftpVersion, this);
		FlowSshC_ClientSftpChannel_SetCloseHandler(m_flowSshC, &ClientSftpChannel::OnChannelClose, this);
	}
	catch (...)
	{
		FlowSshC_ClientSftpChannel_Release(m_flowSshC);
		m_flowSshC = nullptr;
		throw;
	}

	// Channel keeps the associated client alive.
	m_client = client;
}

void ClientSftpChannel::OnDestroy() const
{
	// Prevent additional handler calls (including those
	// on derived classes) before destroying the object.

	try { FlowSshC_ClientSftpChannel_SetVersionHandler(m_flowSshC, 0, 0); } catch (...) {}
	try { FlowSshC_ClientSftpChannel_SetCloseHandler(m_flowSshC, 0, 0); } catch (...) {}	
	
	try { FlowSshC_ClientSftpChannel_Release(m_flowSshC); } catch (...) {}
	m_flowSshC = nullptr;

	RefCountable::OnDestroy();
}

void ClientSftpChannel::Open(RefPtr<ProgressHandler> const& progress)
{
	FLOWSSHCPP_REQUEST(ProgressHandler, progress, this,
	FlowSshC_ClientSftpChannel_Open(m_flowSshC, &ProgressHandler::FlowSshC, progress.Get()));
}

void ClientSftpChannel::Open(wchar_t const* subsystem, RefPtr<ProgressHandler> const& progress)
{
	FLOWSSHCPP_REQUEST(ProgressHandler, progress, this,
	FlowSshC_ClientSftpChannel_Open2(m_flowSshC, subsystem, &ProgressHandler::FlowSshC, progress.Get()));
}

void ClientSftpChannel::RealPath(wchar_t const* queryPath, RefPtr<RealPathHandler> const& realPath)								
{
	FLOWSSHCPP_REQUEST(RealPathHandler, realPath, this,
	FlowSshC_ClientSftpChannel_RealPath(m_flowSshC, queryPath, &RealPathHandler::FlowSshC, realPath.Get()));
}

void ClientSftpChannel::Stat(wchar_t const* path, unsigned int desiredAttrFlags, RefPtr<StatHandler> const& stat)		
{
	FLOWSSHCPP_REQUEST(StatHandler, stat, this,
	FlowSshC_ClientSftpChannel_Stat(m_flowSshC, path, desiredAttrFlags, &StatHandler::FlowSshC, stat.Get()));
}

void ClientSftpChannel::SetStat(wchar_t const* path, FileAttrs const& fileAttrs, RefPtr<SftpHandler> const& response)	
{
	FLOWSSHCPP_REQUEST(SftpHandler, response, this,
		FlowSshC_FileAttrs attrs = FileAttrs_2_FlowSshC_FileAttrs(fileAttrs); 
	    FlowSshC_ClientSftpChannel_SetStat(m_flowSshC, path, &attrs, &SftpHandler::FlowSshC, response.Get()));
}

void ClientSftpChannel::MkDir(wchar_t const* path, FileAttrs const& fileAttrs, RefPtr<SftpHandler> const& response)	
{
	FLOWSSHCPP_REQUEST(SftpHandler, response, this,
		FlowSshC_FileAttrs attrs = FileAttrs_2_FlowSshC_FileAttrs(fileAttrs);
		FlowSshC_ClientSftpChannel_MkDir(m_flowSshC, path, &attrs, &SftpHandler::FlowSshC, response.Get()));
}

void ClientSftpChannel::RmDir(wchar_t const* path, RefPtr<SftpHandler> const& response)
{
	FLOWSSHCPP_REQUEST(SftpHandler, response, this,
	FlowSshC_ClientSftpChannel_RmDir(m_flowSshC, path, &SftpHandler::FlowSshC, response.Get()));
}

void ClientSftpChannel::Remove(wchar_t const* path, RefPtr<SftpHandler> const& response)
{
	FLOWSSHCPP_REQUEST(SftpHandler, response, this,
	FlowSshC_ClientSftpChannel_Remove(m_flowSshC, path, &SftpHandler::FlowSshC, response.Get()));
}

void ClientSftpChannel::Rename(wchar_t const* oldPath, wchar_t const* newPath, RefPtr<SftpHandler> const& response)
{
	FLOWSSHCPP_REQUEST(SftpHandler, response, this,
	FlowSshC_ClientSftpChannel_Rename(m_flowSshC, oldPath, newPath, &SftpHandler::FlowSshC, response.Get()));
}

void ClientSftpChannel::List(wchar_t const* path, RefPtr<ListHandler> const& list)
{
	FLOWSSHCPP_REQUEST(ListHandler, list, this,
	FlowSshC_ClientSftpChannel_List(m_flowSshC, path, &ListHandler::FlowSshC, list.Get()));
}

void ClientSftpChannel::Upload(wchar_t const* localPath, wchar_t const* remotePath, unsigned int transferFlags, RefPtr<TransferHandler> const& transfer)	
{
	FLOWSSHCPP_REQUEST(TransferHandler, transfer, this,
	FlowSshC_ClientSftpChannel_Upload(m_flowSshC, localPath, remotePath, transferFlags, &TransferHandler::FlowSshC, transfer.Get()));
}

void ClientSftpChannel::Upload(wchar_t const* localPath, wchar_t const* remotePath, unsigned int transferFlags, unsigned int pipelineSize, RefPtr<TransferHandler> const& transfer)	
{
	FLOWSSHCPP_REQUEST(TransferHandler, transfer, this,
	FlowSshC_ClientSftpChannel_Upload2(m_flowSshC, localPath, remotePath, transferFlags, pipelineSize, &TransferHandler::FlowSshC, transfer.Get()));
}

void ClientSftpChannel::Download(wchar_t const* remotePath, wchar_t const* localPath, unsigned int transferFlags, RefPtr<TransferHandler> const& transfer)
{
	FLOWSSHCPP_REQUEST(TransferHandler, transfer, this,
	FlowSshC_ClientSftpChannel_Download(m_flowSshC, remotePath, localPath, transferFlags, &TransferHandler::FlowSshC, transfer.Get()));
}

void ClientSftpChannel::Download(wchar_t const* remotePath, wchar_t const* localPath, unsigned int transferFlags, unsigned int pipelineSize, RefPtr<TransferHandler> const& transfer)
{
	FLOWSSHCPP_REQUEST(TransferHandler, transfer, this,
	FlowSshC_ClientSftpChannel_Download2(m_flowSshC, remotePath, localPath, transferFlags, pipelineSize, &TransferHandler::FlowSshC, transfer.Get()));
}


void ClientSftpChannel::Close(RefPtr<ProgressHandler> const& progress)
{
	FLOWSSHCPP_REQUEST(ProgressHandler, progress, this,
	FlowSshC_ClientSftpChannel_Close(m_flowSshC, &ProgressHandler::FlowSshC, progress.Get()));
}


void ClientSftpChannel::OnSftpVersion(void* thisPtr, unsigned int version)
{
	ClientSftpChannel* pSftp = static_cast<ClientSftpChannel*>(thisPtr);
	if (!pSftp)
		return;

	try { pSftp->OnSftpVersion(version); }
	catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"ClientSftpChannel::OnSftpVersion"); }
}

void ClientSftpChannel::OnChannelClose(void* thisPtr)
{
	ClientSftpChannel* pSftp = static_cast<ClientSftpChannel*>(thisPtr);
	if (!pSftp)
		return;

	try { pSftp->OnChannelClose(); }
	catch (std::exception const& e) { FLOWSSHCPP_EXCEPTION_IN_HANDLER(L"ClientSftpChannel::OnChannelClose"); }
}



} // namespace FlowSshCpp
