#include "FlowSshNetIncludes.h"
#include "FlowSshNet.h"


#if defined(_DEBUG) && defined(_CRTDBG_MAP_ALLOC)
#define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__)
#define new DEBUG_NEW
int dummy56Xyvdsf = _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif



namespace Bitvise {
namespace FlowSshNet {


// FLOWSSHNET_REQUEST
//
// "S_OnStartFailed(handlerObj)" already guarantees that handlerObj cannot
// be GC collected prematurely, so the ending "GC::KeepAlive(handlerObj)" 
// wouldn't really be necessary. It doesn't harm though and makes the code
// less error prone with possible future changes.

#define FLOWSSHNET_REQUEST(handlerType, handlerObj, mainObj, call) \
	handlerType::S_TryLock(handlerObj); \
	try { handlerType::S_OnStart(handlerObj, mainObj); call; } \
	catch (std::exception const& e) { handlerType::S_OnStartFailed(handlerObj); throw gcnew FlowSshNet::Exception(Helper::CharToNetStr(e.what())); } \
	catch (System::Exception^) { handlerType::S_OnStartFailed(handlerObj); throw; } \
	GC::KeepAlive(handlerObj);


// Helpers

inline FlowSshCpp::RefPtr<FlowSshCpp::ProgressHandler> NoCppProgress()
{
	return FlowSshCpp::RefPtr<FlowSshCpp::ProgressHandler>(nullptr);
}


// ErrorHandler

class ErrorHandler
{
public:
	ErrorHandler()
	{
		FlowSshCpp::Initializer::RegisterExtErrHandler(&ErrorHandler::FlowSshC);
		FlowSshC_Initialize(&ErrorHandler::FlowSshC, NULL);
	}

	static void __cdecl FlowSshC(void*, unsigned int flags, wchar_t const* desc)
	{
		String^ descNet = Helper::WCharToNetStr(desc);
		if (flags == FlowSshC_ErrorFlags_InCall) throw gcnew FlowSshNet::Exception(descNet);
		else
		{
			descNet = "Internal FlowSshC/FlowSshCpp exception: " + descNet;
			SshNet::Raise_OnExceptionInEvent(nullptr, true, gcnew System::Exception(descNet));
		}
	}

} g_errorHandler;


// Version

Version::Version(FlowSshC_Version const& x)
{
	Major = x.m_major;
	Minor = x.m_minor;
}


// FlowSshNet

Version^ SshNet::GetVersion()
{
	return gcnew Version(FlowSshC_GetVersion());
}

void SshNet::SetActCode(String^ actCodeHex)
{
	pin_ptr<const wchar_t> actCodeHexPin = PtrToStringChars(actCodeHex);
	FlowSshC_SetActCode(actCodeHexPin);
}

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

void SshNet::Raise_OnExceptionInEvent(Object^ sender, bool fatal, System::Exception^ e)
{
	try { OnExceptionInEvent(sender, fatal, e); }
	catch (System::Exception^) { assert(!"Don't throw exceptions from OnExceptionInEvent handlers."); }
}


// WaitImpl

HandlerBusyException::HandlerBusyException(String^ objName)
	: FlowSshNet::Exception("You cannot pass the same instance of '" + objName + "' to several requests at once.")
{}


bool WaitImpl::IsDone::get()
{
	if (IsDisposed) throw gcnew ObjectDisposedException(this->GetType()->ToString());
	return WaitDone(0);
}

void WaitImpl::WaitDone()
{
	if (IsDisposed) throw gcnew ObjectDisposedException(this->GetType()->ToString());
	m_doneEvent->WaitOne();
}

bool WaitImpl::WaitDone(int millisecondsTimeout)
{
	if (IsDisposed) throw gcnew ObjectDisposedException(this->GetType()->ToString());
	return m_doneEvent->WaitOne(millisecondsTimeout, false);
}

ManualResetEvent^ WaitImpl::GetDoneEvent()
{
	if (IsDisposed) throw gcnew ObjectDisposedException(this->GetType()->ToString());
	return m_doneEvent;
}

WaitImpl::WaitImpl()
{
	m_doneEvent = gcnew ManualResetEvent(true);
}

void WaitImpl::Done()
{
	m_doneEvent->Set();
}

void WaitImpl::Start()
{
	Lock lock(m_doneEvent);
	if (IsDone)	m_doneEvent->Reset();
	else		throw gcnew HandlerBusyException(this->GetType()->ToString());
}


// ProgressHandler

ProgressHandler::ProgressHandler()
{
	m_flowSshCpp = new ProgressHandlerCpp(this);
	m_flowSshCpp->AddRef();
}

ProgressHandler::~ProgressHandler()
{
	Lock lock(this);
	if (m_flowSshCpp)
	{
		m_flowSshCpp->FlowSshNetDisposed();
		this->!ProgressHandler();
		WaitImpl::Done();
	}
}

ProgressHandler::!ProgressHandler() 
{
	Lock lock(this);
	if (m_flowSshCpp)
	{
		m_flowSshCpp->Release();
		m_flowSshCpp = nullptr;
	}
}

bool ProgressHandler::IsDisposed::get()
{
	Lock lock(this);
	return (m_flowSshCpp == nullptr);
}

bool ProgressHandler::Success::get()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return m_flowSshCpp->Success(); 
}

TaskState ProgressHandler::GetTaskState() 
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return static_cast<TaskState>(m_flowSshCpp->GetTaskState());
}

unsigned int ProgressHandler::GetTaskSpecificStep()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return m_flowSshCpp->GetTaskSpecificStep();
}

String^ ProgressHandler::GetAuxInfo()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return Helper::WStrToNetStr(m_flowSshCpp->GetAuxInfo()); 
}

String^ ProgressHandler::DescribeConnectError()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return DescribeConnectError(GetTaskSpecificStep(), GetAuxInfo());
}

String^ ProgressHandler::DescribeSftpChannelOpenError()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return DescribeSftpChannelOpenError(GetTaskSpecificStep(), GetAuxInfo());
}

String^ ProgressHandler::DescribeConnectError(unsigned int step, String^ auxInfo)
{
	if (auxInfo->Length < 1)
		auxInfo = "(no additional info)";
		
    switch ((ConnectStep) step)
    {
    case ConnectStep::ConnectToProxy:	  return String::Format("Connecting to proxy server failed: {0}", auxInfo);
    case ConnectStep::ConnectToSshServer: return String::Format("Connecting to SSH server failed: {0}", auxInfo);
    case ConnectStep::SshVersionString:   return String::Format("SSH version string failed: {0}", auxInfo);
    case ConnectStep::SshKeyExchange:	  return String::Format("SSH key exchange failed: {0}", auxInfo);
    case ConnectStep::SshUserAuth:		  return String::Format("SSH authentication failed: {0}", auxInfo);
    default:                              return String::Format("Connecting failed at unknown step: {0}", auxInfo);
    }
}

String^ ProgressHandler::DescribeSftpChannelOpenError(unsigned int step, String^ auxInfo)
{
	if (auxInfo->Length < 1)
		auxInfo = "(no additional info)";
		
    switch ((ClientSftpChannelOpenStep) step)
    {
    case ClientSftpChannelOpenStep::OpenRequest: return String::Format("Opening SFTP channel failed: {0}", auxInfo);
    case ClientSftpChannelOpenStep::SftpRequest: return String::Format("Requesting SFTP subsystem failed: {0}", auxInfo);
    case ClientSftpChannelOpenStep::InitPacket:  return String::Format("Initializing SFTP protocol failed: {0}", auxInfo);
    default:                                     return String::Format("Opening SFTP channel failed at unknown step: {0}", auxInfo);
    }
}

void ProgressHandler::Raise_OnProgress(unsigned int taskSpecificStep)
{
	try { OnProgress(this, taskSpecificStep); }
	catch (System::Exception^ e) { SshNet::Raise_OnExceptionInEvent(this, false, e); }
}

void ProgressHandler::Raise_OnSuccess()
{
	try { OnSuccess(this); }
	catch (System::Exception^ e) { SshNet::Raise_OnExceptionInEvent(this, false, e); }
}

void ProgressHandler::Raise_OnError(unsigned int taskSpecificStep, String^ auxInfo)
{
	try { OnError(this, taskSpecificStep, auxInfo); }
	catch (System::Exception^ e) { SshNet::Raise_OnExceptionInEvent(this, false, e); }
}

FlowSshCpp::RefPtr<ProgressHandlerCpp> ProgressHandler::GetFlowSshCpp()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	FlowSshCpp::RefPtr<ProgressHandlerCpp> flowCppRefPtr(m_flowSshCpp);
	return flowCppRefPtr; 
}


// Keypair

Keypair::Keypair(String^ alg, unsigned int bitCount)
{
	pin_ptr<const wchar_t> algPin = PtrToStringChars(alg);

	m_flowSshCpp = new FlowSshCpp::Keypair(algPin, bitCount);
	m_flowSshCpp->AddRef();
}

Keypair::Keypair(array<Byte>^ data, String^ passphrase)
{
	pin_ptr<Byte> dataPin = nullptr; 
	int dataSize = 0;
	if (data != nullptr)
	{
		dataPin = &data[0];
		dataSize = data->GetLength(0);
	}	
	FlowSshCpp::Data dataCpp(dataPin, (unsigned int)dataSize);
	pin_ptr<const wchar_t> passphrasePin = PtrToStringChars(passphrase);
	
	try
	{
		m_flowSshCpp = new FlowSshCpp::Keypair(dataCpp, passphrasePin);
		m_flowSshCpp->AddRef();
	}
	catch (FlowSshCpp::KeypairLoadException const& e)
	{
		String^ whatNet = Helper::WCharToNetStr(e.What());
		throw gcnew FlowSshNet::KeypairLoadException(whatNet);
	}
}

Keypair::!Keypair()
{
	Lock lock(this);
	if (m_flowSshCpp)
	{
		m_flowSshCpp->Release();
		m_flowSshCpp = nullptr;
	}
}

bool Keypair::IsDisposed::get()
{
	Lock lock(this);
	return (m_flowSshCpp == nullptr);
}

void Keypair::SetPassphrase(String^ passphrase)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> passphrasePin = PtrToStringChars(passphrase);
	m_flowSshCpp->SetPassphrase(passphrasePin);
}

array<Byte>^ Keypair::GetBitviseData()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	try 
	{ 
		FlowSshCpp::Data data = m_flowSshCpp->GetBitviseData();
		return Helper::UnsignedCharsToByteArray(data.GetPtr(), data.GetSize()); 
	}
	catch (FlowSshCpp::DataAllocException const& e)
	{
		String^ whatNet = Helper::WCharToNetStr(e.What());
		throw gcnew FlowSshNet::DataAllocException(whatNet);
	}
}

array<Byte>^ Keypair::GetOpenSshData()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	try 
	{
		FlowSshCpp::Data data = m_flowSshCpp->GetOpenSshData();
		return Helper::UnsignedCharsToByteArray(data.GetPtr(), data.GetSize());
	}
	catch (FlowSshCpp::KeypairExportException const& e)
	{
		String^ whatNet = Helper::WCharToNetStr(e.What());
		throw gcnew FlowSshNet::KeypairExportException(whatNet);
	}
	catch (FlowSshCpp::DataAllocException const& e)
	{
		String^ whatNet = Helper::WCharToNetStr(e.What());
		throw gcnew FlowSshNet::DataAllocException(whatNet);
	}
}

