Files
itgmania212121/src/archutils/Win32/CrashHandlerNetworking.cpp
T
2024-10-05 19:25:41 -07:00

773 lines
21 KiB
C++

#include "global.h"
#include "CrashHandlerNetworking.h"
#include "RageThreads.h"
#include "RageLog.h"
#include "RageThreads.h"
#include "RageTimer.h"
#include "RageUtil.h"
#include <cstddef>
#include <cstdint>
#if defined(WINDOWS)
#include <windows.h>
#include <winsock2.h>
#pragma comment(lib, "wsock32.lib")
#pragma comment(lib, "ws2_32.lib")
#endif
/* This has an arch-like abstraction layout, since it's intended to become one.
* It'll be moved out of here as things get polished further. The Winsock and
* BSD implementations will be kept separate, since while they look similar,
* they're different in the details, especially when you want to cleanly support
* cancellation.
*
* Design notes:
* - Allow cancellation at any time. The only thing more annoying than a laggy
* network, is an application that won't immediately respond to cancelling.
* (This is even more important here, since we're using this after a crash.)
* - Assume some operations and/or some architectures are going to have problems
* doing everything nonblockingly, and do everything in a thread. This also allows
* more complex network interactions, since it doesn't need to maintain an equally
* complex state machine.
* - When an operation is cancelled or an error occurs, all operations until the
* next call to Close() become no-ops. This allows lenient error checking; it's
* guaranteed that further calls to Read or Write will not reset the error state.
* - The only function that may be called from another thread without serialization is
* Cancel().
*
* All calls are blocking, except for:
*
* - Cancel(), which is always nonblocking. The operation in progress, if any,
* will be aborted immediately. (The only exception here is incomplete
* implementations, which may block);
* - Close(), if Shutdown() was called first, since the necessary blocking occurs
* during Shutdown;
* - Close(), if an error or cancellation occurred.
*
* Accepting incoming TCP connections is beyond the immediate scope of this interface
* (that would involve a second listener class, which would be a factory for NetworkStream).
* UDP is probably within the scope of this class, but there are some outstanding design
* issues and it's beyond the work I'm doing right now. */
class NetworkStream
{
public:
enum ConnectionType
{
CONN_TCP,
CONN_UDP,
};
NetworkStream() { }
virtual ~NetworkStream() { }
// Open a connection. Must be in STATE_IDLE.
virtual void Open( const RString &sHost, int iPort, ConnectionType ct = CONN_TCP ) = 0;
// Close down a connection. Returns to STATE_IDLE.
virtual void Close() = 0;
// Wait for all sent data to be flushed, and shut down the connection.
virtual void Shutdown() = 0;
/* Read data. Block until any data is received, then return all data
* available. Return the number of bytes read. The return value will always
* be >= 0, unless an error or cancellation occured. */
virtual int Read( void *pBuffer, size_t iSize ) = 0;
/* Write data to the socket. (Design note: we always write all of the data
* unless an error or cancellation occurs, and those states are checked
* with GetState(). If that happens, the number of bytes written is
* meaningless, since it may have simply been buffered and never sent.
* So, this function returns no value.) */
virtual void Write( const void *pBuffer, size_t iSize ) = 0;
/* Cancel the connection. This operation can clear an error state, aborts
* any blocking calls, never fails, and will always result in the socket
* being in STATE_CANCELLED.
* This is true even if the state was STATE_IDLE. For example, the user
* thread may be closing one connection on this object and opening another,
* and the UI thread may call Cancel() in between these operations.
* This operation is threadsafe: a socket can be cancelled from any thread
* while another thread is reading or writing. This is the only function
* that can be called without serialization. */
virtual void Cancel() = 0;
enum State
{
// The stream is closed, and is ready to be opened.
STATE_IDLE,
// The stream is connected and able to send and receive data.
STATE_CONNECTED,
/* The stream has been shut down on either end (either an EOF from
* the other end, or shutdown() being called). */
STATE_SHUTDOWN,
/* When in CANCELLED, all blocking calls fail immediately; this state must be
* cleared explicitly by calling Close(). */
STATE_CANCELLED,
STATE_ERROR
};
State GetState() const { return m_State; }
RString GetError() const { return m_sError; }
protected:
State m_State;
RString m_sError;
};
class NetworkStream_Win32: public NetworkStream
{
public:
NetworkStream_Win32();
~NetworkStream_Win32();
void Open( const RString &sHost, int iPort, ConnectionType ct = CONN_TCP );
void Shutdown();
void Close();
int Read( void *pBuffer, size_t iSize );
void Write( const void *pBuffer, size_t iSize );
void Cancel();
private:
int WaitForCompletionOrCancellation( int iEvent );
void SetError( const RString &sError );
static RString WinSockErrorToString( int iError );
SOCKET m_Socket;
HANDLE m_hResolve;
HWND m_hResolveHwnd;
// This event is signalled on cancellation, to wake us up if we're blocking.
HANDLE m_hCompletionEvent;
RString m_sHost;
int m_iPort;
RageMutex m_Mutex;
};
NetworkStream *CreateNetworkStream()
{
static bool bInitted = false;
if( !bInitted )
{
bInitted = true;
WSADATA WSAData;
WORD iVersionRequested = MAKEWORD(2,0);
if( WSAStartup(iVersionRequested, &WSAData) != 0 )
return nullptr;
}
return new NetworkStream_Win32;
}
/** @brief WinSock implementation of NetworkStream. */
NetworkStream_Win32::NetworkStream_Win32():
m_Mutex( "NetworkTCPSocket" )
{
m_iPort = -1;
m_State = STATE_IDLE;
m_Socket = NULL;
#if defined(WINDOWS)
m_hResolve = nullptr;
m_hResolveHwnd = nullptr;
m_hCompletionEvent = CreateEvent( nullptr, true, false, nullptr );
#endif
}
NetworkStream_Win32::~NetworkStream_Win32()
{
Close();
CloseHandle( m_hCompletionEvent );
}
/* Wait for the specified network event to occur, or cancellation, whichever
* happens first. On cancellation, return -1; on error, return the error
* code; on success, return 0. */
int NetworkStream_Win32::WaitForCompletionOrCancellation( int iEvent )
{
while(1)
{
int iRet = WaitForSingleObject( m_hCompletionEvent, INFINITE );
if( iRet != WAIT_OBJECT_0 )
continue;
m_Mutex.Lock();
// This will reset the event. Do this while we hold the lock.
WSANETWORKEVENTS events;
WSAEnumNetworkEvents( m_Socket, m_hCompletionEvent, &events );
// Was the event signalled due to cancellation?
if( m_State == STATE_CANCELLED )
{
m_Mutex.Unlock();
return -1;
}
m_Mutex.Unlock();
// If the event didn't actually occur, keep waiting.
if( (events.lNetworkEvents & (1<<iEvent)) )
return events.iErrorCode[iEvent];
/* If the socket was closed while we were waiting, stop. Note that when the
* connection closes immediately after sending data, we'll receive both this
* message and FD_READ at the same time. Only do this if the event we really
* want hasn't happened yet. */
if( (events.lNetworkEvents & (1<<FD_CLOSE_BIT)) )
return WSAECONNRESET;
}
}
RString NetworkStream_Win32::WinSockErrorToString( int iError )
{
/* If iError is -1, we were cancelled and WaitForCompletionOrCancellation
* returned it. We won't use the error string. */
if( iError == -1 )
return RString();
switch( iError )
{
case WSAEINTR: return "Interrupted function call.";
case WSAEACCES: return "Permission denied.";
case WSAEFAULT: return "Bad address.";
case WSAEINVAL: return "Invalid argument.";
case WSAEMFILE: return "Too many open files.";
case WSAEWOULDBLOCK: return "Resource temporarily unavailable.";
case WSAEINPROGRESS: return "Operation now in progress.";
case WSAEALREADY: return "Operation already in progress.";
case WSAENOTSOCK: return "Socket operation on nonsocket.";
case WSAEDESTADDRREQ: return "Destination address required.";
case WSAEMSGSIZE: return "Message too long.";
case WSAEPROTOTYPE: return "Protocol wrong type for socket.";
case WSAENOPROTOOPT: return "Bad protocol option.";
case WSAEPROTONOSUPPORT: return "Protocol not supported.";
case WSAESOCKTNOSUPPORT: return "Socket type not supported.";
case WSAEOPNOTSUPP: return "Operation not supported.";
case WSAEPFNOSUPPORT: return "Protocol family not supported.";
case WSAEAFNOSUPPORT: return "Address family not supported by protocol family.";
case WSAEADDRINUSE: return "Address already in use.";
case WSAEADDRNOTAVAIL: return "Cannot assign requested address.";
case WSAENETDOWN: return "Network is down.";
case WSAENETUNREACH: return "Network is unreachable.";
case WSAENETRESET: return "Network dropped connection on reset.";
case WSAECONNABORTED: return "Software caused connection abort.";
case WSAECONNRESET: return "Connection reset by peer.";
case WSAENOBUFS: return "No buffer space available.";
case WSAEISCONN: return "Socket is already connected.";
case WSAENOTCONN: return "Socket is not connected.";
case WSAESHUTDOWN: return "Cannot send after socket shutdown.";
case WSAETIMEDOUT: return "Connection timed out.";
case WSAECONNREFUSED: return "Connection refused.";
case WSAEHOSTDOWN: return "Host is down.";
case WSAEHOSTUNREACH: return "No route to host.";
case WSAEPROCLIM: return "Too many processes.";
case WSASYSNOTREADY: return "Network subsystem is unavailable.";
case WSAVERNOTSUPPORTED: return "Winsock.dll version out of range.";
case WSANOTINITIALISED: return "Successful WSAStartup not yet performed.";
case WSAEDISCON: return "Graceful shutdown in progress. ";
case WSATYPE_NOT_FOUND: return "Class type not found.";
case WSAHOST_NOT_FOUND: return "Host not found.";
case WSATRY_AGAIN: return "Nonauthoritative host not found.";
case WSANO_RECOVERY: return "This is a nonrecoverable error.";
case WSANO_DATA: return "Valid name, no data record of requested type.";
case WSA_INVALID_HANDLE: return "Specified event object handle is invalid.";
case WSA_INVALID_PARAMETER: return "One or more parameters are invalid.";
case WSA_IO_INCOMPLETE: return "Overlapped I/O event object not in signaled state.";
case WSA_IO_PENDING: return "Overlapped operations will complete later.";
case WSA_NOT_ENOUGH_MEMORY: return "Insufficient memory available.";
case WSA_OPERATION_ABORTED: return "Overlapped operation aborted.";
case WSASYSCALLFAILURE: return "System call failure.";
default: return ssprintf( "unknown Winsock error %i", iError );
}
}
void NetworkStream_Win32::SetError( const RString &sError )
{
m_Mutex.Lock();
if( m_State != STATE_CANCELLED )
{
m_sError = sError;
m_State = STATE_ERROR;
}
if( m_Socket != INVALID_SOCKET )
{
closesocket( m_Socket );
m_Socket = INVALID_SOCKET;
}
m_Mutex.Unlock();
}
// WSAAsyncGetHostByName returns events through a window.
#include "MessageWindow.h"
class ResolveMessageWindow: public MessageWindow
{
public:
ResolveMessageWindow(): MessageWindow("DNS notification window")
{
m_iResult = 0;
}
int GetResult() const { return m_iResult; }
protected:
bool HandleMessage( UINT msg, WPARAM wParam, LPARAM lParam )
{
if( msg == WM_USER )
{
m_iResult = WSAGETASYNCERROR( lParam );
StopRunning();
return true;
}
if( msg == WM_USER+1 )
{
m_iResult = 0;
StopRunning();
return true;
}
return false;
}
int m_iResult;
};
void NetworkStream_Win32::Open( const RString &sHost, int iPort, ConnectionType ct )
{
m_Mutex.Lock();
if( m_State == STATE_CANCELLED )
{
m_Mutex.Unlock();
return;
}
// Always shut down a stream completely before reusing it.
ASSERT_M( m_State == STATE_IDLE, ssprintf("%s:%i: %i", sHost.c_str(), iPort, m_State) );
m_sHost = sHost;
m_iPort = iPort;
// Look up the hostname.
hostent *pHost = nullptr;
char pBuf[MAXGETHOSTSTRUCT];
{
pHost = (hostent *) pBuf;
ResolveMessageWindow mw;
m_hResolve = WSAAsyncGetHostByName(
mw.GetHwnd(),
WM_USER,
m_sHost,
(char *) pHost,
MAXGETHOSTSTRUCT
);
m_hResolveHwnd = mw.GetHwnd();
m_Mutex.Unlock();
mw.Run();
m_Mutex.Lock();
m_hResolve = nullptr;
m_hResolveHwnd = nullptr;
if( m_State == STATE_CANCELLED )
{
m_Mutex.Unlock();
return;
}
int iError = mw.GetResult();
if( iError )
{
SetError( ssprintf("DNS error: %s", WinSockErrorToString(iError).c_str() ) );
m_Mutex.Unlock();
return;
}
m_Mutex.Unlock();
}
{
sockaddr_in addr;
addr.sin_addr.s_addr = *(DWORD *)pHost->h_addr_list[0];
addr.sin_family = PF_INET;
addr.sin_port = htons( (uint16_t) iPort );
m_Mutex.Lock();
m_Socket = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP );
if( m_Socket == INVALID_SOCKET )
{
int iError = WSAGetLastError();
SetError( ssprintf("Error creating socket: %s", WinSockErrorToString(iError).c_str() ) );
return;
}
/* Set up the completion event to be signalled when these events occur.
* This also sets the socket to nonblocking. */
WSAEventSelect( m_Socket, m_hCompletionEvent, FD_CONNECT|FD_READ|FD_WRITE|FD_CLOSE );
//fcntl( m_Socket, O_NONBLOCK, 1 );
// Start opening the connection.
int iResult = connect(m_Socket, (SOCKADDR *) &addr, sizeof(addr));
m_Mutex.Unlock();
// We expect EINPROGRESS/WSAEWOULDBLOCK.
if( iResult == SOCKET_ERROR )
{
// Block until the connection attempt completes.
int iError = WSAGetLastError();
if( iError == WSAEWOULDBLOCK )
iError = WaitForCompletionOrCancellation( FD_CONNECT_BIT );
if( iError )
{
SetError( ssprintf("Couldn't connect: %s", WinSockErrorToString(iError).c_str() ) );
return;
}
}
}
m_State = STATE_CONNECTED;
}
void NetworkStream_Win32::Close()
{
if( m_State == STATE_IDLE )
return;
/* If we have an active, stable connection, make sure we flush any data
* completely before closing. If you don't want to do this, call Cancel()
* first. */
Shutdown();
m_Mutex.Lock();
if( m_State == STATE_CANCELLED )
return;
if( m_Socket != INVALID_SOCKET )
closesocket( m_Socket );
m_Socket = INVALID_SOCKET;
m_State = STATE_IDLE;
m_Mutex.Unlock();
}
void NetworkStream_Win32::Shutdown()
{
/* If we're not in a normal, stable state (eg. an error occurred, or we were
* cancelled), don't wait to flush. */
if( m_State != STATE_CONNECTED )
return;
shutdown( m_Socket, SD_BOTH );
m_State = STATE_SHUTDOWN;
}
void NetworkStream_Win32::Cancel()
{
m_Mutex.Lock();
// Mark cancellation.
m_State = STATE_CANCELLED;
// If resolving, abort the resolve.
if( m_hResolve != nullptr )
{
/* When we cancel the request, no message at all will be sent to the window,
* so we need to do it ourself to inform it that it was cancelled. Be sure
* to only do this on successful cancel. */
if( WSACancelAsyncRequest(m_hResolve) == 0 )
PostMessage( m_hResolveHwnd, WM_USER+1, 0, 0 );
}
// Break out if we're waiting in WaitForCompletionOrCancellation().
SetEvent( m_hCompletionEvent );
m_Mutex.Unlock();
}
int NetworkStream_Win32::Read( void *pBuffer, size_t iSize )
{
if( m_State != STATE_CONNECTED )
return 0;
char *p = (char *) pBuffer;
int iRead = 0;
while( iSize > 0 )
{
int iRet = recv( m_Socket, p, iSize, 0 );
if( iRet > 0 )
{
p += iRet;
iSize -= iRet;
iRead += iRet;
continue;
}
if( iRet == 0 )
{
// We hit EOF.
break;
}
int iError = WSAGetLastError();
if( iError == WSAEWOULDBLOCK )
{
// There's no date to read. If we've already received some data, return it.
if( iRead > 0 )
break;
iError = WaitForCompletionOrCancellation( FD_READ_BIT );
if( iError == 0 )
continue;
}
// If the other side closed the connection, just return EOF.
if( iError == WSAECONNRESET )
break;
/* If we're cancelled or hit an error while reading, do return the data
* we managed to get. Be sure not to overwrite CANCELLED with ERROR. */
SetError( ssprintf("Error reading: %s", WinSockErrorToString(iError).c_str() ) );
break;
}
return iRead;
}
void NetworkStream_Win32::Write( const void *pBuffer, size_t iSize )
{
if( m_State != STATE_CONNECTED )
return;
const char *p = (const char *) pBuffer;
while( iSize > 0 )
{
int iRet = send( m_Socket, p, iSize, 0 );
ASSERT( iRet != 0 );
if( iRet > 0 )
{
p += iRet;
iSize -= iRet;
continue;
}
int iError = WSAGetLastError();
if( iError == WSAEWOULDBLOCK )
{
iError = WaitForCompletionOrCancellation( FD_WRITE_BIT );
if( iError == 0 )
continue;
}
SetError( ssprintf("Error writing: %s", WinSockErrorToString(iError).c_str() ) );
return;
}
}
/** @brief Send a set of data over HTTP, as a POST form. */
NetworkPostData::NetworkPostData():
m_Mutex( "NetworkPostData" )
{
m_pStream = CreateNetworkStream();
}
NetworkPostData::~NetworkPostData()
{
delete m_pStream;
}
/** @brief Create a MIME multipart data block from the given set of fields. */
void NetworkPostData::CreateMimeData( const std::map<RString, RString> &mapNameToData, RString &sOut, RString &sMimeBoundaryOut )
{
// Find a non-conflicting mime boundary.
while(1)
{
sMimeBoundaryOut = ssprintf( "--%08i", rand() );
for (auto const &d : mapNameToData)
if( d.second.find(sMimeBoundaryOut) != RString::npos )
continue;
break;
}
for (auto const &d : mapNameToData)
{
sOut += "--" + sMimeBoundaryOut + "\r\n";
sOut += ssprintf( "Content-Disposition: form-data; name=\"%s\"\r\n", d.first.c_str() );
sOut += "\r\n";
sOut += d.second;
sOut += "\r\n";
}
if( sOut.size() )
sOut += "--" + sMimeBoundaryOut + "--\r\n";
}
void NetworkPostData::HttpThread()
{
RString sData, sMimeBoundary;
CreateMimeData( m_Data, sData, sMimeBoundary );
// Stick to HTTP/1.0, since the protocol is simpler.
RString sBuf = ssprintf(
"%s %s HTTP/1.0\r\n"
"Accept: */*\r\n"
"User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)\r\n"
"Host: %s\r\n"
"Cache-Control: no-cache\r\n",
sData.size()? "POST":"GET",
m_sPath.c_str(),
m_sHost.c_str() );
if( sData.size() )
{
// sBuf += "Content-Type: application/x-www-form-urlencoded\r\n"
sBuf += "Content-Type: multipart/form-data; boundary=" + sMimeBoundary + "\r\n";
sBuf += ssprintf( "Content-Length: %i\r\n", sData.size() );
}
sBuf += "\r\n";
if( sData.size() )
sBuf += sData;
/* The "progress" is currently faked; it shows when we've connected, and when
* we've received data. We send and receive too little data to do more. */
SetProgress( 0 );
// Begin connecting.
m_pStream->Open( m_sHost, m_iPort );
SetProgress( 0.25f );
// Send the form.
m_pStream->Write( sBuf.data(), sBuf.size() );
// Read the result.
RString sResult;
while( m_pStream->GetState() == NetworkStream::STATE_CONNECTED )
{
sBuf.clear();
char * buffer = new char[1024];
std::fill(buffer, buffer + 1024, '\0');
int iGot = m_pStream->Read( static_cast<void *>(buffer), 1024 );
RString tmp = buffer;
delete[] buffer;
if (iGot < 0)
{
break;
}
sResult += tmp;
}
SetProgress( 1.0f );
// Parse the results.
int iStart = 0, iSize = -1;
std::map<RString, RString> mapHeaders;
while( 1 )
{
split( sResult, "\n", iStart, iSize, false );
if( iStart == (int) sResult.size() )
break;
RString sLine = sResult.substr( iStart, iSize );
StripCrnl( sLine );
if( sLine.empty() )
{
m_sResult = sResult.substr( iStart+iSize+1 );
break;
}
}
m_bFinished = true;
}
void NetworkPostData::Start( const RString &sHost, int iPort, const RString &sPath )
{
m_bFinished = false;
m_sHost = sHost;
m_iPort = iPort;
m_sPath = sPath;
m_sStatus.clear();
m_fProgress = 0;
m_Thread.SetName( "HTTP thread" );
m_Thread.Create( HttpThread_Start, this );
}
void NetworkPostData::Cancel()
{
m_pStream->Cancel();
m_Thread.Wait();
}
bool NetworkPostData::IsFinished()
{
if( !m_bFinished )
return false;
m_Thread.Wait();
m_bFinished = false;
return true;
}
RString NetworkPostData::GetStatus() const
{
LockMut( m_Mutex );
return m_sStatus;
}
float NetworkPostData::GetProgress() const
{
LockMut( m_Mutex );
return m_fProgress;
}
RString NetworkPostData::GetError() const
{
return m_pStream->GetError();
}
void NetworkPostData::SetProgress( float fProgress )
{
m_Mutex.Lock();
m_fProgress = fProgress;
m_Mutex.Unlock();
}
void NetworkPostData::SetData( const RString &sKey, const RString &sData )
{
m_Data[sKey] = sData;
}
/*
* (c) 2006 Glenn Maynard
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, and/or sell copies of the Software, and to permit persons to
* whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/