// This is a part of the SocketPro package.
// Copyright (C) 2000-2002 UDAParts
// All rights reserved.
//
// This source code is only intended as a supplement to the
// SocketPro Classes Reference and related
// electronic documentation provided with the package.
// See these sources for detailed information regarding this
// UDAParts product.
// Please don't disclose any source code of the software to any person or entity,
//
// Please don't decompile, disassemble, or reverse engineer any object code of
// any portion of the software.
//
// Copyright (c) 2002 by Yuancai (Charlie) Ye
// yekerui@yahoo.com
#ifndef __Socket_BASE_H__
#define __Socket_BASE_H__
#include <winsock2.h>
#ifndef WM_MIN_SOCKETPRO
#define WM_MIN_SOCKETPRO (WM_USER + 0x0100)
#endif
#ifndef WM_MAX_SOCKETPRO
#define WM_MAX_SOCKETPRO 0x0FFF
#endif
#ifndef WM_WSAGetHostByName
#define WM_WSAGetHostByName (WM_USER+0x0377)
#endif
#ifndef WM_WSAGetHostByAddr
#define WM_WSAGetHostByAddr (WM_USER+0x0378)
#endif
#ifndef WM_WSAAsyncGetHostByName
#define WM_WSAAsyncGetHostByName (WM_USER+0x0379)
#endif
#ifndef WM_SOCKET_SVR_NOTIFY
#define WM_SOCKET_SVR_NOTIFY (WM_USER+0x0380)
#endif
#ifndef WM_SOCKET_CLIENT_NOTIFY
#define WM_SOCKET_CLIENT_NOTIFY (WM_USER+0x0381)
#endif
#ifndef WM_SOCKET_THREAD_STARTING
#define WM_SOCKET_THREAD_STARTING (WM_SOCKET_CLIENT_NOTIFY + 1)
#endif
#ifndef WM_SOCKET_THREAD_PROCESSING
#define WM_SOCKET_THREAD_PROCESSING (WM_SOCKET_CLIENT_NOTIFY + 2)
#endif
#ifndef WM_SOCKET_THREAD_ENDING
#define WM_SOCKET_THREAD_ENDING (WM_SOCKET_CLIENT_NOTIFY + 3)
#endif
#ifndef WM_SOCKET_REQUEST_DONE
#define WM_SOCKET_REQUEST_DONE (WM_SOCKET_CLIENT_NOTIFY + 4)
#endif
#ifndef WM_SOCKET_ASKFOR_PROCESS
#define WM_SOCKET_ASKFOR_PROCESS (WM_SOCKET_CLIENT_NOTIFY + 5)
#endif
#ifndef WM_SOCKET_CLEAN_DEAD_CLIENT
#define WM_SOCKET_CLEAN_DEAD_CLIENT (WM_SOCKET_CLIENT_NOTIFY + 6)
#endif
#ifdef DONT_NEED_NETBASE_DLL //need original source codes
#define NETBASE_EXPORT
#else
#ifdef BUILD_NETBASE
#define NETBASE_EXPORT __declspec(dllexport)
#else
#define NETBASE_EXPORT __declspec(dllimport)
#endif
#endif
#if !defined(BUILD_NETBASE) && !defined(DONT_NEED_NETBASE_DLL)
#ifdef __BORLANDC__ //Borland compiler
#ifdef _DEBUG
#pragma comment(lib, "NetBasBD.lib")
#else
#pragma comment(lib, "NetBasBR.lib")
#endif
#else //Visual C++
#ifdef _DEBUG
#pragma comment(lib, "NetBaseR.lib")
#else
#pragma comment(lib, "NetBaseR.lib")
#endif
#endif
#endif
bool NETBASE_EXPORT InitSocket();
void NETBASE_EXPORT UninitSocket();
extern NETBASE_EXPORT char g_strNetBaseKey[40];
extern NETBASE_EXPORT CRITICAL_SECTION g_CriticalSection;
bool NETBASE_EXPORT IsDead(SOCKET hSocket);
typedef HRESULT SockMethodID;
typedef HRESULT (*SLOW_FUNC) (SOCKET hSocket, SockMethodID nMethodID, void *pBuffer, DWORD nBufferLen, void* pSockWithThread);
class CSockWithThread;
class CAsySocketClient;
class CAsySocketSvr;
class CSockThreadInfo;
class NETBASE_EXPORT CAsySocket
{
public:
CAsySocket();
virtual ~CAsySocket();
public:
virtual void ShutDown(int nHow=SD_SEND);
virtual void Close(int nHow=SD_SEND);
virtual bool Create(UINT nSocketPort, LPCTSTR lpszSocketAddress=NULL);
bool GetSockAddr(UINT& nSocketPort, LPSTR& strIPAddress);
bool GetSockAddr( SOCKADDR* lpSockAddr, int* lpSockAddrLen );
bool AsyncSelect(long lNewEvent);
bool GetSockOpt(int nOptionName, void* pnOptionValue, int nOptionLen, int nLevel=SOL_SOCKET);
bool SetSockOpt(int nOptionName, const void* pnOptionValue, int nOptionLen, int nLevel=SOL_SOCKET );
bool IOCtl(long nCommand, DWORD& nArgument);
HWND GetWnd();
bool SetWnd(HWND hWnd);
bool IsOpen();
void SetInstance(HINSTANCE hInstance);
HINSTANCE GetInstance();
void SetAddressFormat(int nAddressFormat=PF_INET);
void SetSocketType(int nSocketType=SOCK_STREAM);
void SetProtocol(int nProtocol);
int GetProtocol();
static bool GetLocalName (LPSTR strLocalName, int nLocalNameLen);
static int GetLastError( );
//native SOCKET messages
virtual void OnReceive(WPARAM hSocket, LPARAM lError)=0;
virtual void OnAccept(WPARAM hSocket, LPARAM lError);
virtual void OnConnect(WPARAM hSocket, LPARAM lError);
virtual void OnSend(WPARAM hSocket, LPARAM lError);
virtual void OnClose(WPARAM hSocket, LPARAM lError);
virtual void OnMessage(UINT Msg, WPARAM wParam, LPARAM lParam);
//usually, you will not meet the following events
virtual void OnAddressListChange(WPARAM hSocket, LPARAM lError);
virtual void OnRoutingInterfaceChange(WPARAM hSocket, LPARAM lError);
virtual void OnGroupQoS(WPARAM hSocket, LPARAM lError);
virtual void OnQoS(WPARAM hSocket, LPARAM lError);
virtual void OnOutOfBandData(WPARAM hSocket, LPARAM lError);
private:
void Messagehandler(UINT Msg, WPARAM hSocket, LPARAM lParam);
bool CreateWnd();
bool Socket();
bool Bind(UINT nSocketPort, LPCTSTR lpszSocketAddress = NULL );
bool Bind(const SOCKADDR* lpSockAddr, int nSockAddrLen );
public:
SOCKET m_hSocket;
int m_nRtn;
protected:
long m_lEvent;
int m_nSocketType;
unsigned int m_nMsg;
int m_nAddressFormat;
private:
HINSTANCE m_hInstance;
int m_nProtocol;
HWND m_hWnd;
bool m_bCreated;
WNDPROC m_pWinProc;
LPCTSTR m_strWndClassName;
friend class CAsySocketClient;
friend class CAsySocketSvr;
friend LRESULT CALLBACK ODBDefWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
};
#define Sock_ErrorOffsetSvr 0x88000000
class NETBASE_EXPORT CAsySocketSvr : public CAsySocket
{
public:
CAsySocketSvr();
virtual ~CAsySocketSvr();
public:
virtual CAsySocketClient* CreateClientSocket()=0;
virtual void Close(int nHow=SD_SEND);
bool Listen(int nMaxBacklog=5);
UINT GetClientCount();
CAsySocketClient* GetClient(UINT nIndex) const;
CAsySocketClient* FindClient(SOCKET hSocket) const;
virtual bool ResetSocketClient(CAsySocketClient* pOldSocketClient, CAsySocketClient* pNewSocketClient);
virtual void OnAccept(WPARAM hSocket, LPARAM lError);
virtual void OnClose(WPARAM hSocket, LPARAM lError);
virtual void OnReceive(WPARAM hSocket, LPARAM lError);
virtual void OnMessage(UINT Msg, WPARAM wParam, LPARAM lParam);
protected:
virtual bool Accept(WPARAM hSocket, LPCONDITIONPROC pConditionProc=NULL, DWORD dwCallbackData=0, SOCKADDR* pSockAddr=NULL, int* pnSockAddrLen=NULL);
private:
static VOID CALLBACK PingTimerProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime);
static DWORD WINAPI PingThreadProc(LPVOID lpParameter);
void OnDeadClientFound(unsigned long nAddr);
private:
HANDLE m_hThreadPing;
DWORD m_dwIDPing;
};
extern NETBASE_EXPORT CAsySocketSvr *g_pSocketSvr;
#define __MAX_UID_LEN__ 128
#define __MAX_PASSWORD_LEN__ 128
#define Sock_ErrorOffsetClient 0x80000000
#define SOCKET_SERVER_SECURITY_CHECK_FALIED (Sock_ErrorOffsetSvr + 0x01000000)
class NETBASE_EXPORT CAsySocketClient : public CAsySocket
{
public:
CAsySocketClient();
public:
//must implement your run function
virtual bool Run()=0;
virtual void Close(int nHow=SD_SEND);
virtual void OnWSAGetHostByName(WPARAM wParam, short nError, short nBufferLen);
virtual void OnWSAGetHostByAddr(WPARAM wParam, short nError, short nBufferLen);
virtual void OnConnect(WPARAM hSocket, LPARAM lError);
virtual void OnMessage(UINT Msg, WPARAM wParam, LPARAM lParam);
virtual unsigned long Receive( void* pBuf, DWORD dwBufLen, LPWSAOVERLAPPED lpOverlapped=NULL, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE=NULL, DWORD nFlags = 0 );
virtual unsigned long ReceiveFrom(void* pBuf, DWORD dwBufLen, SOCKADDR* pSockAddr, int* pnSockAddrLen, LPWSAOVERLAPPED lpOverlapped=NULL, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE=NULL, DWORD dwFlags = 0 );
virtual unsigned long ReceiveFrom(void* pBuf, DWORD dwBufLen, LPSTR& strSocketAddress, UINT& nSocketPort, LPWSAOVERLAPPED lpOverlapped=NULL, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE=NULL, DWORD dwFlags = 0 );
virtual unsigned long Send( const void* pBuf, DWORD dwBufLen, LPWSAOVERLAPPED lpOverlapped=NULL, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE=NULL, DWORD nFlags = 0 );
virtual unsigned long SendTo(const void* pBuf, DWORD dwBufLen, UINT nHostPort, LPCTSTR lpszHostAddress=NULL, LPWSAOVERLAPPED lpOverlapped=NULL, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE=NULL, DWORD dwFlags=0);
virtual unsigned long SendTo(const void* pBuf, DWORD dwBufLen, const SOCKADDR* pSockAddr, UINT nSockAddrLen, LPWSAOVERLAPPED lpOverlapped=NULL, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE=NULL, DWORD dwFlags=0);
virtual unsigned long SendBigChunk(const void* pBuf, DWORD dwLen, DWORD dwFlags=0);
virtual unsigned long RecvBigChunk(const void* pBuf, DWORD dwLen, DWORD dwFlags=0);
virtual bool Connect( LPCTSTR lpszHostAddress, UINT nHostPort, LPWSABUF pCallerData=NULL, LPWSABUF pCalleeData=NULL, LPQOS pSQOS=NULL);
virtual bool Connect( const SOCKADDR* lpSockAddr, UINT nSockAddrLen, LPWSABUF pCallerData=NULL, LPWSABUF pCalleeData=NULL, LPQOS pSQOS=NULL);
void SetUseMsgPump(bool bUseMsgPump=true);
bool GetPeerName(UINT& nPeerPort, LPSTR& strPeerName);
bool GetPeerName(SOCKADDR* lpSockAddr, int* pnSockAddrLen);
void SetConnectTimeout(DWORD nConnectTimeout);
DWORD GetConnectTimeout();
long GetClassID(){return m_nClassID;}
const hostent* GetHostByName(LPCSTR strHostName, bool bBlocking=false);
const hostent* GetHostByAddr(LPCSTR strHostAddr, int nAddressFormat=PF_INET, bool bBlocking=false);
virtual void OnReceive(WPARAM hSocket, LPARAM lError);
virtual bool IsPermitted();
virtual bool IsFromSockWithThread();
public:
char m_strUID[1+__MAX_UID_LEN__];
char m_strPassword[1+__MAX_PASSWORD_LEN__];
protected:
long m_nClassID;
HANDLE m_hWSAAsync;
UINT m_nHostPort;
char m_strHostEntBuffer[MAXGETHOSTSTRUCT];
private:
virtual void OnWSAAsyncGetHostByName(WPARAM wParam, short nError, short nBufferLen);
static void CALLBACK SocketTimerProc(HWND hWnd, UINT uMsg, UINT idEvent, DWORD dwTime);
bool PumpMessages(UINT uStopFlag);
void DestroyTimer();
DWORD m_nConnectTimeout;
UINT m_nTimer;
bool m_bNeedPumpMessages;
LPWSABUF m_pCallerData;
LPWSABUF m_pCalleeData;
LPQOS m_pSQOS;
friend CAsySocketSvr;
};
class NETBASE_EXPORT CStreamHeader
{
public:
CStreamHeader(){memset(this, 0, sizeof(CStreamHeader));}
public:
DWORD m_nLen; //buffer length, not including the structure size
HRESULT m_nValue; //indicating a methodid or a returned result, or others
};
#define CREATE_START_SOCK_CLIENT 0xA00000AA
#define SockWarningFromSvr 0x08000000
#define KILL_WORKER_THREAD (-1)
#define ENABLE_NAGLE (-2)
#define DISABLE_NAGLE (-3)
#define SET_SO_RCVBUF (-4)
#define SET_SO_SNDBUF (-5)
#define ENABLE_SO_BROADCAST (-6)
#define DISABLE_SO_BROADCAST (-7)
#define SET_SO_RCVTIMEO (-8)
#define SET_SO_SNDTIMEO (-9)
#define GET_NAGLE (-10)
#define GET_SO_RCVBUF (-11)
#define GET_SO_SNDBUF (-12)
#define GET_SO_BROADCAST (-13)
#define GET_SO_RCVTIMEO (-14)
#define GET_SO_SNDTIMEO (-15)
class NETBASE_EXPORT CSockWithThreadSvr : public CAsySocketSvr
{
public:
CSockWithThreadSvr();
public:
void SetKillIdleThreadTime(DWORD dwTime);
virtual void Close(int nHow=SD_SEND);
virtual bool Create(UINT nSocketPort, LPCTSTR lpszSocketAddress=NULL);
virtual void OnMessage(UINT Msg, WPARAM wParam, LPARAM lParam);
virtual void OnJobDone(WPARAM wParam, LPARAM lParam);
virtual void OnSocketThreadStarting(WPARAM wParam, LPARAM lParam);
virtual void OnSocketThreadProcessing(WPARAM wParam, LPARAM lParam);
virtual void OnSocketThreadEnding(WPARAM wParam, LPARAM lParam);
private:
static void CALLBACK SocketSvrTimerProc(HWND hWnd, UINT uMsg, UINT idEvent, DWORD dwTime);
private:
DWORD m_dwTimerInterval;
UINT m_nTimer;
};
class NETBASE_EXPORT CSockThreadInfo
{
public:
CSockThreadInfo();
virtual ~CSockThreadInfo();
public:
unsigned long RecvBuffer(void *pBuffer, DWORD dwBufferLen);
void Empty();
private:
void AppendBuffer(void *pBuffer, DWORD dwBufferLen);
public:
SLOW_FUNC m_pSlowProcess;
SockMethodID *m_pMethodIDs;
unsigned short m_nCountOfMethods;
void *m_pOtherInfo;
private:
SOCKET m_hSocket;
HANDLE m_hThreadHandle;
DWORD m_dwThreadID;
DWORD m_dwTickCount;
DWORD m_dwRefByWorkerThread;
void *m_pBuffer;
unsigned long m_nBufferLen;
friend CSockWithThread;
friend CSockWithThreadSvr;
};
class NETBASE_EXPORT CSockWithThread : public CAsySocketClient
{
public:
CSockWithThread();
virtual ~CSockWithThread();
public:
DWORD GetTickCountJobDone();
bool IsJobDone(DWORD dwThreadID=0);
bool IsJobDone(SockMethodID nMethodID);
unsigned short GetCountOfThreadInfos();
DWORD GetThreadID(unsigned short nIndex);
virtual void Close(int nHow=SD_SEND);
virtual unsigned long SendBigChunk(const void* pBuf, DWORD dwLen, DWORD dwFlags=0);
virtual bool Run();
virtual void OnSocketThreadStarting(WPARAM wParam, LPARAM lParam);
virtual void OnSocketThreadProcessing(WPARAM wParam, LPARAM lParam);
virtual void OnJobDone(WPARAM wParam, LPARAM lParam);
virtual void OnSocketThreadEnding(WPARAM wParam, LPARAM lParam);
virtual bool IsFromSockWithThread();
virtual bool IsKillIdleThread();
virtual bool QuickProcess(SockMethodID nMethodID)=0;
virtual bool RetrieveHeader();
protected:
CSockThreadInfo* GetSockThreadInfo(DWORD dwThreadID=0) const;
CSockThreadInfo* GetSockThreadInfo(SockMethodID nMethodID) const;
private:
HRESULT EnaleNagle(BOOL bTrue);
HRESULT SetRcvBuf();
HRESULT SetSndBuf();
HRESULT EnableBroadcast(BOOL bTrue);
HRESULT SetRcvTimeout();
HRESULT SetSndTimeout();
bool Blocking();
bool ExecuteFunc(SockMethodID nMethodID);
bool CreateWorkerThread(SockMethodID nMethodID);
void KillAllThreads();
void KillWorkerThread(DWORD dwThreadID);
public:
CAsySocketClient *m_pClientStart;
LPTHREAD_START_ROUTINE m_pThreadStartFunc;
protected:
CStreamHeader m_StreamHeader;
DWORD m_nBytes;
DWORD m_dwRefByWorkerThread;
DWORD m_dwTickCount;
CSockThreadInfo *m_pSockThreadInfo;
unsigned short m_nCountOfThreadInfos;
bool m_bUseRcvBuf;
private:
friend CSockWithThreadSvr;
static DWORD WINAPI DefaultThreadProc(LPVOID lpParameter);
static void CALLBACK CleanThreadTimerProc(HWND hWnd, UINT uMsg, UINT idEvent, DWORD dwTime);
};
#define SOCKET_SECURITY_CHECK_FALIED (Sock_ErrorOffsetSvr + 0x01000000)
class CMultipleStart : public CAsySocketClient
{
public:
CMultipleStart()
{
m_nClassID=CREATE_START_SOCK_CLIENT;
}
protected:
virtual bool RetrieveHeader()
{
BYTE strHeader[sizeof(m_StreamHeader)+sizeof(m_strUID)+sizeof(m_strPassword)]={0};
if(Receive(strHeader, sizeof(strHeader))< sizeof(m_StreamHeader))
{
m_StreamHeader.m_nLen=0;
m_StreamHeader.m_nValue=CAsySocketClient::GetLastError()+Sock_ErrorOffsetSvr;
SendBigChunk(&m_StreamHeader, sizeof(CStreamHeader));
return false;
}
memcpy(&m_StreamHeader, strHeader, sizeof(m_StreamHeader));
memcpy(m_strUID, strHeader+sizeof(m_StreamHeader), sizeof(m_strUID));
memcpy(m_strPassword, strHeader+sizeof(m_StreamHeader)+sizeof(m_strUID), sizeof(m_strPassword));
return true;
}
protected:
CStreamHeader m_StreamHeader;
};
#define SWITCH_TO_OBJECT(CMySockWithThread) {if(!IsPermitted()) \
{ \
m_StreamHeader.m_nValue=FILE_SECURITY_CHECK_FALIED; \
m_StreamHeader.m_nLen=0; \
SendBigChunk(&m_StreamHeader, sizeof(CStreamHeader)); \
::SendMessage(g_pSocketSvr->GetWnd(), WM_SOCKET_SVR_NOTIFY, m_hSocket, FD_CLOSE); \
return false; \
} \
m_StreamHeader.m_nLen=0; \
CMySockWithThread *pNewSockClient=new CMySockWithThread; \
if(pNewSockClient) \
{ \
g_pSocketSvr->ResetSocketClient(this, pNewSockClient); \
m_StreamHeader.m_nValue=S_OK; \
pNewSockClient->SendBigChunk(&m_StreamHeader, sizeof(CStreamHeader)); \
pNewSockClient->m_pClientStart=this; \
return true; \
} \
m_StreamHeader.m_nValue=E_OUTOFMEMORY; \
SendBigChunk(&m_StreamHeader, sizeof(CStreamHeader)); \
return false;}
#include "sfiledef.h"
class NETBASE_EXPORT CFileSockClient : public CSockWithThread
{
public:
CFileSockClient();
virtual ~CFileSockClient();
public:
virtual bool Run();
protected:
virtual void RemoveDirectory();
virtual void GetCurrentDirectory();
virtual void CreateDirectory();
virtual void FindNextFile();
virtual void FindFirstFile();
virtual void FindFile();
virtual void MoveFile();
virtual void DeleteFile();
virtual void CloseFile();
virtual void CreateFile();
virtual void FindClose();
virtual void SetFileAttributes();
virtual void GetDiskFreeSpaceEx();
virtual void FlushFileBuffers();
virtual void SetCurrentDirectory();
virtual void SetFilePointer();
virtual bool ReadFile();
virtual bool WriteFile();
protected:
virtual bool QuickProcess(long nMethodID);
private:
void GetFileName();
private:
char m_strFile[_MAX_PATH];
BYTE *m_pBuffer;
HANDLE m_hFile;
HANDLE m_hFindFile;
DWORD m_nNumberOfBytesToRead;
CSockThreadInfo m_SockThreadInfo;
SockMethodID m_pMethodIDs[5];
private:
static HRESULT ReadProc(SOCKET hSocket, SockMethodID nMethodID, void *pBuffer, DWORD nBufferLen, void* pSockWithThread);
static HRESULT WriteProc(SOCKET hSocket, SockMethodID nMethodID, void *pBuffer, DWORD nBufferLen, void* pSockWithThread);
static HRESULT FileThreadProc(SOCKET hSocket, SockMethodID nMethodID, void *pBuffer, DWORD nBufferLen, void* pSockWithThread);
static HRESULT WriteFile(SOCKET hSocket, SockMethodID nMethodID, void *pBuffer, DWORD nBufferLen, void* pSockWithThread);
static HRESULT ReadFile(SOCKET hSocket, SockMethodID nMethodID, void *pBuffer, DWORD nBufferLen, void* pSockWithThread);
static HRESULT CopyFile(SOCKET hSocket, SockMethodID nMethodID, void *pBuffer, DWORD nBufferLen, void* pSockWithThread);
};
#define SOCKET_BROKER_CLIENT 0xB00000AA
#define BROKER_CONNET_TO_TARGET (0x01000000+1)
#define BROKER_DISCONNECT_TO_TARGET (0x01000000+2)
#define BROKER_EXECUTE_COMMAND (0x01000000+3)
#define BROKER_IS_CONNECTED_TO_DESTINATION (0x01000000+4)
class NETBASE_EXPORT CSockBroker : public CSockWithThread
{
public:
CSockBroker();
private:
CSockThreadInfo m_BrokerSockThreadInfo;
SockMethodID m_pMethodIDs[4];
protected:
virtual bool QuickProcess(SockMethodID nMethodID);
private:
static DWORD WINAPI BrokerThreadProc(LPVOID lpParameter);
};
#endif
// This is a part of the SocketPro package.
// Copyright (C) 2000-2002 UDAParts
// All rights reserved.
//
// This source code is only intended as a supplement to the
// SocketPro Classes Reference and related
// electronic documentation provided with the package.
// See these sources for detailed information regarding this
// UDAParts product.
// Please don't disclose any source code of the software to any person or entity,
//
// Please don't decompile, disassemble, or reverse engineer any object code of
// any portion of the software.
//
// Copyright (c) 2002 by Yuancai (Charlie) Ye
// yekerui@yahoo.com