array<Byte>^ Keypair::GetPuttyData()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	try 
	{
		FlowSshCpp::Data data = m_flowSshCpp->GetPuttyData();
		return Helper::UnsignedCharsToByteArray(data.GetPtr(), data.GetSize());
	}
	catch (FlowSshCpp::KeypairExportException const& e)
	{
		String^ whatNet = Helper::WCharToNetStr(e.What());
		throw gcnew FlowSshNet::KeypairExportException(whatNet);
	}
	catch (FlowSshCpp::DataAllocException const& e)
	{
		String^ whatNet = Helper::WCharToNetStr(e.What());
		throw gcnew FlowSshNet::DataAllocException(whatNet);
	}
}

Keypair^ Keypair::CreateNew(String^ alg, unsigned int bitCount)
{
	return gcnew Keypair(alg, bitCount);
}

Keypair^ Keypair::CreateFromData(array<Byte>^ data, String^ passphrase)
{
	return gcnew Keypair(data, passphrase);
}

FlowSshCpp::RefPtr<FlowSshCpp::Keypair> Keypair::GetFlowSshCpp()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	FlowSshCpp::RefPtr<FlowSshCpp::Keypair> flowCppRefPtr(m_flowSshCpp);
	return flowCppRefPtr; 
}


// PublicKey

PublicKey::PublicKey(Keypair^ keypair)
{
	if (keypair == nullptr) throw gcnew ArgumentNullException();

	m_flowSshCpp = new FlowSshCpp::PublicKey(keypair->GetFlowSshCpp());
	m_flowSshCpp->AddRef();
}

PublicKey::PublicKey(array<Byte>^ data)
{
	pin_ptr<Byte> dataPin = nullptr;
	int dataSize = 0;	
	if (data != nullptr)
	{
		dataPin = &data[0];
		dataSize = data->GetLength(0);
	}	
	FlowSshCpp::Data dataCpp(dataPin, (unsigned int)dataSize);
	
	try
	{
		m_flowSshCpp = new FlowSshCpp::PublicKey(dataCpp);
		m_flowSshCpp->AddRef();
	}
	catch (FlowSshCpp::PublicKeyLoadException const& e)
	{
		String^ whatNet = Helper::WCharToNetStr(e.What());
		throw gcnew FlowSshNet::PublicKeyLoadException(whatNet);
	}
}

PublicKey::PublicKey(FlowSshCpp::RefPtr<FlowSshCpp::PublicKey> publicKey)
{
	m_flowSshCpp = publicKey.Get();
	m_flowSshCpp->AddRef();
}

PublicKey::!PublicKey()
{
	Lock lock(this);
	if (m_flowSshCpp)
	{
		m_flowSshCpp->Release();
		m_flowSshCpp = nullptr;
	}
}

bool PublicKey::IsDisposed::get()
{
	Lock lock(this);
	return (m_flowSshCpp == nullptr);
}

unsigned int PublicKey::GetBitCount()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return m_flowSshCpp->GetBitCount();
}

unsigned int PublicKey::GetEffectiveSecurity()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return m_flowSshCpp->GetEffectiveSecurity();
}

bool PublicKey::IsEqual(PublicKey^ other)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);
	
	return m_flowSshCpp->IsEqual(other->m_flowSshCpp);
}

String^ PublicKey::GetAlg()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return Helper::WStrToNetStr(m_flowSshCpp->GetAlg());
}

String^ PublicKey::GetMd5()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return Helper::WStrToNetStr(m_flowSshCpp->GetMd5());
}

String^ PublicKey::GetBubbleBabble()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return Helper::WStrToNetStr(m_flowSshCpp->GetBubbleBabble());
}

String^ PublicKey::GetSha256()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return Helper::WStrToNetStr(m_flowSshCpp->GetSha256());
}

array<Byte>^ PublicKey::GetSsh2Data()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	try 
	{
		FlowSshCpp::Data data = m_flowSshCpp->GetSsh2Data();
		return Helper::UnsignedCharsToByteArray(data.GetPtr(), data.GetSize());
	}
	catch (FlowSshCpp::DataAllocException const& e)	
	{
		String^ whatNet = Helper::WCharToNetStr(e.What());
		throw gcnew FlowSshNet::DataAllocException(whatNet);
	}
}

array<Byte>^ PublicKey::GetOpenSshData()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	try
	{
		FlowSshCpp::Data data = m_flowSshCpp->GetOpenSshData();
		return Helper::UnsignedCharsToByteArray(data.GetPtr(), data.GetSize());
	}
	catch (FlowSshCpp::DataAllocException const& e)	
	{
		String^ whatNet = Helper::WCharToNetStr(e.What());
		throw gcnew FlowSshNet::DataAllocException(whatNet);
	}
}

PublicKey^ PublicKey::CreateFromKeypair(Keypair^ keypair)
{
	return gcnew PublicKey(keypair);
}

PublicKey^ PublicKey::CreateFromData(array<Byte>^ data)
{
	return gcnew PublicKey(data);
}

FlowSshCpp::RefPtr<FlowSshCpp::PublicKey> PublicKey::GetFlowSshCpp()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	FlowSshCpp::RefPtr<FlowSshCpp::PublicKey> flowCppRefPtr(m_flowSshCpp);
	return flowCppRefPtr; 
}


// FurtherAuth

bool FurtherAuth::IsDisposed::get()
{
	Lock lock(this);
	return (m_flowSshCpp == nullptr);
}

void FurtherAuth::SetUserName(String^ userName)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> userNamePin = PtrToStringChars(userName);
	m_flowSshCpp->SetUserName(userNamePin);
}

void FurtherAuth::SetPassword(String^ password)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> passwordPin = PtrToStringChars(password);
	m_flowSshCpp->SetPassword(passwordPin);
}

void FurtherAuth::SetKeypair(Keypair^ keypair)
{
	if (keypair == nullptr) throw gcnew ArgumentNullException();

	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);
	
	m_flowSshCpp->SetKeypair(keypair->GetFlowSshCpp());
}

bool FurtherAuth::HavePartialSuccess()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return m_flowSshCpp->HavePartialSuccess();
}

bool FurtherAuth::IsPasswordRemaining()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return m_flowSshCpp->IsPasswordRemaining();
}

bool FurtherAuth::IsPublicKeyRemaining()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return m_flowSshCpp->IsPublicKeyRemaining();
}

FurtherAuth::FurtherAuth(FlowSshCpp::Client::FurtherAuth* flowCpp) 
	: m_flowSshCpp(flowCpp) 
{}

void FurtherAuth::ResetFlowSshCpp()
{
	Lock lock(this);
	m_flowSshCpp = nullptr;
}


// PasswordChange

bool PasswordChange::IsDisposed::get()
{
	Lock lock(this);
	return (m_flowSshCpp == nullptr);
}

String^ PasswordChange::GetPrompt()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return Helper::WStrToNetStr(m_flowSshCpp->GetPrompt());
}

void PasswordChange::SetNewPassword(String^ password)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> passwordPin = PtrToStringChars(password);
	m_flowSshCpp->SetNewPassword(passwordPin);
}

PasswordChange::PasswordChange(FlowSshCpp::Client::PasswordChange* flowCpp)
	: m_flowSshCpp(flowCpp) 
{}

void PasswordChange::ResetFlowSshCpp()
{
	Lock lock(this);
	m_flowSshCpp = nullptr;
}


// Algorithms

KeyExchangeAlgs::KeyExchangeAlgs()
{
	MlKem1024nistp384 = 1;
	MlKem768x25519 = 1;

	Curve25519 = 1;
	EcdhSecp256k1 = 1;
	EcdhNistp512 = 1;
	EcdhNistp384 = 1;
	EcdhNistp256 = 1;

	GexBitsMin = 0;
	GexBitsOpt = 0;
	GexBitsMax = 0;

	DhG16Sha512 = 1;
	DhG15Sha512 = 1;
	DhG14Sha256 = 1;
	DhG14Sha1 = 0;
	DhG1Sha1 = 0;
	DhGexSha256 = 1;
	DhGexSha1 = 0;
}

FlowSshCpp::KeyExchangeAlgs const KeyExchangeAlgs::ToFlowCpp()
{
	FlowSshCpp::KeyExchangeAlgs to;
	to.m_mlkem1024nistp384 = MlKem1024nistp384;
	to.m_mlkem768x25519 = MlKem768x25519;

	to.m_curve25519 = Curve25519;
	to.m_ecdhSecp256k1 = EcdhSecp256k1;
	to.m_ecdhNistp512 = EcdhNistp512;
	to.m_ecdhNistp384 = EcdhNistp384;
	to.m_ecdhNistp256 = EcdhNistp256;

	to.m_gexBitsMin = GexBitsMin;
	to.m_gexBitsOpt = GexBitsOpt;
	to.m_gexBitsMax = GexBitsMax;

	to.m_dhG16Sha512 = DhG16Sha512;
	to.m_dhG15Sha512 = DhG15Sha512;
	to.m_dhG14Sha256 = DhG14Sha256;
	to.m_dhG14Sha1 = DhG14Sha1;
	to.m_dhG1Sha1 = DhG1Sha1;
	to.m_dhGexSha256 = DhGexSha256;
	to.m_dhGexSha1 = DhGexSha1;
	return to;
}

HostKeyAlgs::HostKeyAlgs()
{
	RsaSha2_512 = 1;
	RsaSha2_256 = 1;
	Ed25519 = 1;
	EcdsaSecp256k1 = 1;
	EcdsaNistp521 = 1;	
	EcdsaNistp384 = 1;	
	EcdsaNistp256 = 1;	
	SshRsa = 0;
	SshDss = 0;
}

FlowSshCpp::HostKeyAlgs const HostKeyAlgs::ToFlowCpp()
{
	FlowSshCpp::HostKeyAlgs to;
	to.m_rsaSha2_512 = RsaSha2_512;
	to.m_rsaSha2_256 = RsaSha2_256;
	to.m_ed25519 = Ed25519;
	to.m_ecdsaSecp256k1 = EcdsaSecp256k1;
	to.m_ecdsaNistp521 = EcdsaNistp521;
	to.m_ecdsaNistp384 = EcdsaNistp384;
	to.m_ecdsaNistp256 = EcdsaNistp256;
	to.m_sshRsa = SshRsa;
	to.m_sshDss = SshDss;
	return to;
}

EncryptionAlgs::EncryptionAlgs()
{
	Aes256Gcm = Aes128Gcm = Aes256Ctr = Aes192Ctr = Aes128Ctr = 1;
	ChaCha20Poly1305 = TripleDesCtr = Aes256Cbc = Aes192Cbc = Aes128Cbc = TripleDesCbc = 0;
}

FlowSshCpp::EncryptionAlgs const EncryptionAlgs::ToFlowCpp()
{
	FlowSshCpp::EncryptionAlgs to;
	to.m_chacha20Poly1305 = ChaCha20Poly1305;
	to.m_aes256Gcm = Aes256Gcm;
	to.m_aes128Gcm = Aes128Gcm;
	to.m_aes256Ctr = Aes256Ctr;
	to.m_aes192Ctr = Aes192Ctr;
	to.m_aes128Ctr = Aes128Ctr;
	to.m_tripleDesCtr = TripleDesCtr;
	to.m_aes256Cbc = Aes256Cbc;
	to.m_aes192Cbc = Aes192Cbc;
	to.m_aes128Cbc = Aes128Cbc;
	to.m_tripleDesCbc = TripleDesCbc;
	return to;
}

MacAlgs::MacAlgs()
{
	HmacSha2_256_Etm = HmacSha2_512_Etm = 0;
	HmacSha2_256     = HmacSha2_512     = 1;
	HmacSha1_Etm     = HmacSha1         = 0;
}

FlowSshCpp::MacAlgs const MacAlgs::ToFlowCpp()
{
	FlowSshCpp::MacAlgs to;
	to.m_hmacSha2_256_etm = HmacSha2_256_Etm;
	to.m_hmacSha2_512_etm = HmacSha2_512_Etm;
	to.m_hmacSha1_etm     = HmacSha1_Etm;
	to.m_hmacSha2_256     = HmacSha2_256;
	to.m_hmacSha2_512     = HmacSha2_512;
	to.m_hmacSha1         = HmacSha1;
	return to;
}

CompressionAlgs::CompressionAlgs()
{
	Zlib = 0; None = 1;
}

FlowSshCpp::CompressionAlgs const CompressionAlgs::ToFlowCpp()
{
	FlowSshCpp::CompressionAlgs to;
	to.m_zlib = Zlib;
	to.m_none = None;
	return to;
}

Options::Options()
{
	StartKeyReExchange = true;
	SendBasedKeepAliveTimeoutMs = -1;
	RecvBasedKeepAliveTimeoutMs = -1;
	RecvBasedKeepAliveRelaxedTimeoutMs = -1;
	RecvBasedUnresponsivenessTimeoutMs = -1;
	Elevation      = Elevation::Default;
	SendExtInfo    = SendExtInfo::Default;
	NoFlowControl  = NoFlowControl::Unsupported;
	GlobalRequests = GlobalRequests::AutoDetect;
	RequireStrictKex = false;
}

FlowSshCpp::Options const Options::ToFlowCpp()
{
	FlowSshCpp::Options to;
	to.m_startKeyReExchange = StartKeyReExchange;
	to.m_sendBasedKeepAliveTimeoutMs = SendBasedKeepAliveTimeoutMs;
	to.m_recvBasedKeepAliveTimeoutMs = RecvBasedKeepAliveTimeoutMs;
	to.m_recvBasedKeepAliveRelaxedTimeoutMs = RecvBasedKeepAliveRelaxedTimeoutMs;
	to.m_recvBasedUnresponsivenessTimeoutMs = RecvBasedUnresponsivenessTimeoutMs;
	to.m_elevation      = static_cast<unsigned int>(Elevation);
	to.m_sendExtInfo    = static_cast<unsigned int>(SendExtInfo);
	to.m_noFlowControl  = static_cast<unsigned int>(NoFlowControl);
	to.m_globalRequests = static_cast<unsigned int>(GlobalRequests);
	to.m_requireStrictKex = RequireStrictKex;
	return to;
}


// ForwardingRule

FlowSshCpp::ForwardingRuleRef const ForwardingRuleRef::ToFlowCpp()
{
	FlowSshCpp::ForwardingRuleRef to;
	to.m_clientToServer = ClientToServer;
	Helper::NetStringToWStr(ListInterface, to.m_listInterface);
	to.m_listPort = ListPort;
	return to;
}

FlowSshCpp::ForwardingRule const ForwardingRule::ToFlowCpp()
{
	FlowSshCpp::ForwardingRule to;
	to.m_clientToServer = ClientToServer;
	Helper::NetStringToWStr(ListInterface, to.m_listInterface);
	to.m_listPort = ListPort;
	Helper::NetStringToWStr(DestHost, to.m_destHost);
	to.m_destPort = DestPort;
	return to;
}

FlowSshCpp::ForwardingRuleExt const ForwardingRuleExt::ToFlowCpp()
{
	FlowSshCpp::ForwardingRuleExt to;
	to.m_clientToServer = ClientToServer;
	Helper::NetStringToWStr(ListInterface, to.m_listInterface);
	to.m_listPort = ListPort;
	Helper::NetStringToWStr(DestHost, to.m_destHost);
	to.m_destPort = DestPort;
	Helper::NetStringToWStr(ConnectInterface, to.m_connectInterface);
	return to;
}


// ProxyForwarding

FlowSshCpp::ProxyForwarding const ProxyForwarding::ToFlowCpp()
{
	FlowSshCpp::ProxyForwarding to;
	Helper::NetStringToWStr(ListInterface, to.m_listInterface);
	to.m_listPort = ListPort;
	Helper::NetStringToWStr(BindPublicAddressIP4, to.m_bindPublicAddressIP4);
	Helper::NetStringToWStr(BindPublicAddressIP6, to.m_bindPublicAddressIP6);
	Helper::NetStringToWStr(BindListInterfaceIP4, to.m_bindListInterfaceIP4);
	Helper::NetStringToWStr(BindListInterfaceIP6, to.m_bindListInterfaceIP6);
	return to;
}


// ForwardingErr

ForwardingErr::ForwardingErr(FlowSshCpp::ForwardingErr const& x)
{
	ErrCode = static_cast<ForwardingErrCode>(x.m_errCode);
	AuxInfo = Helper::WStrToNetStr(x.m_auxInfo);
	Desc = Helper::WStrToNetStr(x.m_desc);
}


// ForwardingHandler

ForwardingHandler::ForwardingHandler()
{
	m_flowSshCpp = new ForwardingHandlerCpp(this);
	m_flowSshCpp->AddRef();
}

ForwardingHandler::~ForwardingHandler()
{
	Lock lock(this);
	if (m_flowSshCpp)
	{
		m_flowSshCpp->FlowSshNetDisposed();
		this->!ForwardingHandler();
		WaitImpl::Done();
	}
}

ForwardingHandler::!ForwardingHandler() 
{
	Lock lock(this);
	if (m_flowSshCpp)
	{
		m_flowSshCpp->Release();
		m_flowSshCpp = nullptr;
	}
}

bool ForwardingHandler::IsDisposed::get()
{
	Lock lock(this);
	return (m_flowSshCpp == nullptr);
}

bool ForwardingHandler::Success::get()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return m_flowSshCpp->Success(); 
}

unsigned int ForwardingHandler::GetListPort() 
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return m_flowSshCpp->GetListPort();
}

ForwardingErr^ ForwardingHandler::GetError()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return gcnew FlowSshNet::ForwardingErr(m_flowSshCpp->GetError());
}

void ForwardingHandler::Raise_OnSuccess(unsigned int listPort)
{
	try { OnSuccess(this, listPort); }
	catch (System::Exception^ e) { SshNet::Raise_OnExceptionInEvent(this, false, e); }
}

void ForwardingHandler::Raise_OnError(ForwardingErr^ error)
{
	try { OnError(this, error); }
	catch (System::Exception^ e) { SshNet::Raise_OnExceptionInEvent(this, false, e); }
}

FlowSshCpp::RefPtr<ForwardingHandlerCpp> ForwardingHandler::GetFlowSshCpp()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	FlowSshCpp::RefPtr<ForwardingHandlerCpp> flowCppRefPtr(m_flowSshCpp);
	return flowCppRefPtr; 
}


// ForwardingLog

ForwardingLog^ ForwardingLog::Create(FlowSshCpp::RefPtrConst<FlowSshCpp::ForwardingLog> const& x)
{
	switch (x->m_event)
	{
	default:
		return gcnew ForwardingLog(x.GetRef());

	case FlowSshCpp::ForwardingLog::C2SOpenSent:
	case FlowSshCpp::ForwardingLog::C2SOpened:
		return gcnew ForwardingLog_C2S(*x.DynamicCast<FlowSshCpp::ForwardingLog_C2S>());
	case FlowSshCpp::ForwardingLog::C2SOpenFailed:
		return gcnew ForwardingLog_C2SOpenFailed(*x.DynamicCast<FlowSshCpp::ForwardingLog_C2SOpenFailed>());
	case FlowSshCpp::ForwardingLog::C2SClosed:
		return gcnew ForwardingLog_C2SClosed(*x.DynamicCast<FlowSshCpp::ForwardingLog_C2SClosed>());

	case FlowSshCpp::ForwardingLog::S2COpenIgnored:
	case FlowSshCpp::ForwardingLog::S2COpenReceived:
	case FlowSshCpp::ForwardingLog::S2COpened:
		return gcnew ForwardingLog_S2C(*x.DynamicCast<FlowSshCpp::ForwardingLog_S2C>());
	case FlowSshCpp::ForwardingLog::S2COpenFailed:
		return gcnew ForwardingLog_S2COpenFailed(*x.DynamicCast<FlowSshCpp::ForwardingLog_S2COpenFailed>());
	case FlowSshCpp::ForwardingLog::S2CClosed:
		return gcnew ForwardingLog_S2CClosed(*x.DynamicCast<FlowSshCpp::ForwardingLog_S2CClosed>());
			
	case FlowSshCpp::ForwardingLog::ServerSideC2SAdded:
	case FlowSshCpp::ForwardingLog::ServerSideC2SCanceled:
		return gcnew ForwardingLog_ServerSideC2S(*x.DynamicCast<FlowSshCpp::ForwardingLog_ServerSideC2S>());
	case FlowSshCpp::ForwardingLog::ServerSideC2SAddFailed:
		return gcnew ForwardingLog_ServerSideC2SAddFailed(*x.DynamicCast<FlowSshCpp::ForwardingLog_ServerSideC2SAddFailed>());

	case FlowSshCpp::ForwardingLog::ProxyAccepted:
		return gcnew ForwardingLog_Proxy(*x.DynamicCast<FlowSshCpp::ForwardingLog_Proxy>());
	case FlowSshCpp::ForwardingLog::ProxyDecodeFailed:
		return gcnew ForwardingLog_ProxyDecodeFailed(*x.DynamicCast<FlowSshCpp::ForwardingLog_ProxyDecodeFailed>());
	case FlowSshCpp::ForwardingLog::ProxyConnectStarted:
		return gcnew ForwardingLog_ProxyStarted(*x.DynamicCast<FlowSshCpp::ForwardingLog_ProxyStarted>());
	case FlowSshCpp::ForwardingLog::ProxyBindFailed:
		return gcnew ForwardingLog_ProxyBindFailed(*x.DynamicCast<FlowSshCpp::ForwardingLog_ProxyBindFailed>());
	case FlowSshCpp::ForwardingLog::ProxyBindStarted:
		return gcnew ForwardingLog_ProxyBindStarted(*x.DynamicCast<FlowSshCpp::ForwardingLog_ProxyBindStarted>());
	case FlowSshCpp::ForwardingLog::ProxyBindAborted:
		return gcnew ForwardingLog_ProxyBindAborted(*x.DynamicCast<FlowSshCpp::ForwardingLog_ProxyBindAborted>());
	}
}

ForwardingLog::ForwardingLog(FlowSshCpp::ForwardingLog const& x)
	: Event(x.m_event)
	, Desc(Helper::WStrToNetStr(x.m_desc))
{
}

ForwardingLog_X2Y::ForwardingLog_X2Y(FlowSshCpp::ForwardingLog_X2Y const& x)
	: ForwardingLog(x)
	, Type(x.m_type)
	, SrvSideRuleDesc(Helper::WStrToNetStr(x.m_srvSideRuleDesc))
	, ListInterface(Helper::WStrToNetStr(x.m_listInterface))
	, ListPort(x.m_listPort)
	, OrigAddress(Helper::WStrToNetStr(x.m_origAddress))
	, OrigPort(x.m_origPort)
	, DestAddress(Helper::WStrToNetStr(x.m_destAddress))
	, DestPort(x.m_destPort)
{
}

ForwardingLog_X2YOpenFailed::ForwardingLog_X2YOpenFailed(FlowSshCpp::ForwardingLog_X2YOpenFailed const& x)
	: ForwardingLog_X2Y(x)
	, AuxInfo(Helper::WStrToNetStr(x.m_auxInfo))
{
}

ForwardingLog_X2YClosed::ForwardingLog_X2YClosed(FlowSshCpp::ForwardingLog_X2YClosed const& x)
	: ForwardingLog_X2Y(x)
	, BytesSent(x.m_bytesSent)
	, BytesReceived(x.m_bytesReceived)
{
}

ForwardingLog_ServerSideC2S::ForwardingLog_ServerSideC2S(FlowSshCpp::ForwardingLog_ServerSideC2S const& x)
	: ForwardingLog(x)
	, ListInterface(Helper::WStrToNetStr(x.m_listInterface))
	, ListPort(x.m_listPort)
	, RuleDesc(Helper::WStrToNetStr(x.m_ruleDesc))
{
}

ForwardingLog_ServerSideC2SAddFailed::ForwardingLog_ServerSideC2SAddFailed(FlowSshCpp::ForwardingLog_ServerSideC2SAddFailed const& x)
	: ForwardingLog_ServerSideC2S(x)
	, ErrCode(x.m_errCode)
	, AuxInfo(Helper::WStrToNetStr(x.m_auxInfo))
{
}

ForwardingLog_Proxy::ForwardingLog_Proxy(FlowSshCpp::ForwardingLog_Proxy const& x)
	: ForwardingLog(x)
	, ProxyListInterface(Helper::WStrToNetStr(x.m_proxyListInterface))
	, ProxyListPort(x.m_proxyListPort)
	, ProxyOrigAddress(Helper::WStrToNetStr(x.m_proxyOrigAddress))
	, ProxyOrigPort(x.m_proxyOrigPort)
{
}

ForwardingLog_ProxyDecodeFailed::ForwardingLog_ProxyDecodeFailed(FlowSshCpp::ForwardingLog_ProxyDecodeFailed const& x)
	: ForwardingLog_Proxy(x)
	, ErrCode(x.m_errCode)
	, AuxInfo(Helper::WStrToNetStr(x.m_auxInfo))
{
}

ForwardingLog_ProxyStarted::ForwardingLog_ProxyStarted(FlowSshCpp::ForwardingLog_ProxyStarted const& x)
	: ForwardingLog_Proxy(x)
	, ProxyType(x.m_proxyType)
	, ProxyReqAddress(Helper::WStrToNetStr(x.m_proxyReqAddress))
	, ProxyReqPort(x.m_proxyReqPort)
{
}

ForwardingLog_ProxyBindFailed::ForwardingLog_ProxyBindFailed(FlowSshCpp::ForwardingLog_ProxyBindFailed const& x)
	: ForwardingLog_ProxyStarted(x)
	, ErrCode(x.m_errCode)
	, AuxInfo(Helper::WStrToNetStr(x.m_auxInfo))
{
}

ForwardingLog_ProxyBindStarted::ForwardingLog_ProxyBindStarted(FlowSshCpp::ForwardingLog_ProxyBindStarted const& x)
	: ForwardingLog_ProxyStarted(x)
	, BindPublicAddress(Helper::WStrToNetStr(x.m_bindPublicAddress))
	, BindPublicPort(x.m_bindPublicPort)
	, BindListInterface(Helper::WStrToNetStr(x.m_bindListInterface))
	, BindListPort(x.m_bindListPort)
{
}

ForwardingLog_ProxyBindAborted::ForwardingLog_ProxyBindAborted(FlowSshCpp::ForwardingLog_ProxyBindAborted const& x)
	: ForwardingLog_ProxyBindStarted(x)
	, AbrtCode(x.m_abrtCode)
	, AuxInfo(Helper::WStrToNetStr(x.m_auxInfo))
{
}


// ClientBase

ClientBase::ClientBase()
{
	m_flowSshCpp = new ClientCpp(this);
	m_flowSshCpp->AddRef();
}

ClientBase::~ClientBase()
{
	Lock lock(this);
	if (m_flowSshCpp)
	{
		m_flowSshCpp->FlowSshNetDisposed();
		this->!ClientBase();
	}
}

ClientBase::!ClientBase()
{
	Lock lock(this);
	if (m_flowSshCpp)
	{
		try { m_flowSshCpp->Disconnect(NoCppProgress()); }
		catch (System::Exception^) {}

		m_flowSshCpp->Release();
		m_flowSshCpp = nullptr;
	}
}

bool ClientBase::IsDisposed::get()
{
	Lock lock(this);
	return (m_flowSshCpp == nullptr);
}

void ClientBase::SetAppName(String^ appNameAndVersion)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> appNameAndVersionPin = PtrToStringChars(appNameAndVersion);
	m_flowSshCpp->SetAppName(appNameAndVersionPin);
}

void ClientBase::SetDebugFile(String^ debugFile, unsigned int debugEventMask)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> debugFilePin = PtrToStringChars(debugFile);
	m_flowSshCpp->SetDebugFile(debugFilePin, debugEventMask);
}

void ClientBase::SetProxyType(ProxyType type)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	m_flowSshCpp->SetProxyType(static_cast<unsigned int>(type));
}

void ClientBase::SetProxyHost(String^ host)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);
	
	pin_ptr<const wchar_t> hostPin = PtrToStringChars(host);
	m_flowSshCpp->SetProxyHost(hostPin);
}

void ClientBase::SetProxyPort(unsigned int port)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	m_flowSshCpp->SetProxyPort(port);
}

void ClientBase::SetProxyUserName(String^ userName)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> userNamePin = PtrToStringChars(userName);
	m_flowSshCpp->SetProxyUserName(userNamePin);
}

void ClientBase::SetProxyPassword(String^ password)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> passwordPin = PtrToStringChars(password);
	m_flowSshCpp->SetProxyPassword(passwordPin);
}

void ClientBase::SetProxyOptions(bool resolveLocally)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	m_flowSshCpp->SetProxyOptions(resolveLocally);
}

void ClientBase::SetHost(String^ host) 
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> string = PtrToStringChars(host);
	m_flowSshCpp->SetHost(string);
}

void ClientBase::SetPort(unsigned int port)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	m_flowSshCpp->SetPort(port);
}

void ClientBase::SetObfuscation(bool enable, String^ keyword)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> string = PtrToStringChars(keyword);
	m_flowSshCpp->SetObfuscation(enable, string);
}

void ClientBase::SetUserName(String^ userName) 
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> userNamePin = PtrToStringChars(userName);
	m_flowSshCpp->SetUserName(userNamePin);
}

void ClientBase::SetPassword(String^ password) 
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> passwordPin = PtrToStringChars(password);
	m_flowSshCpp->SetPassword(passwordPin);
}

void ClientBase::SetKeypair(Keypair^ keypair)
{
	if (keypair == nullptr) throw gcnew ArgumentNullException();

	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	m_flowSshCpp->SetKeypair(keypair->GetFlowSshCpp());
}

void ClientBase::SetSessionId(String^ clientSessionId, unsigned int timeoutMs)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> clientSessionIdPin = PtrToStringChars(clientSessionId);
	m_flowSshCpp->SetSessionId(clientSessionIdPin, timeoutMs);
}

void ClientBase::SetKeyExchangeAlgs(KeyExchangeAlgs^ algs)
{
	if (algs == nullptr) algs = gcnew KeyExchangeAlgs();

	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	m_flowSshCpp->SetKeyExchangeAlgs(algs->ToFlowCpp());
}

void ClientBase::SetHostKeyAlgs(HostKeyAlgs^ algs)
{
	if (algs == nullptr) algs = gcnew HostKeyAlgs();

	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	m_flowSshCpp->SetHostKeyAlgs(algs->ToFlowCpp());
}

void ClientBase::SetEncryptionAlgs(EncryptionAlgs^ algs)
{
	if (algs == nullptr) algs = gcnew EncryptionAlgs();

	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	m_flowSshCpp->SetEncryptionAlgs(algs->ToFlowCpp());
}

void ClientBase::SetMacAlgs(MacAlgs^ algs)
{
	if (algs == nullptr) algs = gcnew MacAlgs();

	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	m_flowSshCpp->SetMacAlgs(algs->ToFlowCpp());
}

void ClientBase::SetCompressionAlgs(CompressionAlgs^ algs)
{
	if (algs == nullptr) algs = gcnew CompressionAlgs();

	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	m_flowSshCpp->SetCompressionAlgs(algs->ToFlowCpp());
}

void ClientBase::SetOptions(Options^ opts)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	m_flowSshCpp->SetOptions(opts->ToFlowCpp());
}

void ClientBase::SetSocketProvider(unsigned int socketProvider)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	m_flowSshCpp->SetSocketProvider(socketProvider);
}

void ClientBase::SetPreferredIPVersion(unsigned int version)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	m_flowSshCpp->SetPreferredIPVersion(version);
}

void ClientBase::SetConnectInterface(String^ interfaceList)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> string = PtrToStringChars(interfaceList);
	m_flowSshCpp->SetConnectInterface(string);
}

void ClientBase::Connect(ProgressHandler^ progress)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	FLOWSSHNET_REQUEST(ProgressHandler, progress, this,
	m_flowSshCpp->Connect(ProgressHandler::S_GetFlowSshCpp(progress)));
}

void ClientBase::Disconnect(ProgressHandler^ progress)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	FLOWSSHNET_REQUEST(ProgressHandler, progress, this,
	m_flowSshCpp->Disconnect(ProgressHandler::S_GetFlowSshCpp(progress)));
}

void ClientBase::AddForwarding(ForwardingRule^ rule, ForwardingHandler^ response)
{
	if (rule == nullptr) throw gcnew ArgumentNullException();

	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	FLOWSSHNET_REQUEST(ForwardingHandler, response, this,
	m_flowSshCpp->AddForwarding(rule->ToFlowCpp(), ForwardingHandler::S_GetFlowSshCpp(response)));
}

void ClientBase::AddForwarding(ForwardingRuleExt^ rule, ForwardingHandler^ response)
{
	if (rule == nullptr) throw gcnew ArgumentNullException();

	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	FLOWSSHNET_REQUEST(ForwardingHandler, response, this,
	m_flowSshCpp->AddForwarding(rule->ToFlowCpp(), ForwardingHandler::S_GetFlowSshCpp(response)));
}

void ClientBase::CancelForwarding(ForwardingRuleRef^ ruleRef, ForwardingHandler^ response)
{
	if (ruleRef == nullptr) throw gcnew ArgumentNullException();

	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	FLOWSSHNET_REQUEST(ForwardingHandler, response, this,
	m_flowSshCpp->CancelForwarding(ruleRef->ToFlowCpp(), ForwardingHandler::S_GetFlowSshCpp(response)));
}

void ClientBase::InviteForwardings(bool clientToServer, ForwardingHandler^ response)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	FLOWSSHNET_REQUEST(ForwardingHandler, response, this,
	m_flowSshCpp->InviteForwardings(clientToServer, ForwardingHandler::S_GetFlowSshCpp(response)));
}

void ClientBase::EnableProxyForwarding(ProxyForwarding^ settings, ForwardingHandler^ response)
{
	if (settings == nullptr) throw gcnew ArgumentNullException();

	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	FLOWSSHNET_REQUEST(ForwardingHandler, response, this,
	m_flowSshCpp->EnableProxyForwarding(settings->ToFlowCpp(), ForwardingHandler::S_GetFlowSshCpp(response)));
}

void ClientBase::DisableProxyForwarding(ForwardingHandler^ response)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	FLOWSSHNET_REQUEST(ForwardingHandler, response, this,
	m_flowSshCpp->DisableProxyForwarding(ForwardingHandler::S_GetFlowSshCpp(response)));
}

void ClientBase::Raise_OnSshVersion(String^ version)
{
	try { OnSshVersion(this, version); }
	catch (System::Exception^ e) { SshNet::Raise_OnExceptionInEvent(this, false, e); }
}

bool ClientBase::Raise_OnHostKey(PublicKey^ publicKey) 
{
	try { return OnHostKey(this, publicKey); }
	catch (System::Exception^ e)
	{
		SshNet::Raise_OnExceptionInEvent(this, false, e);
		return false;
	}
}

void ClientBase::Raise_OnKexDone(unsigned __int64 kexNr, bool incoming, String^ kexAlg, String^ encAlg, String^ macAlg, String^ cmprAlg)
{
	try { OnKexDone(this, kexNr, incoming, kexAlg, encAlg, macAlg, cmprAlg); }
	catch (System::Exception^ e) { SshNet::Raise_OnExceptionInEvent(this, false, e); }
}

bool ClientBase::Raise_OnFurtherAuth(FurtherAuth^ furtherAuth) 
{
	try { return OnFurtherAuth(this, furtherAuth); }
	catch (System::Exception^e)
	{
		SshNet::Raise_OnExceptionInEvent(this, false, e);
		return false;
	}
	finally { furtherAuth->ResetFlowSshCpp(); }
}

bool ClientBase::Raise_OnPasswordChange(PasswordChange^ passwordChange) 
{
	try { return OnPasswordChange(this, passwordChange); }
	catch (System::Exception^ e)
	{
		SshNet::Raise_OnExceptionInEvent(this, false, e);
		return false;
	}
	finally { passwordChange->ResetFlowSshCpp(); }
}

void ClientBase::Raise_OnBanner(String^ banner)
{
	try { OnBanner(this, banner); }
	catch (System::Exception^ e) { SshNet::Raise_OnExceptionInEvent(this, false, e); }
}

void ClientBase::Raise_OnSessionIdReply(unsigned int code)
{
	try { OnSessionIdReply(this, code); }
	catch (System::Exception^ e) { SshNet::Raise_OnExceptionInEvent(this, false, e); }
}

void ClientBase::Raise_OnServerNotice(unsigned int severity, String^ facility, String^ status, String^ description)
{
	try { OnServerNotice(this, severity, facility, status, description); }
	catch (System::Exception^ e) { SshNet::Raise_OnExceptionInEvent(this, false, e); }
}

bool ClientBase::Raise_OnHostKeySync(PublicKey^ publicKey, bool keyVerified)
{
	try { return OnHostKeySync(this, publicKey, keyVerified); }
	catch (System::Exception^ e)
	{
		SshNet::Raise_OnExceptionInEvent(this, false, e);
		return false;
	}
}

void ClientBase::Raise_OnForwardingLog(ForwardingLog^ log)
{
	try { OnForwardingLog(this, log); }
	catch (System::Exception^ e) { SshNet::Raise_OnExceptionInEvent(this, false, e); }
}

void ClientBase::Raise_OnDisconnect(DisconnectReason reason, String^ desc)
{
	try { OnDisconnect(this, reason, desc); }
	catch (System::Exception^ e) { SshNet::Raise_OnExceptionInEvent(this, false, e); }
}

FlowSshCpp::RefPtr<ClientCpp> ClientBase::GetFlowSshCpp()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	FlowSshCpp::RefPtr<ClientCpp> flowCppRefPtr(m_flowSshCpp);
	return flowCppRefPtr; 
}


// Client

Client::Client()
{
	OnSshVersion      += gcnew SshVersionEventHandler    (this, &Client::BuiltIn_OnSshVersion);
	OnHostKey         += gcnew HostKeyEventHandler       (this, &Client::BuiltIn_OnHostKey);
	OnKexDone         += gcnew KexDoneEventHandler       (this, &Client::BuiltIn_OnKexDone);
	OnFurtherAuth     += gcnew FurtherAuthEventHandler   (this, &Client::BuiltIn_OnFurtherAuth);
	OnPasswordChange  += gcnew PasswordChangeEventHandler(this, &Client::BuiltIn_OnPasswordChange);
	OnBanner          += gcnew BannerEventHandler        (this, &Client::BuiltIn_OnBanner);
	OnSessionIdReply  += gcnew SessionIdReplyEventHandler(this, &Client::BuiltIn_OnSessionIdReply);
	OnServerNotice	  += gcnew ServerNoticeEventHandler  (this, &Client::BuiltIn_OnServerNotice);
	OnForwardingLog   += gcnew ForwardingLogHandler      (this, &Client::BuiltIn_OnForwardingLog);
	OnDisconnect      += gcnew DisconnectEventHandler    (this, &Client::BuiltIn_OnDisconnect);	
}

void Client::ClearHostKeyState()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	m_acceptHostKeys_keys = nullptr;
	m_acceptHostKeys_bb = nullptr;
	m_acceptHostKeys_md5 = nullptr;
	m_acceptHostKeys_sha256 = nullptr;
	m_serverHostKey = nullptr;
}

void Client::AcceptHostKey(PublicKey^ pk)
{
	if (pk == nullptr) throw gcnew ArgumentNullException();

	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	if (!m_acceptHostKeys_keys)
		m_acceptHostKeys_keys = gcnew List<PublicKey^>();

	m_acceptHostKeys_keys->Add(pk);
}

void Client::AcceptHostKeyBB(String^ fp)
{
	if (fp == nullptr) throw gcnew ArgumentNullException();

	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	if (!m_acceptHostKeys_bb)
		m_acceptHostKeys_bb = gcnew List<String^>();

	m_acceptHostKeys_bb->Add(fp);
}

void Client::AcceptHostKeyMd5(String^ fp)
{
	if (fp == nullptr) throw gcnew ArgumentNullException();

	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	if (!m_acceptHostKeys_md5)
		m_acceptHostKeys_md5 = gcnew List<String^>();

	m_acceptHostKeys_md5->Add(fp);
}

void Client::AcceptHostKeySha256(String^ fp)
{
	if (fp == nullptr) throw gcnew ArgumentNullException();

	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	if (!m_acceptHostKeys_sha256)
		m_acceptHostKeys_sha256 = gcnew List<String^>();

	m_acceptHostKeys_sha256->Add(fp);
}

PublicKey^ Client::GetServerHostKey()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return m_serverHostKey;
}

void Client::BuiltIn_OnSshVersion(Object^ /*sender*/, String^ version)
{
	Console::WriteLine("Server version string: {0}", version);
}

bool Client::BuiltIn_OnHostKey(Object^ /*sender*/, PublicKey^ publicKey)
{
	m_serverHostKey = publicKey;

	if (m_acceptHostKeys_keys != nullptr)
		for each (PublicKey^ pk in m_acceptHostKeys_keys)
			if (publicKey->IsEqual(pk))
				return true;

	if (m_acceptHostKeys_bb != nullptr)
		for each (String^ fp in m_acceptHostKeys_bb)
			if (publicKey->GetBubbleBabble()->Equals(fp, StringComparison::InvariantCultureIgnoreCase))
				return true;

	if (m_acceptHostKeys_md5 != nullptr)
		for each (String^ fp in m_acceptHostKeys_md5)
			if (publicKey->GetMd5()->Equals(fp, StringComparison::InvariantCultureIgnoreCase))
				return true;

	if (m_acceptHostKeys_sha256 != nullptr)
		for each (String^ fp in m_acceptHostKeys_sha256)
			if (publicKey->GetSha256()->Equals(fp, StringComparison::InvariantCulture))		// Base64 - case sensitive!
				return true;

	return false;
}

void Client::BuiltIn_OnKexDone(Object^ /*sender*/, unsigned __int64 kexNr, bool incoming, String^ kexAlg, String^ encAlg, String^ macAlg, String^ cmprAlg)
{
	String^ dir;
	if (incoming) dir = "incoming";
	else		  dir = "outgoing";
	Console::WriteLine("Key exchange {0} done ({1}), key exchange: {2}, encryption: {3}, integrity: {4}, compression: {5}",
		kexNr, dir, kexAlg, encAlg, macAlg, cmprAlg);
}

bool Client::BuiltIn_OnFurtherAuth(Object^ /*sender*/, FurtherAuth^ furtherAuth)
{
	String^ verb;
	if (furtherAuth->HavePartialSuccess())
		verb = " succeeded partially";
	else
		verb = " failed";

	String^ pwContinue;
	if (furtherAuth->IsPasswordRemaining())
		pwContinue = "; could continue with 'password'";

	String^ pkContinue;
	if (furtherAuth->IsPublicKeyRemaining())
		pkContinue = "; could continue with 'publickey'";

	Console::WriteLine("Authentication {0}{1}{2}", verb, pwContinue, pkContinue);
	return false;
}

bool Client::BuiltIn_OnPasswordChange(Object^ /*sender*/, PasswordChange^ /*passwordChange*/)
{
	Console::WriteLine("Password change requested by server, but not supported by this client.");
	return false;
}

void Client::BuiltIn_OnBanner(Object^ /*sender*/, String^ banner)
{
	Console::WriteLine("----- Server Authentication Banner -----");
	Console::Write(banner);
	Console::WriteLine("----- End of Banner -----");
}

void Client::BuiltIn_OnSessionIdReply(Object^ /*sender*/, unsigned int code)
{
	switch (code)
	{
	case FlowSshC_SessionIdReplyCode_Undisclosed:
		Console::WriteLine("The server accepted our client-session-id request without disclosing further information.");
		break;
		
	case FlowSshC_SessionIdReplyCode_SessionUnique:
		Console::WriteLine("The server accepted our client-session-id request without taking further actions.");
		break;
		
	case FlowSshC_SessionIdReplyCode_ConnectionsTerminated:
		Console::WriteLine("The server disconnected one or more other connections logged into the same account with a matching client session ID.");
		break;

	case FlowSshC_SessionIdReplyCode_TimeoutExpired:
		Console::WriteLine("The server has begun disconnecting one or more other connections logged into the same account with a matching client session ID.");
		break;

	default:
		Console::WriteLine(L"The server responded to our client-session-id request with reply code {0}.", code);
		break;
	}
}

void Client::BuiltIn_OnServerNotice(Object^ /*sender*/, unsigned int severity, String^ /*facility*/, String^ /*status*/, String^ description)
{
	switch (severity)
	{
	case FlowSshC_ServerNoticeSeverity_Error: 	Console::WriteLine("Received error message from server: "	+ description); break;
	case FlowSshC_ServerNoticeSeverity_Warning: Console::WriteLine("Received warning message from server: " + description); break;
	case FlowSshC_ServerNoticeSeverity_Info:    Console::WriteLine("Received info message from server: "	+ description); break;
	case FlowSshC_ServerNoticeSeverity_Trace:   Console::WriteLine("Received trace message from server: "	+ description); break;
	}
}

bool Client::BuiltIn_OnHostKeySync(Object^ /*sender*/, PublicKey^ /*publicKey*/, bool /*keyVerified*/)
{
	return false;
}

void Client::BuiltIn_OnForwardingLog(Object^ /*sender*/, ForwardingLog^ /*log*/)
{
	// Forwarding logs are suppresed by default.
}

void Client::BuiltIn_OnDisconnect(Object^ /*sender*/, DisconnectReason reason, String^ desc)
{
	Console::WriteLine("The connection has been disconnected for the following reason:");
	Console::WriteLine("{0}, {1}", reason, desc);
}


// ReceiveHandler

ReceiveHandler::ReceiveHandler()
{
	m_flowSshCpp = new ReceiveHandlerCpp(this);
	m_flowSshCpp->AddRef();
}

ReceiveHandler::~ReceiveHandler()
{
	Lock lock(this);
	if (m_flowSshCpp)
	{
		m_flowSshCpp->FlowSshNetDisposed();
		this->!ReceiveHandler();
		WaitImpl::Done();
	}
}

ReceiveHandler::!ReceiveHandler() 
{
	Lock lock(this);
	if (m_flowSshCpp)
	{
		m_flowSshCpp->Release();
		m_flowSshCpp = nullptr;
	}
}

bool ReceiveHandler::IsDisposed::get()
{
	Lock lock(this);
	return (m_flowSshCpp == nullptr);
}

bool ReceiveHandler::Success::get()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return m_flowSshCpp->Success(); 
}

bool ReceiveHandler::StdErr()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return m_flowSshCpp->StdErr(); 
}

bool ReceiveHandler::Eof()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return m_flowSshCpp->Eof(); 
}

array<Byte>^ ReceiveHandler::GetData() 
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	if (IsDone)	
		return Helper::UnsignedCharsToByteArray(m_flowSshCpp->GetDataPtr(), m_flowSshCpp->GetDataSize());
	return nullptr;
}

void ReceiveHandler::Raise_OnReceive(bool stdErr, array<Byte>^ data, bool eof)
{
	try { OnReceive(this, stdErr, data, eof); }
	catch (System::Exception^ e) { SshNet::Raise_OnExceptionInEvent(this, false, e); }
}

void ReceiveHandler::Raise_OnError()
{
	try { OnError(this); }
	catch (System::Exception^ e) { SshNet::Raise_OnExceptionInEvent(this, false, e); }
}

FlowSshCpp::RefPtr<ReceiveHandlerCpp> ReceiveHandler::GetFlowSshCpp()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	FlowSshCpp::RefPtr<ReceiveHandlerCpp> flowCppRefPtr(m_flowSshCpp);
	return flowCppRefPtr; 
}


// ExitStatus

ExitStatus::ExitStatus(FlowSshC_ExitStatus const& x)
{
	Code = x.m_code;
}


// ExitSignal

ExitSignal::ExitSignal(FlowSshCpp::ClientSessionChannel::ExitSignal const& x)
{
	Name = Helper::WStrToNetStr(x.m_name);
	CoreDumped = x.m_coreDumped;
	ErrMsg = Helper::WStrToNetStr(x.m_errMsg);
}


// ClientSessionChannel

ClientSessionChannel::ClientSessionChannel(ClientBase^ client)
{
	if (client == nullptr) throw gcnew ArgumentNullException();
	
	m_flowSshCpp = new ClientSessionChannelCpp(client->GetFlowSshCpp(), this);
	m_flowSshCpp->AddRef();

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

ClientSessionChannel::~ClientSessionChannel()
{
	Lock lock(this);
	if (m_flowSshCpp)
	{
		m_flowSshCpp->FlowSshNetDisposed();
		this->!ClientSessionChannel();
		m_client = nullptr;
	}
}

ClientSessionChannel::!ClientSessionChannel() 
{
	Lock lock(this);
	if (m_flowSshCpp)
	{
		try { m_flowSshCpp->Close(NoCppProgress()); }
		catch (System::Exception^) {}

		m_flowSshCpp->Release();
		m_flowSshCpp = nullptr;
	}
}

bool ClientSessionChannel::IsDisposed::get()
{
	Lock lock(this);
	return (m_flowSshCpp == nullptr);
}

ClientBase^ ClientSessionChannel::GetClient()
{
	Lock lock(this);
	return m_client;
}

void ClientSessionChannel::OpenRequest(ProgressHandler^ progress)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	FLOWSSHNET_REQUEST(ProgressHandler, progress, this,
	m_flowSshCpp->OpenRequest(ProgressHandler::S_GetFlowSshCpp(progress)));
}

void ClientSessionChannel::PtyRequest(String^ term, unsigned int widthCols, unsigned int heightRows, ProgressHandler^ progress)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> termPin = PtrToStringChars(term);
	FLOWSSHNET_REQUEST(ProgressHandler, progress, this,
	m_flowSshCpp->PtyRequest(termPin, widthCols, heightRows, ProgressHandler::S_GetFlowSshCpp(progress)));
}

void ClientSessionChannel::ExecRequest(String^ command, ProgressHandler^ progress)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> commandPin = PtrToStringChars(command);
	FLOWSSHNET_REQUEST(ProgressHandler, progress, this,
	m_flowSshCpp->ExecRequest(commandPin, ProgressHandler::S_GetFlowSshCpp(progress)));
}

void ClientSessionChannel::ShellRequest(ProgressHandler^ progress)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	FLOWSSHNET_REQUEST(ProgressHandler, progress, this,
	m_flowSshCpp->ShellRequest(ProgressHandler::S_GetFlowSshCpp(progress)));
}

void ClientSessionChannel::Receive(ReceiveHandler^ receive)
{	
	this->Receive(32*1024, receive);
}

void ClientSessionChannel::Receive(unsigned int maxBytes, ReceiveHandler^ receive)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	FLOWSSHNET_REQUEST(ReceiveHandler, receive, this,
	m_flowSshCpp->Receive(maxBytes, ReceiveHandler::S_GetFlowSshCpp(receive)));
}

void ClientSessionChannel::Send(array<Byte>^ data, bool eof, ProgressHandler^ progress)
{
	int size = 0;
	if (data != nullptr)
		size = data->Length;
	this->Send(data, 0, size, eof, progress);
}

void ClientSessionChannel::Send(array<Byte>^ data, int offset, int size, bool eof, ProgressHandler^ progress)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<Byte> dataPin = nullptr;
	int dataSize = 0;
	if (data != nullptr && size > 0)
	{
		// throw IndexOutOfRangeException for invalid offset/size
		int index = offset + size - 1;
		if (offset < 0 || index < 0 || index >= data->Length)
			throw gcnew IndexOutOfRangeException();

		dataPin = &data[offset];
		dataSize = size;
	}
	FlowSshCpp::Data dataCpp(dataPin, (unsigned int)dataSize);

	FLOWSSHNET_REQUEST(ProgressHandler, progress, this,
	m_flowSshCpp->Send(dataCpp, eof, ProgressHandler::S_GetFlowSshCpp(progress)));
}

void ClientSessionChannel::Signal(String^ signalName, ProgressHandler^ progress)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> signalNamePin = PtrToStringChars(signalName);
	FLOWSSHNET_REQUEST(ProgressHandler, progress, this,
	m_flowSshCpp->Signal(signalNamePin, ProgressHandler::S_GetFlowSshCpp(progress)));
}

void ClientSessionChannel::Close(ProgressHandler^ progress)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	FLOWSSHNET_REQUEST(ProgressHandler, progress, this,
	m_flowSshCpp->Close(ProgressHandler::S_GetFlowSshCpp(progress)));
}

void ClientSessionChannel::Raise_OnExitStatus(ExitStatus^ status)
{
	try { OnExitStatus(this, status); }
	catch (System::Exception^ e) { SshNet::Raise_OnExceptionInEvent(this, false, e); }
}

void ClientSessionChannel::Raise_OnExitSignal(ExitSignal^ signal)
{
	try { OnExitSignal(this, signal); }
	catch (System::Exception^ e) { SshNet::Raise_OnExceptionInEvent(this, false, e); }
}

void ClientSessionChannel::Raise_OnChannelClose()
{
	try { OnChannelClose(this); }
	catch (System::Exception^ e) { SshNet::Raise_OnExceptionInEvent(this, false, e); }
}

FlowSshCpp::RefPtr<ClientSessionChannelCpp> ClientSessionChannel::GetFlowSshCpp()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	FlowSshCpp::RefPtr<ClientSessionChannelCpp> flowCppRefPtr(m_flowSshCpp);
	return flowCppRefPtr;
}


// FileAttrs

FileAttrs::FileAttrs(FlowSshCpp::FileAttrs const& x)
{
	bool useSubseconds = (x.m_validAttrFlags & FlowSshC_AttrFlags_Subseconds) != 0;

	ValidAttrFlags	= static_cast<AttrFlags>(x.m_validAttrFlags);
	Type			= static_cast<FileType>(x.m_type);
	Size			= x.m_size;
	Uid				= x.m_uid;
	Gid				= x.m_gid;
	Permissions		= x.m_permissions;
	if (useSubseconds)
	{
		AccessTime	= Helper::UnixTimeToDateTime(x.m_accessTime, x.m_accessTimeNs);
		CreateTime	= Helper::UnixTimeToDateTime(x.m_createTime, x.m_createTimeNs);
		ModifyTime	= Helper::UnixTimeToDateTime(x.m_modifyTime, x.m_modifyTimeNs);
	}
	else
	{
		AccessTime	= Helper::UnixTimeToDateTime(x.m_accessTime, 0);
		CreateTime	= Helper::UnixTimeToDateTime(x.m_createTime, 0);
		ModifyTime	= Helper::UnixTimeToDateTime(x.m_modifyTime, 0);
	}
	Owner			= Helper::WStrToNetStr(x.m_owner);
	Group			= Helper::WStrToNetStr(x.m_group);
	AllocSize		= x.m_allocSize;
	TextHint		= static_cast< ::Bitvise::FlowSshNet::TextHint>(x.m_textHint);
}

FlowSshCpp::FileAttrs const FileAttrs::ToFlowCpp()
{
	FlowSshCpp::FileAttrs to;
	to.m_validAttrFlags	= static_cast<unsigned int>(ValidAttrFlags);
	to.m_type			= static_cast<unsigned char>(Type);
	to.m_size			= Size;
	to.m_uid			= Uid;
	to.m_gid			= Gid;
	to.m_permissions	= Permissions;
	Helper::DateTimeToUnixTime(AccessTime, to.m_accessTime, to.m_accessTimeNs);
	Helper::DateTimeToUnixTime(CreateTime, to.m_createTime, to.m_createTimeNs);
	Helper::DateTimeToUnixTime(ModifyTime, to.m_modifyTime, to.m_modifyTimeNs);
	Helper::NetStringToWStr(Owner, to.m_owner);
	Helper::NetStringToWStr(Group, to.m_group);
	to.m_allocSize		= AllocSize;
	to.m_textHint		= static_cast<unsigned char>(TextHint);
	return to;
}


// FileInfo

FileInfo::FileInfo(FlowSshCpp::FileInfo const& x)
{
	Name  = Helper::WStrToNetStr(x.m_name);
	Attrs = gcnew FileAttrs(x.m_attrs);
}


// SftpErr

SftpErr::SftpErr(FlowSshCpp::SftpErr const& x)
{
	ErrCode = static_cast<SftpErrCode>(x.m_errCode);
	ErrMsg  = Helper::WStrToNetStr(x.m_errMsg);
}

String^ SftpErr::Describe()
{
	String^ msg;
	if (!!ErrMsg->Length)
		msg = (", message: " + ErrMsg);

	return String::Format("SFTP error code {0}{1}", ErrCode, msg);
}


// ListErr

ListErr::ListErr(FlowSshCpp::ListErr const& x)
{
	ListOp = static_cast< ::Bitvise::FlowSshNet::ListOp>(x.m_listOp);
	ErrCode = x.m_errCode; 
	ErrMsg  = Helper::WStrToNetStr(x.m_errMsg);
}

String^ ListErr::Describe()
{
	String^ code;
	if ((int) ListOp > 99)
		code = String::Format(", SFTP error code: {0}", (SftpErrCode) ErrCode);

	String^ msg;
	if (!!ErrMsg->Length)
		msg = (", message: " + ErrMsg);

	return String::Format("List error, type: {0}{1}{2}", ListOp, code, msg);
}


// TransferErr

TransferErr::TransferErr(FlowSshCpp::TransferErr const& x)
{
	TransferOp = static_cast< ::Bitvise::FlowSshNet::TransferOp>(x.m_transferOp);
	ErrCode	   = x.m_errCode; 
	ErrMsg	   = Helper::WStrToNetStr(x.m_errMsg);
}

String^ TransferErr::Describe()
{
	String^ code;
	if (TransferOp <= (FlowSshNet::TransferOp) 99)
		code = "";
	else if (TransferOp <= (FlowSshNet::TransferOp) 199)
		code = String::Format(", SFTP error code: {0}", (SftpErrCode) ErrCode);
	else if (TransferOp <= (FlowSshNet::TransferOp) 299)
		code = String::Format(", Windows error code: {0}", ErrCode);
	else if (TransferOp <= (FlowSshNet::TransferOp) 399)
		code = String::Format(", resume error code: {0}", (ResumeErrCode) ErrCode);
	
	String^ msg;
	if (!!ErrMsg->Length)
		msg = (", message: " + ErrMsg);

	return String::Format("Transfer error, type: {0}{1}{2}", TransferOp, code, msg);
}


// TransferStat

TransferStat::TransferStat(FlowSshC_TransferStat const& x)
{
	BytesTransferred   = x.m_bytesTransferred;
	TimeElapsedMs	   = x.m_timeElapsedMs;
	CurrentFileSize	   = x.m_currentFileSize;
	FinalFileSize	   = x.m_finalFileSize;
	FinalFileSizeValid = x.m_finalFileSizeValid;
}


// RealPathHandler

RealPathHandler::RealPathHandler()
{
	m_flowSshCpp = new RealPathHandlerCpp(this);
	m_flowSshCpp->AddRef();
}

RealPathHandler::~RealPathHandler()
{
	Lock lock(this);
	if (m_flowSshCpp)
	{
		m_flowSshCpp->FlowSshNetDisposed();
		this->!RealPathHandler();
		WaitImpl::Done();
	}
}

RealPathHandler::!RealPathHandler() 
{
	Lock lock(this);
	if (m_flowSshCpp)
	{
		m_flowSshCpp->Release();
		m_flowSshCpp = nullptr;
	}
}

bool RealPathHandler::IsDisposed::get()
{
	Lock lock(this);
	return (m_flowSshCpp == nullptr);
}

bool RealPathHandler::Success::get()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return m_flowSshCpp->Success(); 
}

String^ RealPathHandler::GetRealPath() 
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return Helper::WStrToNetStr(m_flowSshCpp->GetRealPath());
}

SftpErr^ RealPathHandler::GetError()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return gcnew FlowSshNet::SftpErr(m_flowSshCpp->GetError());
}

void RealPathHandler::Raise_OnRealPath(String^ realPath)
{
	try { OnRealPath(this, realPath); }
	catch (System::Exception^ e) { SshNet::Raise_OnExceptionInEvent(this, false, e); }
}

void RealPathHandler::Raise_OnError(SftpErr^ error)
{
	try { OnError(this, error); }
	catch (System::Exception^ e) { SshNet::Raise_OnExceptionInEvent(this, false, e); }
}

FlowSshCpp::RefPtr<RealPathHandlerCpp> RealPathHandler::GetFlowSshCpp()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	FlowSshCpp::RefPtr<RealPathHandlerCpp> flowCppRefPtr(m_flowSshCpp);
	return flowCppRefPtr; 
}


// StatHandler

StatHandler::StatHandler()
{
	m_flowSshCpp = new StatHandlerCpp(this);
	m_flowSshCpp->AddRef();
}

StatHandler::~StatHandler()
{
	Lock lock(this);
	if (m_flowSshCpp)
	{
		m_flowSshCpp->FlowSshNetDisposed();
		this->!StatHandler();
		WaitImpl::Done();
	}
}


StatHandler::!StatHandler() 
{
	Lock lock(this);
	if (m_flowSshCpp)
	{
		m_flowSshCpp->Release();
		m_flowSshCpp = nullptr;
	}
}

bool StatHandler::IsDisposed::get()
{
	Lock lock(this);
	return (m_flowSshCpp == nullptr);
}

bool StatHandler::Success::get()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return m_flowSshCpp->Success(); 
}

FileAttrs^ StatHandler::GetFileAttrs() 
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return gcnew FileAttrs(m_flowSshCpp->GetFileAttrs());
}

SftpErr^ StatHandler::GetError()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return gcnew FlowSshNet::SftpErr(m_flowSshCpp->GetError());
}

void StatHandler::Raise_OnStat(FileAttrs^ fileAttrs)
{
	try { OnStat(this, fileAttrs); }
	catch (System::Exception^ e) { SshNet::Raise_OnExceptionInEvent(this, false, e); }
}

void StatHandler::Raise_OnError(SftpErr^ error)
{
	try { OnError(this, error); }
	catch (System::Exception^ e) { SshNet::Raise_OnExceptionInEvent(this, false, e); }
}

FlowSshCpp::RefPtr<StatHandlerCpp> StatHandler::GetFlowSshCpp()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	FlowSshCpp::RefPtr<StatHandlerCpp> flowCppRefPtr(m_flowSshCpp);
	return flowCppRefPtr; 
}


// SftpHandler

SftpHandler::SftpHandler()
{
	m_flowSshCpp = new SftpHandlerCpp(this);
	m_flowSshCpp->AddRef();
}

SftpHandler::~SftpHandler()
{
	Lock lock(this);
	if (m_flowSshCpp)
	{
		m_flowSshCpp->FlowSshNetDisposed();
		this->!SftpHandler();
		WaitImpl::Done();
	}
}

SftpHandler::!SftpHandler() 
{
	Lock lock(this);
	if (m_flowSshCpp)
	{
		m_flowSshCpp->Release();
		m_flowSshCpp = nullptr;
	}
}

bool SftpHandler::IsDisposed::get()
{
	Lock lock(this);
	return (m_flowSshCpp == nullptr);
}

bool SftpHandler::Success::get()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return m_flowSshCpp->Success(); 
}

SftpErr^ SftpHandler::GetError()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return gcnew FlowSshNet::SftpErr(m_flowSshCpp->GetError());
}

void SftpHandler::Raise_OnSuccess()
{
	try { OnSuccess(this); }
	catch (System::Exception^ e) { SshNet::Raise_OnExceptionInEvent(this, false, e); }
}

void SftpHandler::Raise_OnError(SftpErr^ error)
{
	try { OnError(this, error); }
	catch (System::Exception^ e) { SshNet::Raise_OnExceptionInEvent(this, false, e); }
}

FlowSshCpp::RefPtr<SftpHandlerCpp> SftpHandler::GetFlowSshCpp()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	FlowSshCpp::RefPtr<SftpHandlerCpp> flowCppRefPtr(m_flowSshCpp);
	return flowCppRefPtr; 
}


// ListHandler

ListHandler::ListHandler(bool useGetFileInfos)
{
	m_flowSshCpp = new ListHandlerCpp(this);
	m_flowSshCpp->AddRef();

	if (useGetFileInfos)
	{
		OnStart += gcnew StartEventHandler(this, &ListHandler::BuiltIn_OnStart);
		m_fileInfos = gcnew array<FlowSshNet::FileInfo^>(0);
	}
	else m_fileInfos = nullptr;
}

ListHandler::~ListHandler()
{
	Lock lock(this);
	if (m_flowSshCpp)
	{
		m_flowSshCpp->FlowSshNetDisposed();
		this->!ListHandler();
		WaitImpl::Done();
	}
}

ListHandler::!ListHandler() 
{
	Lock lock(this);
	if (m_flowSshCpp)
	{
		m_flowSshCpp->Release();
		m_flowSshCpp = nullptr;
	}
}

bool ListHandler::IsDisposed::get()
{
	Lock lock(this);
	return (m_flowSshCpp == nullptr);
}

bool ListHandler::Success::get()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return m_flowSshCpp->Success(); 
}


ListErr^ ListHandler::GetError()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return gcnew FlowSshNet::ListErr(m_flowSshCpp->GetError());
}

array<FileInfo^>^ ListHandler::GetFileInfos()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	if ((m_fileInfos != nullptr) && IsDone)
		return m_fileInfos;
	return nullptr;
}

void ListHandler::BuiltIn_OnStart(Object^ /*sender*/)
{
	if (m_fileInfos != nullptr)
		Array::Resize(m_fileInfos, 0);
}

void ListHandler::OnList::add(ListEventHandler^ del)
{
	m_del = static_cast<ListEventHandler^>(Delegate::Combine(m_del, del));
}

void ListHandler::OnList::remove(ListEventHandler^ del)
{
	m_del = static_cast<ListEventHandler^>(Delegate::Remove(m_del, del));
}

bool ListHandler::OnList::raise(Object^ sender, array<FileInfo^>^ fileInfos, bool endOfList)
{
	if (m_del != nullptr) return m_del->Invoke(sender, fileInfos, endOfList);
	else return true;
}

bool ListHandler::Raise_OnList(array<FileInfo^>^ fileInfos, bool endOfList)
{
	if (m_fileInfos != nullptr)
	{
		int existingLength = m_fileInfos->Length;
		Array::Resize(m_fileInfos, existingLength + fileInfos->Length);
		fileInfos->CopyTo(m_fileInfos, existingLength);	
	}

	try { return OnList(this, fileInfos, endOfList); }
	catch (System::Exception^ e)
	{
		SshNet::Raise_OnExceptionInEvent(this, false, e);
		return false;
	}
}

void ListHandler::Raise_OnError(ListErr^ error)
{
	try { OnError(this, error); }
	catch (System::Exception^ e) { SshNet::Raise_OnExceptionInEvent(this, false, e); }
}

FlowSshCpp::RefPtr<ListHandlerCpp> ListHandler::GetFlowSshCpp()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	FlowSshCpp::RefPtr<ListHandlerCpp> flowCppRefPtr(m_flowSshCpp);
	return flowCppRefPtr; 
}


// TransferHandler

TransferHandler::TransferHandler()
{
	m_flowSshCpp = new TransferHandlerCpp(this);
	m_flowSshCpp->AddRef();
}

TransferHandler::~TransferHandler()
{
	Lock lock(this);
	if (m_flowSshCpp)
	{
		m_flowSshCpp->FlowSshNetDisposed();
		this->!TransferHandler();
		WaitImpl::Done();
	}
}

TransferHandler::!TransferHandler() 
{
	Lock lock(this);
	if (m_flowSshCpp)
	{
		m_flowSshCpp->Release();
		m_flowSshCpp = nullptr;
	}
}

bool TransferHandler::IsDisposed::get()
{
	Lock lock(this);
	return (m_flowSshCpp == nullptr);
}

bool TransferHandler::Success::get()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return m_flowSshCpp->Success();
}

TransferErr^ TransferHandler::GetError()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return gcnew FlowSshNet::TransferErr(m_flowSshCpp->GetError());
}

TransferStat^ TransferHandler::GetTransferStat()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	return gcnew FlowSshNet::TransferStat(m_flowSshCpp->GetTransferStat());
}

void TransferHandler::OnTransfer::add(TransferEventHandler^ del)
{
	m_del = static_cast<TransferEventHandler^>(Delegate::Combine(m_del, del));
}

void TransferHandler::OnTransfer::remove(TransferEventHandler^ del)
{
	m_del = static_cast<TransferEventHandler^>(Delegate::Remove(m_del, del));
}

bool TransferHandler::OnTransfer::raise(Object^ sender, bool done, TransferStat^ transferStat)
{
	if (m_del != nullptr) return m_del->Invoke(sender, done, transferStat);
	else return true;
}

bool TransferHandler::Raise_OnTransfer(bool done, TransferStat^ transferStat)
{
	try { return OnTransfer(this, done, transferStat); }
	catch (System::Exception^ e)
	{
		SshNet::Raise_OnExceptionInEvent(this, false, e);
		return false;
	}
}

void TransferHandler::Raise_OnError(TransferErr^ error)
{
	try { OnError(this, error); }
	catch (System::Exception^ e) { SshNet::Raise_OnExceptionInEvent(this, false, e); }
}

FlowSshCpp::RefPtr<TransferHandlerCpp> TransferHandler::GetFlowSshCpp()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	FlowSshCpp::RefPtr<TransferHandlerCpp> flowCppRefPtr(m_flowSshCpp);
	return flowCppRefPtr; 
}


// ClientSftpChannel

ClientSftpChannel::ClientSftpChannel(ClientBase^ client)
{
	if (client == nullptr) throw gcnew ArgumentNullException();

	m_flowSshCpp = new ClientSftpChannelCpp(client->GetFlowSshCpp(), this);
	m_flowSshCpp->AddRef();

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

ClientSftpChannel::~ClientSftpChannel()
{
	Lock lock(this);
	if (m_flowSshCpp)
	{
		m_flowSshCpp->FlowSshNetDisposed();
		this->!ClientSftpChannel();
		m_client = nullptr;
	}
}

ClientSftpChannel::!ClientSftpChannel()
{
	Lock lock(this);
	if (m_flowSshCpp)
	{
		try { m_flowSshCpp->Close(NoCppProgress()); }
		catch (System::Exception^) {}

		m_flowSshCpp->Release();
		m_flowSshCpp = nullptr;
	}
}

bool ClientSftpChannel::IsDisposed::get()
{
	Lock lock(this);
	return (m_flowSshCpp == nullptr);
}

ClientBase^ ClientSftpChannel::GetClient()
{
	Lock lock(this);
	return m_client;
}

void ClientSftpChannel::Open(ProgressHandler^ progress)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	FLOWSSHNET_REQUEST(ProgressHandler, progress, this,
	m_flowSshCpp->Open(ProgressHandler::S_GetFlowSshCpp(progress)));
}

void ClientSftpChannel::Open(String^ subsystem, ProgressHandler^ progress)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> subsystemPin = PtrToStringChars(subsystem);
	FLOWSSHNET_REQUEST(ProgressHandler, progress, this,
	m_flowSshCpp->Open(subsystemPin, ProgressHandler::S_GetFlowSshCpp(progress)));
}

void ClientSftpChannel::RealPath(String^ queryPath, RealPathHandler^ realPath)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> queryPathPin = PtrToStringChars(queryPath);
	FLOWSSHNET_REQUEST(RealPathHandler, realPath, this,
	m_flowSshCpp->RealPath(queryPathPin, RealPathHandler::S_GetFlowSshCpp(realPath)));
}

void ClientSftpChannel::Stat(String^ path, AttrFlags desiredAttrFlags, StatHandler^ stat)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> pathPin = PtrToStringChars(path);
	FLOWSSHNET_REQUEST(StatHandler, stat, this,
	m_flowSshCpp->Stat(pathPin, static_cast<unsigned int>(desiredAttrFlags), StatHandler::S_GetFlowSshCpp(stat)));
}

void ClientSftpChannel::SetStat(String^ path, FileAttrs^ fileAttrs, SftpHandler^ response)
{
	if (fileAttrs == nullptr) throw gcnew ArgumentNullException();

	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> pathPin = PtrToStringChars(path);
	FLOWSSHNET_REQUEST(SftpHandler, response, this,
	m_flowSshCpp->SetStat(pathPin, fileAttrs->ToFlowCpp(), SftpHandler::S_GetFlowSshCpp(response)));
}

void ClientSftpChannel::MkDir(String^ path, FileAttrs^ fileAttrs, SftpHandler^ response)
{
	if (fileAttrs == nullptr) throw gcnew ArgumentNullException();

	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> pathPin = PtrToStringChars(path);
	FLOWSSHNET_REQUEST(SftpHandler, response, this,
	m_flowSshCpp->MkDir(pathPin, fileAttrs->ToFlowCpp(), SftpHandler::S_GetFlowSshCpp(response)));
}

void ClientSftpChannel::RmDir(String^ path, SftpHandler^ response)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> pathPin = PtrToStringChars(path);
	FLOWSSHNET_REQUEST(SftpHandler, response, this,
	m_flowSshCpp->RmDir(pathPin, SftpHandler::S_GetFlowSshCpp(response)));
}

void ClientSftpChannel::Remove(String^ path, SftpHandler^ response)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> pathPin = PtrToStringChars(path);
	FLOWSSHNET_REQUEST(SftpHandler, response, this,
	m_flowSshCpp->Remove(pathPin, SftpHandler::S_GetFlowSshCpp(response)));
}

void ClientSftpChannel::Rename(String^ oldPath, String^ newPath, SftpHandler^ response)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> oldPathPin = PtrToStringChars(oldPath);
	pin_ptr<const wchar_t> newPathPin = PtrToStringChars(newPath);

	FLOWSSHNET_REQUEST(SftpHandler, response, this,
	m_flowSshCpp->Rename(oldPathPin, newPathPin, SftpHandler::S_GetFlowSshCpp(response)));
}

void ClientSftpChannel::List(String^ path, ListHandler^ list)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> pathPin = PtrToStringChars(path);
	FLOWSSHNET_REQUEST(ListHandler, list, this,
	m_flowSshCpp->List(pathPin, ListHandler::S_GetFlowSshCpp(list)));
}

void ClientSftpChannel::Upload(String^ localPath, String^ remotePath, TransferFlags transferFlags, TransferHandler^ transfer)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> localPathPin  = PtrToStringChars(localPath);
	pin_ptr<const wchar_t> remotePathPin = PtrToStringChars(remotePath);

	FLOWSSHNET_REQUEST(TransferHandler, transfer, this,
	m_flowSshCpp->Upload(localPathPin, remotePathPin, static_cast<unsigned int>(transferFlags), TransferHandler::S_GetFlowSshCpp(transfer)));
}

void ClientSftpChannel::Upload(String^ localPath, String^ remotePath, TransferFlags transferFlags, unsigned int pipelineSize, TransferHandler^ transfer)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> localPathPin  = PtrToStringChars(localPath);
	pin_ptr<const wchar_t> remotePathPin = PtrToStringChars(remotePath);

	FLOWSSHNET_REQUEST(TransferHandler, transfer, this,
	m_flowSshCpp->Upload(localPathPin, remotePathPin, static_cast<unsigned int>(transferFlags), pipelineSize, TransferHandler::S_GetFlowSshCpp(transfer)));
}

void ClientSftpChannel::Download(String^ remotePath, String^ localPath, TransferFlags transferFlags, TransferHandler^ transfer)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> remotePathPin = PtrToStringChars(remotePath);
	pin_ptr<const wchar_t> localPathPin  = PtrToStringChars(localPath);

	FLOWSSHNET_REQUEST(TransferHandler, transfer, this,
	m_flowSshCpp->Download(remotePathPin, localPathPin, static_cast<unsigned int>(transferFlags), TransferHandler::S_GetFlowSshCpp(transfer)));
}

void ClientSftpChannel::Download(String^ remotePath, String^ localPath, TransferFlags transferFlags, unsigned int pipelineSize, TransferHandler^ transfer)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	pin_ptr<const wchar_t> remotePathPin = PtrToStringChars(remotePath);
	pin_ptr<const wchar_t> localPathPin  = PtrToStringChars(localPath);

	FLOWSSHNET_REQUEST(TransferHandler, transfer, this,
	m_flowSshCpp->Download(remotePathPin, localPathPin, static_cast<unsigned int>(transferFlags), pipelineSize, TransferHandler::S_GetFlowSshCpp(transfer)));
}

void ClientSftpChannel::Close(ProgressHandler^ progress)
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	FLOWSSHNET_REQUEST(ProgressHandler, progress, this,
	m_flowSshCpp->Close(ProgressHandler::S_GetFlowSshCpp(progress)));
}

void ClientSftpChannel::Raise_OnSftpVersion(unsigned int version)
{
	try { OnSftpVersion(this, version); }
	catch (System::Exception^ e) { SshNet::Raise_OnExceptionInEvent(this, false, e); }
}

void ClientSftpChannel::Raise_OnChannelClose()
{
	try { OnChannelClose(this); }
	catch (System::Exception^ e) { SshNet::Raise_OnExceptionInEvent(this, false, e); }
}

FlowSshCpp::RefPtr<ClientSftpChannelCpp> ClientSftpChannel::GetFlowSshCpp()
{
	Lock lock(this);
	if (IsDisposed) Helper::ThrowDisposed(this);

	FlowSshCpp::RefPtr<ClientSftpChannelCpp> flowCppRefPtr(m_flowSshCpp);
	return flowCppRefPtr; 
}


} // namespace FlowSshNet
} // namespace Bitvise
