From 40504ab11fcaaf050f0a53e7826a146593488bc3 Mon Sep 17 00:00:00 2001 From: NH Date: Thu, 17 Apr 2025 20:38:35 +0800 Subject: [PATCH] BG --- .gitignore | 6 + ArqHelper.cpp | 40 + ArqHelper.h | 791 +++ CMakeLists.txt | 23 + HPSocket-SSL.cpp | 200 + HPSocket.cpp | 749 +++ HPSocket4C-SSL.cpp | 380 ++ HPSocket4C.cpp | 3747 ++++++++++++ HPThreadPool.cpp | 496 ++ HPThreadPool.h | 168 + HttpAgent.cpp | 479 ++ HttpAgent.h | 216 + HttpClient.cpp | 598 ++ HttpClient.h | 397 ++ HttpCookie.cpp | 927 +++ HttpCookie.h | 194 + HttpHelper.cpp | 469 ++ HttpHelper.h | 1385 +++++ HttpServer.cpp | 615 ++ HttpServer.h | 223 + InternalDef.h | 132 + MiscHelper.cpp | 51 + MiscHelper.h | 157 + SSLAgent.cpp | 229 + SSLAgent.h | 107 + SSLClient.cpp | 150 + SSLClient.h | 105 + SSLHelper.cpp | 1200 ++++ SSLHelper.h | 496 ++ SSLServer.cpp | 229 + SSLServer.h | 115 + SocketHelper.cpp | 1669 ++++++ SocketHelper.h | 959 +++ SocketObject4C.h | 770 +++ TcpAgent.cpp | 1306 ++++ TcpAgent.h | 306 + TcpClient.cpp | 723 +++ TcpClient.h | 248 + TcpPackAgent.cpp | 24 + TcpPackAgent.h | 221 + TcpPackClient.cpp | 24 + TcpPackClient.h | 126 + TcpPackServer.cpp | 24 + TcpPackServer.h | 221 + TcpPullAgent.cpp | 24 + TcpPullAgent.h | 173 + TcpPullClient.cpp | 24 + TcpPullClient.h | 89 + TcpPullServer.cpp | 24 + TcpPullServer.h | 173 + TcpServer.cpp | 1187 ++++ TcpServer.h | 306 + UdpArqClient.cpp | 186 + UdpArqClient.h | 114 + UdpArqServer.cpp | 253 + UdpArqServer.h | 120 + UdpCast.cpp | 701 +++ UdpCast.h | 219 + UdpClient.cpp | 830 +++ UdpClient.h | 234 + UdpNode.cpp | 757 +++ UdpNode.h | 198 + UdpServer.cpp | 1231 ++++ UdpServer.h | 301 + brotli/decode.h | 409 ++ brotli/encode.h | 501 ++ brotli/port.h | 305 + brotli/shared_dictionary.h | 100 + brotli/types.h | 83 + common/BufferPool.cpp | 298 + common/BufferPool.h | 869 +++ common/BufferPtr.h | 198 + common/CriSec.h | 291 + common/Event.cpp | 24 + common/Event.h | 530 ++ common/FileHelper.cpp | 237 + common/FileHelper.h | 123 + common/FuncHelper.cpp | 393 ++ common/FuncHelper.h | 426 ++ common/GeneralHelper.h | 40 + common/IODispatcher.cpp | 348 ++ common/IODispatcher.h | 272 + common/PollHelper.cpp | 62 + common/PollHelper.h | 51 + common/PrivateHeap.h | 152 + common/RWLock.cpp | 228 + common/RWLock.h | 128 + common/RingBuffer.h | 1731 ++++++ common/STLHelper.h | 1068 ++++ common/Semaphore.h | 117 + common/SignalHandler.h | 183 + common/Singleton.h | 117 + common/StringT.h | 930 +++ common/SysHelper.cpp | 66 + common/SysHelper.h | 186 + common/Thread.cpp | 50 + common/Thread.h | 612 ++ common/http/Readme.txt | 11 + common/http/llhttp.h | 903 +++ common/http/llhttp_api.c | 520 ++ common/http/llhttp_internal.c | 10180 ++++++++++++++++++++++++++++++++ common/http/llhttp_support.c | 170 + common/http/llhttp_url.c | 640 ++ common/http/llhttp_url.h | 60 + common/kcp/Readme.txt | 4 + common/kcp/ikcp.c | 1307 ++++ common/kcp/ikcp.h | 416 ++ hpsocket/GlobalDef.h | 278 + hpsocket/GlobalErrno.h | 252 + hpsocket/HPSocket-SSL.h | 338 ++ hpsocket/HPSocket.h | 818 +++ hpsocket/HPSocket4C-SSL.h | 411 ++ hpsocket/HPSocket4C.h | 2833 +++++++++ hpsocket/HPTypeDef.h | 600 ++ hpsocket/SocketInterface.h | 3114 ++++++++++ libbrotli.a | Bin 0 -> 1020996 bytes libbrotli.lib | Bin 0 -> 1020996 bytes 117 files changed, 62822 insertions(+) create mode 100644 .gitignore create mode 100644 ArqHelper.cpp create mode 100644 ArqHelper.h create mode 100644 CMakeLists.txt create mode 100644 HPSocket-SSL.cpp create mode 100644 HPSocket.cpp create mode 100644 HPSocket4C-SSL.cpp create mode 100644 HPSocket4C.cpp create mode 100644 HPThreadPool.cpp create mode 100644 HPThreadPool.h create mode 100644 HttpAgent.cpp create mode 100644 HttpAgent.h create mode 100644 HttpClient.cpp create mode 100644 HttpClient.h create mode 100644 HttpCookie.cpp create mode 100644 HttpCookie.h create mode 100644 HttpHelper.cpp create mode 100644 HttpHelper.h create mode 100644 HttpServer.cpp create mode 100644 HttpServer.h create mode 100644 InternalDef.h create mode 100644 MiscHelper.cpp create mode 100644 MiscHelper.h create mode 100644 SSLAgent.cpp create mode 100644 SSLAgent.h create mode 100644 SSLClient.cpp create mode 100644 SSLClient.h create mode 100644 SSLHelper.cpp create mode 100644 SSLHelper.h create mode 100644 SSLServer.cpp create mode 100644 SSLServer.h create mode 100644 SocketHelper.cpp create mode 100644 SocketHelper.h create mode 100644 SocketObject4C.h create mode 100644 TcpAgent.cpp create mode 100644 TcpAgent.h create mode 100644 TcpClient.cpp create mode 100644 TcpClient.h create mode 100644 TcpPackAgent.cpp create mode 100644 TcpPackAgent.h create mode 100644 TcpPackClient.cpp create mode 100644 TcpPackClient.h create mode 100644 TcpPackServer.cpp create mode 100644 TcpPackServer.h create mode 100644 TcpPullAgent.cpp create mode 100644 TcpPullAgent.h create mode 100644 TcpPullClient.cpp create mode 100644 TcpPullClient.h create mode 100644 TcpPullServer.cpp create mode 100644 TcpPullServer.h create mode 100644 TcpServer.cpp create mode 100644 TcpServer.h create mode 100644 UdpArqClient.cpp create mode 100644 UdpArqClient.h create mode 100644 UdpArqServer.cpp create mode 100644 UdpArqServer.h create mode 100644 UdpCast.cpp create mode 100644 UdpCast.h create mode 100644 UdpClient.cpp create mode 100644 UdpClient.h create mode 100644 UdpNode.cpp create mode 100644 UdpNode.h create mode 100644 UdpServer.cpp create mode 100644 UdpServer.h create mode 100644 brotli/decode.h create mode 100644 brotli/encode.h create mode 100644 brotli/port.h create mode 100644 brotli/shared_dictionary.h create mode 100644 brotli/types.h create mode 100644 common/BufferPool.cpp create mode 100644 common/BufferPool.h create mode 100644 common/BufferPtr.h create mode 100644 common/CriSec.h create mode 100644 common/Event.cpp create mode 100644 common/Event.h create mode 100644 common/FileHelper.cpp create mode 100644 common/FileHelper.h create mode 100644 common/FuncHelper.cpp create mode 100644 common/FuncHelper.h create mode 100644 common/GeneralHelper.h create mode 100644 common/IODispatcher.cpp create mode 100644 common/IODispatcher.h create mode 100644 common/PollHelper.cpp create mode 100644 common/PollHelper.h create mode 100644 common/PrivateHeap.h create mode 100644 common/RWLock.cpp create mode 100644 common/RWLock.h create mode 100644 common/RingBuffer.h create mode 100644 common/STLHelper.h create mode 100644 common/Semaphore.h create mode 100644 common/SignalHandler.h create mode 100644 common/Singleton.h create mode 100644 common/StringT.h create mode 100644 common/SysHelper.cpp create mode 100644 common/SysHelper.h create mode 100644 common/Thread.cpp create mode 100644 common/Thread.h create mode 100644 common/http/Readme.txt create mode 100644 common/http/llhttp.h create mode 100644 common/http/llhttp_api.c create mode 100644 common/http/llhttp_internal.c create mode 100644 common/http/llhttp_support.c create mode 100644 common/http/llhttp_url.c create mode 100644 common/http/llhttp_url.h create mode 100644 common/kcp/Readme.txt create mode 100644 common/kcp/ikcp.c create mode 100644 common/kcp/ikcp.h create mode 100644 hpsocket/GlobalDef.h create mode 100644 hpsocket/GlobalErrno.h create mode 100644 hpsocket/HPSocket-SSL.h create mode 100644 hpsocket/HPSocket.h create mode 100644 hpsocket/HPSocket4C-SSL.h create mode 100644 hpsocket/HPSocket4C.h create mode 100644 hpsocket/HPTypeDef.h create mode 100644 hpsocket/SocketInterface.h create mode 100644 libbrotli.a create mode 100644 libbrotli.lib diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3f10f1d --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +################################################################################ +# 此 .gitignore 文件已由 Microsoft(R) Visual Studio 自动创建。 +################################################################################ + +/.vs +/out/build/x64-Debug diff --git a/ArqHelper.cpp b/ArqHelper.cpp new file mode 100644 index 0000000..29cc297 --- /dev/null +++ b/ArqHelper.cpp @@ -0,0 +1,40 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ArqHelper.h" + +#ifdef _UDP_SUPPORT + +DWORD GenerateConversationID() +{ + static volatile DWORD s_dwConvID = ::TimeGetTime(); + + DWORD dwConvID = ::InterlockedIncrement(&s_dwConvID); + + if(dwConvID == 0) + dwConvID = ::InterlockedIncrement(&s_dwConvID); + + return dwConvID; +} + +#endif diff --git a/ArqHelper.h b/ArqHelper.h new file mode 100644 index 0000000..ec8ad26 --- /dev/null +++ b/ArqHelper.h @@ -0,0 +1,791 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "hpsocket/HPTypeDef.h" + +#ifdef _UDP_SUPPORT + +#include "SocketHelper.h" + +#include "common/FuncHelper.h" +#include "common/BufferPool.h" +#include "common/IODispatcher.h" +#include "common/kcp/ikcp.h" + +#include + +using namespace std; + +#define DEFAULT_ARQ_NO_DELAY FALSE +#define DEFAULT_ARQ_TURNOFF_NC FALSE +#define DEFAULT_ARQ_FLUSH_INTERVAL 60 +#define DEFAULT_ARQ_RESEND_BY_ACKS 0 +#define DEFAULT_ARQ_SEND_WND_SIZE 128 +#define DEFAULT_ARQ_RECV_WND_SIZE 512 +#define DEFAULT_ARQ_MIN_RTO 30 +#define DEFAULT_ARQ_FAST_LIMIT 5 +#define DEFAULT_ARQ_MAX_TRANS_UNIT DEFAULT_UDP_MAX_DATAGRAM_SIZE +#define DEFAULT_ARQ_MAX_MSG_SIZE DEFAULT_BUFFER_CACHE_CAPACITY +#define DEFAULT_ARQ_HANND_SHAKE_TIMEOUT 5000 + +#define KCP_HEADER_SIZE 24 +#define KCP_MIN_RECV_WND 128 + +#define ARQ_MAX_HANDSHAKE_INTERVAL 2000 + +using Fn_ArqOutputProc = int (*)(const char* pBuffer, int iLength, IKCPCB* kcp, LPVOID pv); + +DWORD GenerateConversationID(); + +/************************************************************************ +名称:ARQ 握手状态 +描述:标识当前连接的 ARQ 握手状态 +************************************************************************/ +enum EnArqHandShakeStatus +{ + ARQ_HSS_INIT = 0, // 初始状态 + ARQ_HSS_PROC = 1, // 正在握手 + ARQ_HSS_SUCC = 2, // 握手成功 +}; + +struct TArqCmd +{ +public: + static const UINT16 MAGIC = 0xBB4F; + static const UINT8 CMD_HANDSHAKE = 0x01; + static const UINT8 FLAG_COMPLETE = 0x01; + static const int PACKAGE_LENGTH = 12; + +public: + UINT16 magic; + UINT8 cmd; + UINT8 flag; + DWORD selfID; + DWORD peerID; + +public: + static BYTE* MakePackage(UINT8 cmd, UINT8 flag, DWORD selfID, DWORD peerID, UINT16 magic = MAGIC) + { + BYTE* buff = new BYTE[PACKAGE_LENGTH]; + + *((UINT16*)(buff + 0)) = magic; + *((UINT8*)(buff + 2)) = cmd; + *((UINT8*)(buff + 3)) = flag; + *((DWORD*)(buff + 4)) = selfID; + *((DWORD*)(buff + 8)) = peerID; + + return buff; + } + + BYTE* MakePackage() + { + return MakePackage(cmd, flag, selfID, peerID, magic); + } + + BOOL Parse(const BYTE buff[PACKAGE_LENGTH]) + { + magic = *((UINT16*)(buff + 0)); + cmd = *((UINT8*) (buff + 2)); + flag = *((UINT8*) (buff + 3)); + selfID = *((DWORD*) (buff + 4)); + peerID = *((DWORD*) (buff + 8)); + + return IsValid(); + } + + BOOL IsValid() {return (magic == MAGIC && cmd == CMD_HANDSHAKE && (flag & 0xFE) == 0);} + +public: + TArqCmd() + { + ::ZeroMemory(this, sizeof(TArqCmd)); + } + + TArqCmd(UINT8 c, UINT8 f, DWORD sid, DWORD pid) + : magic (MAGIC) + , cmd (c) + , flag (f) + , selfID(sid) + , peerID(pid) + { + + } +}; + +struct TArqAttr +{ + BOOL bNoDelay; + BOOL bTurnoffNc; + DWORD dwResendByAcks; + DWORD dwFlushInterval; + DWORD dwSendWndSize; + DWORD dwRecvWndSize; + DWORD dwMinRto; + DWORD dwMtu; + DWORD dwFastLimit; + DWORD dwMaxMessageSize; + DWORD dwHandShakeTimeout; + +public: + TArqAttr( BOOL no_delay = DEFAULT_ARQ_NO_DELAY + , BOOL turnoff_nc = DEFAULT_ARQ_TURNOFF_NC + , DWORD resend_by_acks = DEFAULT_ARQ_RESEND_BY_ACKS + , DWORD flush_interval = DEFAULT_ARQ_FLUSH_INTERVAL + , DWORD send_wnd_size = DEFAULT_ARQ_SEND_WND_SIZE + , DWORD recv_wnd_size = DEFAULT_ARQ_RECV_WND_SIZE + , DWORD min_rto = DEFAULT_ARQ_MIN_RTO + , DWORD mtu = DEFAULT_ARQ_MAX_TRANS_UNIT + , DWORD fast_limit = DEFAULT_ARQ_FAST_LIMIT + , DWORD max_msg_size = DEFAULT_ARQ_MAX_MSG_SIZE + , DWORD hand_shake_timeout = DEFAULT_ARQ_HANND_SHAKE_TIMEOUT + ) + : bNoDelay (no_delay) + , bTurnoffNc (turnoff_nc) + , dwResendByAcks (resend_by_acks) + , dwFlushInterval (flush_interval) + , dwSendWndSize (send_wnd_size) + , dwRecvWndSize (recv_wnd_size) + , dwMinRto (min_rto) + , dwMtu (mtu) + , dwFastLimit (fast_limit) + , dwMaxMessageSize (max_msg_size) + , dwHandShakeTimeout(hand_shake_timeout) + { + ASSERT(IsValid()); + } + + BOOL IsValid() const + { + return ((int)dwResendByAcks >= 0) && + ((int)dwFlushInterval > 0) && + ((int)dwSendWndSize > 0) && + ((int)dwRecvWndSize > 0) && + ((int)dwMinRto > 0) && + ((int)dwFastLimit >= 0) && + ((int)dwHandShakeTimeout > 2 * (int)dwMinRto) && + ((int)dwMtu >= 3 * KCP_HEADER_SIZE && dwMtu <= MAXIMUM_UDP_MAX_DATAGRAM_SIZE) && + ((int)dwMaxMessageSize > 0 && dwMaxMessageSize < ((KCP_MIN_RECV_WND - 1) * (dwMtu - KCP_HEADER_SIZE))) ; + } + +}; + +template class CArqSessionT +{ +public: + CArqSessionT* Renew(T* pContext, S* pSocket, const TArqAttr& attr, DWORD dwPeerConvID = 0) + { + m_pContext = pContext; + m_pSocket = pSocket; + m_dwSelfConvID = ::GenerateConversationID(); + + DoRenew(attr, dwPeerConvID); + RenewExtra(attr); + + m_dwCreateTime = ::TimeGetTime(); + m_dwHSNextTime = m_dwCreateTime; + m_dwHSSndCount = 0; + m_bHSComplete = FALSE; + m_enStatus = ARQ_HSS_PROC; + + Check(); + + return this; + } + + BOOL Reset() + { + if(!IsValid()) + return FALSE; + + { + CReentrantCriSecLock recvlock(m_csRecv); + CReentrantCriSecLock sendlock(m_csSend); + + if(!IsValid()) + return FALSE; + + m_enStatus = ARQ_HSS_INIT; + + DoReset(); + } + + ResetExtra(); + + return TRUE; + } + + BOOL Check() + { + if(IsReady()) + { + if(m_bHSComplete || DoHandShake()) + return Flush(); + else + return FALSE; + } + else if(IsHandShaking()) + return DoHandShake(); + else + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + } + + BOOL DoHandShake() + { + if(!IsValid()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + unique_ptr bufCmdPtr; + + { + CReentrantCriSecLock recvlock(m_csRecv); + + if(!IsValid()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + DWORD dwCurrent = ::TimeGetTime(); + + if(::GetTimeGap32(m_dwCreateTime, dwCurrent) > m_pContext->GetHandShakeTimeout()) + { + ::SetLastError(ERROR_TIMEOUT); + return FALSE; + } + + if((int)(::GetTimeGap32(m_dwHSNextTime, dwCurrent)) < 0) + return TRUE; + + m_dwHSNextTime = dwCurrent + MIN(m_kcp->interval * (++m_dwHSSndCount), ARQ_MAX_HANDSHAKE_INTERVAL); + UINT8 iFlag = IsReady() ? TArqCmd::FLAG_COMPLETE : 0; + + bufCmdPtr.reset(TArqCmd::MakePackage(TArqCmd::CMD_HANDSHAKE, iFlag, m_dwSelfConvID, m_dwPeerConvID)); + } + + return m_pContext->DoSend(m_pSocket, bufCmdPtr.get(), TArqCmd::PACKAGE_LENGTH); + } + + BOOL Flush(BOOL bForce = FALSE) + { + if(!IsReady()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + { + CReentrantCriSecTryLock recvlock(m_csRecv); + + if(recvlock.IsValid()) + { + CReentrantCriSecTryLock sendlock(m_csSend); + + if(sendlock.IsValid()) + { + if(!IsReady()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + if(bForce) + ::ikcp_flush(m_kcp); + else + ::ikcp_update(m_kcp, ::TimeGetTime()); + } + } + } + + return TRUE; + } + + int Send(const BYTE* pBuffer, int iLength) + { + if(!IsReady()) + return ERROR_INVALID_STATE; + + int rs = NO_ERROR; + + { + CReentrantCriSecLock sendlock(m_csSend); + + if(!IsReady()) + return ERROR_INVALID_STATE; + + rs = ::ikcp_send(m_kcp, (const char*)pBuffer, iLength); + if(rs < 0) rs = ERROR_INCORRECT_SIZE; + } + + if(rs == NO_ERROR) + Flush(TRUE); + + return rs; + } + + int GetWaitingSend() + { + if(!IsValid()) + { + ::SetLastError(ERROR_INVALID_STATE); + return -1; + } + + CReentrantCriSecLock sendlock(m_csSend); + + if(!IsValid()) + { + ::SetLastError(ERROR_INVALID_STATE); + return -1; + } + + return ::ikcp_waitsnd(m_kcp); + } + + EnHandleResult Receive(const BYTE* pData, int iLength, BYTE* pBuffer, int iCapacity) + { + if(iLength >= KCP_HEADER_SIZE) + return ReceiveArq(pData, iLength, pBuffer, iCapacity); + else if(iLength == TArqCmd::PACKAGE_LENGTH) + return ReceiveHandShake(pData); + else + { + ::WSASetLastError(ERROR_INVALID_DATA); + return HR_ERROR; + } + } + + EnHandleResult ReceiveHandShake(const BYTE* pBuffer) + { + TArqCmd cmd; + cmd.Parse(pBuffer); + + if(!cmd.IsValid()) + { + ::WSASetLastError(ERROR_INVALID_DATA); + return HR_ERROR; + } + + { + CReentrantCriSecLock recvlock(m_csRecv); + + if(!IsValid()) + { + ::WSASetLastError(ERROR_INVALID_STATE); + return HR_ERROR; + } + + if(IsReady()) + { + BOOL bReset = FALSE; + + if(cmd.selfID != m_dwPeerConvID) + bReset = TRUE; + else if(cmd.peerID != m_dwSelfConvID) + { + if(cmd.peerID != 0) + bReset = TRUE; + else + { + if(::GetTimeGap32(m_dwCreateTime) > 2 * m_pContext->GetHandShakeTimeout()) + bReset = TRUE; + } + } + + if(bReset) + { + ::WSASetLastError(ERROR_CONNRESET); + return HR_ERROR; + } + } + else + { + if(m_dwPeerConvID == 0) + { + m_dwPeerConvID = cmd.selfID; + m_dwHSNextTime = ::TimeGetTime(); + m_dwHSSndCount = 0; + } + else if(cmd.selfID != m_dwPeerConvID) + { + ::WSASetLastError(ERROR_CONNRESET); + return HR_ERROR; + } + + if(cmd.peerID == m_dwSelfConvID) + { + m_enStatus = ARQ_HSS_SUCC; + return m_pContext->DoFireHandShake(m_pSocket); + } + else if(cmd.peerID != 0) + { + ::WSASetLastError(ERROR_CONNRESET); + return HR_ERROR; + } + } + } + + if(!m_bHSComplete && cmd.flag == TArqCmd::FLAG_COMPLETE) + m_bHSComplete = TRUE; + + DoHandShake(); + + return HR_OK; + } + + EnHandleResult ReceiveArq(const BYTE* pData, int iLength, BYTE* pBuffer, int iCapacity) + { + if(!IsReady()) return HR_IGNORE; + + { + CReentrantCriSecLock recvlock(m_csRecv); + + if(!IsReady()) + { + ::WSASetLastError(ERROR_INVALID_STATE); + return HR_ERROR; + } + + if(iLength < KCP_HEADER_SIZE) + { + ::WSASetLastError(ERROR_INVALID_DATA); + return HR_ERROR; + } + + int rs = ::ikcp_input(m_kcp, (const char*)pData, iLength); + + if(rs != NO_ERROR) + { + ::WSASetLastError(ERROR_INVALID_DATA); + return HR_ERROR; + } + + while(TRUE) + { + int iRead = ::ikcp_recv(m_kcp, (char*)pBuffer, iCapacity); + + if(iRead >= 0) + { + EnHandleResult result = m_pContext->DoFireReceive(m_pSocket, pBuffer, iRead); + + if(result == HR_ERROR) + return result; + } + else if(iRead == -3) + { + ::WSASetLastError(ERROR_INCORRECT_SIZE); + return HR_ERROR; + } + else + break; + } + } + + Flush(TRUE); + + return HR_OK; + } + +private: + void DoRenew(const TArqAttr& attr, DWORD dwPeerConvID = 0) + { + ASSERT(attr.IsValid()); + + DoReset(); + + m_dwPeerConvID = dwPeerConvID; + m_kcp = ::ikcp_create(m_dwSelfConvID, m_pSocket); + + ::ikcp_nodelay(m_kcp, attr.bNoDelay ? 1 : 0, (int)attr.dwFlushInterval, (int)attr.dwResendByAcks, attr.bTurnoffNc ? 1 : 0); + ::ikcp_wndsize(m_kcp, (int)attr.dwSendWndSize, (int)attr.dwRecvWndSize); + ::ikcp_setmtu(m_kcp, attr.dwMtu); + + m_kcp->rx_minrto = (int)attr.dwMinRto; + m_kcp->fastlimit = (int)attr.dwFastLimit; + m_kcp->output = m_pContext->GetArqOutputProc(); + } + + void DoReset() + { + if(m_kcp != nullptr) + { + ::ikcp_release(m_kcp); + m_kcp = nullptr; + } + } + +public: + BOOL IsValid() const {return GetStatus() != ARQ_HSS_INIT;} + BOOL IsHandShaking() const {return GetStatus() == ARQ_HSS_PROC;} + BOOL IsReady() const {return GetStatus() == ARQ_HSS_SUCC;} + IKCPCB* GetKcp() {return m_kcp;} + DWORD GetConvID() const {if(!IsValid()) return 0; return m_kcp->conv;} + DWORD GetSelfConvID() const {return m_dwSelfConvID;} + DWORD GetPeerConvID() const {return m_dwPeerConvID;} + + EnArqHandShakeStatus GetStatus() const {return m_enStatus;} + +protected: + virtual void RenewExtra(const TArqAttr& attr) {} + virtual void ResetExtra() {} + +public: + CArqSessionT() + : m_pContext (nullptr) + , m_pSocket (nullptr) + , m_kcp (nullptr) + , m_enStatus (ARQ_HSS_INIT) + , m_dwSelfConvID(0) + , m_dwPeerConvID(0) + , m_dwCreateTime(0) + , m_dwHSNextTime(0) + , m_dwHSSndCount(0) + , m_bHSComplete (FALSE) + { + + } + + virtual ~CArqSessionT() + { + Reset(); + } + + static CArqSessionT* Construct() + {return new CArqSessionT();} + + static void Destruct(CArqSessionT* pSession) + {if(pSession) delete pSession;} + +protected: + T* m_pContext; + S* m_pSocket; + +private: + BOOL m_bHSComplete; + DWORD m_dwHSNextTime; + DWORD m_dwHSSndCount; + DWORD m_dwCreateTime; + DWORD m_dwSelfConvID; + DWORD m_dwPeerConvID; + EnArqHandShakeStatus m_enStatus; + + CReentrantCriSec m_csRecv; + CReentrantCriSec m_csSend; + IKCPCB* m_kcp; +}; + +template class CArqSessionPoolT; + +template class CArqSessionExT : public CArqSessionT, public CSafeCounter +{ + using __super = CArqSessionT; + + using __super::Reset; + + friend class CArqSessionPoolT; + +public: + DWORD GetFreeTime () const {return m_dwFreeTime;} + FD GetTimer () const {return m_fdTimer;} + +protected: + virtual void RenewExtra(const TArqAttr& attr) + { + ResetCount(); + + m_fdTimer = m_ioDispatcher.AddTimer(attr.dwFlushInterval, this); + ASSERT(IS_VALID_FD(m_fdTimer)); + } + + virtual void ResetExtra() + { + m_ioDispatcher.DelTimer(m_fdTimer); + + m_dwFreeTime = ::TimeGetTime(); + m_fdTimer = INVALID_FD; + } + +public: + CArqSessionExT(CIODispatcher& ioDispatcher) + : m_ioDispatcher(ioDispatcher) + , m_fdTimer (INVALID_FD) + , m_dwFreeTime (0) + { + + } + + virtual ~CArqSessionExT() + { + Reset(); + } + + static CArqSessionExT* Construct(CIODispatcher& ioDispatcher) + {return new CArqSessionExT(ioDispatcher);} + + static void Destruct(CArqSessionExT* pSession) + {if(pSession) delete pSession;} + +private: + CIODispatcher& m_ioDispatcher; + + FD m_fdTimer; + DWORD m_dwFreeTime; +}; + +template class CArqSessionPoolT : private CIOHandler +{ + using CArqSessionEx = CArqSessionExT; + using TArqSessionList = CRingPool; + using TArqSessionQueue = CCASQueue; + +public: + CArqSessionEx* PickFreeSession(T* pContext, S* pSocket, const TArqAttr& attr) + { + DWORD dwIndex; + CArqSessionEx* pSession = nullptr; + + if(m_lsFreeSession.TryLock(&pSession, dwIndex)) + { + if(::GetTimeGap32(pSession->GetFreeTime()) >= m_dwSessionLockTime) + ENSURE(m_lsFreeSession.ReleaseLock(nullptr, dwIndex)); + else + { + ENSURE(m_lsFreeSession.ReleaseLock(pSession, dwIndex)); + pSession = nullptr; + } + } + + if(!pSession) pSession = CArqSessionEx::Construct(m_ioDispatcher); + + ASSERT(pSession); + return (CArqSessionEx*)pSession->Renew(pContext, pSocket, attr); + } + + void PutFreeSession(CArqSessionEx* pSession) + { + if(pSession->Reset()) + { +#ifndef USE_EXTERNAL_GC + ReleaseGCSession(); +#endif + if(!m_lsFreeSession.TryPut(pSession)) + m_lsGCSession.PushBack(pSession); + } + } + + void Prepare() + { + m_lsFreeSession.Reset(m_dwSessionPoolSize); + + m_ioDispatcher.Start(this, m_pContext->GetPostReceiveCount(), m_pContext->GetWorkerThreadCount()); + } + + void Clear() + { + m_ioDispatcher.Stop(); + + m_lsFreeSession.Clear(); + + ReleaseGCSession(TRUE); + ENSURE(m_lsGCSession.IsEmpty()); + } + + void ReleaseGCSession(BOOL bForce = FALSE) + { + ::ReleaseGCObj(m_lsGCSession, m_dwSessionLockTime, bForce); + } + + virtual BOOL OnReadyRead(const TDispContext* pContext, PVOID pv, UINT events) override + { + + if(events & _EPOLL_ALL_ERROR_EVENTS) + return FALSE; + + CArqSessionEx* pSession = (CArqSessionEx*)pv; + + CLocalSafeCounter localcounter(*pSession); + + if(!pSession->Check() && pSession->IsValid() && TUdpSocketObj::IsValid(pSession->m_pSocket)) + pSession->m_pContext->Disconnect(pSession->m_pSocket->connID); + + ::ReadTimer(pSession->GetTimer()); + + return TRUE; + } + +public: + void SetSessionLockTime (DWORD dwSessionLockTime) {m_dwSessionLockTime = dwSessionLockTime;} + void SetSessionPoolSize (DWORD dwSessionPoolSize) {m_dwSessionPoolSize = dwSessionPoolSize;} + void SetSessionPoolHold (DWORD dwSessionPoolHold) {m_dwSessionPoolHold = dwSessionPoolHold;} + + DWORD GetSessionLockTime() {return m_dwSessionLockTime;} + DWORD GetSessionPoolSize() {return m_dwSessionPoolSize;} + DWORD GetSessionPoolHold() {return m_dwSessionPoolHold;} + +public: + CArqSessionPoolT(T* pContext, + DWORD dwPoolSize = DEFAULT_SESSION_POOL_SIZE, + DWORD dwPoolHold = DEFAULT_SESSION_POOL_HOLD, + DWORD dwLockTime = DEFAULT_SESSION_LOCK_TIME) + : m_pContext(pContext) + , m_dwSessionPoolSize(dwPoolSize) + , m_dwSessionPoolHold(dwPoolHold) + , m_dwSessionLockTime(dwLockTime) + { + + } + + ~CArqSessionPoolT() {Clear();} + + DECLARE_NO_COPY_CLASS(CArqSessionPoolT) + +public: + static const DWORD DEFAULT_SESSION_LOCK_TIME; + static const DWORD DEFAULT_SESSION_POOL_SIZE; + static const DWORD DEFAULT_SESSION_POOL_HOLD; + +private: + T* m_pContext; + + DWORD m_dwSessionLockTime; + DWORD m_dwSessionPoolSize; + DWORD m_dwSessionPoolHold; + + TArqSessionList m_lsFreeSession; + TArqSessionQueue m_lsGCSession; + + CIODispatcher m_ioDispatcher; +}; + +template const DWORD CArqSessionPoolT::DEFAULT_SESSION_LOCK_TIME = DEFAULT_OBJECT_CACHE_LOCK_TIME; +template const DWORD CArqSessionPoolT::DEFAULT_SESSION_POOL_SIZE = DEFAULT_OBJECT_CACHE_POOL_SIZE; +template const DWORD CArqSessionPoolT::DEFAULT_SESSION_POOL_HOLD = DEFAULT_OBJECT_CACHE_POOL_HOLD; + +#endif diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5dc4bf0 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.10) +project(hpsocket) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +file(GLOB_RECURSE ALL_SRC_FILES CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/*.c" +) + +add_library(hpsocket STATIC ${ALL_SRC_FILES}) +target_include_directories(hpsocket PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +# 解包 brotli 的 .a 为 .o 文件 +file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/brotli_objects) +execute_process( + COMMAND ${CMAKE_AR} -x ${CMAKE_CURRENT_SOURCE_DIR}/libbrotli.a + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/brotli_objects +) + +file(GLOB BROTLI_OBJECTS "${CMAKE_BINARY_DIR}/brotli_objects/*.o") +target_sources(hpsocket PRIVATE ${BROTLI_OBJECTS}) diff --git a/HPSocket-SSL.cpp b/HPSocket-SSL.cpp new file mode 100644 index 0000000..e4b0001 --- /dev/null +++ b/HPSocket-SSL.cpp @@ -0,0 +1,200 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hpsocket/HPSocket-SSL.h" + +#ifdef _SSL_SUPPORT + +#include "TcpServer.h" +#include "TcpAgent.h" +#include "TcpClient.h" +#include "TcpPullServer.h" +#include "TcpPullClient.h" +#include "TcpPullAgent.h" +#include "TcpPackServer.h" +#include "TcpPackClient.h" +#include "TcpPackAgent.h" + +#ifdef _HTTP_SUPPORT +#include "HttpServer.h" +#include "HttpAgent.h" +#include "HttpClient.h" +#endif + +/*****************************************************************************************************************************************************/ +/******************************************************************** SSL Exports ********************************************************************/ +/*****************************************************************************************************************************************************/ + +HPSOCKET_API ITcpServer* HP_Create_SSLServer(ITcpServerListener* pListener) +{ + return new CSSLServer(pListener); +} + +HPSOCKET_API ITcpAgent* HP_Create_SSLAgent(ITcpAgentListener* pListener) +{ + return new CSSLAgent(pListener); +} + +HPSOCKET_API ITcpClient* HP_Create_SSLClient(ITcpClientListener* pListener) +{ + return new CSSLClient(pListener); +} + +HPSOCKET_API ITcpPullServer* HP_Create_SSLPullServer(ITcpServerListener* pListener) +{ + return (ITcpPullServer*)(new CSSLPullServer(pListener)); +} + +HPSOCKET_API ITcpPullAgent* HP_Create_SSLPullAgent(ITcpAgentListener* pListener) +{ + return (ITcpPullAgent*)(new CSSLPullAgent(pListener)); +} + +HPSOCKET_API ITcpPullClient* HP_Create_SSLPullClient(ITcpClientListener* pListener) +{ + return (ITcpPullClient*)(new CSSLPullClient(pListener)); +} + +HPSOCKET_API ITcpPackServer* HP_Create_SSLPackServer(ITcpServerListener* pListener) +{ + return (ITcpPackServer*)(new CSSLPackServer(pListener)); +} + +HPSOCKET_API ITcpPackAgent* HP_Create_SSLPackAgent(ITcpAgentListener* pListener) +{ + return (ITcpPackAgent*)(new CSSLPackAgent(pListener)); +} + +HPSOCKET_API ITcpPackClient* HP_Create_SSLPackClient(ITcpClientListener* pListener) +{ + return (ITcpPackClient*)(new CSSLPackClient(pListener)); +} + +HPSOCKET_API void HP_Destroy_SSLServer(ITcpServer* pServer) +{ + delete pServer; +} + +HPSOCKET_API void HP_Destroy_SSLAgent(ITcpAgent* pAgent) +{ + delete pAgent; +} + +HPSOCKET_API void HP_Destroy_SSLClient(ITcpClient* pClient) +{ + delete pClient; +} + +HPSOCKET_API void HP_Destroy_SSLPullServer(ITcpPullServer* pServer) +{ + delete pServer; +} + +HPSOCKET_API void HP_Destroy_SSLPullAgent(ITcpPullAgent* pAgent) +{ + delete pAgent; +} + +HPSOCKET_API void HP_Destroy_SSLPullClient(ITcpPullClient* pClient) +{ + delete pClient; +} + +HPSOCKET_API void HP_Destroy_SSLPackServer(ITcpPackServer* pServer) +{ + delete pServer; +} + +HPSOCKET_API void HP_Destroy_SSLPackAgent(ITcpPackAgent* pAgent) +{ + delete pAgent; +} + +HPSOCKET_API void HP_Destroy_SSLPackClient(ITcpPackClient* pClient) +{ + delete pClient; +} + +/*****************************************************************************************************************************************************/ +/*************************************************************** Global Function Exports *************************************************************/ +/*****************************************************************************************************************************************************/ + +HPSOCKET_API int __HP_CALL HP_SSL_DefaultServerNameCallback(LPCTSTR lpszServerName, PVOID pContext) +{ + return CSSLContext::DefaultServerNameCallback(lpszServerName, pContext); +} + +HPSOCKET_API void HP_SSL_RemoveThreadLocalState(THR_ID dwThreadID) +{ + CSSLContext::RemoveThreadLocalState(dwThreadID); +} + +/*****************************************************************************************************************************************************/ +/******************************************************************** HTTPS Exports ******************************************************************/ +/*****************************************************************************************************************************************************/ + +#ifdef _HTTP_SUPPORT + +HPSOCKET_API IHttpServer* HP_Create_HttpsServer(IHttpServerListener* pListener) +{ + return (IHttpServer*)(new CHttpsServer(pListener)); +} + +HPSOCKET_API IHttpAgent* HP_Create_HttpsAgent(IHttpAgentListener* pListener) +{ + return (IHttpAgent*)(new CHttpsAgent(pListener)); +} + +HPSOCKET_API IHttpClient* HP_Create_HttpsClient(IHttpClientListener* pListener) +{ + return (IHttpClient*)(new CHttpsClient(pListener)); +} + +HPSOCKET_API IHttpSyncClient* HP_Create_HttpsSyncClient(IHttpClientListener* pListener) +{ + return (IHttpSyncClient*)(new CHttpsSyncClient(pListener)); +} + +HPSOCKET_API void HP_Destroy_HttpsServer(IHttpServer* pServer) +{ + delete pServer; +} + +HPSOCKET_API void HP_Destroy_HttpsAgent(IHttpAgent* pAgent) +{ + delete pAgent; +} + +HPSOCKET_API void HP_Destroy_HttpsClient(IHttpClient* pClient) +{ + delete pClient; +} + +HPSOCKET_API void HP_Destroy_HttpsSyncClient(IHttpSyncClient* pClient) +{ + delete pClient; +} + +#endif + +#endif \ No newline at end of file diff --git a/HPSocket.cpp b/HPSocket.cpp new file mode 100644 index 0000000..9aa9893 --- /dev/null +++ b/HPSocket.cpp @@ -0,0 +1,749 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hpsocket/HPSocket.h" +#include "TcpServer.h" +#include "TcpAgent.h" +#include "TcpClient.h" +#include "TcpPullServer.h" +#include "TcpPullClient.h" +#include "TcpPullAgent.h" +#include "TcpPackServer.h" +#include "TcpPackClient.h" +#include "TcpPackAgent.h" +#include "HPThreadPool.h" + +#ifdef _UDP_SUPPORT +#include "UdpServer.h" +#include "UdpClient.h" +#include "UdpCast.h" +#include "UdpNode.h" +#include "UdpArqServer.h" +#include "UdpArqClient.h" +#endif + +#ifdef _HTTP_SUPPORT +#include "HttpServer.h" +#include "HttpAgent.h" +#include "HttpClient.h" +#endif + +/*****************************************************************************************************************************************************/ +/****************************************************************** TCP/UDP Exports ******************************************************************/ +/*****************************************************************************************************************************************************/ + +HPSOCKET_API ITcpServer* HP_Create_TcpServer(ITcpServerListener* pListener) +{ + return new CTcpServer(pListener); +} + +HPSOCKET_API ITcpAgent* HP_Create_TcpAgent(ITcpAgentListener* pListener) +{ + return new CTcpAgent(pListener); +} + +HPSOCKET_API ITcpClient* HP_Create_TcpClient(ITcpClientListener* pListener) +{ + return new CTcpClient(pListener); +} + +HPSOCKET_API ITcpPullServer* HP_Create_TcpPullServer(ITcpServerListener* pListener) +{ + return (ITcpPullServer*)(new CTcpPullServer(pListener)); +} + +HPSOCKET_API ITcpPullAgent* HP_Create_TcpPullAgent(ITcpAgentListener* pListener) +{ + return (ITcpPullAgent*)(new CTcpPullAgent(pListener)); +} + +HPSOCKET_API ITcpPullClient* HP_Create_TcpPullClient(ITcpClientListener* pListener) +{ + return (ITcpPullClient*)(new CTcpPullClient(pListener)); +} + +HPSOCKET_API ITcpPackServer* HP_Create_TcpPackServer(ITcpServerListener* pListener) +{ + return (ITcpPackServer*)(new CTcpPackServer(pListener)); +} + +HPSOCKET_API ITcpPackAgent* HP_Create_TcpPackAgent(ITcpAgentListener* pListener) +{ + return (ITcpPackAgent*)(new CTcpPackAgent(pListener)); +} + +HPSOCKET_API ITcpPackClient* HP_Create_TcpPackClient(ITcpClientListener* pListener) +{ + return (ITcpPackClient*)(new CTcpPackClient(pListener)); +} + +HPSOCKET_API void HP_Destroy_TcpServer(ITcpServer* pServer) +{ + delete pServer; +} + +HPSOCKET_API void HP_Destroy_TcpAgent(ITcpAgent* pAgent) +{ + delete pAgent; +} + +HPSOCKET_API void HP_Destroy_TcpClient(ITcpClient* pClient) +{ + delete pClient; +} + +HPSOCKET_API void HP_Destroy_TcpPullServer(ITcpPullServer* pServer) +{ + delete pServer; +} + +HPSOCKET_API void HP_Destroy_TcpPullAgent(ITcpPullAgent* pAgent) +{ + delete pAgent; +} + +HPSOCKET_API void HP_Destroy_TcpPullClient(ITcpPullClient* pClient) +{ + delete pClient; +} + +HPSOCKET_API void HP_Destroy_TcpPackServer(ITcpPackServer* pServer) +{ + delete pServer; +} + +HPSOCKET_API void HP_Destroy_TcpPackAgent(ITcpPackAgent* pAgent) +{ + delete pAgent; +} + +HPSOCKET_API void HP_Destroy_TcpPackClient(ITcpPackClient* pClient) +{ + delete pClient; +} + +#ifdef _UDP_SUPPORT + +HPSOCKET_API IUdpServer* HP_Create_UdpServer(IUdpServerListener* pListener) +{ + return new CUdpServer(pListener); +} + +HPSOCKET_API IUdpClient* HP_Create_UdpClient(IUdpClientListener* pListener) +{ + return new CUdpClient(pListener); +} + +HPSOCKET_API IUdpCast* HP_Create_UdpCast(IUdpCastListener* pListener) +{ + return new CUdpCast(pListener); +} + +HPSOCKET_API IUdpNode* HP_Create_UdpNode(IUdpNodeListener* pListener) +{ + return new CUdpNode(pListener); +} + +HPSOCKET_API IUdpArqServer* HP_Create_UdpArqServer(IUdpServerListener* pListener) +{ + return (IUdpArqServer*)(new CUdpArqServer(pListener)); +} + +HPSOCKET_API IUdpArqClient* HP_Create_UdpArqClient(IUdpClientListener* pListener) +{ + return (IUdpArqClient*)(new CUdpArqClient(pListener)); +} + +HPSOCKET_API void HP_Destroy_UdpServer(IUdpServer* pServer) +{ + delete pServer; +} + +HPSOCKET_API void HP_Destroy_UdpClient(IUdpClient* pClient) +{ + delete pClient; +} + +HPSOCKET_API void HP_Destroy_UdpCast(IUdpCast* pCast) +{ + delete pCast; +} + +HPSOCKET_API void HP_Destroy_UdpNode(IUdpNode* pNode) +{ + delete pNode; +} + +HPSOCKET_API void HP_Destroy_UdpArqServer(IUdpArqServer* pServer) +{ + delete pServer; +} + +HPSOCKET_API void HP_Destroy_UdpArqClient(IUdpArqClient* pClient) +{ + delete pClient; +} + +#endif + +/*****************************************************************************************************************************************************/ +/*************************************************************** Global Function Exports *************************************************************/ +/*****************************************************************************************************************************************************/ + +HPSOCKET_API DWORD HP_GetHPSocketVersion() +{ + return ::GetHPSocketVersion(); +} + +HPSOCKET_API LPCTSTR HP_GetSocketErrorDesc(EnSocketError enCode) +{ + return ::GetSocketErrorDesc(enCode); +} + +HPSOCKET_API DWORD SYS_GetLastError() +{ + return ::GetLastError(); +} + +HPSOCKET_API LPCSTR SYS_GetLastErrorStr() +{ + return ::GetLastErrorStr(); +} + +HPSOCKET_API int SYS_SetSocketOption(SOCKET sock, int level, int name, LPVOID val, int len) +{ + return ::SSO_SetSocketOption(sock, level, name, val, len); +} + +HPSOCKET_API int SYS_GetSocketOption(SOCKET sock, int level, int name, LPVOID val, int* len) +{ + return ::SSO_GetSocketOption(sock, level, name, val, len); +} + +HPSOCKET_API int SYS_IoctlSocket(SOCKET sock, long cmd, ULONG* arg) +{ + return ::SSO_IoctlSocket(sock, cmd, arg); +} + +HPSOCKET_API BOOL SYS_fcntl_SETFL(FD fd, INT fl, BOOL bSet) +{ + return ::fcntl_SETFL(fd, fl, bSet); +} + +HPSOCKET_API int SYS_SSO_NoBlock(SOCKET sock, BOOL bNoBlock) +{ + return ::SSO_NoBlock(sock, bNoBlock); +} + +HPSOCKET_API int SYS_SSO_NoDelay(SOCKET sock, BOOL bNoDelay) +{ + return ::SSO_NoDelay(sock, bNoDelay); +} + +HPSOCKET_API int SYS_SSO_DontLinger(SOCKET sock, BOOL bDont) +{ + return ::SSO_DontLinger(sock, bDont); +} + +HPSOCKET_API int SYS_SSO_Linger(SOCKET sock, USHORT l_onoff, USHORT l_linger) +{ + return ::SSO_Linger(sock, l_onoff, l_linger); +} + +HPSOCKET_API int SYS_SSO_RecvBuffSize(SOCKET sock, int size) +{ + return ::SSO_RecvBuffSize(sock, size); +} + +HPSOCKET_API int SYS_SSO_SendBuffSize(SOCKET sock, int size) +{ + return ::SSO_SendBuffSize(sock, size); +} + +HPSOCKET_API int SYS_SSO_RecvTimeOut(SOCKET sock, int ms) +{ + return ::SSO_RecvTimeOut(sock, ms); +} + +HPSOCKET_API int SYS_SSO_SendTimeOut(SOCKET sock, int ms) +{ + return ::SSO_SendTimeOut(sock, ms); +} + +HPSOCKET_API int SYS_SSO_ReuseAddress(SOCKET sock, EnReuseAddressPolicy opt) +{ + return ::SSO_ReuseAddress(sock, opt); +} + +HPSOCKET_API BOOL SYS_GetSocketLocalAddress(SOCKET socket, TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort) +{ + return ::GetSocketLocalAddress(socket, lpszAddress, iAddressLen, usPort); +} + +HPSOCKET_API BOOL SYS_GetSocketRemoteAddress(SOCKET socket, TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort) +{ + return ::GetSocketRemoteAddress(socket, lpszAddress, iAddressLen, usPort); +} + +HPSOCKET_API BOOL SYS_EnumHostIPAddresses(LPCTSTR lpszHost, EnIPAddrType enType, LPTIPAddr** lpppIPAddr, int& iIPAddrCount) +{ + return ::EnumHostIPAddresses(lpszHost, enType, lpppIPAddr, iIPAddrCount); +} + +HPSOCKET_API BOOL SYS_FreeHostIPAddresses(LPTIPAddr* lppIPAddr) +{ + return ::FreeHostIPAddresses(lppIPAddr); +} + +HPSOCKET_API BOOL SYS_IsIPAddress(LPCTSTR lpszAddress, EnIPAddrType* penType) +{ + return ::IsIPAddress(lpszAddress, penType); +} + +HPSOCKET_API BOOL SYS_GetIPAddress(LPCTSTR lpszHost, TCHAR lpszIP[], int& iIPLenth, EnIPAddrType& enType) +{ + return ::GetIPAddress(lpszHost, lpszIP, iIPLenth, enType); +} + +HPSOCKET_API ULONGLONG SYS_NToH64(ULONGLONG value) +{ + return ::NToH64(value); +} + +HPSOCKET_API ULONGLONG SYS_HToN64(ULONGLONG value) +{ + return ::HToN64(value); +} + +HPSOCKET_API USHORT SYS_SwapEndian16(USHORT value) +{ + return ENDIAN_SWAP_16(value); +} + +HPSOCKET_API DWORD SYS_SwapEndian32(DWORD value) +{ + return ENDIAN_SWAP_32(value); +} + +HPSOCKET_API BOOL SYS_IsLittleEndian() +{ + return ::IsLittleEndian(); +} + +HPSOCKET_API LPBYTE SYS_Malloc(int size) +{ + return MALLOC(BYTE, size); +} + +HPSOCKET_API LPBYTE SYS_Realloc(LPBYTE p, int size) +{ + return REALLOC(BYTE, p, size); +} + +HPSOCKET_API VOID SYS_Free(LPBYTE p) +{ + FREE(p); +} + +HPSOCKET_API LPVOID SYS_Calloc(int number, int size) +{ + return CALLOC(number, size); +} + +HPSOCKET_API DWORD SYS_GuessBase64EncodeBound(DWORD dwSrcLen) +{ + return ::GuessBase64EncodeBound(dwSrcLen); +} + +HPSOCKET_API DWORD SYS_GuessBase64DecodeBound(const BYTE* lpszSrc, DWORD dwSrcLen) +{ + return ::GuessBase64DecodeBound(lpszSrc, dwSrcLen); +} + +HPSOCKET_API int SYS_Base64Encode(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen) +{ + return ::Base64Encode(lpszSrc, dwSrcLen, lpszDest, dwDestLen); +} + +HPSOCKET_API int SYS_Base64Decode(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen) +{ + return ::Base64Decode(lpszSrc, dwSrcLen, lpszDest, dwDestLen); +} + +HPSOCKET_API DWORD SYS_GuessUrlEncodeBound(const BYTE* lpszSrc, DWORD dwSrcLen) +{ + return ::GuessUrlEncodeBound(lpszSrc, dwSrcLen); +} + +HPSOCKET_API DWORD SYS_GuessUrlDecodeBound(const BYTE* lpszSrc, DWORD dwSrcLen) +{ + return ::GuessUrlDecodeBound(lpszSrc, dwSrcLen); +} + +HPSOCKET_API int SYS_UrlEncode(BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen) +{ + return ::UrlEncode(lpszSrc, dwSrcLen, lpszDest, dwDestLen); +} + +HPSOCKET_API int SYS_UrlDecode(BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen) +{ + return ::UrlDecode(lpszSrc, dwSrcLen, lpszDest, dwDestLen); +} + +#ifdef _ZLIB_SUPPORT + +HPSOCKET_API int SYS_Compress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen) +{ + return ::Compress(lpszSrc, dwSrcLen, lpszDest, dwDestLen); +} + +HPSOCKET_API int SYS_CompressEx(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen, int iLevel, int iMethod, int iWindowBits, int iMemLevel, int iStrategy) +{ + return ::CompressEx(lpszSrc, dwSrcLen, lpszDest, dwDestLen, iLevel, iMethod, iWindowBits, iMemLevel, iStrategy); +} + +HPSOCKET_API int SYS_Uncompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen) +{ + return ::Uncompress(lpszSrc, dwSrcLen, lpszDest, dwDestLen); +} + +HPSOCKET_API int SYS_UncompressEx(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen, int iWindowBits) +{ + return ::UncompressEx(lpszSrc, dwSrcLen, lpszDest, dwDestLen, iWindowBits); +} + +HPSOCKET_API DWORD SYS_GuessCompressBound(DWORD dwSrcLen, BOOL bGZip) +{ + return ::GuessCompressBound(dwSrcLen, bGZip); +} + +HPSOCKET_API int SYS_GZipCompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen) +{ + return ::GZipCompress(lpszSrc, dwSrcLen, lpszDest, dwDestLen); +} + +HPSOCKET_API int SYS_GZipUncompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen) +{ + return ::GZipUncompress(lpszSrc, dwSrcLen, lpszDest, dwDestLen); +} + +HPSOCKET_API DWORD SYS_GZipGuessUncompressBound(const BYTE* lpszSrc, DWORD dwSrcLen) +{ + return ::GZipGuessUncompressBound(lpszSrc, dwSrcLen); +} + +#endif + +#ifdef _BROTLI_SUPPORT + +HPSOCKET_API int SYS_BrotliCompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen) +{ + return ::BrotliCompress(lpszSrc, dwSrcLen, lpszDest, dwDestLen); +} + +HPSOCKET_API int SYS_BrotliCompressEx(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen, int iQuality, int iWindow, int iMode) +{ + return ::BrotliCompressEx(lpszSrc, dwSrcLen, lpszDest, dwDestLen, iQuality, iWindow, (BrotliEncoderMode)iMode); +} + +HPSOCKET_API int SYS_BrotliUncompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen) +{ + return ::BrotliUncompress(lpszSrc, dwSrcLen, lpszDest, dwDestLen); +} + +HPSOCKET_API DWORD SYS_BrotliGuessCompressBound(DWORD dwSrcLen) +{ + return ::BrotliGuessCompressBound(dwSrcLen); +} + +#endif + +#ifdef _ICONV_SUPPORT + +HPSOCKET_API BOOL SYS_CharsetConvert(LPCSTR lpszFromCharset, LPCSTR lpszToCharset, LPCSTR lpszInBuf, int iInBufLen, LPSTR lpszOutBuf, int& iOutBufLen) +{ + return ::CharsetConvert(lpszFromCharset, lpszToCharset, lpszInBuf, iInBufLen, lpszOutBuf, iOutBufLen); +} + +HPSOCKET_API BOOL SYS_GbkToUnicodeEx(const char szSrc[], int iSrcLength, WCHAR szDest[], int& iDestLength) +{ + return ::GbkToUnicodeEx(szSrc, iSrcLength, szDest, iDestLength); +} + +HPSOCKET_API BOOL SYS_UnicodeToGbkEx(const WCHAR szSrc[], int iSrcLength, char szDest[], int& iDestLength) +{ + return ::UnicodeToGbkEx(szSrc, iSrcLength, szDest, iDestLength); +} + +HPSOCKET_API BOOL SYS_Utf8ToUnicodeEx(const char szSrc[], int iSrcLength, WCHAR szDest[], int& iDestLength) +{ + return ::Utf8ToUnicodeEx(szSrc, iSrcLength, szDest, iDestLength); +} + +HPSOCKET_API BOOL SYS_UnicodeToUtf8Ex(const WCHAR szSrc[], int iSrcLength, char szDest[], int& iDestLength) +{ + return ::UnicodeToUtf8Ex(szSrc, iSrcLength, szDest, iDestLength); +} + +HPSOCKET_API BOOL SYS_GbkToUtf8Ex(const char szSrc[], int iSrcLength, char szDest[], int& iDestLength) +{ + return ::GbkToUtf8Ex(szSrc, iSrcLength, szDest, iDestLength); +} + +HPSOCKET_API BOOL SYS_Utf8ToGbkEx(const char szSrc[], int iSrcLength, char szDest[], int& iDestLength) +{ + return ::Utf8ToGbkEx(szSrc, iSrcLength, szDest, iDestLength); +} + +HPSOCKET_API BOOL SYS_GbkToUnicode(const char szSrc[], WCHAR szDest[], int& iDestLength) +{ + return ::GbkToUnicode(szSrc, szDest, iDestLength); +} + +HPSOCKET_API BOOL SYS_UnicodeToGbk(const WCHAR szSrc[], char szDest[], int& iDestLength) +{ + return ::UnicodeToGbk(szSrc, szDest, iDestLength); +} + +HPSOCKET_API BOOL SYS_Utf8ToUnicode(const char szSrc[], WCHAR szDest[], int& iDestLength) +{ + return ::Utf8ToUnicode(szSrc, szDest, iDestLength); +} + +HPSOCKET_API BOOL SYS_UnicodeToUtf8(const WCHAR szSrc[], char szDest[], int& iDestLength) +{ + return ::UnicodeToUtf8(szSrc, szDest, iDestLength); +} + +HPSOCKET_API BOOL SYS_GbkToUtf8(const char szSrc[], char szDest[], int& iDestLength) +{ + return ::GbkToUtf8(szSrc, szDest, iDestLength); +} + +HPSOCKET_API BOOL SYS_Utf8ToGbk(const char szSrc[], char szDest[], int& iDestLength) +{ + return ::Utf8ToGbk(szSrc, szDest, iDestLength); +} + +#endif + +/*****************************************************************************************************************************************************/ +/******************************************************************** HTTP Exports *******************************************************************/ +/*****************************************************************************************************************************************************/ + +#ifdef _HTTP_SUPPORT + +HPSOCKET_API IHttpServer* HP_Create_HttpServer(IHttpServerListener* pListener) +{ + return (IHttpServer*)(new CHttpServer(pListener)); +} + +HPSOCKET_API IHttpAgent* HP_Create_HttpAgent(IHttpAgentListener* pListener) +{ + return (IHttpAgent*)(new CHttpAgent(pListener)); +} + +HPSOCKET_API IHttpClient* HP_Create_HttpClient(IHttpClientListener* pListener) +{ + return (IHttpClient*)(new CHttpClient(pListener)); +} + +HPSOCKET_API IHttpSyncClient* HP_Create_HttpSyncClient(IHttpClientListener* pListener) +{ + return (IHttpSyncClient*)(new CHttpSyncClient(pListener)); +} + +HPSOCKET_API void HP_Destroy_HttpServer(IHttpServer* pServer) +{ + delete pServer; +} + +HPSOCKET_API void HP_Destroy_HttpAgent(IHttpAgent* pAgent) +{ + delete pAgent; +} + +HPSOCKET_API void HP_Destroy_HttpClient(IHttpClient* pClient) +{ + delete pClient; +} + +HPSOCKET_API void HP_Destroy_HttpSyncClient(IHttpSyncClient* pClient) +{ + delete pClient; +} + +/**************************************************************************/ +/*************************** HTTP Cookie 管理方法 **************************/ + +HPSOCKET_API BOOL HP_HttpCookie_MGR_LoadFromFile(LPCSTR lpszFile, BOOL bKeepExists) +{ + return g_CookieMgr.LoadFromFile(lpszFile, bKeepExists); +} + +HPSOCKET_API BOOL HP_HttpCookie_MGR_SaveToFile(LPCSTR lpszFile, BOOL bKeepExists) +{ + return g_CookieMgr.SaveToFile(lpszFile, bKeepExists); +} + +HPSOCKET_API BOOL HP_HttpCookie_MGR_ClearCookies(LPCSTR lpszDomain, LPCSTR lpszPath) +{ + return g_CookieMgr.ClearCookies(lpszDomain, lpszPath); +} + +HPSOCKET_API BOOL HP_HttpCookie_MGR_RemoveExpiredCookies(LPCSTR lpszDomain, LPCSTR lpszPath) +{ + return g_CookieMgr.RemoveExpiredCookies(lpszDomain, lpszPath); +} + +HPSOCKET_API BOOL HP_HttpCookie_MGR_SetCookie(LPCSTR lpszName, LPCSTR lpszValue, LPCSTR lpszDomain, LPCSTR lpszPath, int iMaxAge, BOOL bHttpOnly, BOOL bSecure, int enSameSite, BOOL bOnlyUpdateValueIfExists) +{ + return g_CookieMgr.SetCookie(lpszName, lpszValue, lpszDomain, lpszPath, iMaxAge, bHttpOnly, bSecure, (CCookie::EnSameSite)enSameSite, bOnlyUpdateValueIfExists); +} + +HPSOCKET_API BOOL HP_HttpCookie_MGR_DeleteCookie(LPCSTR lpszDomain, LPCSTR lpszPath, LPCSTR lpszName) +{ + return g_CookieMgr.DeleteCookie(lpszDomain, lpszPath, lpszName); +} + +HPSOCKET_API void HP_HttpCookie_MGR_SetEnableThirdPartyCookie(BOOL bEnableThirdPartyCookie) +{ + g_CookieMgr.SetEnableThirdPartyCookie(bEnableThirdPartyCookie); +} + +HPSOCKET_API BOOL HP_HttpCookie_MGR_IsEnableThirdPartyCookie() +{ + return g_CookieMgr.IsEnableThirdPartyCookie(); +} + +HPSOCKET_API BOOL HP_HttpCookie_HLP_ParseExpires(LPCSTR lpszExpires, __time64_t& tmExpires) +{ + return CCookie::ParseExpires(lpszExpires, tmExpires); +} + +HPSOCKET_API BOOL HP_HttpCookie_HLP_MakeExpiresStr(char lpszBuff[], int& iBuffLen, __time64_t tmExpires) +{ + return CCookie::MakeExpiresStr(lpszBuff, iBuffLen, tmExpires); +} + +HPSOCKET_API BOOL HP_HttpCookie_HLP_ToString(char lpszBuff[], int& iBuffLen, LPCSTR lpszName, LPCSTR lpszValue, LPCSTR lpszDomain, LPCSTR lpszPath, int iMaxAge, BOOL bHttpOnly, BOOL bSecure, int enSameSite) +{ + return CCookie::ToString(lpszBuff, iBuffLen, lpszName, lpszValue, lpszDomain, lpszPath, iMaxAge, bHttpOnly, bSecure, (CCookie::EnSameSite)enSameSite); +} + +HPSOCKET_API __time64_t HP_HttpCookie_HLP_CurrentUTCTime() +{ + return CCookie::CurrentUTCTime(); +} + +HPSOCKET_API __time64_t HP_HttpCookie_HLP_MaxAgeToExpires(int iMaxAge) +{ + return CCookie::MaxAgeToExpires(iMaxAge); +} + +HPSOCKET_API int HP_HttpCookie_HLP_ExpiresToMaxAge(__time64_t tmExpires) +{ + return CCookie::ExpiresToMaxAge(tmExpires); +} + +/*****************************************************************************************************************************************************/ +/************************************************************ HTTP Global Function Exports ***********************************************************/ +/*****************************************************************************************************************************************************/ + +#endif + +/*****************************************************************************************************************************************************/ +/**************************************************************** Thread Pool Exports ****************************************************************/ +/*****************************************************************************************************************************************************/ + +HPSOCKET_API IHPThreadPool* HP_Create_ThreadPool(IHPThreadPoolListener* pListener) +{ + return new CHPThreadPool(pListener); +} + +HPSOCKET_API void HP_Destroy_ThreadPool(IHPThreadPool* pThreadPool) +{ + delete pThreadPool; +} + +HPSOCKET_API LPTSocketTask HP_Create_SocketTaskObj(Fn_SocketTaskProc fnTaskProc, PVOID pSender, CONNID dwConnID, LPCBYTE pBuffer, INT iBuffLen, EnTaskBufferType enBuffType, WPARAM wParam, LPARAM lParam) +{ + return ::CreateSocketTaskObj(fnTaskProc, pSender, dwConnID, pBuffer, iBuffLen, enBuffType, wParam, lParam); +} + +HPSOCKET_API void HP_Destroy_SocketTaskObj(LPTSocketTask pTask) +{ + ::DestroySocketTaskObj(pTask); +} + +/*****************************************************************************************************************************************************/ +/********************************************************* Compressor / Decompressor Exports *********************************************************/ +/*****************************************************************************************************************************************************/ + +HPSOCKET_API void HP_Destroy_Compressor(IHPCompressor* pCompressor) +{ + ::DestroyCompressor(pCompressor); +} + +HPSOCKET_API void HP_Destroy_Decompressor(IHPDecompressor* pDecompressor) +{ + ::DestroyDecompressor(pDecompressor); +} + +#ifdef _ZLIB_SUPPORT + +HPSOCKET_API IHPCompressor* HP_Create_ZLibCompressor(Fn_CompressDataCallback fnCallback, int iWindowBits, int iLevel, int iMethod, int iMemLevel, int iStrategy, DWORD dwBuffSize) +{ + return ::CreateZLibCompressor(fnCallback, iWindowBits, iLevel, iMethod, iMemLevel, iStrategy, dwBuffSize); +} + +HPSOCKET_API IHPCompressor* HP_Create_GZipCompressor(Fn_CompressDataCallback fnCallback, int iLevel, int iMethod, int iMemLevel, int iStrategy, DWORD dwBuffSize) +{ + return ::CreateGZipCompressor(fnCallback, iLevel, iMethod, iMemLevel, iStrategy, dwBuffSize); +} + +HPSOCKET_API IHPDecompressor* HP_Create_ZLibDecompressor(Fn_DecompressDataCallback fnCallback, int iWindowBits, DWORD dwBuffSize) +{ + return ::CreateZLibDecompressor(fnCallback, iWindowBits, dwBuffSize); +} + +HPSOCKET_API IHPDecompressor* HP_Create_GZipDecompressor(Fn_DecompressDataCallback fnCallback, DWORD dwBuffSize) +{ + return ::CreateGZipDecompressor(fnCallback, dwBuffSize); +} + +#endif + +#ifdef _BROTLI_SUPPORT + +HPSOCKET_API IHPCompressor* HP_Create_BrotliCompressor(Fn_CompressDataCallback fnCallback, int iQuality, int iWindow, int iMode, DWORD dwBuffSize) +{ + return ::CreateBrotliCompressor(fnCallback, iQuality, iWindow, iMode, dwBuffSize); +} + +HPSOCKET_API IHPDecompressor* HP_Create_BrotliDecompressor(Fn_DecompressDataCallback fnCallback, DWORD dwBuffSize) +{ + return ::CreateBrotliDecompressor(fnCallback, dwBuffSize); +} + +#endif diff --git a/HPSocket4C-SSL.cpp b/HPSocket4C-SSL.cpp new file mode 100644 index 0000000..568138d --- /dev/null +++ b/HPSocket4C-SSL.cpp @@ -0,0 +1,380 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hpsocket/HPSocket4C-SSL.h" + +#ifdef _SSL_SUPPORT + +#include "SocketObject4C.h" +#include "TcpServer.h" +#include "TcpClient.h" +#include "TcpAgent.h" +#include "TcpPullServer.h" +#include "TcpPullClient.h" +#include "TcpPullAgent.h" +#include "TcpPackServer.h" +#include "TcpPackClient.h" +#include "TcpPackAgent.h" + +#ifdef _HTTP_SUPPORT +#include "HttpServer.h" +#include "HttpAgent.h" +#include "HttpClient.h" +#endif + +/*****************************************************************************************************************************************************/ +/******************************************************************** SSL Exports ********************************************************************/ +/*****************************************************************************************************************************************************/ + +typedef C_HP_ObjectT C_HP_SSLServer; +typedef C_HP_ObjectT C_HP_SSLPullServer; +typedef C_HP_ObjectT C_HP_SSLPackServer; + +typedef C_HP_ObjectT C_HP_SSLAgent; +typedef C_HP_ObjectT C_HP_SSLPullAgent; +typedef C_HP_ObjectT C_HP_SSLPackAgent; + +typedef C_HP_ObjectT C_HP_SSLClient; +typedef C_HP_ObjectT C_HP_SSLPullClient; +typedef C_HP_ObjectT C_HP_SSLPackClient; + +/********************************************************/ +/************** HPSocket4C-SSL 对象创建函数 **************/ + +HPSOCKET_API HP_SSLServer __HP_CALL Create_HP_SSLServer(HP_TcpServerListener pListener) +{ + return (HP_SSLServer)(new C_HP_SSLServer((ITcpServerListener*)pListener)); +} + +HPSOCKET_API HP_SSLAgent __HP_CALL Create_HP_SSLAgent(HP_TcpAgentListener pListener) +{ + return (HP_SSLAgent)(new C_HP_SSLAgent((ITcpAgentListener*)pListener)); +} + +HPSOCKET_API HP_SSLClient __HP_CALL Create_HP_SSLClient(HP_TcpClientListener pListener) +{ + return (HP_SSLClient)(new C_HP_SSLClient((ITcpClientListener*)pListener)); +} + +HPSOCKET_API HP_SSLPullServer __HP_CALL Create_HP_SSLPullServer(HP_TcpPullServerListener pListener) +{ + return (HP_SSLPullServer)(new C_HP_SSLPullServer((ITcpServerListener*)pListener)); +} + +HPSOCKET_API HP_SSLPullAgent __HP_CALL Create_HP_SSLPullAgent(HP_TcpPullAgentListener pListener) +{ + return (HP_SSLPullAgent)(new C_HP_SSLPullAgent((ITcpAgentListener*)pListener)); +} + +HPSOCKET_API HP_SSLPullClient __HP_CALL Create_HP_SSLPullClient(HP_TcpPullClientListener pListener) +{ + return (HP_SSLPullClient)(new C_HP_SSLPullClient((ITcpClientListener*)pListener)); +} + +HPSOCKET_API HP_SSLPackServer __HP_CALL Create_HP_SSLPackServer(HP_TcpServerListener pListener) +{ + return (HP_SSLPackServer)(new C_HP_SSLPackServer((ITcpServerListener*)pListener)); +} + +HPSOCKET_API HP_SSLPackAgent __HP_CALL Create_HP_SSLPackAgent(HP_TcpAgentListener pListener) +{ + return (HP_SSLPackAgent)(new C_HP_SSLPackAgent((ITcpAgentListener*)pListener)); +} + +HPSOCKET_API HP_SSLPackClient __HP_CALL Create_HP_SSLPackClient(HP_TcpClientListener pListener) +{ + return (HP_SSLPackClient)(new C_HP_SSLPackClient((ITcpClientListener*)pListener)); +} + +HPSOCKET_API void __HP_CALL Destroy_HP_SSLServer(HP_SSLServer pServer) +{ + delete (C_HP_SSLServer*)pServer; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_SSLAgent(HP_SSLAgent pAgent) +{ + delete (C_HP_SSLAgent*)pAgent; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_SSLClient(HP_SSLClient pClient) +{ + delete (C_HP_SSLClient*)pClient; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_SSLPullServer(HP_SSLPullServer pServer) +{ + delete (C_HP_SSLPullServer*)pServer; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_SSLPullAgent(HP_SSLPullAgent pAgent) +{ + delete (C_HP_SSLPullAgent*)pAgent; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_SSLPullClient(HP_SSLPullClient pClient) +{ + delete (C_HP_SSLPullClient*)pClient; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_SSLPackServer(HP_SSLPackServer pServer) +{ + delete (C_HP_SSLPackServer*)pServer; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_SSLPackAgent(HP_SSLPackAgent pAgent) +{ + delete (C_HP_SSLPackAgent*)pAgent; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_SSLPackClient(HP_SSLPackClient pClient) +{ + delete (C_HP_SSLPackClient*)pClient; +} + +/*****************************************************************************************************************************************************/ +/*************************************************************** Global Function Exports *************************************************************/ +/*****************************************************************************************************************************************************/ + +/***************************************************************************************/ +/************************************ SSL 初始化方法 ************************************/ + +HPSOCKET_API int __HP_CALL HP_SSL_DefaultServerNameCallback(LPCTSTR lpszServerName, PVOID pContext) +{ + return CSSLContext::DefaultServerNameCallback(lpszServerName, pContext); +} + +HPSOCKET_API void __HP_CALL HP_SSL_RemoveThreadLocalState(THR_ID dwThreadID) +{ + CSSLContext::RemoveThreadLocalState(dwThreadID); +} + +HPSOCKET_API BOOL __HP_CALL HP_SSLServer_SetupSSLContext(HP_SSLServer pServer, int iVerifyMode, LPCTSTR lpszPemCertFile, LPCTSTR lpszPemKeyFile, LPCTSTR lpszKeyPassword, LPCTSTR lpszCAPemCertFileOrPath, HP_Fn_SNI_ServerNameCallback fnServerNameCallback) +{ + return C_HP_Object::ToSecond(pServer)->SetupSSLContext(iVerifyMode, lpszPemCertFile, lpszPemKeyFile, lpszKeyPassword, lpszCAPemCertFileOrPath, fnServerNameCallback); +} + +HPSOCKET_API BOOL __HP_CALL HP_SSLServer_SetupSSLContextByMemory(HP_SSLServer pServer, int iVerifyMode, LPCSTR lpszPemCert, LPCSTR lpszPemKey, LPCSTR lpszKeyPassword, LPCSTR lpszCAPemCert, HP_Fn_SNI_ServerNameCallback fnServerNameCallback) +{ + return C_HP_Object::ToSecond(pServer)->SetupSSLContextByMemory(iVerifyMode, lpszPemCert, lpszPemKey, lpszKeyPassword, lpszCAPemCert, fnServerNameCallback); +} + +HPSOCKET_API int __HP_CALL HP_SSLServer_AddSSLContext(HP_SSLServer pServer, int iVerifyMode, LPCTSTR lpszPemCertFile, LPCTSTR lpszPemKeyFile, LPCTSTR lpszKeyPassword, LPCTSTR lpszCAPemCertFileOrPath) +{ + return C_HP_Object::ToSecond(pServer)->AddSSLContext(iVerifyMode, lpszPemCertFile, lpszPemKeyFile, lpszKeyPassword, lpszCAPemCertFileOrPath); +} + +HPSOCKET_API int __HP_CALL HP_SSLServer_AddSSLContextByMemory(HP_SSLServer pServer, int iVerifyMode, LPCSTR lpszPemCert, LPCSTR lpszPemKey, LPCSTR lpszKeyPassword, LPCSTR lpszCAPemCert) +{ + return C_HP_Object::ToSecond(pServer)->AddSSLContextByMemory(iVerifyMode, lpszPemCert, lpszPemKey, lpszKeyPassword, lpszCAPemCert); +} + +HPSOCKET_API BOOL __HP_CALL HP_SSLServer_BindSSLServerName(HP_SSLServer pServer, LPCTSTR lpszServerName, int iContextIndex) +{ + return C_HP_Object::ToSecond(pServer)->BindSSLServerName(lpszServerName, iContextIndex); +} + +HPSOCKET_API void __HP_CALL HP_SSLServer_CleanupSSLContext(HP_SSLServer pServer) +{ + C_HP_Object::ToSecond(pServer)->CleanupSSLContext(); +} + +HPSOCKET_API BOOL __HP_CALL HP_SSLAgent_SetupSSLContext(HP_SSLAgent pAgent, int iVerifyMode, LPCTSTR lpszPemCertFile, LPCTSTR lpszPemKeyFile, LPCTSTR lpszKeyPassword, LPCTSTR lpszCAPemCertFileOrPath) +{ + return C_HP_Object::ToSecond(pAgent)->SetupSSLContext(iVerifyMode, lpszPemCertFile, lpszPemKeyFile, lpszKeyPassword, lpszCAPemCertFileOrPath); +} + +HPSOCKET_API BOOL __HP_CALL HP_SSLAgent_SetupSSLContextByMemory(HP_SSLAgent pAgent, int iVerifyMode, LPCSTR lpszPemCert, LPCSTR lpszPemKey, LPCSTR lpszKeyPassword, LPCSTR lpszCAPemCert) +{ + return C_HP_Object::ToSecond(pAgent)->SetupSSLContextByMemory(iVerifyMode, lpszPemCert, lpszPemKey, lpszKeyPassword, lpszCAPemCert); +} + +HPSOCKET_API void __HP_CALL HP_SSLAgent_CleanupSSLContext(HP_SSLAgent pAgent) +{ + C_HP_Object::ToSecond(pAgent)->CleanupSSLContext(); +} + +HPSOCKET_API BOOL __HP_CALL HP_SSLClient_SetupSSLContext(HP_SSLClient pClient, int iVerifyMode, LPCTSTR lpszPemCertFile, LPCTSTR lpszPemKeyFile, LPCTSTR lpszKeyPassword, LPCTSTR lpszCAPemCertFileOrPath) +{ + return C_HP_Object::ToSecond(pClient)->SetupSSLContext(iVerifyMode, lpszPemCertFile, lpszPemKeyFile, lpszKeyPassword, lpszCAPemCertFileOrPath); +} + +HPSOCKET_API BOOL __HP_CALL HP_SSLClient_SetupSSLContextByMemory(HP_SSLClient pClient, int iVerifyMode, LPCSTR lpszPemCert, LPCSTR lpszPemKey, LPCSTR lpszKeyPassword, LPCSTR lpszCAPemCert) +{ + return C_HP_Object::ToSecond(pClient)->SetupSSLContextByMemory(iVerifyMode, lpszPemCert, lpszPemKey, lpszKeyPassword, lpszCAPemCert); +} + +HPSOCKET_API void __HP_CALL HP_SSLClient_CleanupSSLContext(HP_SSLClient pClient) +{ + C_HP_Object::ToSecond(pClient)->CleanupSSLContext(); +} + +/***************************************************************************************/ +/************************************* SSL 操作方法 ************************************/ + +HPSOCKET_API BOOL __HP_CALL HP_SSLServer_StartSSLHandShake(HP_SSLServer pServer, HP_CONNID dwConnID) +{ + return C_HP_Object::ToSecond(pServer)->StartSSLHandShake(dwConnID); +} + +HPSOCKET_API void __HP_CALL HP_SSLServer_SetSSLAutoHandShake(HP_SSLServer pServer, BOOL bAutoHandShake) +{ + C_HP_Object::ToSecond(pServer)->SetSSLAutoHandShake(bAutoHandShake); +} + +HPSOCKET_API BOOL __HP_CALL HP_SSLServer_IsSSLAutoHandShake(HP_SSLServer pServer) +{ + return C_HP_Object::ToSecond(pServer)->IsSSLAutoHandShake(); +} + +HPSOCKET_API void __HP_CALL HP_SSLServer_SetSSLCipherList(HP_SSLServer pServer, LPCTSTR lpszCipherList) +{ + C_HP_Object::ToSecond(pServer)->SetSSLCipherList(lpszCipherList); +} + +HPSOCKET_API LPCTSTR __HP_CALL HP_SSLServer_GetSSLCipherList(HP_SSLServer pServer) +{ + return C_HP_Object::ToSecond(pServer)->GetSSLCipherList(); +} + +HPSOCKET_API BOOL __HP_CALL HP_SSLServer_GetSSLSessionInfo(HP_SSLServer pServer, HP_CONNID dwConnID, En_HP_SSLSessionInfo enInfo, LPVOID* lppInfo) +{ + return C_HP_Object::ToSecond(pServer)->GetSSLSessionInfo(dwConnID, enInfo, lppInfo); +} + +HPSOCKET_API BOOL __HP_CALL HP_SSLAgent_StartSSLHandShake(HP_SSLAgent pAgent, HP_CONNID dwConnID) +{ + return C_HP_Object::ToSecond(pAgent)->StartSSLHandShake(dwConnID); +} + +HPSOCKET_API void __HP_CALL HP_SSLAgent_SetSSLAutoHandShake(HP_SSLAgent pAgent, BOOL bAutoHandShake) +{ + C_HP_Object::ToSecond(pAgent)->SetSSLAutoHandShake(bAutoHandShake); +} + +HPSOCKET_API BOOL __HP_CALL HP_SSLAgent_IsSSLAutoHandShake(HP_SSLAgent pAgent) +{ + return C_HP_Object::ToSecond(pAgent)->IsSSLAutoHandShake(); +} + +HPSOCKET_API void __HP_CALL HP_SSLAgent_SetSSLCipherList(HP_SSLAgent pAgent, LPCTSTR lpszCipherList) +{ + C_HP_Object::ToSecond(pAgent)->SetSSLCipherList(lpszCipherList); +} + +HPSOCKET_API LPCTSTR __HP_CALL HP_SSLAgent_GetSSLCipherList(HP_SSLAgent pAgent) +{ + return C_HP_Object::ToSecond(pAgent)->GetSSLCipherList(); +} + +HPSOCKET_API BOOL __HP_CALL HP_SSLAgent_GetSSLSessionInfo(HP_SSLAgent pAgent, HP_CONNID dwConnID, En_HP_SSLSessionInfo enInfo, LPVOID* lppInfo) +{ + return C_HP_Object::ToSecond(pAgent)->GetSSLSessionInfo(dwConnID, enInfo, lppInfo); +} + +HPSOCKET_API BOOL __HP_CALL HP_SSLClient_StartSSLHandShake(HP_SSLClient pClient) +{ + return C_HP_Object::ToSecond(pClient)->StartSSLHandShake(); +} + +HPSOCKET_API void __HP_CALL HP_SSLClient_SetSSLAutoHandShake(HP_SSLClient pClient, BOOL bAutoHandShake) +{ + C_HP_Object::ToSecond(pClient)->SetSSLAutoHandShake(bAutoHandShake); +} + +HPSOCKET_API BOOL __HP_CALL HP_SSLClient_IsSSLAutoHandShake(HP_SSLClient pClient) +{ + return C_HP_Object::ToSecond(pClient)->IsSSLAutoHandShake(); +} + +HPSOCKET_API void __HP_CALL HP_SSLClient_SetSSLCipherList(HP_SSLClient pClient, LPCTSTR lpszCipherList) +{ + C_HP_Object::ToSecond(pClient)->SetSSLCipherList(lpszCipherList); +} + +HPSOCKET_API LPCTSTR __HP_CALL HP_SSLClient_GetSSLCipherList(HP_SSLClient pClient) +{ + return C_HP_Object::ToSecond(pClient)->GetSSLCipherList(); +} + +HPSOCKET_API BOOL __HP_CALL HP_SSLClient_GetSSLSessionInfo(HP_SSLClient pClient, En_HP_SSLSessionInfo enInfo, LPVOID* lppInfo) +{ + return C_HP_Object::ToSecond(pClient)->GetSSLSessionInfo(enInfo, lppInfo); +} + +/*****************************************************************************************************************************************************/ +/******************************************************************** HTTPS Exports ******************************************************************/ +/*****************************************************************************************************************************************************/ + +#ifdef _HTTP_SUPPORT + +typedef C_HP_ObjectT C_HP_HttpsServer; +typedef C_HP_ObjectT C_HP_HttpsAgent; +typedef C_HP_ObjectT C_HP_HttpsClient; +typedef C_HP_ObjectT C_HP_HttpsSyncClient; + +/****************************************************/ +/**************** HTTPS 对象创建函数 *****************/ + +HPSOCKET_API HP_HttpsServer __HP_CALL Create_HP_HttpsServer(HP_HttpServerListener pListener) +{ + return (HP_HttpsServer)(new C_HP_HttpsServer((IHttpServerListener*)pListener)); +} + +HPSOCKET_API HP_HttpsAgent __HP_CALL Create_HP_HttpsAgent(HP_HttpAgentListener pListener) +{ + return (HP_HttpsAgent)(new C_HP_HttpsAgent((IHttpAgentListener*)pListener)); +} + +HPSOCKET_API HP_HttpsClient __HP_CALL Create_HP_HttpsClient(HP_HttpClientListener pListener) +{ + return (HP_HttpsClient)(new C_HP_HttpsClient((IHttpClientListener*)pListener)); +} + +HPSOCKET_API HP_HttpsSyncClient __HP_CALL Create_HP_HttpsSyncClient(HP_HttpClientListener pListener) +{ + return (HP_HttpsSyncClient)(new C_HP_HttpsSyncClient((IHttpClientListener*)pListener)); +} + +HPSOCKET_API void __HP_CALL Destroy_HP_HttpsServer(HP_HttpsServer pServer) +{ + delete (C_HP_HttpsServer*)pServer; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_HttpsAgent(HP_HttpsAgent pAgent) +{ + delete (C_HP_HttpsAgent*)pAgent; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_HttpsClient(HP_HttpsClient pClient) +{ + delete (C_HP_HttpsClient*)pClient; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_HttpsSyncClient(HP_HttpsSyncClient pClient) +{ + delete (C_HP_HttpsSyncClient*)pClient; +} + +#endif + +#endif \ No newline at end of file diff --git a/HPSocket4C.cpp b/HPSocket4C.cpp new file mode 100644 index 0000000..fb6b8af --- /dev/null +++ b/HPSocket4C.cpp @@ -0,0 +1,3747 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SocketObject4C.h" +#include "TcpServer.h" +#include "TcpClient.h" +#include "TcpAgent.h" +#include "TcpPullServer.h" +#include "TcpPullClient.h" +#include "TcpPullAgent.h" +#include "TcpPackServer.h" +#include "TcpPackClient.h" +#include "TcpPackAgent.h" +#include "HPThreadPool.h" + +#ifdef _UDP_SUPPORT +#include "UdpServer.h" +#include "UdpClient.h" +#include "UdpCast.h" +#include "UdpNode.h" +#include "UdpArqServer.h" +#include "UdpArqClient.h" +#endif + +#ifdef _HTTP_SUPPORT +#include "HttpServer.h" +#include "HttpAgent.h" +#include "HttpClient.h" +#endif + +/*****************************************************************************************************************************************************/ +/****************************************************************** TCP/UDP Exports ******************************************************************/ +/*****************************************************************************************************************************************************/ + +typedef C_HP_ObjectT C_HP_TcpServer; +typedef C_HP_ObjectT C_HP_TcpPullServer; +typedef C_HP_ObjectT C_HP_TcpPackServer; + +typedef C_HP_ObjectT C_HP_TcpAgent; +typedef C_HP_ObjectT C_HP_TcpPullAgent; +typedef C_HP_ObjectT C_HP_TcpPackAgent; + +typedef C_HP_ObjectT C_HP_TcpClient; +typedef C_HP_ObjectT C_HP_TcpPullClient; +typedef C_HP_ObjectT C_HP_TcpPackClient; + +#ifdef _UDP_SUPPORT + +typedef C_HP_ObjectT C_HP_UdpServer; +typedef C_HP_ObjectT C_HP_UdpClient; +typedef C_HP_ObjectT C_HP_UdpCast; +typedef C_HP_ObjectT C_HP_UdpNode; + +typedef C_HP_ObjectT C_HP_UdpArqServer; +typedef C_HP_ObjectT C_HP_UdpArqClient; + +#endif + +/****************************************************/ +/**************** TCP/UDP 对象创建函数 ***************/ + +HPSOCKET_API HP_TcpServer __HP_CALL Create_HP_TcpServer(HP_TcpServerListener pListener) +{ + return (HP_TcpServer)(new C_HP_TcpServer((ITcpServerListener*)pListener)); +} + +HPSOCKET_API HP_TcpAgent __HP_CALL Create_HP_TcpAgent(HP_TcpAgentListener pListener) +{ + return (HP_TcpAgent)(new C_HP_TcpAgent((ITcpAgentListener*)pListener)); +} + +HPSOCKET_API HP_TcpClient __HP_CALL Create_HP_TcpClient(HP_TcpClientListener pListener) +{ + return (HP_TcpClient)(new C_HP_TcpClient((ITcpClientListener*)pListener)); +} + +HPSOCKET_API HP_TcpPullServer __HP_CALL Create_HP_TcpPullServer(HP_TcpPullServerListener pListener) +{ + return (HP_TcpPullServer)(new C_HP_TcpPullServer((ITcpServerListener*)pListener)); +} + +HPSOCKET_API HP_TcpPullAgent __HP_CALL Create_HP_TcpPullAgent(HP_TcpPullAgentListener pListener) +{ + return (HP_TcpPullAgent)(new C_HP_TcpPullAgent((ITcpAgentListener*)pListener)); +} + +HPSOCKET_API HP_TcpPullClient __HP_CALL Create_HP_TcpPullClient(HP_TcpPullClientListener pListener) +{ + return (HP_TcpPullClient)(new C_HP_TcpPullClient((ITcpClientListener*)pListener)); +} + +HPSOCKET_API HP_TcpPackServer __HP_CALL Create_HP_TcpPackServer(HP_TcpServerListener pListener) +{ + return (HP_TcpPackServer)(new C_HP_TcpPackServer((ITcpServerListener*)pListener)); +} + +HPSOCKET_API HP_TcpPackAgent __HP_CALL Create_HP_TcpPackAgent(HP_TcpAgentListener pListener) +{ + return (HP_TcpPackAgent)(new C_HP_TcpPackAgent((ITcpAgentListener*)pListener)); +} + +HPSOCKET_API HP_TcpPackClient __HP_CALL Create_HP_TcpPackClient(HP_TcpClientListener pListener) +{ + return (HP_TcpPackClient)(new C_HP_TcpPackClient((ITcpClientListener*)pListener)); +} + +HPSOCKET_API void __HP_CALL Destroy_HP_TcpServer(HP_TcpServer pServer) +{ + delete (C_HP_TcpServer*)pServer; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_TcpAgent(HP_TcpAgent pAgent) +{ + delete (C_HP_TcpAgent*)pAgent; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_TcpClient(HP_TcpClient pClient) +{ + delete (C_HP_TcpClient*)pClient; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_TcpPullServer(HP_TcpPullServer pServer) +{ + delete (C_HP_TcpPullServer*)pServer; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_TcpPullAgent(HP_TcpPullAgent pAgent) +{ + delete (C_HP_TcpPullAgent*)pAgent; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_TcpPullClient(HP_TcpPullClient pClient) +{ + delete (C_HP_TcpPullClient*)pClient; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_TcpPackServer(HP_TcpPackServer pServer) +{ + delete (C_HP_TcpPackServer*)pServer; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_TcpPackAgent(HP_TcpPackAgent pAgent) +{ + delete (C_HP_TcpPackAgent*)pAgent; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_TcpPackClient(HP_TcpPackClient pClient) +{ + delete (C_HP_TcpPackClient*)pClient; +} + +HPSOCKET_API HP_TcpServerListener __HP_CALL Create_HP_TcpServerListener() +{ + return (HP_TcpServerListener)(new C_HP_TcpServerListener); +} + +HPSOCKET_API HP_TcpAgentListener __HP_CALL Create_HP_TcpAgentListener() +{ + return (HP_TcpAgentListener)(new C_HP_TcpAgentListener); +} + +HPSOCKET_API HP_TcpClientListener __HP_CALL Create_HP_TcpClientListener() +{ + return (HP_TcpClientListener)(new C_HP_TcpClientListener); +} + +HPSOCKET_API HP_TcpPullServerListener __HP_CALL Create_HP_TcpPullServerListener() +{ + return (HP_TcpPullServerListener)(new C_HP_TcpPullServerListener); +} + +HPSOCKET_API HP_TcpPullAgentListener __HP_CALL Create_HP_TcpPullAgentListener() +{ + return (HP_TcpPullAgentListener)(new C_HP_TcpPullAgentListener); +} + +HPSOCKET_API HP_TcpPullClientListener __HP_CALL Create_HP_TcpPullClientListener() +{ + return (HP_TcpPullClientListener)(new C_HP_TcpPullClientListener); +} + +HPSOCKET_API HP_TcpPackServerListener __HP_CALL Create_HP_TcpPackServerListener() +{ + return (HP_TcpPackServerListener)(new C_HP_TcpPackServerListener); +} + +HPSOCKET_API HP_TcpPackAgentListener __HP_CALL Create_HP_TcpPackAgentListener() +{ + return (HP_TcpPackAgentListener)(new C_HP_TcpPackAgentListener); +} + +HPSOCKET_API HP_TcpPackClientListener __HP_CALL Create_HP_TcpPackClientListener() +{ + return (HP_TcpPackClientListener)(new C_HP_TcpPackClientListener); +} + +HPSOCKET_API void __HP_CALL Destroy_HP_TcpServerListener(HP_TcpServerListener pListener) +{ + delete (C_HP_TcpServerListener*)pListener; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_TcpAgentListener(HP_TcpAgentListener pListener) +{ + delete (C_HP_TcpAgentListener*)pListener; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_TcpClientListener(HP_TcpClientListener pListener) +{ + delete (C_HP_TcpClientListener*)pListener; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_TcpPullServerListener(HP_TcpPullServerListener pListener) +{ + delete (C_HP_TcpPullServerListener*)pListener; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_TcpPullAgentListener(HP_TcpPullAgentListener pListener) +{ + delete (C_HP_TcpPullAgentListener*)pListener; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_TcpPullClientListener(HP_TcpPullClientListener pListener) +{ + delete (C_HP_TcpPullClientListener*)pListener; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_TcpPackServerListener(HP_TcpPackServerListener pListener) +{ + delete (C_HP_TcpPackServerListener*)pListener; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_TcpPackAgentListener(HP_TcpPackAgentListener pListener) +{ + delete (C_HP_TcpPackAgentListener*)pListener; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_TcpPackClientListener(HP_TcpPackClientListener pListener) +{ + delete (C_HP_TcpPackClientListener*)pListener; +} + +#ifdef _UDP_SUPPORT + +HPSOCKET_API HP_UdpServer __HP_CALL Create_HP_UdpServer(HP_UdpServerListener pListener) +{ + return (HP_UdpServer)(new C_HP_UdpServer((IUdpServerListener*)pListener)); +} + +HPSOCKET_API HP_UdpClient __HP_CALL Create_HP_UdpClient(HP_UdpClientListener pListener) +{ + return (HP_UdpClient)(new C_HP_UdpClient((IUdpClientListener*)pListener)); +} + +HPSOCKET_API HP_UdpCast __HP_CALL Create_HP_UdpCast(HP_UdpCastListener pListener) +{ + return (HP_UdpCast)(new C_HP_UdpCast((IUdpCastListener*)pListener)); +} + +HPSOCKET_API HP_UdpNode __HP_CALL Create_HP_UdpNode(HP_UdpNodeListener pListener) +{ + return (HP_UdpNode)(new C_HP_UdpNode((IUdpNodeListener*)pListener)); +} + +HPSOCKET_API HP_UdpArqServer __HP_CALL Create_HP_UdpArqServer(HP_UdpServerListener pListener) +{ + return (HP_UdpArqServer)(new C_HP_UdpArqServer((IUdpServerListener*)pListener)); +} + +HPSOCKET_API HP_UdpArqClient __HP_CALL Create_HP_UdpArqClient(HP_UdpClientListener pListener) +{ + return (HP_UdpArqClient)(new C_HP_UdpArqClient((IUdpClientListener*)pListener)); +} + +HPSOCKET_API void __HP_CALL Destroy_HP_UdpServer(HP_UdpServer pServer) +{ + delete (C_HP_UdpServer*)pServer; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_UdpClient(HP_UdpClient pClient) +{ + delete (C_HP_UdpClient*)pClient; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_UdpCast(HP_UdpCast pCast) +{ + delete (C_HP_UdpCast*)pCast; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_UdpNode(HP_UdpNode pNode) +{ + delete (C_HP_UdpNode*)pNode; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_UdpArqServer(HP_UdpArqServer pServer) +{ + delete (C_HP_UdpArqServer*)pServer; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_UdpArqClient(HP_UdpArqClient pClient) +{ + delete (C_HP_UdpArqClient*)pClient; +} + +HPSOCKET_API HP_UdpServerListener __HP_CALL Create_HP_UdpServerListener() +{ + return (HP_UdpServerListener)(new C_HP_UdpServerListener); +} + +HPSOCKET_API HP_UdpClientListener __HP_CALL Create_HP_UdpClientListener() +{ + return (HP_UdpClientListener)(new C_HP_UdpClientListener); +} + +HPSOCKET_API HP_UdpCastListener __HP_CALL Create_HP_UdpCastListener() +{ + return (HP_UdpCastListener)(new C_HP_UdpCastListener); +} + +HPSOCKET_API HP_UdpNodeListener __HP_CALL Create_HP_UdpNodeListener() +{ + return (HP_UdpNodeListener)(new C_HP_UdpNodeListener); +} + +HPSOCKET_API HP_UdpArqServerListener __HP_CALL Create_HP_UdpArqServerListener() +{ + return (HP_UdpArqServerListener)(new C_HP_UdpArqServerListener); +} + +HPSOCKET_API HP_UdpArqClientListener __HP_CALL Create_HP_UdpArqClientListener() +{ + return (HP_UdpArqClientListener)(new C_HP_UdpArqClientListener); +} + +HPSOCKET_API void __HP_CALL Destroy_HP_UdpServerListener(HP_UdpServerListener pListener) +{ + delete (C_HP_UdpServerListener*)pListener; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_UdpClientListener(HP_UdpClientListener pListener) +{ + delete (C_HP_UdpClientListener*)pListener; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_UdpCastListener(HP_UdpCastListener pListener) +{ + delete (C_HP_UdpClientListener*)pListener; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_UdpNodeListener(HP_UdpNodeListener pListener) +{ + delete (C_HP_UdpNodeListener*)pListener; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_UdpArqServerListener(HP_UdpArqServerListener pListener) +{ + delete (C_HP_UdpArqServerListener*)pListener; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_UdpArqClientListener(HP_UdpArqClientListener pListener) +{ + delete (C_HP_UdpArqClientListener*)pListener; +} + +#endif + +/**********************************************************************************/ +/***************************** Server 回调函数设置方法 *****************************/ + +HPSOCKET_API void __HP_CALL HP_Set_FN_Server_OnPrepareListen(HP_ServerListener pListener, HP_FN_Server_OnPrepareListen fn) +{ + ((C_HP_TcpServerListener*)pListener)->m_fnOnPrepareListen = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_Server_OnAccept(HP_ServerListener pListener, HP_FN_Server_OnAccept fn) +{ + ((C_HP_TcpServerListener*)pListener)->m_fnOnAccept = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_Server_OnHandShake(HP_ServerListener pListener, HP_FN_Server_OnHandShake fn) +{ + ((C_HP_TcpServerListener*)pListener)->m_fnOnHandShake = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_Server_OnSend(HP_ServerListener pListener, HP_FN_Server_OnSend fn) +{ + ((C_HP_TcpServerListener*)pListener)->m_fnOnSend = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_Server_OnReceive(HP_ServerListener pListener, HP_FN_Server_OnReceive fn) +{ + ((C_HP_TcpServerListener*)pListener)->m_fnOnReceive = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_Server_OnPullReceive(HP_ServerListener pListener, HP_FN_Server_OnPullReceive fn) +{ + ((C_HP_TcpServerListener*)pListener)->m_fnOnPullReceive = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_Server_OnClose(HP_ServerListener pListener, HP_FN_Server_OnClose fn) +{ + ((C_HP_TcpServerListener*)pListener)->m_fnOnClose = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_Server_OnShutdown(HP_ServerListener pListener, HP_FN_Server_OnShutdown fn) +{ + ((C_HP_TcpServerListener*)pListener)->m_fnOnShutdown = fn; +} + +/**********************************************************************************/ +/***************************** Agent 回调函数设置方法 *****************************/ + +HPSOCKET_API void __HP_CALL HP_Set_FN_Agent_OnPrepareConnect(HP_AgentListener pListener, HP_FN_Agent_OnPrepareConnect fn) +{ + ((C_HP_TcpAgentListener*)pListener)->m_fnOnPrepareConnect = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_Agent_OnConnect(HP_AgentListener pListener, HP_FN_Agent_OnConnect fn) +{ + ((C_HP_TcpAgentListener*)pListener)->m_fnOnConnect = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_Agent_OnHandShake(HP_AgentListener pListener, HP_FN_Agent_OnHandShake fn) +{ + ((C_HP_TcpAgentListener*)pListener)->m_fnOnHandShake = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_Agent_OnSend(HP_AgentListener pListener, HP_FN_Agent_OnSend fn) +{ + ((C_HP_TcpAgentListener*)pListener)->m_fnOnSend = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_Agent_OnReceive(HP_AgentListener pListener, HP_FN_Agent_OnReceive fn) +{ + ((C_HP_TcpAgentListener*)pListener)->m_fnOnReceive = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_Agent_OnPullReceive(HP_AgentListener pListener, HP_FN_Agent_OnPullReceive fn) +{ + ((C_HP_TcpAgentListener*)pListener)->m_fnOnPullReceive = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_Agent_OnClose(HP_AgentListener pListener, HP_FN_Agent_OnClose fn) +{ + ((C_HP_TcpAgentListener*)pListener)->m_fnOnClose = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_Agent_OnShutdown(HP_AgentListener pListener, HP_FN_Agent_OnShutdown fn) +{ + ((C_HP_TcpAgentListener*)pListener)->m_fnOnShutdown = fn; +} + +/**********************************************************************************/ +/***************************** Client 回调函数设置方法 *****************************/ + +HPSOCKET_API void __HP_CALL HP_Set_FN_Client_OnPrepareConnect(HP_ClientListener pListener, HP_FN_Client_OnPrepareConnect fn) +{ + ((C_HP_TcpClientListener*)pListener)->m_fnOnPrepareConnect = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_Client_OnConnect(HP_ClientListener pListener, HP_FN_Client_OnConnect fn) +{ + ((C_HP_TcpClientListener*)pListener)->m_fnOnConnect = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_Client_OnHandShake(HP_ClientListener pListener, HP_FN_Client_OnHandShake fn) +{ + ((C_HP_TcpClientListener*)pListener)->m_fnOnHandShake = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_Client_OnSend(HP_ClientListener pListener, HP_FN_Client_OnSend fn) +{ + ((C_HP_TcpClientListener*)pListener)->m_fnOnSend = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_Client_OnReceive(HP_ClientListener pListener, HP_FN_Client_OnReceive fn) +{ + ((C_HP_TcpClientListener*)pListener)->m_fnOnReceive = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_Client_OnPullReceive(HP_ClientListener pListener, HP_FN_Client_OnPullReceive fn) +{ + ((C_HP_TcpClientListener*)pListener)->m_fnOnPullReceive = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_Client_OnClose(HP_ClientListener pListener, HP_FN_Client_OnClose fn) +{ + ((C_HP_TcpClientListener*)pListener)->m_fnOnClose = fn; +} + +#ifdef _UDP_SUPPORT + +/**********************************************************************************/ +/***************************** UdpNode 回调函数设置方法 *****************************/ + + +HPSOCKET_API void __HP_CALL HP_Set_FN_UdpNode_OnPrepareListen(HP_UdpNodeListener pListener, HP_FN_UdpNode_OnPrepareListen fn) +{ + ((C_HP_UdpNodeListener*)pListener)->m_fnOnPrepareListen = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_UdpNode_OnSend(HP_UdpNodeListener pListener, HP_FN_UdpNode_OnSend fn) +{ + ((C_HP_UdpNodeListener*)pListener)->m_fnOnSend = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_UdpNode_OnReceive(HP_UdpNodeListener pListener, HP_FN_UdpNode_OnReceive fn) +{ + ((C_HP_UdpNodeListener*)pListener)->m_fnOnReceive = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_UdpNode_OnError(HP_UdpNodeListener pListener, HP_FN_UdpNode_OnError fn) +{ + ((C_HP_UdpNodeListener*)pListener)->m_fnOnError = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_UdpNode_OnShutdown(HP_UdpNodeListener pListener, HP_FN_UdpNode_OnShutdown fn) +{ + ((C_HP_UdpNodeListener*)pListener)->m_fnOnShutdown = fn; +} + +#endif + +/**************************************************************************/ +/***************************** Server 操作方法 *****************************/ + +HPSOCKET_API BOOL __HP_CALL HP_Server_Start(HP_Server pServer, LPCTSTR lpszBindAddress, USHORT usPort) +{ + return C_HP_Object::ToSecond(pServer)->Start(lpszBindAddress, usPort); +} + +HPSOCKET_API BOOL __HP_CALL HP_Server_Stop(HP_Server pServer) +{ + return C_HP_Object::ToSecond(pServer)->Stop(); +} + +HPSOCKET_API BOOL __HP_CALL HP_Server_Wait(HP_Server pServer, DWORD dwMilliseconds) +{ + return C_HP_Object::ToSecond(pServer)->Wait(dwMilliseconds); +} + +HPSOCKET_API BOOL __HP_CALL HP_Server_Send(HP_Server pServer, HP_CONNID dwConnID, const BYTE* pBuffer, int iLength) +{ + return C_HP_Object::ToSecond(pServer)->Send(dwConnID, pBuffer, iLength); +} + +HPSOCKET_API BOOL __HP_CALL HP_Server_SendPart(HP_Server pServer, HP_CONNID dwConnID, const BYTE* pBuffer, int iLength, int iOffset) +{ + return C_HP_Object::ToSecond(pServer)->Send(dwConnID, pBuffer, iLength, iOffset); +} + +HPSOCKET_API BOOL __HP_CALL HP_Server_SendPackets(HP_Server pServer, HP_CONNID dwConnID, const WSABUF pBuffers[], int iCount) +{ + return C_HP_Object::ToSecond(pServer)->SendPackets(dwConnID, pBuffers, iCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_Server_PauseReceive(HP_Server pServer, HP_CONNID dwConnID, BOOL bPause) +{ + return C_HP_Object::ToSecond(pServer)->PauseReceive(dwConnID, bPause); +} + +HPSOCKET_API BOOL __HP_CALL HP_Server_Disconnect(HP_Server pServer, HP_CONNID dwConnID, BOOL bForce) +{ + return C_HP_Object::ToSecond(pServer)->Disconnect(dwConnID, bForce); +} + +HPSOCKET_API BOOL __HP_CALL HP_Server_DisconnectLongConnections(HP_Server pServer, DWORD dwPeriod, BOOL bForce) +{ + return C_HP_Object::ToSecond(pServer)->DisconnectLongConnections(dwPeriod, bForce); +} + +HPSOCKET_API BOOL __HP_CALL HP_Server_DisconnectSilenceConnections(HP_Server pServer, DWORD dwPeriod, BOOL bForce) +{ + return C_HP_Object::ToSecond(pServer)->DisconnectSilenceConnections(dwPeriod, bForce); +} + +/******************************************************************************/ +/***************************** Server 属性访问方法 *****************************/ + +HPSOCKET_API BOOL __HP_CALL HP_Server_SetConnectionExtra(HP_Server pServer, HP_CONNID dwConnID, PVOID pExtra) +{ + return C_HP_Object::ToSecond(pServer)->SetConnectionExtra(dwConnID, pExtra); +} + +HPSOCKET_API BOOL __HP_CALL HP_Server_GetConnectionExtra(HP_Server pServer, HP_CONNID dwConnID, PVOID* ppExtra) +{ + return C_HP_Object::ToSecond(pServer)->GetConnectionExtra(dwConnID, ppExtra); +} + +HPSOCKET_API BOOL __HP_CALL HP_Server_IsSecure(HP_Server pServer) +{ + return C_HP_Object::ToSecond(pServer)->IsSecure(); +} + +HPSOCKET_API BOOL __HP_CALL HP_Server_HasStarted(HP_Server pServer) +{ + return C_HP_Object::ToSecond(pServer)->HasStarted(); +} + +HPSOCKET_API En_HP_ServiceState __HP_CALL HP_Server_GetState(HP_Server pServer) +{ + return C_HP_Object::ToSecond(pServer)->GetState(); +} + +HPSOCKET_API En_HP_SocketError __HP_CALL HP_Server_GetLastError(HP_Server pServer) +{ + return C_HP_Object::ToSecond(pServer)->GetLastError(); +} + +HPSOCKET_API LPCTSTR __HP_CALL HP_Server_GetLastErrorDesc(HP_Server pServer) +{ + return C_HP_Object::ToSecond(pServer)->GetLastErrorDesc(); +} + +HPSOCKET_API BOOL __HP_CALL HP_Server_GetPendingDataLength(HP_Server pServer, HP_CONNID dwConnID, int* piPending) +{ + return C_HP_Object::ToSecond(pServer)->GetPendingDataLength(dwConnID, *piPending); +} + +HPSOCKET_API BOOL __HP_CALL HP_Server_IsPauseReceive(HP_Server pServer, HP_CONNID dwConnID, BOOL* pbPaused) +{ + return C_HP_Object::ToSecond(pServer)->IsPauseReceive(dwConnID, *pbPaused); +} + +HPSOCKET_API BOOL __HP_CALL HP_Server_IsConnected(HP_Server pServer, HP_CONNID dwConnID) +{ + return C_HP_Object::ToSecond(pServer)->IsConnected(dwConnID); +} + +HPSOCKET_API DWORD __HP_CALL HP_Server_GetConnectionCount(HP_Server pServer) +{ + return C_HP_Object::ToSecond(pServer)->GetConnectionCount(); +} + +HPSOCKET_API BOOL __HP_CALL HP_Server_GetAllConnectionIDs(HP_Server pServer, HP_CONNID pIDs[], DWORD* pdwCount) +{ + return C_HP_Object::ToSecond(pServer)->GetAllConnectionIDs(pIDs, *pdwCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_Server_GetConnectPeriod(HP_Server pServer, HP_CONNID dwConnID, DWORD* pdwPeriod) +{ + return C_HP_Object::ToSecond(pServer)->GetConnectPeriod(dwConnID, *pdwPeriod); +} + +HPSOCKET_API BOOL __HP_CALL HP_Server_GetSilencePeriod(HP_Server pServer, HP_CONNID dwConnID, DWORD* pdwPeriod) +{ + return C_HP_Object::ToSecond(pServer)->GetSilencePeriod(dwConnID, *pdwPeriod); +} + +HPSOCKET_API BOOL __HP_CALL HP_Server_GetListenAddress(HP_Server pServer, TCHAR lpszAddress[], int* piAddressLen, USHORT* pusPort) +{ + return C_HP_Object::ToSecond(pServer)->GetListenAddress(lpszAddress, *piAddressLen, *pusPort); +} + +HPSOCKET_API BOOL __HP_CALL HP_Server_GetLocalAddress(HP_Server pServer, HP_CONNID dwConnID, TCHAR lpszAddress[], int* piAddressLen, USHORT* pusPort) +{ + return C_HP_Object::ToSecond(pServer)->GetLocalAddress(dwConnID, lpszAddress, *piAddressLen, *pusPort); +} + +HPSOCKET_API BOOL __HP_CALL HP_Server_GetRemoteAddress(HP_Server pServer, HP_CONNID dwConnID, TCHAR lpszAddress[], int* piAddressLen, USHORT* pusPort) +{ + return C_HP_Object::ToSecond(pServer)->GetRemoteAddress(dwConnID, lpszAddress, *piAddressLen, *pusPort); +} + +HPSOCKET_API void __HP_CALL HP_Server_SetReuseAddressPolicy(HP_Server pServer, En_HP_ReuseAddressPolicy enReusePolicy) +{ + C_HP_Object::ToSecond(pServer)->SetReuseAddressPolicy(enReusePolicy); +} + +HPSOCKET_API void __HP_CALL HP_Server_SetSendPolicy(HP_Server pServer, En_HP_SendPolicy enSendPolicy) +{ + C_HP_Object::ToSecond(pServer)->SetSendPolicy(enSendPolicy); +} + +HPSOCKET_API void __HP_CALL HP_Server_SetOnSendSyncPolicy(HP_Server pServer, En_HP_OnSendSyncPolicy enSyncPolicy) +{ + C_HP_Object::ToSecond(pServer)->SetOnSendSyncPolicy(enSyncPolicy); +} + +HPSOCKET_API void __HP_CALL HP_Server_SetFreeSocketObjLockTime(HP_Server pServer, DWORD dwFreeSocketObjLockTime) +{ + C_HP_Object::ToSecond(pServer)->SetFreeSocketObjLockTime(dwFreeSocketObjLockTime); +} + +HPSOCKET_API void __HP_CALL HP_Server_SetFreeSocketObjPool(HP_Server pServer, DWORD dwFreeSocketObjPool) +{ + C_HP_Object::ToSecond(pServer)->SetFreeSocketObjPool(dwFreeSocketObjPool); +} + +HPSOCKET_API void __HP_CALL HP_Server_SetFreeBufferObjPool(HP_Server pServer, DWORD dwFreeBufferObjPool) +{ + C_HP_Object::ToSecond(pServer)->SetFreeBufferObjPool(dwFreeBufferObjPool); +} + +HPSOCKET_API void __HP_CALL HP_Server_SetFreeSocketObjHold(HP_Server pServer, DWORD dwFreeSocketObjHold) +{ + C_HP_Object::ToSecond(pServer)->SetFreeSocketObjHold(dwFreeSocketObjHold); +} + +HPSOCKET_API void __HP_CALL HP_Server_SetFreeBufferObjHold(HP_Server pServer, DWORD dwFreeBufferObjHold) +{ + C_HP_Object::ToSecond(pServer)->SetFreeBufferObjHold(dwFreeBufferObjHold); +} + +HPSOCKET_API void __HP_CALL HP_Server_SetMaxConnectionCount(HP_Server pServer, DWORD dwMaxConnectionCount) +{ + C_HP_Object::ToSecond(pServer)->SetMaxConnectionCount(dwMaxConnectionCount); +} + +HPSOCKET_API void __HP_CALL HP_Server_SetWorkerThreadCount(HP_Server pServer, DWORD dwWorkerThreadCount) +{ + C_HP_Object::ToSecond(pServer)->SetWorkerThreadCount(dwWorkerThreadCount); +} + +HPSOCKET_API void __HP_CALL HP_Server_SetMarkSilence(HP_Server pServer, BOOL bMarkSilence) +{ + C_HP_Object::ToSecond(pServer)->SetMarkSilence(bMarkSilence); +} + +HPSOCKET_API En_HP_ReuseAddressPolicy __HP_CALL HP_Server_GetReuseAddressPolicy(HP_Server pServer) +{ + return C_HP_Object::ToSecond(pServer)->GetReuseAddressPolicy(); +} + +HPSOCKET_API En_HP_SendPolicy __HP_CALL HP_Server_GetSendPolicy(HP_Server pServer) +{ + return C_HP_Object::ToSecond(pServer)->GetSendPolicy(); +} + +HPSOCKET_API En_HP_OnSendSyncPolicy __HP_CALL HP_Server_GetOnSendSyncPolicy(HP_Server pServer) +{ + return C_HP_Object::ToSecond(pServer)->GetOnSendSyncPolicy(); +} + +HPSOCKET_API DWORD __HP_CALL HP_Server_GetFreeSocketObjLockTime(HP_Server pServer) +{ + return C_HP_Object::ToSecond(pServer)->GetFreeSocketObjLockTime(); +} + +HPSOCKET_API DWORD __HP_CALL HP_Server_GetFreeSocketObjPool(HP_Server pServer) +{ + return C_HP_Object::ToSecond(pServer)->GetFreeSocketObjPool(); +} + +HPSOCKET_API DWORD __HP_CALL HP_Server_GetFreeBufferObjPool(HP_Server pServer) +{ + return C_HP_Object::ToSecond(pServer)->GetFreeBufferObjPool(); +} + +HPSOCKET_API DWORD __HP_CALL HP_Server_GetFreeSocketObjHold(HP_Server pServer) +{ + return C_HP_Object::ToSecond(pServer)->GetFreeSocketObjHold(); +} + +HPSOCKET_API DWORD __HP_CALL HP_Server_GetFreeBufferObjHold(HP_Server pServer) +{ + return C_HP_Object::ToSecond(pServer)->GetFreeBufferObjHold(); +} + +HPSOCKET_API DWORD __HP_CALL HP_Server_GetMaxConnectionCount(HP_Server pServer) +{ + return C_HP_Object::ToSecond(pServer)->GetMaxConnectionCount(); +} + +HPSOCKET_API DWORD __HP_CALL HP_Server_GetWorkerThreadCount(HP_Server pServer) +{ + return C_HP_Object::ToSecond(pServer)->GetWorkerThreadCount(); +} + +HPSOCKET_API BOOL __HP_CALL HP_Server_IsMarkSilence(HP_Server pServer) +{ + return C_HP_Object::ToSecond(pServer)->IsMarkSilence(); +} + +/**********************************************************************************/ +/******************************* TCP Server 操作方法 *******************************/ + +HPSOCKET_API BOOL __HP_CALL HP_TcpServer_SendSmallFile(HP_Server pServer, HP_CONNID dwConnID, LPCTSTR lpszFileName, const LPWSABUF pHead, const LPWSABUF pTail) +{ + return C_HP_Object::ToSecond(pServer)->SendSmallFile(dwConnID, lpszFileName, pHead, pTail); +} + +/**********************************************************************************/ +/***************************** TCP Server 属性访问方法 *****************************/ + +HPSOCKET_API void __HP_CALL HP_TcpServer_SetAcceptSocketCount(HP_TcpServer pServer, DWORD dwAcceptSocketCount) +{ + C_HP_Object::ToSecond(pServer)->SetAcceptSocketCount(dwAcceptSocketCount); +} + +HPSOCKET_API void __HP_CALL HP_TcpServer_SetSocketBufferSize(HP_TcpServer pServer, DWORD dwSocketBufferSize) +{ + C_HP_Object::ToSecond(pServer)->SetSocketBufferSize(dwSocketBufferSize); +} + +HPSOCKET_API void __HP_CALL HP_TcpServer_SetSocketListenQueue(HP_TcpServer pServer, DWORD dwSocketListenQueue) +{ + C_HP_Object::ToSecond(pServer)->SetSocketListenQueue(dwSocketListenQueue); +} + +HPSOCKET_API void __HP_CALL HP_TcpServer_SetKeepAliveTime(HP_TcpServer pServer, DWORD dwKeepAliveTime) +{ + C_HP_Object::ToSecond(pServer)->SetKeepAliveTime(dwKeepAliveTime); +} + +HPSOCKET_API void __HP_CALL HP_TcpServer_SetKeepAliveInterval(HP_TcpServer pServer, DWORD dwKeepAliveInterval) +{ + C_HP_Object::ToSecond(pServer)->SetKeepAliveInterval(dwKeepAliveInterval); +} + +HPSOCKET_API void __HP_CALL HP_TcpServer_SetNoDelay(HP_TcpServer pServer, BOOL bNoDelay) +{ + C_HP_Object::ToSecond(pServer)->SetNoDelay(bNoDelay); +} + +HPSOCKET_API DWORD __HP_CALL HP_TcpServer_GetAcceptSocketCount(HP_TcpServer pServer) +{ + return C_HP_Object::ToSecond(pServer)->GetAcceptSocketCount(); +} + +HPSOCKET_API DWORD __HP_CALL HP_TcpServer_GetSocketBufferSize(HP_TcpServer pServer) +{ + return C_HP_Object::ToSecond(pServer)->GetSocketBufferSize(); +} + +HPSOCKET_API DWORD __HP_CALL HP_TcpServer_GetSocketListenQueue(HP_TcpServer pServer) +{ + return C_HP_Object::ToSecond(pServer)->GetSocketListenQueue(); +} + +HPSOCKET_API DWORD __HP_CALL HP_TcpServer_GetKeepAliveTime(HP_TcpServer pServer) +{ + return C_HP_Object::ToSecond(pServer)->GetKeepAliveTime(); +} + +HPSOCKET_API DWORD __HP_CALL HP_TcpServer_GetKeepAliveInterval(HP_TcpServer pServer) +{ + return C_HP_Object::ToSecond(pServer)->GetKeepAliveInterval(); +} + +HPSOCKET_API BOOL __HP_CALL HP_TcpServer_IsNoDelay(HP_TcpServer pServer) +{ + return C_HP_Object::ToSecond(pServer)->IsNoDelay(); +} + +#ifdef _UDP_SUPPORT + +/**********************************************************************************/ +/***************************** UDP Server 属性访问方法 *****************************/ + +HPSOCKET_API void __HP_CALL HP_UdpServer_SetMaxDatagramSize(HP_UdpServer pServer, DWORD dwMaxDatagramSize) +{ + C_HP_Object::ToSecond(pServer)->SetMaxDatagramSize(dwMaxDatagramSize); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpServer_GetMaxDatagramSize(HP_UdpServer pServer) +{ + return C_HP_Object::ToSecond(pServer)->GetMaxDatagramSize(); +} + +HPSOCKET_API void __HP_CALL HP_UdpServer_SetPostReceiveCount(HP_UdpServer pServer, DWORD dwPostReceiveCount) +{ + C_HP_Object::ToSecond(pServer)->SetPostReceiveCount(dwPostReceiveCount); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpServer_GetPostReceiveCount(HP_UdpServer pServer) +{ + return C_HP_Object::ToSecond(pServer)->GetPostReceiveCount(); +} + +HPSOCKET_API void __HP_CALL HP_UdpServer_SetDetectAttempts(HP_UdpServer pServer, DWORD dwDetectAttempts) +{ + C_HP_Object::ToSecond(pServer)->SetDetectAttempts(dwDetectAttempts); +} + +HPSOCKET_API void __HP_CALL HP_UdpServer_SetDetectInterval(HP_UdpServer pServer, DWORD dwDetectInterval) +{ + C_HP_Object::ToSecond(pServer)->SetDetectInterval(dwDetectInterval); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpServer_GetDetectAttempts(HP_UdpServer pServer) +{ + return C_HP_Object::ToSecond(pServer)->GetDetectAttempts(); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpServer_GetDetectInterval(HP_UdpServer pServer) +{ + return C_HP_Object::ToSecond(pServer)->GetDetectInterval(); +} + +/**********************************************************************************/ +/*************************** UDP ARQ Server 属性访问方法 ***************************/ + +HPSOCKET_API void __HP_CALL HP_UdpArqServer_SetNoDelay(HP_UdpArqServer pServer, BOOL bNoDelay) +{ + C_HP_Object::ToFirst(pServer)->SetNoDelay(bNoDelay); +} + +HPSOCKET_API void __HP_CALL HP_UdpArqServer_SetTurnoffCongestCtrl(HP_UdpArqServer pServer, BOOL bTurnOff) +{ + C_HP_Object::ToFirst(pServer)->SetTurnoffCongestCtrl(bTurnOff); +} + +HPSOCKET_API void __HP_CALL HP_UdpArqServer_SetFlushInterval(HP_UdpArqServer pServer, DWORD dwFlushInterval) +{ + C_HP_Object::ToFirst(pServer)->SetFlushInterval(dwFlushInterval); +} + +HPSOCKET_API void __HP_CALL HP_UdpArqServer_SetResendByAcks(HP_UdpArqServer pServer, DWORD dwResendByAcks) +{ + C_HP_Object::ToFirst(pServer)->SetResendByAcks(dwResendByAcks); +} + +HPSOCKET_API void __HP_CALL HP_UdpArqServer_SetSendWndSize(HP_UdpArqServer pServer, DWORD dwSendWndSize) +{ + C_HP_Object::ToFirst(pServer)->SetSendWndSize(dwSendWndSize); +} + +HPSOCKET_API void __HP_CALL HP_UdpArqServer_SetRecvWndSize(HP_UdpArqServer pServer, DWORD dwRecvWndSize) +{ + C_HP_Object::ToFirst(pServer)->SetRecvWndSize(dwRecvWndSize); +} + +HPSOCKET_API void __HP_CALL HP_UdpArqServer_SetMinRto(HP_UdpArqServer pServer, DWORD dwMinRto) +{ + C_HP_Object::ToFirst(pServer)->SetMinRto(dwMinRto); +} + +HPSOCKET_API void __HP_CALL HP_UdpArqServer_SetFastLimit(HP_UdpArqServer pServer, DWORD dwFastLimit) +{ + C_HP_Object::ToFirst(pServer)->SetFastLimit(dwFastLimit); +} + +HPSOCKET_API void __HP_CALL HP_UdpArqServer_SetMaxTransUnit(HP_UdpArqServer pServer, DWORD dwMaxTransUnit) +{ + C_HP_Object::ToFirst(pServer)->SetMaxTransUnit(dwMaxTransUnit); +} + +HPSOCKET_API void __HP_CALL HP_UdpArqServer_SetMaxMessageSize(HP_UdpArqServer pServer, DWORD dwMaxMessageSize) +{ + C_HP_Object::ToFirst(pServer)->SetMaxMessageSize(dwMaxMessageSize); +} + +HPSOCKET_API void __HP_CALL HP_UdpArqServer_SetHandShakeTimeout(HP_UdpArqServer pServer, DWORD dwHandShakeTimeout) +{ + C_HP_Object::ToFirst(pServer)->SetHandShakeTimeout(dwHandShakeTimeout); +} + +HPSOCKET_API BOOL __HP_CALL HP_UdpArqServer_IsNoDelay(HP_UdpArqServer pServer) +{ + return C_HP_Object::ToFirst(pServer)->IsNoDelay(); +} + +HPSOCKET_API BOOL __HP_CALL HP_UdpArqServer_IsTurnoffCongestCtrl(HP_UdpArqServer pServer) +{ + return C_HP_Object::ToFirst(pServer)->IsTurnoffCongestCtrl(); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpArqServer_GetFlushInterval(HP_UdpArqServer pServer) +{ + return C_HP_Object::ToFirst(pServer)->GetFlushInterval(); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpArqServer_GetResendByAcks(HP_UdpArqServer pServer) +{ + return C_HP_Object::ToFirst(pServer)->GetResendByAcks(); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpArqServer_GetSendWndSize(HP_UdpArqServer pServer) +{ + return C_HP_Object::ToFirst(pServer)->GetSendWndSize(); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpArqServer_GetRecvWndSize(HP_UdpArqServer pServer) +{ + return C_HP_Object::ToFirst(pServer)->GetRecvWndSize(); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpArqServer_GetMinRto(HP_UdpArqServer pServer) +{ + return C_HP_Object::ToFirst(pServer)->GetMinRto(); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpArqServer_GetFastLimit(HP_UdpArqServer pServer) +{ + return C_HP_Object::ToFirst(pServer)->GetFastLimit(); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpArqServer_GetMaxTransUnit(HP_UdpArqServer pServer) +{ + return C_HP_Object::ToFirst(pServer)->GetMaxTransUnit(); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpArqServer_GetMaxMessageSize(HP_UdpArqServer pServer) +{ + return C_HP_Object::ToFirst(pServer)->GetMaxMessageSize(); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpArqServer_GetHandShakeTimeout(HP_UdpArqServer pServer) +{ + return C_HP_Object::ToFirst(pServer)->GetHandShakeTimeout(); +} + +HPSOCKET_API BOOL __HP_CALL HP_UdpArqServer_GetWaitingSendMessageCount(HP_UdpArqServer pServer, HP_CONNID dwConnID, int* piCount) +{ + return C_HP_Object::ToFirst(pServer)->GetWaitingSendMessageCount(dwConnID, *piCount); +} + +#endif + +/**************************************************************************/ +/***************************** Agent 操作方法 *****************************/ + +HPSOCKET_API BOOL __HP_CALL HP_Agent_Start(HP_Agent pAgent, LPCTSTR lpszBindAddress, BOOL bAsyncConnect) +{ + return C_HP_Object::ToSecond(pAgent)->Start(lpszBindAddress, bAsyncConnect); +} + +HPSOCKET_API BOOL __HP_CALL HP_Agent_Stop(HP_Agent pAgent) +{ + return C_HP_Object::ToSecond(pAgent)->Stop(); +} + +HPSOCKET_API BOOL __HP_CALL HP_Agent_Wait(HP_Agent pAgent, DWORD dwMilliseconds) +{ + return C_HP_Object::ToSecond(pAgent)->Wait(dwMilliseconds); +} + +HPSOCKET_API BOOL __HP_CALL HP_Agent_Connect(HP_Agent pAgent, LPCTSTR lpszRemoteAddress, USHORT usPort, HP_CONNID* pdwConnID) +{ + return C_HP_Object::ToSecond(pAgent)->Connect(lpszRemoteAddress, usPort, pdwConnID); +} + +HPSOCKET_API BOOL __HP_CALL HP_Agent_ConnectWithExtra(HP_Agent pAgent, LPCTSTR lpszRemoteAddress, USHORT usPort, HP_CONNID* pdwConnID, PVOID pExtra) +{ + return C_HP_Object::ToSecond(pAgent)->Connect(lpszRemoteAddress, usPort, pdwConnID, pExtra); +} + +HPSOCKET_API BOOL __HP_CALL HP_Agent_ConnectWithLocalPort(HP_Agent pAgent, LPCTSTR lpszRemoteAddress, USHORT usPort, HP_CONNID* pdwConnID, USHORT usLocalPort) +{ + return C_HP_Object::ToSecond(pAgent)->Connect(lpszRemoteAddress, usPort, pdwConnID, nullptr, usLocalPort); +} + +HPSOCKET_API BOOL __HP_CALL HP_Agent_ConnectWithLocalAddress(HP_Agent pAgent, LPCTSTR lpszRemoteAddress, USHORT usPort, HP_CONNID* pdwConnID, LPCTSTR lpszLocalAddress) +{ + return C_HP_Object::ToSecond(pAgent)->Connect(lpszRemoteAddress, usPort, pdwConnID, nullptr, 0, lpszLocalAddress); +} + +HPSOCKET_API BOOL __HP_CALL HP_Agent_ConnectWithExtraAndLocalPort(HP_Agent pAgent, LPCTSTR lpszRemoteAddress, USHORT usPort, HP_CONNID* pdwConnID, PVOID pExtra, USHORT usLocalPort) +{ + return C_HP_Object::ToSecond(pAgent)->Connect(lpszRemoteAddress, usPort, pdwConnID, pExtra, usLocalPort); +} + +HPSOCKET_API BOOL __HP_CALL HP_Agent_ConnectWithExtraAndLocalAddressPort(HP_Agent pAgent, LPCTSTR lpszRemoteAddress, USHORT usPort, HP_CONNID* pdwConnID, PVOID pExtra, USHORT usLocalPort, LPCTSTR lpszLocalAddress) +{ + return C_HP_Object::ToSecond(pAgent)->Connect(lpszRemoteAddress, usPort, pdwConnID, pExtra, usLocalPort, lpszLocalAddress); +} + +HPSOCKET_API BOOL __HP_CALL HP_Agent_Send(HP_Agent pAgent, HP_CONNID dwConnID, const BYTE* pBuffer, int iLength) +{ + return C_HP_Object::ToSecond(pAgent)->Send(dwConnID, pBuffer, iLength); +} + +HPSOCKET_API BOOL __HP_CALL HP_Agent_SendPart(HP_Agent pAgent, HP_CONNID dwConnID, const BYTE* pBuffer, int iLength, int iOffset) +{ + return C_HP_Object::ToSecond(pAgent)->Send(dwConnID, pBuffer, iLength, iOffset); +} + +HPSOCKET_API BOOL __HP_CALL HP_Agent_SendPackets(HP_Agent pAgent, HP_CONNID dwConnID, const WSABUF pBuffers[], int iCount) +{ + return C_HP_Object::ToSecond(pAgent)->SendPackets(dwConnID, pBuffers, iCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_Agent_PauseReceive(HP_Agent pAgent, HP_CONNID dwConnID, BOOL bPause) +{ + return C_HP_Object::ToSecond(pAgent)->PauseReceive(dwConnID, bPause); +} + +HPSOCKET_API BOOL __HP_CALL HP_Agent_Disconnect(HP_Agent pAgent, HP_CONNID dwConnID, BOOL bForce) +{ + return C_HP_Object::ToSecond(pAgent)->Disconnect(dwConnID, bForce); +} + +HPSOCKET_API BOOL __HP_CALL HP_Agent_DisconnectLongConnections(HP_Agent pAgent, DWORD dwPeriod, BOOL bForce) +{ + return C_HP_Object::ToSecond(pAgent)->DisconnectLongConnections(dwPeriod, bForce); +} + +HPSOCKET_API BOOL __HP_CALL HP_Agent_DisconnectSilenceConnections(HP_Agent pAgent, DWORD dwPeriod, BOOL bForce) +{ + return C_HP_Object::ToSecond(pAgent)->DisconnectSilenceConnections(dwPeriod, bForce); +} + +/******************************************************************************/ +/***************************** Agent 属性访问方法 *****************************/ + +HPSOCKET_API BOOL __HP_CALL HP_Agent_SetConnectionExtra(HP_Agent pAgent, HP_CONNID dwConnID, PVOID pExtra) +{ + return C_HP_Object::ToSecond(pAgent)->SetConnectionExtra(dwConnID, pExtra); +} + +HPSOCKET_API BOOL __HP_CALL HP_Agent_GetConnectionExtra(HP_Agent pAgent, HP_CONNID dwConnID, PVOID* ppExtra) +{ + return C_HP_Object::ToSecond(pAgent)->GetConnectionExtra(dwConnID, ppExtra); +} + +HPSOCKET_API BOOL __HP_CALL HP_Agent_IsSecure(HP_Agent pAgent) +{ + return C_HP_Object::ToSecond(pAgent)->IsSecure(); +} + +HPSOCKET_API BOOL __HP_CALL HP_Agent_HasStarted(HP_Agent pAgent) +{ + return C_HP_Object::ToSecond(pAgent)->HasStarted(); +} + +HPSOCKET_API En_HP_ServiceState __HP_CALL HP_Agent_GetState(HP_Agent pAgent) +{ + return C_HP_Object::ToSecond(pAgent)->GetState(); +} + +HPSOCKET_API En_HP_SocketError __HP_CALL HP_Agent_GetLastError(HP_Agent pAgent) +{ + return C_HP_Object::ToSecond(pAgent)->GetLastError(); +} + +HPSOCKET_API LPCTSTR __HP_CALL HP_Agent_GetLastErrorDesc(HP_Agent pAgent) +{ + return C_HP_Object::ToSecond(pAgent)->GetLastErrorDesc(); +} + +HPSOCKET_API BOOL __HP_CALL HP_Agent_GetPendingDataLength(HP_Agent pAgent, HP_CONNID dwConnID, int* piPending) +{ + return C_HP_Object::ToSecond(pAgent)->GetPendingDataLength(dwConnID, *piPending); +} + +HPSOCKET_API BOOL __HP_CALL HP_Agent_IsPauseReceive(HP_Agent pAgent, HP_CONNID dwConnID, BOOL* pbPaused) +{ + return C_HP_Object::ToSecond(pAgent)->IsPauseReceive(dwConnID, *pbPaused); +} + +HPSOCKET_API BOOL __HP_CALL HP_Agent_IsConnected(HP_Agent pAgent, HP_CONNID dwConnID) +{ + return C_HP_Object::ToSecond(pAgent)->IsConnected(dwConnID); +} + +HPSOCKET_API DWORD __HP_CALL HP_Agent_GetConnectionCount(HP_Agent pAgent) +{ + return C_HP_Object::ToSecond(pAgent)->GetConnectionCount(); +} + +HPSOCKET_API BOOL __HP_CALL HP_Agent_GetAllConnectionIDs(HP_Agent pAgent, HP_CONNID pIDs[], DWORD* pdwCount) +{ + return C_HP_Object::ToSecond(pAgent)->GetAllConnectionIDs(pIDs, *pdwCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_Agent_GetConnectPeriod(HP_Agent pAgent, HP_CONNID dwConnID, DWORD* pdwPeriod) +{ + return C_HP_Object::ToSecond(pAgent)->GetConnectPeriod(dwConnID, *pdwPeriod); +} + +HPSOCKET_API BOOL __HP_CALL HP_Agent_GetSilencePeriod(HP_Agent pAgent, HP_CONNID dwConnID, DWORD* pdwPeriod) +{ + return C_HP_Object::ToSecond(pAgent)->GetSilencePeriod(dwConnID, *pdwPeriod); +} + +HPSOCKET_API BOOL __HP_CALL HP_Agent_GetLocalAddress(HP_Agent pAgent, HP_CONNID dwConnID, TCHAR lpszAddress[], int* piAddressLen, USHORT* pusPort) +{ + return C_HP_Object::ToSecond(pAgent)->GetLocalAddress(dwConnID, lpszAddress, *piAddressLen, *pusPort); +} + +HPSOCKET_API BOOL __HP_CALL HP_Agent_GetRemoteAddress(HP_Agent pAgent, HP_CONNID dwConnID, TCHAR lpszAddress[], int* piAddressLen, USHORT* pusPort) +{ + return C_HP_Object::ToSecond(pAgent)->GetRemoteAddress(dwConnID, lpszAddress, *piAddressLen, *pusPort); +} + +HPSOCKET_API BOOL __HP_CALL HP_Agent_GetRemoteHost(HP_Agent pAgent, HP_CONNID dwConnID, TCHAR lpszHost[], int* piHostLen, USHORT* pusPort) +{ + return C_HP_Object::ToSecond(pAgent)->GetRemoteHost(dwConnID, lpszHost, *piHostLen, *pusPort); +} + +HPSOCKET_API void __HP_CALL HP_Agent_SetReuseAddressPolicy(HP_Agent pAgent, En_HP_ReuseAddressPolicy enReusePolicy) +{ + C_HP_Object::ToSecond(pAgent)->SetReuseAddressPolicy(enReusePolicy); +} + +HPSOCKET_API void __HP_CALL HP_Agent_SetSendPolicy(HP_Agent pAgent, En_HP_SendPolicy enSendPolicy) +{ + C_HP_Object::ToSecond(pAgent)->SetSendPolicy(enSendPolicy); +} + +HPSOCKET_API void __HP_CALL HP_Agent_SetOnSendSyncPolicy(HP_Agent pAgent, En_HP_OnSendSyncPolicy enSyncPolicy) +{ + C_HP_Object::ToSecond(pAgent)->SetOnSendSyncPolicy(enSyncPolicy); +} + +HPSOCKET_API void __HP_CALL HP_Agent_SetFreeSocketObjLockTime(HP_Agent pAgent, DWORD dwFreeSocketObjLockTime) +{ + C_HP_Object::ToSecond(pAgent)->SetFreeSocketObjLockTime(dwFreeSocketObjLockTime); +} + +HPSOCKET_API void __HP_CALL HP_Agent_SetFreeSocketObjPool(HP_Agent pAgent, DWORD dwFreeSocketObjPool) +{ + C_HP_Object::ToSecond(pAgent)->SetFreeSocketObjPool(dwFreeSocketObjPool); +} + +HPSOCKET_API void __HP_CALL HP_Agent_SetFreeBufferObjPool(HP_Agent pAgent, DWORD dwFreeBufferObjPool) +{ + C_HP_Object::ToSecond(pAgent)->SetFreeBufferObjPool(dwFreeBufferObjPool); +} + +HPSOCKET_API void __HP_CALL HP_Agent_SetFreeSocketObjHold(HP_Agent pAgent, DWORD dwFreeSocketObjHold) +{ + C_HP_Object::ToSecond(pAgent)->SetFreeSocketObjHold(dwFreeSocketObjHold); +} + +HPSOCKET_API void __HP_CALL HP_Agent_SetFreeBufferObjHold(HP_Agent pAgent, DWORD dwFreeBufferObjHold) +{ + C_HP_Object::ToSecond(pAgent)->SetFreeBufferObjHold(dwFreeBufferObjHold); +} + +HPSOCKET_API void __HP_CALL HP_Agent_SetMaxConnectionCount(HP_Agent pAgent, DWORD dwMaxConnectionCount) +{ + C_HP_Object::ToSecond(pAgent)->SetMaxConnectionCount(dwMaxConnectionCount); +} + +HPSOCKET_API void __HP_CALL HP_Agent_SetWorkerThreadCount(HP_Agent pAgent, DWORD dwWorkerThreadCount) +{ + C_HP_Object::ToSecond(pAgent)->SetWorkerThreadCount(dwWorkerThreadCount); +} + +HPSOCKET_API void __HP_CALL HP_Agent_SetMarkSilence(HP_Agent pAgent, BOOL bMarkSilence) +{ + C_HP_Object::ToSecond(pAgent)->SetMarkSilence(bMarkSilence); +} + +HPSOCKET_API En_HP_ReuseAddressPolicy __HP_CALL HP_Agent_GetReuseAddressPolicy(HP_Agent pAgent) +{ + return C_HP_Object::ToSecond(pAgent)->GetReuseAddressPolicy(); +} + +HPSOCKET_API En_HP_SendPolicy __HP_CALL HP_Agent_GetSendPolicy(HP_Agent pAgent) +{ + return C_HP_Object::ToSecond(pAgent)->GetSendPolicy(); +} + +HPSOCKET_API En_HP_OnSendSyncPolicy __HP_CALL HP_Agent_GetOnSendSyncPolicy(HP_Agent pAgent) +{ + return C_HP_Object::ToSecond(pAgent)->GetOnSendSyncPolicy(); +} + +HPSOCKET_API DWORD __HP_CALL HP_Agent_GetFreeSocketObjLockTime(HP_Agent pAgent) +{ + return C_HP_Object::ToSecond(pAgent)->GetFreeSocketObjLockTime(); +} + +HPSOCKET_API DWORD __HP_CALL HP_Agent_GetFreeSocketObjPool(HP_Agent pAgent) +{ + return C_HP_Object::ToSecond(pAgent)->GetFreeSocketObjPool(); +} + +HPSOCKET_API DWORD __HP_CALL HP_Agent_GetFreeBufferObjPool(HP_Agent pAgent) +{ + return C_HP_Object::ToSecond(pAgent)->GetFreeBufferObjPool(); +} + +HPSOCKET_API DWORD __HP_CALL HP_Agent_GetFreeSocketObjHold(HP_Agent pAgent) +{ + return C_HP_Object::ToSecond(pAgent)->GetFreeSocketObjHold(); +} + +HPSOCKET_API DWORD __HP_CALL HP_Agent_GetFreeBufferObjHold(HP_Agent pAgent) +{ + return C_HP_Object::ToSecond(pAgent)->GetFreeBufferObjHold(); +} + +HPSOCKET_API DWORD __HP_CALL HP_Agent_GetMaxConnectionCount(HP_Agent pAgent) +{ + return C_HP_Object::ToSecond(pAgent)->GetMaxConnectionCount(); +} + +HPSOCKET_API DWORD __HP_CALL HP_Agent_GetWorkerThreadCount(HP_Agent pAgent) +{ + return C_HP_Object::ToSecond(pAgent)->GetWorkerThreadCount(); +} + +HPSOCKET_API BOOL __HP_CALL HP_Agent_IsMarkSilence(HP_Agent pAgent) +{ + return C_HP_Object::ToSecond(pAgent)->IsMarkSilence(); +} + +/**********************************************************************************/ +/******************************* TCP Agent 操作方法 *******************************/ + +HPSOCKET_API BOOL __HP_CALL HP_TcpAgent_SendSmallFile(HP_Agent pAgent, HP_CONNID dwConnID, LPCTSTR lpszFileName, const LPWSABUF pHead, const LPWSABUF pTail) +{ + return C_HP_Object::ToSecond(pAgent)->SendSmallFile(dwConnID, lpszFileName, pHead, pTail); +} + +/**********************************************************************************/ +/***************************** TCP Agent 属性访问方法 *****************************/ + +HPSOCKET_API void __HP_CALL HP_TcpAgent_SetSyncConnectTimeout(HP_TcpAgent pAgent, DWORD dwSyncConnectTimeout) +{ + C_HP_Object::ToSecond(pAgent)->SetSyncConnectTimeout(dwSyncConnectTimeout); +} + +HPSOCKET_API void __HP_CALL HP_TcpAgent_SetSocketBufferSize(HP_TcpAgent pAgent, DWORD dwSocketBufferSize) +{ + C_HP_Object::ToSecond(pAgent)->SetSocketBufferSize(dwSocketBufferSize); +} + +HPSOCKET_API void __HP_CALL HP_TcpAgent_SetKeepAliveTime(HP_TcpAgent pAgent, DWORD dwKeepAliveTime) +{ + C_HP_Object::ToSecond(pAgent)->SetKeepAliveTime(dwKeepAliveTime); +} + +HPSOCKET_API void __HP_CALL HP_TcpAgent_SetKeepAliveInterval(HP_TcpAgent pAgent, DWORD dwKeepAliveInterval) +{ + C_HP_Object::ToSecond(pAgent)->SetKeepAliveInterval(dwKeepAliveInterval); +} + +HPSOCKET_API void __HP_CALL HP_TcpAgent_SetNoDelay(HP_TcpAgent pAgent, BOOL bNoDelay) +{ + C_HP_Object::ToSecond(pAgent)->SetNoDelay(bNoDelay); +} + +HPSOCKET_API DWORD __HP_CALL HP_TcpAgent_GetSyncConnectTimeout(HP_TcpAgent pAgent) +{ + return C_HP_Object::ToSecond(pAgent)->GetSyncConnectTimeout(); +} + +HPSOCKET_API DWORD __HP_CALL HP_TcpAgent_GetSocketBufferSize(HP_TcpAgent pAgent) +{ + return C_HP_Object::ToSecond(pAgent)->GetSocketBufferSize(); +} + +HPSOCKET_API DWORD __HP_CALL HP_TcpAgent_GetKeepAliveTime(HP_TcpAgent pAgent) +{ + return C_HP_Object::ToSecond(pAgent)->GetKeepAliveTime(); +} + +HPSOCKET_API DWORD __HP_CALL HP_TcpAgent_GetKeepAliveInterval(HP_TcpAgent pAgent) +{ + return C_HP_Object::ToSecond(pAgent)->GetKeepAliveInterval(); +} + +HPSOCKET_API BOOL __HP_CALL HP_TcpAgent_IsNoDelay(HP_TcpAgent pAgent) +{ + return C_HP_Object::ToSecond(pAgent)->IsNoDelay(); +} + +/******************************************************************************/ +/***************************** Client 组件操作方法 *****************************/ + +HPSOCKET_API BOOL __HP_CALL HP_Client_Start(HP_Client pClient, LPCTSTR lpszRemoteAddress, USHORT usPort, BOOL bAsyncConnect) +{ + return C_HP_Object::ToSecond(pClient)->Start(lpszRemoteAddress, usPort, bAsyncConnect); +} + +HPSOCKET_API BOOL __HP_CALL HP_Client_StartWithBindAddress(HP_Client pClient, LPCTSTR lpszRemoteAddress, USHORT usPort, BOOL bAsyncConnect, LPCTSTR lpszBindAddress) +{ + return C_HP_Object::ToSecond(pClient)->Start(lpszRemoteAddress, usPort, bAsyncConnect, lpszBindAddress); +} + +HPSOCKET_API BOOL __HP_CALL HP_Client_StartWithBindAddressAndLocalPort(HP_Client pClient, LPCTSTR lpszRemoteAddress, USHORT usPort, BOOL bAsyncConnect, LPCTSTR lpszBindAddress, USHORT usLocalPort) +{ + return C_HP_Object::ToSecond(pClient)->Start(lpszRemoteAddress, usPort, bAsyncConnect, lpszBindAddress, usLocalPort); +} + +HPSOCKET_API BOOL __HP_CALL HP_Client_Stop(HP_Client pClient) +{ + return C_HP_Object::ToSecond(pClient)->Stop(); +} + +HPSOCKET_API BOOL __HP_CALL HP_Client_Send(HP_Client pClient, const BYTE* pBuffer, int iLength) +{ + return C_HP_Object::ToSecond(pClient)->Send(pBuffer, iLength); +} + +HPSOCKET_API BOOL __HP_CALL HP_Client_SendPart(HP_Client pClient, const BYTE* pBuffer, int iLength, int iOffset) +{ + return C_HP_Object::ToSecond(pClient)->Send(pBuffer, iLength, iOffset); +} + +HPSOCKET_API BOOL __HP_CALL HP_Client_SendPackets(HP_Client pClient, const WSABUF pBuffers[], int iCount) +{ + return C_HP_Object::ToSecond(pClient)->SendPackets(pBuffers, iCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_Client_PauseReceive(HP_Client pClient, BOOL bPause) +{ + return C_HP_Object::ToSecond(pClient)->PauseReceive(bPause); +} + +HPSOCKET_API BOOL __HP_CALL HP_Client_Wait(HP_Client pClient, DWORD dwMilliseconds) +{ + return C_HP_Object::ToSecond(pClient)->Wait(dwMilliseconds); +} + +/******************************************************************************/ +/***************************** Client 属性访问方法 *****************************/ + +HPSOCKET_API void __HP_CALL HP_Client_SetExtra(HP_Client pClient, PVOID pExtra) +{ + C_HP_Object::ToSecond(pClient)->SetExtra(pExtra); +} + +HPSOCKET_API PVOID __HP_CALL HP_Client_GetExtra(HP_Client pClient) +{ + return C_HP_Object::ToSecond(pClient)->GetExtra(); +} + +HPSOCKET_API BOOL __HP_CALL HP_Client_IsSecure(HP_Client pClient) +{ + return C_HP_Object::ToSecond(pClient)->IsSecure(); +} + +HPSOCKET_API BOOL __HP_CALL HP_Client_HasStarted(HP_Client pClient) +{ + return C_HP_Object::ToSecond(pClient)->HasStarted(); +} + +HPSOCKET_API En_HP_ServiceState __HP_CALL HP_Client_GetState(HP_Client pClient) +{ + return C_HP_Object::ToSecond(pClient)->GetState(); +} + +HPSOCKET_API En_HP_SocketError __HP_CALL HP_Client_GetLastError(HP_Client pClient) +{ + return C_HP_Object::ToSecond(pClient)->GetLastError(); +} + +HPSOCKET_API LPCTSTR __HP_CALL HP_Client_GetLastErrorDesc(HP_Client pClient) +{ + return C_HP_Object::ToSecond(pClient)->GetLastErrorDesc(); +} + +HPSOCKET_API HP_CONNID __HP_CALL HP_Client_GetConnectionID(HP_Client pClient) +{ + return C_HP_Object::ToSecond(pClient)->GetConnectionID(); +} + +HPSOCKET_API BOOL __HP_CALL HP_Client_GetLocalAddress(HP_Client pClient, TCHAR lpszAddress[], int* piAddressLen, USHORT* pusPort) +{ + return C_HP_Object::ToSecond(pClient)->GetLocalAddress(lpszAddress, *piAddressLen, *pusPort); +} + +HPSOCKET_API BOOL __HP_CALL HP_Client_GetRemoteHost(HP_Client pClient, TCHAR lpszHost[], int* piHostLen, USHORT* pusPort) +{ + return C_HP_Object::ToSecond(pClient)->GetRemoteHost(lpszHost, *piHostLen, *pusPort); +} + +HPSOCKET_API BOOL __HP_CALL HP_Client_GetPendingDataLength(HP_Client pClient, int* piPending) +{ + return C_HP_Object::ToSecond(pClient)->GetPendingDataLength(*piPending); +} + +HPSOCKET_API BOOL __HP_CALL HP_Client_IsPauseReceive(HP_Client pClient, BOOL* pbPaused) +{ + return C_HP_Object::ToSecond(pClient)->IsPauseReceive(*pbPaused); +} + +HPSOCKET_API BOOL __HP_CALL HP_Client_IsConnected(HP_Client pClient) +{ + return C_HP_Object::ToSecond(pClient)->IsConnected(); +} + +HPSOCKET_API void __HP_CALL HP_Client_SetReuseAddressPolicy(HP_Client pClient, En_HP_ReuseAddressPolicy enReusePolicy) +{ + C_HP_Object::ToSecond(pClient)->SetReuseAddressPolicy(enReusePolicy); +} + +HPSOCKET_API void __HP_CALL HP_Client_SetFreeBufferPoolSize(HP_Client pClient, DWORD dwFreeBufferPoolSize) +{ + C_HP_Object::ToSecond(pClient)->SetFreeBufferPoolSize(dwFreeBufferPoolSize); +} + +HPSOCKET_API void __HP_CALL HP_Client_SetFreeBufferPoolHold(HP_Client pClient, DWORD dwFreeBufferPoolHold) +{ + C_HP_Object::ToSecond(pClient)->SetFreeBufferPoolHold(dwFreeBufferPoolHold); +} + +HPSOCKET_API En_HP_ReuseAddressPolicy __HP_CALL HP_Client_GetReuseAddressPolicy(HP_Client pClient) +{ + return C_HP_Object::ToSecond(pClient)->GetReuseAddressPolicy(); +} + +HPSOCKET_API DWORD __HP_CALL HP_Client_GetFreeBufferPoolSize(HP_Client pClient) +{ + return C_HP_Object::ToSecond(pClient)->GetFreeBufferPoolSize(); +} + +HPSOCKET_API DWORD __HP_CALL HP_Client_GetFreeBufferPoolHold(HP_Client pClient) +{ + return C_HP_Object::ToSecond(pClient)->GetFreeBufferPoolHold(); +} + +/**********************************************************************************/ +/******************************* TCP Client 操作方法 *******************************/ + +HPSOCKET_API BOOL __HP_CALL HP_TcpClient_SendSmallFile(HP_Client pClient, LPCTSTR lpszFileName, const LPWSABUF pHead, const LPWSABUF pTail) +{ + return C_HP_Object::ToSecond(pClient)->SendSmallFile(lpszFileName, pHead, pTail); +} + +/**********************************************************************************/ +/***************************** TCP Client 属性访问方法 *****************************/ + +HPSOCKET_API void __HP_CALL HP_TcpClient_SetSyncConnectTimeout(HP_TcpClient pClient, DWORD dwSyncConnectTimeout) +{ + C_HP_Object::ToSecond(pClient)->SetSyncConnectTimeout(dwSyncConnectTimeout); +} + +HPSOCKET_API void __HP_CALL HP_TcpClient_SetSocketBufferSize(HP_TcpClient pClient, DWORD dwSocketBufferSize) +{ + C_HP_Object::ToSecond(pClient)->SetSocketBufferSize(dwSocketBufferSize); +} + +HPSOCKET_API void __HP_CALL HP_TcpClient_SetKeepAliveTime(HP_TcpClient pClient, DWORD dwKeepAliveTime) +{ + C_HP_Object::ToSecond(pClient)->SetKeepAliveTime(dwKeepAliveTime); +} + +HPSOCKET_API void __HP_CALL HP_TcpClient_SetKeepAliveInterval(HP_TcpClient pClient, DWORD dwKeepAliveInterval) +{ + C_HP_Object::ToSecond(pClient)->SetKeepAliveInterval(dwKeepAliveInterval); +} + +HPSOCKET_API void __HP_CALL HP_TcpClient_SetNoDelay(HP_TcpClient pClient, BOOL bNoDelay) +{ + C_HP_Object::ToSecond(pClient)->SetNoDelay(bNoDelay); +} + +HPSOCKET_API DWORD __HP_CALL HP_TcpClient_GetSyncConnectTimeout(HP_TcpClient pClient) +{ + return C_HP_Object::ToSecond(pClient)->GetSyncConnectTimeout(); +} + +HPSOCKET_API DWORD __HP_CALL HP_TcpClient_GetSocketBufferSize(HP_TcpClient pClient) +{ + return C_HP_Object::ToSecond(pClient)->GetSocketBufferSize(); +} + +HPSOCKET_API DWORD __HP_CALL HP_TcpClient_GetKeepAliveTime(HP_TcpClient pClient) +{ + return C_HP_Object::ToSecond(pClient)->GetKeepAliveTime(); +} + +HPSOCKET_API DWORD __HP_CALL HP_TcpClient_GetKeepAliveInterval(HP_TcpClient pClient) +{ + return C_HP_Object::ToSecond(pClient)->GetKeepAliveInterval(); +} + +HPSOCKET_API BOOL __HP_CALL HP_TcpClient_IsNoDelay(HP_TcpClient pClient) +{ + return C_HP_Object::ToSecond(pClient)->IsNoDelay(); +} + +#ifdef _UDP_SUPPORT + +/**********************************************************************************/ +/***************************** UDP Client 属性访问方法 *****************************/ + +HPSOCKET_API void __HP_CALL HP_UdpClient_SetMaxDatagramSize(HP_UdpClient pClient, DWORD dwMaxDatagramSize) +{ + C_HP_Object::ToSecond(pClient)->SetMaxDatagramSize(dwMaxDatagramSize); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpClient_GetMaxDatagramSize(HP_UdpClient pClient) +{ + return C_HP_Object::ToSecond(pClient)->GetMaxDatagramSize(); +} + +HPSOCKET_API void __HP_CALL HP_UdpClient_SetDetectAttempts(HP_UdpClient pClient, DWORD dwDetectAttempts) +{ + C_HP_Object::ToSecond(pClient)->SetDetectAttempts(dwDetectAttempts); +} + +HPSOCKET_API void __HP_CALL HP_UdpClient_SetDetectInterval(HP_UdpClient pClient, DWORD dwDetectInterval) +{ + C_HP_Object::ToSecond(pClient)->SetDetectInterval(dwDetectInterval); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpClient_GetDetectAttempts(HP_UdpClient pClient) +{ + return C_HP_Object::ToSecond(pClient)->GetDetectAttempts(); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpClient_GetDetectInterval(HP_UdpClient pClient) +{ + return C_HP_Object::ToSecond(pClient)->GetDetectInterval(); +} + +/**********************************************************************************/ +/*************************** UDP ARQ Client 属性访问方法 ***************************/ + +HPSOCKET_API void __HP_CALL HP_UdpArqClient_SetNoDelay(HP_UdpArqClient pClient, BOOL bNoDelay) +{ + C_HP_Object::ToFirst(pClient)->SetNoDelay(bNoDelay); +} + +HPSOCKET_API void __HP_CALL HP_UdpArqClient_SetTurnoffCongestCtrl(HP_UdpArqClient pClient, BOOL bTurnOff) +{ + C_HP_Object::ToFirst(pClient)->SetTurnoffCongestCtrl(bTurnOff); +} + +HPSOCKET_API void __HP_CALL HP_UdpArqClient_SetFlushInterval(HP_UdpArqClient pClient, DWORD dwFlushInterval) +{ + C_HP_Object::ToFirst(pClient)->SetFlushInterval(dwFlushInterval); +} + +HPSOCKET_API void __HP_CALL HP_UdpArqClient_SetResendByAcks(HP_UdpArqClient pClient, DWORD dwResendByAcks) +{ + C_HP_Object::ToFirst(pClient)->SetResendByAcks(dwResendByAcks); +} + +HPSOCKET_API void __HP_CALL HP_UdpArqClient_SetSendWndSize(HP_UdpArqClient pClient, DWORD dwSendWndSize) +{ + C_HP_Object::ToFirst(pClient)->SetSendWndSize(dwSendWndSize); +} + +HPSOCKET_API void __HP_CALL HP_UdpArqClient_SetRecvWndSize(HP_UdpArqClient pClient, DWORD dwRecvWndSize) +{ + C_HP_Object::ToFirst(pClient)->SetRecvWndSize(dwRecvWndSize); +} + +HPSOCKET_API void __HP_CALL HP_UdpArqClient_SetMinRto(HP_UdpArqClient pClient, DWORD dwMinRto) +{ + C_HP_Object::ToFirst(pClient)->SetMinRto(dwMinRto); +} + +HPSOCKET_API void __HP_CALL HP_UdpArqClient_SetFastLimit(HP_UdpArqClient pClient, DWORD dwFastLimit) +{ + C_HP_Object::ToFirst(pClient)->SetFastLimit(dwFastLimit); +} + +HPSOCKET_API void __HP_CALL HP_UdpArqClient_SetMaxTransUnit(HP_UdpArqClient pClient, DWORD dwMaxTransUnit) +{ + C_HP_Object::ToFirst(pClient)->SetMaxTransUnit(dwMaxTransUnit); +} + +HPSOCKET_API void __HP_CALL HP_UdpArqClient_SetMaxMessageSize(HP_UdpArqClient pClient, DWORD dwMaxMessageSize) +{ + C_HP_Object::ToFirst(pClient)->SetMaxMessageSize(dwMaxMessageSize); +} + +HPSOCKET_API void __HP_CALL HP_UdpArqClient_SetHandShakeTimeout(HP_UdpArqClient pClient, DWORD dwHandShakeTimeout) +{ + C_HP_Object::ToFirst(pClient)->SetHandShakeTimeout(dwHandShakeTimeout); +} + +HPSOCKET_API BOOL __HP_CALL HP_UdpArqClient_IsNoDelay(HP_UdpArqClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->IsNoDelay(); +} + +HPSOCKET_API BOOL __HP_CALL HP_UdpArqClient_IsTurnoffCongestCtrl(HP_UdpArqClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->IsTurnoffCongestCtrl(); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpArqClient_GetFlushInterval(HP_UdpArqClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->GetFlushInterval(); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpArqClient_GetResendByAcks(HP_UdpArqClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->GetResendByAcks(); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpArqClient_GetSendWndSize(HP_UdpArqClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->GetSendWndSize(); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpArqClient_GetRecvWndSize(HP_UdpArqClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->GetRecvWndSize(); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpArqClient_GetMinRto(HP_UdpArqClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->GetMinRto(); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpArqClient_GetFastLimit(HP_UdpArqClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->GetFastLimit(); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpArqClient_GetMaxTransUnit(HP_UdpArqClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->GetMaxTransUnit(); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpArqClient_GetMaxMessageSize(HP_UdpArqClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->GetMaxMessageSize(); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpArqClient_GetHandShakeTimeout(HP_UdpArqClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->GetHandShakeTimeout(); +} + +HPSOCKET_API BOOL __HP_CALL HP_UdpArqClient_GetWaitingSendMessageCount(HP_UdpArqClient pClient, int* piCount) +{ + return C_HP_Object::ToFirst(pClient)->GetWaitingSendMessageCount(*piCount); +} + +/**********************************************************************************/ +/****************************** UDP Cast 属性访问方法 ******************************/ + +HPSOCKET_API void __HP_CALL HP_UdpCast_SetMaxDatagramSize(HP_UdpCast pCast, DWORD dwMaxDatagramSize) +{ + C_HP_Object::ToSecond(pCast)->SetMaxDatagramSize(dwMaxDatagramSize); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpCast_GetMaxDatagramSize(HP_UdpCast pCast) +{ + return C_HP_Object::ToSecond(pCast)->GetMaxDatagramSize(); +} + +HPSOCKET_API BOOL __HP_CALL HP_UdpCast_GetRemoteAddress(HP_UdpCast pCast, TCHAR lpszAddress[], int* piAddressLen, USHORT* pusPort) +{ + return C_HP_Object::ToSecond(pCast)->GetRemoteAddress(lpszAddress, *piAddressLen, *pusPort); +} + +HPSOCKET_API void __HP_CALL HP_UdpCast_SetCastMode(HP_UdpCast pCast, En_HP_CastMode enCastMode) +{ + C_HP_Object::ToSecond(pCast)->SetCastMode(enCastMode); +} + +HPSOCKET_API En_HP_CastMode __HP_CALL HP_UdpCast_GetCastMode(HP_UdpCast pCast) +{ + return C_HP_Object::ToSecond(pCast)->GetCastMode(); +} + +HPSOCKET_API void __HP_CALL HP_UdpCast_SetMultiCastTtl(HP_UdpCast pCast, int iMCTtl) +{ + C_HP_Object::ToSecond(pCast)->SetMultiCastTtl(iMCTtl); +} + +HPSOCKET_API int __HP_CALL HP_UdpCast_GetMultiCastTtl(HP_UdpCast pCast) +{ + return C_HP_Object::ToSecond(pCast)->GetMultiCastTtl(); +} + +HPSOCKET_API void __HP_CALL HP_UdpCast_SetMultiCastLoop(HP_UdpCast pCast, BOOL bMCLoop) +{ + C_HP_Object::ToSecond(pCast)->SetMultiCastLoop(bMCLoop); +} + +HPSOCKET_API BOOL __HP_CALL HP_UdpCast_IsMultiCastLoop(HP_UdpCast pCast) +{ + return C_HP_Object::ToSecond(pCast)->IsMultiCastLoop(); +} + +/**********************************************************************************/ +/****************************** UDP Node 组件操作方法 ******************************/ + +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_Start(HP_UdpNode pNode, LPCTSTR lpszBindAddress, USHORT usPort) +{ + return C_HP_Object::ToSecond(pNode)->Start(lpszBindAddress, usPort); +} + +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_StartWithCast(HP_UdpNode pNode, LPCTSTR lpszBindAddress, USHORT usPort, En_HP_CastMode enCastMode, LPCTSTR lpszCastAddress) +{ + return C_HP_Object::ToSecond(pNode)->Start(lpszBindAddress, usPort, enCastMode, lpszCastAddress); +} + +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_Stop(HP_UdpNode pNode) +{ + return C_HP_Object::ToSecond(pNode)->Stop(); +} + +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_Wait(HP_UdpNode pNode, DWORD dwMilliseconds) +{ + return C_HP_Object::ToSecond(pNode)->Wait(dwMilliseconds); +} + +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_Send(HP_UdpNode pNode, LPCTSTR lpszRemoteAddress, USHORT usRemotePort, const BYTE* pBuffer, int iLength) +{ + return C_HP_Object::ToSecond(pNode)->Send(lpszRemoteAddress, usRemotePort, pBuffer, iLength); +} + +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_SendPart(HP_UdpNode pNode, LPCTSTR lpszRemoteAddress, USHORT usRemotePort, const BYTE* pBuffer, int iLength, int iOffset) +{ + return C_HP_Object::ToSecond(pNode)->Send(lpszRemoteAddress, usRemotePort, pBuffer, iLength, iOffset); +} + +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_SendPackets(HP_UdpNode pNode, LPCTSTR lpszRemoteAddress, USHORT usRemotePort, const WSABUF pBuffers[], int iCount) +{ + return C_HP_Object::ToSecond(pNode)->SendPackets(lpszRemoteAddress, usRemotePort, pBuffers, iCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_SendCast(HP_UdpNode pNode, const BYTE* pBuffer, int iLength) +{ + return C_HP_Object::ToSecond(pNode)->SendCast(pBuffer, iLength); +} + +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_SendCastPart(HP_UdpNode pNode, const BYTE* pBuffer, int iLength, int iOffset) +{ + return C_HP_Object::ToSecond(pNode)->SendCast(pBuffer, iLength, iOffset); +} + +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_SendCastPackets(HP_UdpNode pNode, const WSABUF pBuffers[], int iCount) +{ + return C_HP_Object::ToSecond(pNode)->SendCastPackets(pBuffers, iCount); +} + +/**********************************************************************************/ +/****************************** UDP Node 属性访问方法 ******************************/ + +HPSOCKET_API void __HP_CALL HP_UdpNode_SetExtra(HP_UdpNode pNode, PVOID pExtra) +{ + C_HP_Object::ToSecond(pNode)->SetExtra(pExtra); +} + +HPSOCKET_API PVOID __HP_CALL HP_UdpNode_GetExtra(HP_UdpNode pNode) +{ + return C_HP_Object::ToSecond(pNode)->GetExtra(); +} + +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_HasStarted(HP_UdpNode pNode) +{ + return C_HP_Object::ToSecond(pNode)->HasStarted(); +} + +HPSOCKET_API En_HP_ServiceState __HP_CALL HP_UdpNode_GetState(HP_UdpNode pNode) +{ + return C_HP_Object::ToSecond(pNode)->GetState(); +} + +HPSOCKET_API En_HP_SocketError __HP_CALL HP_UdpNode_GetLastError(HP_UdpNode pNode) +{ + return C_HP_Object::ToSecond(pNode)->GetLastError(); +} + +HPSOCKET_API LPCTSTR __HP_CALL HP_UdpNode_GetLastErrorDesc(HP_UdpNode pNode) +{ + return C_HP_Object::ToSecond(pNode)->GetLastErrorDesc(); +} + +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_GetLocalAddress(HP_UdpNode pNode, TCHAR lpszAddress[], int* piAddressLen, USHORT* pusPort) +{ + return C_HP_Object::ToSecond(pNode)->GetLocalAddress(lpszAddress, *piAddressLen, *pusPort); +} + +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_GetCastAddress(HP_UdpNode pNode, TCHAR lpszAddress[], int* piAddressLen, USHORT* pusPort) +{ + return C_HP_Object::ToSecond(pNode)->GetCastAddress(lpszAddress, *piAddressLen, *pusPort); +} + +HPSOCKET_API En_HP_CastMode __HP_CALL HP_UdpNode_GetCastMode(HP_UdpNode pNode) +{ + return C_HP_Object::ToSecond(pNode)->GetCastMode(); +} + +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_GetPendingDataLength(HP_UdpNode pNode, int* piPending) +{ + return C_HP_Object::ToSecond(pNode)->GetPendingDataLength(*piPending); +} + +HPSOCKET_API void __HP_CALL HP_UdpNode_SetMaxDatagramSize(HP_UdpNode pNode, DWORD dwMaxDatagramSize) +{ + C_HP_Object::ToSecond(pNode)->SetMaxDatagramSize(dwMaxDatagramSize); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpNode_GetMaxDatagramSize(HP_UdpNode pNode) +{ + return C_HP_Object::ToSecond(pNode)->GetMaxDatagramSize(); +} + +HPSOCKET_API void __HP_CALL HP_UdpNode_SetMultiCastTtl(HP_UdpNode pNode, int iMCTtl) +{ + C_HP_Object::ToSecond(pNode)->SetMultiCastTtl(iMCTtl); +} + +HPSOCKET_API int __HP_CALL HP_UdpNode_GetMultiCastTtl(HP_UdpNode pNode) +{ + return C_HP_Object::ToSecond(pNode)->GetMultiCastTtl(); +} + +HPSOCKET_API void __HP_CALL HP_UdpNode_SetMultiCastLoop(HP_UdpNode pNode, BOOL bMCLoop) +{ + C_HP_Object::ToSecond(pNode)->SetMultiCastLoop(bMCLoop); +} + +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_IsMultiCastLoop(HP_UdpNode pNode) +{ + return C_HP_Object::ToSecond(pNode)->IsMultiCastLoop(); +} + +HPSOCKET_API void __HP_CALL HP_UdpNode_SetReuseAddressPolicy(HP_UdpNode pNode, En_HP_ReuseAddressPolicy enReusePolicy) +{ + C_HP_Object::ToSecond(pNode)->SetReuseAddressPolicy(enReusePolicy); +} + +HPSOCKET_API void __HP_CALL HP_UdpNode_SetWorkerThreadCount(HP_UdpNode pNode, DWORD dwWorkerThreadCount) +{ + C_HP_Object::ToSecond(pNode)->SetWorkerThreadCount(dwWorkerThreadCount); +} + +HPSOCKET_API void __HP_CALL HP_UdpNode_SetPostReceiveCount(HP_UdpNode pNode, DWORD dwPostReceiveCount) +{ + C_HP_Object::ToSecond(pNode)->SetPostReceiveCount(dwPostReceiveCount); +} + +HPSOCKET_API void __HP_CALL HP_UdpNode_SetFreeBufferPoolSize(HP_UdpNode pNode, DWORD dwFreeBufferPoolSize) +{ + C_HP_Object::ToSecond(pNode)->SetFreeBufferPoolSize(dwFreeBufferPoolSize); +} + +HPSOCKET_API void __HP_CALL HP_UdpNode_SetFreeBufferPoolHold(HP_UdpNode pNode, DWORD dwFreeBufferPoolHold) +{ + C_HP_Object::ToSecond(pNode)->SetFreeBufferPoolHold(dwFreeBufferPoolHold); +} + +HPSOCKET_API En_HP_ReuseAddressPolicy __HP_CALL HP_UdpNode_GetReuseAddressPolicy(HP_UdpNode pNode) +{ + return C_HP_Object::ToSecond(pNode)->GetReuseAddressPolicy(); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpNode_GetWorkerThreadCount(HP_UdpNode pNode) +{ + return C_HP_Object::ToSecond(pNode)->GetWorkerThreadCount(); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpNode_GetPostReceiveCount(HP_UdpNode pNode) +{ + return C_HP_Object::ToSecond(pNode)->GetPostReceiveCount(); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpNode_GetFreeBufferPoolSize(HP_UdpNode pNode) +{ + return C_HP_Object::ToSecond(pNode)->GetFreeBufferPoolSize(); +} + +HPSOCKET_API DWORD __HP_CALL HP_UdpNode_GetFreeBufferPoolHold(HP_UdpNode pNode) +{ + return C_HP_Object::ToSecond(pNode)->GetFreeBufferPoolHold(); +} + +#endif + +/***************************************************************************************/ +/***************************** TCP Pull Server 组件操作方法 *****************************/ + +HPSOCKET_API En_HP_FetchResult __HP_CALL HP_TcpPullServer_Fetch(HP_TcpPullServer pServer, HP_CONNID dwConnID, BYTE* pData, int iLength) +{ + return C_HP_Object::ToFirst(pServer)->Fetch(dwConnID, pData, iLength); +} + +HPSOCKET_API En_HP_FetchResult __HP_CALL HP_TcpPullServer_Peek(HP_TcpPullServer pServer, HP_CONNID dwConnID, BYTE* pData, int iLength) +{ + return C_HP_Object::ToFirst(pServer)->Peek(dwConnID, pData, iLength); +} + +/***************************************************************************************/ +/***************************** TCP Pull Server 属性访问方法 *****************************/ + +/***************************************************************************************/ +/***************************** TCP Pull Agent 组件操作方法 *****************************/ + +HPSOCKET_API En_HP_FetchResult __HP_CALL HP_TcpPullAgent_Fetch(HP_TcpPullAgent pAgent, HP_CONNID dwConnID, BYTE* pData, int iLength) +{ + return C_HP_Object::ToFirst(pAgent)->Fetch(dwConnID, pData, iLength); +} + +HPSOCKET_API En_HP_FetchResult __HP_CALL HP_TcpPullAgent_Peek(HP_TcpPullAgent pAgent, HP_CONNID dwConnID, BYTE* pData, int iLength) +{ + return C_HP_Object::ToFirst(pAgent)->Peek(dwConnID, pData, iLength); +} + +/***************************************************************************************/ +/***************************** TCP Pull Agent 属性访问方法 *****************************/ + +/***************************************************************************************/ +/***************************** TCP Pull Client 组件操作方法 *****************************/ + +HPSOCKET_API En_HP_FetchResult __HP_CALL HP_TcpPullClient_Fetch(HP_TcpPullClient pClient, BYTE* pData, int iLength) +{ + return C_HP_Object::ToFirst(pClient)->Fetch(pData, iLength); +} + +HPSOCKET_API En_HP_FetchResult __HP_CALL HP_TcpPullClient_Peek(HP_TcpPullClient pClient, BYTE* pData, int iLength) +{ + return C_HP_Object::ToFirst(pClient)->Peek(pData, iLength); +} + +/***************************************************************************************/ +/***************************** TCP Pull Client 属性访问方法 *****************************/ + +/***************************************************************************************/ +/***************************** TCP Pack Server 组件操作方法 *****************************/ + +/***************************************************************************************/ +/***************************** TCP Pack Server 属性访问方法 *****************************/ + +HPSOCKET_API void __HP_CALL HP_TcpPackServer_SetMaxPackSize(HP_TcpPackServer pServer, DWORD dwMaxPackSize) +{ + C_HP_Object::ToFirst(pServer)->SetMaxPackSize(dwMaxPackSize); +} + +HPSOCKET_API void __HP_CALL HP_TcpPackServer_SetPackHeaderFlag(HP_TcpPackServer pServer, USHORT usPackHeaderFlag) +{ + C_HP_Object::ToFirst(pServer)->SetPackHeaderFlag(usPackHeaderFlag); +} + +HPSOCKET_API DWORD __HP_CALL HP_TcpPackServer_GetMaxPackSize(HP_TcpPackServer pServer) +{ + return C_HP_Object::ToFirst(pServer)->GetMaxPackSize(); +} + +HPSOCKET_API USHORT __HP_CALL HP_TcpPackServer_GetPackHeaderFlag(HP_TcpPackServer pServer) +{ + return C_HP_Object::ToFirst(pServer)->GetPackHeaderFlag(); +} + +/***************************************************************************************/ +/***************************** TCP Pack Agent 组件操作方法 *****************************/ + +/***************************************************************************************/ +/***************************** TCP Pack Agent 属性访问方法 *****************************/ + +HPSOCKET_API void __HP_CALL HP_TcpPackAgent_SetMaxPackSize(HP_TcpPackAgent pAgent, DWORD dwMaxPackSize) +{ + C_HP_Object::ToFirst(pAgent)->SetMaxPackSize(dwMaxPackSize); +} + +HPSOCKET_API void __HP_CALL HP_TcpPackAgent_SetPackHeaderFlag(HP_TcpPackAgent pAgent, USHORT usPackHeaderFlag) +{ + C_HP_Object::ToFirst(pAgent)->SetPackHeaderFlag(usPackHeaderFlag); +} + +HPSOCKET_API DWORD __HP_CALL HP_TcpPackAgent_GetMaxPackSize(HP_TcpPackAgent pAgent) +{ + return C_HP_Object::ToFirst(pAgent)->GetMaxPackSize(); +} + +HPSOCKET_API USHORT __HP_CALL HP_TcpPackAgent_GetPackHeaderFlag(HP_TcpPackAgent pAgent) +{ + return C_HP_Object::ToFirst(pAgent)->GetPackHeaderFlag(); +} + +/***************************************************************************************/ +/***************************** TCP Pack Client 组件操作方法 *****************************/ + +/***************************************************************************************/ +/***************************** TCP Pack Client 属性访问方法 *****************************/ + +HPSOCKET_API void __HP_CALL HP_TcpPackClient_SetMaxPackSize(HP_TcpPackClient pClient, DWORD dwMaxPackSize) +{ + C_HP_Object::ToFirst(pClient)->SetMaxPackSize(dwMaxPackSize); +} + +HPSOCKET_API void __HP_CALL HP_TcpPackClient_SetPackHeaderFlag(HP_TcpPackClient pClient, USHORT usPackHeaderFlag) +{ + C_HP_Object::ToFirst(pClient)->SetPackHeaderFlag(usPackHeaderFlag); +} + +HPSOCKET_API DWORD __HP_CALL HP_TcpPackClient_GetMaxPackSize(HP_TcpPackClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->GetMaxPackSize(); +} + +HPSOCKET_API USHORT __HP_CALL HP_TcpPackClient_GetPackHeaderFlag(HP_TcpPackClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->GetPackHeaderFlag(); +} + +/*****************************************************************************************************************************************************/ +/*************************************************************** Global Function Exports *************************************************************/ +/*****************************************************************************************************************************************************/ + +HPSOCKET_API DWORD __HP_CALL HP_GetHPSocketVersion() +{ + return ::GetHPSocketVersion(); +} + +HPSOCKET_API LPCTSTR __HP_CALL HP_GetSocketErrorDesc(En_HP_SocketError enCode) +{ + return ::GetSocketErrorDesc(enCode); +} + +HPSOCKET_API DWORD __HP_CALL SYS_GetLastError() +{ + return ::GetLastError(); +} + +HPSOCKET_API LPCSTR __HP_CALL SYS_GetLastErrorStr() +{ + return ::GetLastErrorStr(); +} + +HPSOCKET_API int __HP_CALL SYS_SetSocketOption(SOCKET sock, int level, int name, LPVOID val, int len) +{ + return ::SSO_SetSocketOption(sock, level, name, val, len); +} + +HPSOCKET_API int __HP_CALL SYS_GetSocketOption(SOCKET sock, int level, int name, LPVOID val, int* len) +{ + return ::SSO_GetSocketOption(sock, level, name, val, len); +} + +HPSOCKET_API int __HP_CALL SYS_IoctlSocket(SOCKET sock, long cmd, ULONG* arg) +{ + return ::SSO_IoctlSocket(sock, cmd, arg); +} + +HPSOCKET_API BOOL __HP_CALL SYS_fcntl_SETFL(FD fd, INT fl, BOOL bSet) +{ + return ::fcntl_SETFL(fd, fl, bSet); +} + +HPSOCKET_API int __HP_CALL SYS_SSO_NoBlock(SOCKET sock, BOOL bNoBlock) +{ + return ::SSO_NoBlock(sock, bNoBlock); +} + +HPSOCKET_API int __HP_CALL SYS_SSO_NoDelay(SOCKET sock, BOOL bNoDelay) +{ + return ::SSO_NoDelay(sock, bNoDelay); +} + +HPSOCKET_API int __HP_CALL SYS_SSO_DontLinger(SOCKET sock, BOOL bDont) +{ + return ::SSO_DontLinger(sock, bDont); +} + +HPSOCKET_API int __HP_CALL SYS_SSO_Linger(SOCKET sock, USHORT l_onoff, USHORT l_linger) +{ + return ::SSO_Linger(sock, l_onoff, l_linger); +} + +HPSOCKET_API int __HP_CALL SYS_SSO_RecvBuffSize(SOCKET sock, int size) +{ + return ::SSO_RecvBuffSize(sock, size); +} + +HPSOCKET_API int __HP_CALL SYS_SSO_SendBuffSize(SOCKET sock, int size) +{ + return ::SSO_SendBuffSize(sock, size); +} + +HPSOCKET_API int __HP_CALL SYS_SSO_RecvTimeOut(SOCKET sock, int ms) +{ + return ::SSO_RecvTimeOut(sock, ms); +} + +HPSOCKET_API int __HP_CALL SYS_SSO_SendTimeOut(SOCKET sock, int ms) +{ + return ::SSO_SendTimeOut(sock, ms); +} + +HPSOCKET_API int __HP_CALL SYS_SSO_ReuseAddress(SOCKET sock, En_HP_ReuseAddressPolicy opt) +{ + return ::SSO_ReuseAddress(sock, opt); +} + +HPSOCKET_API BOOL __HP_CALL SYS_GetSocketLocalAddress(SOCKET socket, TCHAR lpszAddress[], int* piAddressLen, USHORT* pusPort) +{ + return ::GetSocketLocalAddress(socket, lpszAddress, *piAddressLen, *pusPort); +} + +HPSOCKET_API BOOL __HP_CALL SYS_GetSocketRemoteAddress(SOCKET socket, TCHAR lpszAddress[], int* piAddressLen, USHORT* pusPort) +{ + return ::GetSocketRemoteAddress(socket, lpszAddress, *piAddressLen, *pusPort); +} + +HPSOCKET_API BOOL __HP_CALL SYS_EnumHostIPAddresses(LPCTSTR lpszHost, En_HP_IPAddrType enType, HP_LPTIPAddr** lpppIPAddr, int* piIPAddrCount) +{ + return ::EnumHostIPAddresses(lpszHost, enType, lpppIPAddr, *piIPAddrCount); +} + +HPSOCKET_API BOOL __HP_CALL SYS_FreeHostIPAddresses(HP_LPTIPAddr* lppIPAddr) +{ + return ::FreeHostIPAddresses(lppIPAddr); +} + +HPSOCKET_API BOOL __HP_CALL SYS_IsIPAddress(LPCTSTR lpszAddress, En_HP_IPAddrType* penType) +{ + return ::IsIPAddress(lpszAddress, penType); +} + +HPSOCKET_API BOOL __HP_CALL SYS_GetIPAddress(LPCTSTR lpszHost, TCHAR lpszIP[], int* piIPLenth, En_HP_IPAddrType* penType) +{ + return ::GetIPAddress(lpszHost, lpszIP, *piIPLenth, *penType); +} + +HPSOCKET_API ULONGLONG __HP_CALL SYS_NToH64(ULONGLONG value) +{ + return ::NToH64(value); +} + +HPSOCKET_API ULONGLONG __HP_CALL SYS_HToN64(ULONGLONG value) +{ + return ::HToN64(value); +} + +HPSOCKET_API USHORT __HP_CALL SYS_SwapEndian16(USHORT value) +{ + return ENDIAN_SWAP_16(value); +} + +HPSOCKET_API DWORD __HP_CALL SYS_SwapEndian32(DWORD value) +{ + return ENDIAN_SWAP_32(value); +} + +HPSOCKET_API BOOL __HP_CALL SYS_IsLittleEndian() +{ + return ::IsLittleEndian(); +} + +HPSOCKET_API LPBYTE __HP_CALL SYS_Malloc(int size) +{ + return MALLOC(BYTE, size); +} + +HPSOCKET_API LPBYTE __HP_CALL SYS_Realloc(LPBYTE p, int size) +{ + return REALLOC(BYTE, p, size); +} + +HPSOCKET_API VOID __HP_CALL SYS_Free(LPBYTE p) +{ + FREE(p); +} + +HPSOCKET_API LPVOID __HP_CALL SYS_Calloc(int number, int size) +{ + return CALLOC(number, size); +} + +HPSOCKET_API DWORD __HP_CALL SYS_GuessBase64EncodeBound(DWORD dwSrcLen) +{ + return ::GuessBase64EncodeBound(dwSrcLen); +} + +HPSOCKET_API DWORD __HP_CALL SYS_GuessBase64DecodeBound(const BYTE* lpszSrc, DWORD dwSrcLen) +{ + return ::GuessBase64DecodeBound(lpszSrc, dwSrcLen); +} + +HPSOCKET_API int __HP_CALL SYS_Base64Encode(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD* pdwDestLen) +{ + return ::Base64Encode(lpszSrc, dwSrcLen, lpszDest, *pdwDestLen); +} + +HPSOCKET_API int __HP_CALL SYS_Base64Decode(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD* pdwDestLen) +{ + return ::Base64Decode(lpszSrc, dwSrcLen, lpszDest, *pdwDestLen); +} + +HPSOCKET_API DWORD __HP_CALL SYS_GuessUrlEncodeBound(const BYTE* lpszSrc, DWORD dwSrcLen) +{ + return ::GuessUrlEncodeBound(lpszSrc, dwSrcLen); +} + +HPSOCKET_API DWORD __HP_CALL SYS_GuessUrlDecodeBound(const BYTE* lpszSrc, DWORD dwSrcLen) +{ + return ::GuessUrlDecodeBound(lpszSrc, dwSrcLen); +} + +HPSOCKET_API int __HP_CALL SYS_UrlEncode(BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD* pdwDestLen) +{ + return ::UrlEncode(lpszSrc, dwSrcLen, lpszDest, *pdwDestLen); +} + +HPSOCKET_API int __HP_CALL SYS_UrlDecode(BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD* pdwDestLen) +{ + return ::UrlDecode(lpszSrc, dwSrcLen, lpszDest, *pdwDestLen); +} + +#ifdef _ZLIB_SUPPORT + +HPSOCKET_API int __HP_CALL SYS_Compress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD* pdwDestLen) +{ + return ::Compress(lpszSrc, dwSrcLen, lpszDest, *pdwDestLen); +} + +HPSOCKET_API int __HP_CALL SYS_CompressEx(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD* pdwDestLen, int iLevel, int iMethod, int iWindowBits, int iMemLevel, int iStrategy) +{ + return ::CompressEx(lpszSrc, dwSrcLen, lpszDest, *pdwDestLen, iLevel, iMethod, iWindowBits, iMemLevel, iStrategy); +} + +HPSOCKET_API int __HP_CALL SYS_Uncompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD* pdwDestLen) +{ + return ::Uncompress(lpszSrc, dwSrcLen, lpszDest, *pdwDestLen); +} + +HPSOCKET_API int __HP_CALL SYS_UncompressEx(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD* pdwDestLen, int iWindowBits) +{ + return ::UncompressEx(lpszSrc, dwSrcLen, lpszDest, *pdwDestLen, iWindowBits); +} + +HPSOCKET_API DWORD __HP_CALL SYS_GuessCompressBound(DWORD dwSrcLen, BOOL bGZip) +{ + return ::GuessCompressBound(dwSrcLen, bGZip); +} + +HPSOCKET_API int __HP_CALL SYS_GZipCompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD* pdwDestLen) +{ + return ::GZipCompress(lpszSrc, dwSrcLen, lpszDest, *pdwDestLen); +} + +HPSOCKET_API int __HP_CALL SYS_GZipUncompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD* pdwDestLen) +{ + return ::GZipUncompress(lpszSrc, dwSrcLen, lpszDest, *pdwDestLen); +} + +HPSOCKET_API DWORD __HP_CALL SYS_GZipGuessUncompressBound(const BYTE* lpszSrc, DWORD dwSrcLen) +{ + return ::GZipGuessUncompressBound(lpszSrc, dwSrcLen); +} + +#endif + +#ifdef _BROTLI_SUPPORT + +HPSOCKET_API int __HP_CALL SYS_BrotliCompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD* pdwDestLen) +{ + return ::BrotliCompress(lpszSrc, dwSrcLen, lpszDest, *pdwDestLen); +} + +HPSOCKET_API int __HP_CALL SYS_BrotliCompressEx(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD* pdwDestLen, int iQuality, int iWindow, int iMode) +{ + return ::BrotliCompressEx(lpszSrc, dwSrcLen, lpszDest, *pdwDestLen, iQuality, iWindow , iMode); +} + +HPSOCKET_API int __HP_CALL SYS_BrotliUncompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD* pdwDestLen) +{ + return ::BrotliUncompress(lpszSrc, dwSrcLen, lpszDest, *pdwDestLen); +} + +HPSOCKET_API DWORD __HP_CALL SYS_BrotliGuessCompressBound(DWORD dwSrcLen) +{ + return ::BrotliGuessCompressBound(dwSrcLen); +} + +#endif + +#ifdef _ICONV_SUPPORT + +HPSOCKET_API BOOL __HP_CALL SYS_CharsetConvert(LPCSTR lpszFromCharset, LPCSTR lpszToCharset, LPCSTR lpszInBuf, int iInBufLen, LPSTR lpszOutBuf, int* piOutBufLen) +{ + return ::CharsetConvert(lpszFromCharset, lpszToCharset, lpszInBuf, iInBufLen, lpszOutBuf, *piOutBufLen); +} + +HPSOCKET_API BOOL __HP_CALL SYS_GbkToUnicodeEx(const char szSrc[], int iSrcLength, WCHAR szDest[], int* piDestLength) +{ + return ::GbkToUnicodeEx(szSrc, iSrcLength, szDest, *piDestLength); +} + +HPSOCKET_API BOOL __HP_CALL SYS_UnicodeToGbkEx(const WCHAR szSrc[], int iSrcLength, char szDest[], int* piDestLength) +{ + return ::UnicodeToGbkEx(szSrc, iSrcLength, szDest, *piDestLength); +} + +HPSOCKET_API BOOL __HP_CALL SYS_Utf8ToUnicodeEx(const char szSrc[], int iSrcLength, WCHAR szDest[], int* piDestLength) +{ + return ::Utf8ToUnicodeEx(szSrc, iSrcLength, szDest, *piDestLength); +} + +HPSOCKET_API BOOL __HP_CALL SYS_UnicodeToUtf8Ex(const WCHAR szSrc[], int iSrcLength, char szDest[], int* piDestLength) +{ + return ::UnicodeToUtf8Ex(szSrc, iSrcLength, szDest, *piDestLength); +} + +HPSOCKET_API BOOL __HP_CALL SYS_GbkToUtf8Ex(const char szSrc[], int iSrcLength, char szDest[], int* piDestLength) +{ + return ::GbkToUtf8Ex(szSrc, iSrcLength, szDest, *piDestLength); +} + +HPSOCKET_API BOOL __HP_CALL SYS_Utf8ToGbkEx(const char szSrc[], int iSrcLength, char szDest[], int* piDestLength) +{ + return ::Utf8ToGbkEx(szSrc, iSrcLength, szDest, *piDestLength); +} + +HPSOCKET_API BOOL __HP_CALL SYS_GbkToUnicode(const char szSrc[], WCHAR szDest[], int* piDestLength) +{ + return ::GbkToUnicode(szSrc, szDest, *piDestLength); +} + +HPSOCKET_API BOOL __HP_CALL SYS_UnicodeToGbk(const WCHAR szSrc[], char szDest[], int* piDestLength) +{ + return ::UnicodeToGbk(szSrc, szDest, *piDestLength); +} + +HPSOCKET_API BOOL __HP_CALL SYS_Utf8ToUnicode(const char szSrc[], WCHAR szDest[], int* piDestLength) +{ + return ::Utf8ToUnicode(szSrc, szDest, *piDestLength); +} + +HPSOCKET_API BOOL __HP_CALL SYS_UnicodeToUtf8(const WCHAR szSrc[], char szDest[], int* piDestLength) +{ + return ::UnicodeToUtf8(szSrc, szDest, *piDestLength); +} + +HPSOCKET_API BOOL __HP_CALL SYS_GbkToUtf8(const char szSrc[], char szDest[], int* piDestLength) +{ + return ::GbkToUtf8(szSrc, szDest, *piDestLength); +} + +HPSOCKET_API BOOL __HP_CALL SYS_Utf8ToGbk(const char szSrc[], char szDest[], int* piDestLength) +{ + return ::Utf8ToGbk(szSrc, szDest, *piDestLength); +} + +#endif + +/*****************************************************************************************************************************************************/ +/******************************************************************** HTTP Exports *******************************************************************/ +/*****************************************************************************************************************************************************/ + +#ifdef _HTTP_SUPPORT + +typedef C_HP_ObjectT C_HP_HttpServer; +typedef C_HP_ObjectT C_HP_HttpAgent; +typedef C_HP_ObjectT C_HP_HttpClient; +typedef C_HP_ObjectT C_HP_HttpSyncClient; + +/****************************************************/ +/***************** HTTP 对象创建函数 *****************/ + +HPSOCKET_API HP_HttpServer __HP_CALL Create_HP_HttpServer(HP_HttpServerListener pListener) +{ + return (HP_HttpServer)(new C_HP_HttpServer((IHttpServerListener*)pListener)); +} + +HPSOCKET_API HP_HttpAgent __HP_CALL Create_HP_HttpAgent(HP_HttpAgentListener pListener) +{ + return (HP_HttpAgent)(new C_HP_HttpAgent((IHttpAgentListener*)pListener)); +} + +HPSOCKET_API HP_HttpClient __HP_CALL Create_HP_HttpClient(HP_HttpClientListener pListener) +{ + return (HP_HttpClient)(new C_HP_HttpClient((IHttpClientListener*)pListener)); +} + +HPSOCKET_API HP_HttpSyncClient __HP_CALL Create_HP_HttpSyncClient(HP_HttpClientListener pListener) +{ + return (HP_HttpSyncClient)(new C_HP_HttpSyncClient((IHttpClientListener*)pListener)); +} + +HPSOCKET_API void __HP_CALL Destroy_HP_HttpServer(HP_HttpServer pServer) +{ + delete (C_HP_HttpServer*)pServer; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_HttpAgent(HP_HttpAgent pAgent) +{ + delete (C_HP_HttpAgent*)pAgent; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_HttpClient(HP_HttpClient pClient) +{ + delete (C_HP_HttpClient*)pClient; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_HttpSyncClient(HP_HttpSyncClient pClient) +{ + delete (C_HP_HttpSyncClient*)pClient; +} + +HPSOCKET_API HP_HttpServerListener __HP_CALL Create_HP_HttpServerListener() +{ + return (HP_HttpServerListener)(new C_HP_HttpServerListener); +} + +HPSOCKET_API HP_HttpAgentListener __HP_CALL Create_HP_HttpAgentListener() +{ + return (HP_HttpAgentListener)(new C_HP_HttpAgentListener); +} + +HPSOCKET_API HP_HttpClientListener __HP_CALL Create_HP_HttpClientListener() +{ + return (HP_HttpClientListener)(new C_HP_HttpClientListener); +} + +HPSOCKET_API void __HP_CALL Destroy_HP_HttpServerListener(HP_HttpServerListener pListener) +{ + delete (C_HP_HttpServerListener*)pListener; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_HttpAgentListener(HP_HttpAgentListener pListener) +{ + delete (C_HP_HttpAgentListener*)pListener; +} + +HPSOCKET_API void __HP_CALL Destroy_HP_HttpClientListener(HP_HttpClientListener pListener) +{ + delete (C_HP_HttpClientListener*)pListener; +} + +/**********************************************************************************/ +/*************************** HTTP Server 回调函数设置方法 **************************/ + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnMessageBegin(HP_HttpServerListener pListener, HP_FN_HttpServer_OnMessageBegin fn) +{ + ((C_HP_HttpServerListener*)pListener)->m_lsnHttp.m_fnOnMessageBegin = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnRequestLine(HP_HttpServerListener pListener, HP_FN_HttpServer_OnRequestLine fn) +{ + ((C_HP_HttpServerListener*)pListener)->m_lsnHttp.m_fnOnRequestLine = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnHeader(HP_HttpServerListener pListener, HP_FN_HttpServer_OnHeader fn) +{ + ((C_HP_HttpServerListener*)pListener)->m_lsnHttp.m_fnOnHeader = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnHeadersComplete(HP_HttpServerListener pListener, HP_FN_HttpServer_OnHeadersComplete fn) +{ + ((C_HP_HttpServerListener*)pListener)->m_lsnHttp.m_fnOnHeadersComplete = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnBody(HP_HttpServerListener pListener, HP_FN_HttpServer_OnBody fn) +{ + ((C_HP_HttpServerListener*)pListener)->m_lsnHttp.m_fnOnBody = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnChunkHeader(HP_HttpServerListener pListener, HP_FN_HttpServer_OnChunkHeader fn) +{ + ((C_HP_HttpServerListener*)pListener)->m_lsnHttp.m_fnOnChunkHeader = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnChunkComplete(HP_HttpServerListener pListener, HP_FN_HttpServer_OnChunkComplete fn) +{ + ((C_HP_HttpServerListener*)pListener)->m_lsnHttp.m_fnOnChunkComplete = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnMessageComplete(HP_HttpServerListener pListener, HP_FN_HttpServer_OnMessageComplete fn) +{ + ((C_HP_HttpServerListener*)pListener)->m_lsnHttp.m_fnOnMessageComplete = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnUpgrade(HP_HttpServerListener pListener, HP_FN_HttpServer_OnUpgrade fn) +{ + ((C_HP_HttpServerListener*)pListener)->m_lsnHttp.m_fnOnUpgrade = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnParseError(HP_HttpServerListener pListener, HP_FN_HttpServer_OnParseError fn) +{ + ((C_HP_HttpServerListener*)pListener)->m_lsnHttp.m_fnOnParseError = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnWSMessageHeader(HP_HttpServerListener pListener, HP_FN_HttpServer_OnWSMessageHeader fn) +{ + ((C_HP_HttpServerListener*)pListener)->m_lsnHttp.m_fnOnWSMessageHeader = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnWSMessageBody(HP_HttpServerListener pListener, HP_FN_HttpServer_OnWSMessageBody fn) +{ + ((C_HP_HttpServerListener*)pListener)->m_lsnHttp.m_fnOnWSMessageBody = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnWSMessageComplete(HP_HttpServerListener pListener, HP_FN_HttpServer_OnWSMessageComplete fn) +{ + ((C_HP_HttpServerListener*)pListener)->m_lsnHttp.m_fnOnWSMessageComplete = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnPrepareListen(HP_HttpServerListener pListener, HP_FN_HttpServer_OnPrepareListen fn) +{ + ((C_HP_HttpServerListener*)pListener)->m_lsnServer.m_fnOnPrepareListen = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnAccept(HP_HttpServerListener pListener, HP_FN_HttpServer_OnAccept fn) +{ + ((C_HP_HttpServerListener*)pListener)->m_lsnServer.m_fnOnAccept = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnHandShake(HP_HttpServerListener pListener, HP_FN_HttpServer_OnHandShake fn) +{ + ((C_HP_HttpServerListener*)pListener)->m_lsnServer.m_fnOnHandShake = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnSend(HP_HttpServerListener pListener, HP_FN_HttpServer_OnSend fn) +{ + ((C_HP_HttpServerListener*)pListener)->m_lsnServer.m_fnOnSend = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnReceive(HP_HttpServerListener pListener, HP_FN_HttpServer_OnReceive fn) +{ + ((C_HP_HttpServerListener*)pListener)->m_lsnServer.m_fnOnReceive = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnClose(HP_HttpServerListener pListener, HP_FN_HttpServer_OnClose fn) +{ + ((C_HP_HttpServerListener*)pListener)->m_lsnServer.m_fnOnClose = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnShutdown(HP_HttpServerListener pListener, HP_FN_HttpServer_OnShutdown fn) +{ + ((C_HP_HttpServerListener*)pListener)->m_lsnServer.m_fnOnShutdown = fn; +} + +/**********************************************************************************/ +/**************************** HTTP Agent 回调函数设置方法 **************************/ + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnMessageBegin(HP_HttpAgentListener pListener, HP_FN_HttpAgent_OnMessageBegin fn) +{ + ((C_HP_HttpAgentListener*)pListener)->m_lsnHttp.m_fnOnMessageBegin = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnStatusLine(HP_HttpAgentListener pListener, HP_FN_HttpAgent_OnStatusLine fn) +{ + ((C_HP_HttpAgentListener*)pListener)->m_lsnHttp.m_fnOnStatusLine = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnHeader(HP_HttpAgentListener pListener, HP_FN_HttpAgent_OnHeader fn) +{ + ((C_HP_HttpAgentListener*)pListener)->m_lsnHttp.m_fnOnHeader = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnHeadersComplete(HP_HttpAgentListener pListener, HP_FN_HttpAgent_OnHeadersComplete fn) +{ + ((C_HP_HttpAgentListener*)pListener)->m_lsnHttp.m_fnOnHeadersComplete = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnBody(HP_HttpAgentListener pListener, HP_FN_HttpAgent_OnBody fn) +{ + ((C_HP_HttpAgentListener*)pListener)->m_lsnHttp.m_fnOnBody = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnChunkHeader(HP_HttpAgentListener pListener, HP_FN_HttpAgent_OnChunkHeader fn) +{ + ((C_HP_HttpAgentListener*)pListener)->m_lsnHttp.m_fnOnChunkHeader = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnChunkComplete(HP_HttpAgentListener pListener, HP_FN_HttpAgent_OnChunkComplete fn) +{ + ((C_HP_HttpAgentListener*)pListener)->m_lsnHttp.m_fnOnChunkComplete = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnMessageComplete(HP_HttpAgentListener pListener, HP_FN_HttpAgent_OnMessageComplete fn) +{ + ((C_HP_HttpAgentListener*)pListener)->m_lsnHttp.m_fnOnMessageComplete = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnUpgrade(HP_HttpAgentListener pListener, HP_FN_HttpAgent_OnUpgrade fn) +{ + ((C_HP_HttpAgentListener*)pListener)->m_lsnHttp.m_fnOnUpgrade = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnParseError(HP_HttpAgentListener pListener, HP_FN_HttpAgent_OnParseError fn) +{ + ((C_HP_HttpAgentListener*)pListener)->m_lsnHttp.m_fnOnParseError = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnWSMessageHeader(HP_HttpAgentListener pListener, HP_FN_HttpAgent_OnWSMessageHeader fn) +{ + ((C_HP_HttpAgentListener*)pListener)->m_lsnHttp.m_fnOnWSMessageHeader = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnWSMessageBody(HP_HttpAgentListener pListener, HP_FN_HttpAgent_OnWSMessageBody fn) +{ + ((C_HP_HttpAgentListener*)pListener)->m_lsnHttp.m_fnOnWSMessageBody = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnWSMessageComplete(HP_HttpAgentListener pListener, HP_FN_HttpAgent_OnWSMessageComplete fn) +{ + ((C_HP_HttpAgentListener*)pListener)->m_lsnHttp.m_fnOnWSMessageComplete = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnPrepareConnect(HP_HttpAgentListener pListener, HP_FN_HttpAgent_OnPrepareConnect fn) +{ + ((C_HP_HttpAgentListener*)pListener)->m_lsnAgent.m_fnOnPrepareConnect = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnConnect(HP_HttpAgentListener pListener, HP_FN_HttpAgent_OnConnect fn) +{ + ((C_HP_HttpAgentListener*)pListener)->m_lsnAgent.m_fnOnConnect = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnHandShake(HP_HttpAgentListener pListener, HP_FN_HttpAgent_OnHandShake fn) +{ + ((C_HP_HttpAgentListener*)pListener)->m_lsnAgent.m_fnOnHandShake = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnReceive(HP_HttpAgentListener pListener, HP_FN_HttpAgent_OnReceive fn) +{ + ((C_HP_HttpAgentListener*)pListener)->m_lsnAgent.m_fnOnReceive = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnSend(HP_HttpAgentListener pListener, HP_FN_HttpAgent_OnSend fn) +{ + ((C_HP_HttpAgentListener*)pListener)->m_lsnAgent.m_fnOnSend = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnClose(HP_HttpAgentListener pListener, HP_FN_HttpAgent_OnClose fn) +{ + ((C_HP_HttpAgentListener*)pListener)->m_lsnAgent.m_fnOnClose = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnShutdown(HP_HttpAgentListener pListener, HP_FN_HttpAgent_OnShutdown fn) +{ + ((C_HP_HttpAgentListener*)pListener)->m_lsnAgent.m_fnOnShutdown = fn; +} + +/**********************************************************************************/ +/*************************** HTTP Client 回调函数设置方法 **************************/ + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnMessageBegin(HP_HttpClientListener pListener, HP_FN_HttpClient_OnMessageBegin fn) +{ + ((C_HP_HttpClientListener*)pListener)->m_lsnHttp.m_fnOnMessageBegin = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnStatusLine(HP_HttpClientListener pListener, HP_FN_HttpClient_OnStatusLine fn) +{ + ((C_HP_HttpClientListener*)pListener)->m_lsnHttp.m_fnOnStatusLine = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnHeader(HP_HttpClientListener pListener, HP_FN_HttpClient_OnHeader fn) +{ + ((C_HP_HttpClientListener*)pListener)->m_lsnHttp.m_fnOnHeader = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnHeadersComplete(HP_HttpClientListener pListener, HP_FN_HttpClient_OnHeadersComplete fn) +{ + ((C_HP_HttpClientListener*)pListener)->m_lsnHttp.m_fnOnHeadersComplete = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnBody(HP_HttpClientListener pListener, HP_FN_HttpClient_OnBody fn) +{ + ((C_HP_HttpClientListener*)pListener)->m_lsnHttp.m_fnOnBody = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnChunkHeader(HP_HttpClientListener pListener, HP_FN_HttpClient_OnChunkHeader fn) +{ + ((C_HP_HttpClientListener*)pListener)->m_lsnHttp.m_fnOnChunkHeader = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnChunkComplete(HP_HttpClientListener pListener, HP_FN_HttpClient_OnChunkComplete fn) +{ + ((C_HP_HttpClientListener*)pListener)->m_lsnHttp.m_fnOnChunkComplete = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnMessageComplete(HP_HttpClientListener pListener, HP_FN_HttpClient_OnMessageComplete fn) +{ + ((C_HP_HttpClientListener*)pListener)->m_lsnHttp.m_fnOnMessageComplete = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnUpgrade(HP_HttpClientListener pListener, HP_FN_HttpClient_OnUpgrade fn) +{ + ((C_HP_HttpClientListener*)pListener)->m_lsnHttp.m_fnOnUpgrade = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnParseError(HP_HttpClientListener pListener, HP_FN_HttpClient_OnParseError fn) +{ + ((C_HP_HttpClientListener*)pListener)->m_lsnHttp.m_fnOnParseError = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnWSMessageHeader(HP_HttpClientListener pListener, HP_FN_HttpClient_OnWSMessageHeader fn) +{ + ((C_HP_HttpClientListener*)pListener)->m_lsnHttp.m_fnOnWSMessageHeader = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnWSMessageBody(HP_HttpClientListener pListener, HP_FN_HttpClient_OnWSMessageBody fn) +{ + ((C_HP_HttpClientListener*)pListener)->m_lsnHttp.m_fnOnWSMessageBody = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnWSMessageComplete(HP_HttpClientListener pListener, HP_FN_HttpClient_OnWSMessageComplete fn) +{ + ((C_HP_HttpClientListener*)pListener)->m_lsnHttp.m_fnOnWSMessageComplete = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnPrepareConnect(HP_HttpClientListener pListener, HP_FN_HttpClient_OnPrepareConnect fn) +{ + ((C_HP_HttpClientListener*)pListener)->m_lsnClient.m_fnOnPrepareConnect = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnConnect(HP_HttpClientListener pListener, HP_FN_HttpClient_OnConnect fn) +{ + ((C_HP_HttpClientListener*)pListener)->m_lsnClient.m_fnOnConnect = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnHandShake(HP_HttpClientListener pListener, HP_FN_HttpClient_OnHandShake fn) +{ + ((C_HP_HttpClientListener*)pListener)->m_lsnClient.m_fnOnHandShake = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnReceive(HP_HttpClientListener pListener, HP_FN_HttpClient_OnReceive fn) +{ + ((C_HP_HttpClientListener*)pListener)->m_lsnClient.m_fnOnReceive = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnSend(HP_HttpClientListener pListener, HP_FN_HttpClient_OnSend fn) +{ + ((C_HP_HttpClientListener*)pListener)->m_lsnClient.m_fnOnSend = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnClose(HP_HttpClientListener pListener, HP_FN_HttpClient_OnClose fn) +{ + ((C_HP_HttpClientListener*)pListener)->m_lsnClient.m_fnOnClose = fn; +} + +/**************************************************************************/ +/*************************** HTTP Server 操作方法 **************************/ + +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_SendResponse(HP_HttpServer pServer, HP_CONNID dwConnID, USHORT usStatusCode, LPCSTR lpszDesc, const HP_THeader lpHeaders[], int iHeaderCount, const BYTE* pData, int iLength) +{ + return C_HP_Object::ToFirst(pServer)->SendResponse(dwConnID, usStatusCode, lpszDesc, lpHeaders, iHeaderCount, pData, iLength); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_SendLocalFile(HP_HttpServer pServer, HP_CONNID dwConnID, LPCSTR lpszFileName, USHORT usStatusCode, LPCSTR lpszDesc, const HP_THeader lpHeaders[], int iHeaderCount) +{ + return C_HP_Object::ToFirst(pServer)->SendLocalFile(dwConnID, lpszFileName, usStatusCode, lpszDesc, lpHeaders, iHeaderCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_SendChunkData(HP_HttpServer pServer, HP_CONNID dwConnID, const BYTE* pData, int iLength, LPCSTR lpszExtensions) +{ + return C_HP_Object::ToFirst(pServer)->SendChunkData(dwConnID, pData, iLength, lpszExtensions); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_SendWSMessage(HP_HttpServer pServer, HP_CONNID dwConnID, BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE* pData, int iLength, ULONGLONG ullBodyLen) +{ + return C_HP_Object::ToFirst(pServer)->SendWSMessage(dwConnID, bFinal, iReserved, iOperationCode, pData, iLength, ullBodyLen); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_Release(HP_HttpServer pServer, HP_CONNID dwConnID) +{ + return C_HP_Object::ToFirst(pServer)->Release(dwConnID); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_StartHttp(HP_HttpServer pServer, HP_CONNID dwConnID) +{ + return C_HP_Object::ToFirst(pServer)->StartHttp(dwConnID); +} + +/******************************************************************************/ +/*************************** HTTP Server 属性访问方法 **************************/ + +HPSOCKET_API void __HP_CALL HP_HttpServer_SetReleaseDelay(HP_HttpServer pServer, DWORD dwReleaseDelay) +{ + C_HP_Object::ToFirst(pServer)->SetReleaseDelay(dwReleaseDelay); +} + +HPSOCKET_API DWORD __HP_CALL HP_HttpServer_GetReleaseDelay(HP_HttpServer pServer) +{ + return C_HP_Object::ToFirst(pServer)->GetReleaseDelay(); +} + +HPSOCKET_API USHORT __HP_CALL HP_HttpServer_GetUrlFieldSet(HP_HttpServer pServer, HP_CONNID dwConnID) +{ + return C_HP_Object::ToFirst(pServer)->GetUrlFieldSet(dwConnID); +} + + +HPSOCKET_API LPCSTR __HP_CALL HP_HttpServer_GetUrlField(HP_HttpServer pServer, HP_CONNID dwConnID, En_HP_HttpUrlField enField) +{ + return C_HP_Object::ToFirst(pServer)->GetUrlField(dwConnID, enField); +} + +HPSOCKET_API LPCSTR __HP_CALL HP_HttpServer_GetMethod(HP_HttpServer pServer, HP_CONNID dwConnID) +{ + return C_HP_Object::ToFirst(pServer)->GetMethod(dwConnID); +} + +HPSOCKET_API void __HP_CALL HP_HttpServer_SetLocalVersion(HP_HttpServer pServer, En_HP_HttpVersion usVersion) +{ + C_HP_Object::ToFirst(pServer)->SetLocalVersion(usVersion); +} + +HPSOCKET_API En_HP_HttpVersion __HP_CALL HP_HttpServer_GetLocalVersion(HP_HttpServer pServer) +{ + return C_HP_Object::ToFirst(pServer)->GetLocalVersion(); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_IsUpgrade(HP_HttpServer pServer, HP_CONNID dwConnID) +{ + return C_HP_Object::ToFirst(pServer)->IsUpgrade(dwConnID); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_IsKeepAlive(HP_HttpServer pServer, HP_CONNID dwConnID) +{ + return C_HP_Object::ToFirst(pServer)->IsKeepAlive(dwConnID); +} + +HPSOCKET_API USHORT __HP_CALL HP_HttpServer_GetVersion(HP_HttpServer pServer, HP_CONNID dwConnID) +{ + return C_HP_Object::ToFirst(pServer)->GetVersion(dwConnID); +} + +HPSOCKET_API LPCSTR __HP_CALL HP_HttpServer_GetHost(HP_HttpServer pServer, HP_CONNID dwConnID) +{ + return C_HP_Object::ToFirst(pServer)->GetHost(dwConnID); +} + +HPSOCKET_API ULONGLONG __HP_CALL HP_HttpServer_GetContentLength(HP_HttpServer pServer, HP_CONNID dwConnID) +{ + return C_HP_Object::ToFirst(pServer)->GetContentLength(dwConnID); +} + +HPSOCKET_API LPCSTR __HP_CALL HP_HttpServer_GetContentType(HP_HttpServer pServer, HP_CONNID dwConnID) +{ + return C_HP_Object::ToFirst(pServer)->GetContentType(dwConnID); +} + +HPSOCKET_API LPCSTR __HP_CALL HP_HttpServer_GetContentEncoding(HP_HttpServer pServer, HP_CONNID dwConnID) +{ + return C_HP_Object::ToFirst(pServer)->GetContentEncoding(dwConnID); +} + +HPSOCKET_API LPCSTR __HP_CALL HP_HttpServer_GetTransferEncoding(HP_HttpServer pServer, HP_CONNID dwConnID) +{ + return C_HP_Object::ToFirst(pServer)->GetTransferEncoding(dwConnID); +} + +HPSOCKET_API En_HP_HttpUpgradeType __HP_CALL HP_HttpServer_GetUpgradeType(HP_HttpServer pServer, HP_CONNID dwConnID) +{ + return C_HP_Object::ToFirst(pServer)->GetUpgradeType(dwConnID); +} + +HPSOCKET_API USHORT __HP_CALL HP_HttpServer_GetParseErrorCode(HP_HttpServer pServer, HP_CONNID dwConnID, LPCSTR* lpszErrorDesc) +{ + return C_HP_Object::ToFirst(pServer)->GetParseErrorCode(dwConnID, lpszErrorDesc); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_GetHeader(HP_HttpServer pServer, HP_CONNID dwConnID, LPCSTR lpszName, LPCSTR* lpszValue) +{ + return C_HP_Object::ToFirst(pServer)->GetHeader(dwConnID, lpszName, lpszValue); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_GetHeaders(HP_HttpServer pServer, HP_CONNID dwConnID, LPCSTR lpszName, LPCSTR lpszValue[], DWORD* pdwCount) +{ + return C_HP_Object::ToFirst(pServer)->GetHeaders(dwConnID, lpszName, lpszValue, *pdwCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_GetAllHeaders(HP_HttpServer pServer, HP_CONNID dwConnID, HP_THeader lpHeaders[], DWORD* pdwCount) +{ + return C_HP_Object::ToFirst(pServer)->GetAllHeaders(dwConnID, lpHeaders, *pdwCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_GetAllHeaderNames(HP_HttpServer pServer, HP_CONNID dwConnID, LPCSTR lpszName[], DWORD* pdwCount) +{ + return C_HP_Object::ToFirst(pServer)->GetAllHeaderNames(dwConnID, lpszName, *pdwCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_GetCookie(HP_HttpServer pServer, HP_CONNID dwConnID, LPCSTR lpszName, LPCSTR* lpszValue) +{ + return C_HP_Object::ToFirst(pServer)->GetCookie(dwConnID, lpszName, lpszValue); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_GetAllCookies(HP_HttpServer pServer, HP_CONNID dwConnID, HP_TCookie lpCookies[], DWORD* pdwCount) +{ + return C_HP_Object::ToFirst(pServer)->GetAllCookies(dwConnID, lpCookies, *pdwCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_GetWSMessageState(HP_HttpServer pServer, HP_CONNID dwConnID, BOOL* lpbFinal, BYTE* lpiReserved, BYTE* lpiOperationCode, LPCBYTE* lpszMask, ULONGLONG* lpullBodyLen, ULONGLONG* lpullBodyRemain) +{ + return C_HP_Object::ToFirst(pServer)->GetWSMessageState(dwConnID, lpbFinal, lpiReserved, lpiOperationCode, lpszMask, lpullBodyLen, lpullBodyRemain); +} + +HPSOCKET_API void __HP_CALL HP_HttpServer_SetHttpAutoStart(HP_HttpServer pServer, BOOL bAutoStart) +{ + C_HP_Object::ToFirst(pServer)->SetHttpAutoStart(bAutoStart); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_IsHttpAutoStart(HP_HttpServer pServer) +{ + return C_HP_Object::ToFirst(pServer)->IsHttpAutoStart(); +} + +/**************************************************************************/ +/*************************** HTTP Agent 操作方法 ***************************/ + +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_SendRequest(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszMethod, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength) +{ + return C_HP_Object::ToFirst(pAgent)->SendRequest(dwConnID, lpszMethod, lpszPath, lpHeaders, iHeaderCount, pBody, iLength); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_SendLocalFile(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszFileName, LPCSTR lpszMethod, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount) +{ + return C_HP_Object::ToFirst(pAgent)->SendLocalFile(dwConnID, lpszFileName, lpszMethod, lpszPath, lpHeaders, iHeaderCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_SendChunkData(HP_HttpAgent pAgent, HP_CONNID dwConnID, const BYTE* pData, int iLength, LPCSTR lpszExtensions) +{ + return C_HP_Object::ToFirst(pAgent)->SendChunkData(dwConnID, pData, iLength, lpszExtensions); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_SendPost(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength) +{ + return C_HP_Object::ToFirst(pAgent)->SendPost(dwConnID, lpszPath, lpHeaders, iHeaderCount, pBody, iLength); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_SendPut(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength) +{ + return C_HP_Object::ToFirst(pAgent)->SendPut(dwConnID, lpszPath, lpHeaders, iHeaderCount, pBody, iLength); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_SendPatch(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength) +{ + return C_HP_Object::ToFirst(pAgent)->SendPatch(dwConnID, lpszPath, lpHeaders, iHeaderCount, pBody, iLength); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_SendGet(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount) +{ + return C_HP_Object::ToFirst(pAgent)->SendGet(dwConnID, lpszPath, lpHeaders, iHeaderCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_SendDelete(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount) +{ + return C_HP_Object::ToFirst(pAgent)->SendDelete(dwConnID, lpszPath, lpHeaders, iHeaderCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_SendHead(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount) +{ + return C_HP_Object::ToFirst(pAgent)->SendHead(dwConnID, lpszPath, lpHeaders, iHeaderCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_SendTrace(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount) +{ + return C_HP_Object::ToFirst(pAgent)->SendTrace(dwConnID, lpszPath, lpHeaders, iHeaderCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_SendOptions(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount) +{ + return C_HP_Object::ToFirst(pAgent)->SendOptions(dwConnID, lpszPath, lpHeaders, iHeaderCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_SendConnect(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszHost, const HP_THeader lpHeaders[], int iHeaderCount) +{ + return C_HP_Object::ToFirst(pAgent)->SendConnect(dwConnID, lpszHost, lpHeaders, iHeaderCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_SendWSMessage(HP_HttpAgent pAgent, HP_CONNID dwConnID, BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], const BYTE* pData, int iLength, ULONGLONG ullBodyLen) +{ + return C_HP_Object::ToFirst(pAgent)->SendWSMessage(dwConnID, bFinal, iReserved, iOperationCode, lpszMask, pData, iLength, ullBodyLen); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_StartHttp(HP_HttpAgent pAgent, HP_CONNID dwConnID) +{ + return C_HP_Object::ToFirst(pAgent)->StartHttp(dwConnID); +} + +/******************************************************************************/ +/*************************** HTTP Agent 属性访问方法 ***************************/ + +HPSOCKET_API USHORT __HP_CALL HP_HttpAgent_GetStatusCode(HP_HttpAgent pAgent, HP_CONNID dwConnID) +{ + return C_HP_Object::ToFirst(pAgent)->GetStatusCode(dwConnID); +} + +HPSOCKET_API void __HP_CALL HP_HttpAgent_SetLocalVersion(HP_HttpAgent pAgent, En_HP_HttpVersion usVersion) +{ + C_HP_Object::ToFirst(pAgent)->SetLocalVersion(usVersion); +} + +HPSOCKET_API En_HP_HttpVersion __HP_CALL HP_HttpAgent_GetLocalVersion(HP_HttpAgent pAgent) +{ + return C_HP_Object::ToFirst(pAgent)->GetLocalVersion(); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_IsUpgrade(HP_HttpAgent pAgent, HP_CONNID dwConnID) +{ + return C_HP_Object::ToFirst(pAgent)->IsUpgrade(dwConnID); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_IsKeepAlive(HP_HttpAgent pAgent, HP_CONNID dwConnID) +{ + return C_HP_Object::ToFirst(pAgent)->IsKeepAlive(dwConnID); +} + +HPSOCKET_API USHORT __HP_CALL HP_HttpAgent_GetVersion(HP_HttpAgent pAgent, HP_CONNID dwConnID) +{ + return C_HP_Object::ToFirst(pAgent)->GetVersion(dwConnID); +} + +HPSOCKET_API ULONGLONG __HP_CALL HP_HttpAgent_GetContentLength(HP_HttpAgent pAgent, HP_CONNID dwConnID) +{ + return C_HP_Object::ToFirst(pAgent)->GetContentLength(dwConnID); +} + +HPSOCKET_API LPCSTR __HP_CALL HP_HttpAgent_GetContentType(HP_HttpAgent pAgent, HP_CONNID dwConnID) +{ + return C_HP_Object::ToFirst(pAgent)->GetContentType(dwConnID); +} + +HPSOCKET_API LPCSTR __HP_CALL HP_HttpAgent_GetContentEncoding(HP_HttpAgent pAgent, HP_CONNID dwConnID) +{ + return C_HP_Object::ToFirst(pAgent)->GetContentEncoding(dwConnID); +} + +HPSOCKET_API LPCSTR __HP_CALL HP_HttpAgent_GetTransferEncoding(HP_HttpAgent pAgent, HP_CONNID dwConnID) +{ + return C_HP_Object::ToFirst(pAgent)->GetTransferEncoding(dwConnID); +} + +HPSOCKET_API En_HP_HttpUpgradeType __HP_CALL HP_HttpAgent_GetUpgradeType(HP_HttpAgent pAgent, HP_CONNID dwConnID) +{ + return C_HP_Object::ToFirst(pAgent)->GetUpgradeType(dwConnID); +} + +HPSOCKET_API USHORT __HP_CALL HP_HttpAgent_GetParseErrorCode(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR* lpszErrorDesc) +{ + return C_HP_Object::ToFirst(pAgent)->GetParseErrorCode(dwConnID, lpszErrorDesc); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_GetHeader(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszName, LPCSTR* lpszValue) +{ + return C_HP_Object::ToFirst(pAgent)->GetHeader(dwConnID, lpszName, lpszValue); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_GetHeaders(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszName, LPCSTR lpszValue[], DWORD* pdwCount) +{ + return C_HP_Object::ToFirst(pAgent)->GetHeaders(dwConnID, lpszName, lpszValue, *pdwCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_GetAllHeaders(HP_HttpAgent pAgent, HP_CONNID dwConnID, HP_THeader lpHeaders[], DWORD* pdwCount) +{ + return C_HP_Object::ToFirst(pAgent)->GetAllHeaders(dwConnID, lpHeaders, *pdwCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_GetAllHeaderNames(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszName[], DWORD* pdwCount) +{ + return C_HP_Object::ToFirst(pAgent)->GetAllHeaderNames(dwConnID, lpszName, *pdwCount); +} + +HPSOCKET_API void __HP_CALL HP_HttpAgent_SetUseCookie(HP_HttpAgent pAgent, BOOL bUseCookie) +{ + C_HP_Object::ToFirst(pAgent)->SetUseCookie(bUseCookie); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_IsUseCookie(HP_HttpAgent pAgent) +{ + return C_HP_Object::ToFirst(pAgent)->IsUseCookie(); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_GetCookie(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszName, LPCSTR* lpszValue) +{ + return C_HP_Object::ToFirst(pAgent)->GetCookie(dwConnID, lpszName, lpszValue); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_GetAllCookies(HP_HttpAgent pAgent, HP_CONNID dwConnID, HP_TCookie lpCookies[], DWORD* pdwCount) +{ + return C_HP_Object::ToFirst(pAgent)->GetAllCookies(dwConnID, lpCookies, *pdwCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_GetWSMessageState(HP_HttpAgent pAgent, HP_CONNID dwConnID, BOOL* lpbFinal, BYTE* lpiReserved, BYTE* lpiOperationCode, LPCBYTE* lpszMask, ULONGLONG* lpullBodyLen, ULONGLONG* lpullBodyRemain) +{ + return C_HP_Object::ToFirst(pAgent)->GetWSMessageState(dwConnID, lpbFinal, lpiReserved, lpiOperationCode, lpszMask, lpullBodyLen, lpullBodyRemain); +} + +HPSOCKET_API void __HP_CALL HP_HttpAgent_SetHttpAutoStart(HP_HttpAgent pAgent, BOOL bAutoStart) +{ + C_HP_Object::ToFirst(pAgent)->SetHttpAutoStart(bAutoStart); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_IsHttpAutoStart(HP_HttpAgent pAgent) +{ + return C_HP_Object::ToFirst(pAgent)->IsHttpAutoStart(); +} + +/**************************************************************************/ +/*************************** HTTP Client 操作方法 **************************/ + +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_SendRequest(HP_HttpClient pClient, LPCSTR lpszMethod, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength) +{ + return C_HP_Object::ToFirst(pClient)->SendRequest(lpszMethod, lpszPath, lpHeaders, iHeaderCount, pBody, iLength); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_SendLocalFile(HP_HttpClient pClient, LPCSTR lpszFileName, LPCSTR lpszMethod, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount) +{ + return C_HP_Object::ToFirst(pClient)->SendLocalFile(lpszFileName, lpszMethod, lpszPath, lpHeaders, iHeaderCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_SendChunkData(HP_HttpClient pClient, const BYTE* pData, int iLength, LPCSTR lpszExtensions) +{ + return C_HP_Object::ToFirst(pClient)->SendChunkData(pData, iLength, lpszExtensions); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_SendPost(HP_HttpClient pClient, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength) +{ + return C_HP_Object::ToFirst(pClient)->SendPost(lpszPath, lpHeaders, iHeaderCount, pBody, iLength); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_SendPut(HP_HttpClient pClient, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength) +{ + return C_HP_Object::ToFirst(pClient)->SendPut(lpszPath, lpHeaders, iHeaderCount, pBody, iLength); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_SendPatch(HP_HttpClient pClient, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength) +{ + return C_HP_Object::ToFirst(pClient)->SendPatch(lpszPath, lpHeaders, iHeaderCount, pBody, iLength); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_SendGet(HP_HttpClient pClient, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount) +{ + return C_HP_Object::ToFirst(pClient)->SendGet(lpszPath, lpHeaders, iHeaderCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_SendDelete(HP_HttpClient pClient, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount) +{ + return C_HP_Object::ToFirst(pClient)->SendDelete(lpszPath, lpHeaders, iHeaderCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_SendHead(HP_HttpClient pClient, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount) +{ + return C_HP_Object::ToFirst(pClient)->SendHead(lpszPath, lpHeaders, iHeaderCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_SendTrace(HP_HttpClient pClient, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount) +{ + return C_HP_Object::ToFirst(pClient)->SendTrace(lpszPath, lpHeaders, iHeaderCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_SendOptions(HP_HttpClient pClient, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount) +{ + return C_HP_Object::ToFirst(pClient)->SendOptions(lpszPath, lpHeaders, iHeaderCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_SendConnect(HP_HttpClient pClient, LPCSTR lpszHost, const HP_THeader lpHeaders[], int iHeaderCount) +{ + return C_HP_Object::ToFirst(pClient)->SendConnect(lpszHost, lpHeaders, iHeaderCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_SendWSMessage(HP_HttpClient pClient, BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], const BYTE* pData, int iLength, ULONGLONG ullBodyLen) +{ + return C_HP_Object::ToFirst(pClient)->SendWSMessage(bFinal, iReserved, iOperationCode, lpszMask, pData, iLength, ullBodyLen); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_StartHttp(HP_HttpClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->StartHttp(); +} + +/******************************************************************************/ +/*************************** HTTP Client 属性访问方法 **************************/ + +HPSOCKET_API USHORT __HP_CALL HP_HttpClient_GetStatusCode(HP_HttpClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->GetStatusCode(); +} + +HPSOCKET_API void __HP_CALL HP_HttpClient_SetLocalVersion(HP_HttpClient pClient, En_HP_HttpVersion usVersion) +{ + C_HP_Object::ToFirst(pClient)->SetLocalVersion(usVersion); +} + +HPSOCKET_API En_HP_HttpVersion __HP_CALL HP_HttpClient_GetLocalVersion(HP_HttpClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->GetLocalVersion(); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_IsUpgrade(HP_HttpClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->IsUpgrade(); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_IsKeepAlive(HP_HttpClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->IsKeepAlive(); +} + +HPSOCKET_API USHORT __HP_CALL HP_HttpClient_GetVersion(HP_HttpClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->GetVersion(); +} + +HPSOCKET_API ULONGLONG __HP_CALL HP_HttpClient_GetContentLength(HP_HttpClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->GetContentLength(); +} + +HPSOCKET_API LPCSTR __HP_CALL HP_HttpClient_GetContentType(HP_HttpClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->GetContentType(); +} + +HPSOCKET_API LPCSTR __HP_CALL HP_HttpClient_GetContentEncoding(HP_HttpClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->GetContentEncoding(); +} + +HPSOCKET_API LPCSTR __HP_CALL HP_HttpClient_GetTransferEncoding(HP_HttpClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->GetTransferEncoding(); +} + +HPSOCKET_API En_HP_HttpUpgradeType __HP_CALL HP_HttpClient_GetUpgradeType(HP_HttpClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->GetUpgradeType(); +} + +HPSOCKET_API USHORT __HP_CALL HP_HttpClient_GetParseErrorCode(HP_HttpClient pClient, LPCSTR* lpszErrorDesc) +{ + return C_HP_Object::ToFirst(pClient)->GetParseErrorCode(lpszErrorDesc); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_GetHeader(HP_HttpClient pClient, LPCSTR lpszName, LPCSTR* lpszValue) +{ + return C_HP_Object::ToFirst(pClient)->GetHeader(lpszName, lpszValue); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_GetHeaders(HP_HttpClient pClient, LPCSTR lpszName, LPCSTR lpszValue[], DWORD* pdwCount) +{ + return C_HP_Object::ToFirst(pClient)->GetHeaders(lpszName, lpszValue, *pdwCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_GetAllHeaders(HP_HttpClient pClient, HP_THeader lpHeaders[], DWORD* pdwCount) +{ + return C_HP_Object::ToFirst(pClient)->GetAllHeaders(lpHeaders, *pdwCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_GetAllHeaderNames(HP_HttpClient pClient, LPCSTR lpszName[], DWORD* pdwCount) +{ + return C_HP_Object::ToFirst(pClient)->GetAllHeaderNames(lpszName, *pdwCount); +} + +HPSOCKET_API void __HP_CALL HP_HttpClient_SetUseCookie(HP_HttpClient pClient, BOOL bUseCookie) +{ + C_HP_Object::ToFirst(pClient)->SetUseCookie(bUseCookie); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_IsUseCookie(HP_HttpClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->IsUseCookie(); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_GetCookie(HP_HttpClient pClient, LPCSTR lpszName, LPCSTR* lpszValue) +{ + return C_HP_Object::ToFirst(pClient)->GetCookie(lpszName, lpszValue); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_GetAllCookies(HP_HttpClient pClient, HP_TCookie lpCookies[], DWORD* pdwCount) +{ + return C_HP_Object::ToFirst(pClient)->GetAllCookies(lpCookies, *pdwCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_GetWSMessageState(HP_HttpClient pClient, BOOL* lpbFinal, BYTE* lpiReserved, BYTE* lpiOperationCode, LPCBYTE* lpszMask, ULONGLONG* lpullBodyLen, ULONGLONG* lpullBodyRemain) +{ + return C_HP_Object::ToFirst(pClient)->GetWSMessageState(lpbFinal, lpiReserved, lpiOperationCode, lpszMask, lpullBodyLen, lpullBodyRemain); +} + +HPSOCKET_API void __HP_CALL HP_HttpClient_SetHttpAutoStart(HP_HttpClient pClient, BOOL bAutoStart) +{ + C_HP_Object::ToFirst(pClient)->SetHttpAutoStart(bAutoStart); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_IsHttpAutoStart(HP_HttpClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->IsHttpAutoStart(); +} + +/**************************************************************************/ +/************************ HTTP Sync Client 操作方法 ************************/ + +HPSOCKET_API BOOL __HP_CALL HP_HttpSyncClient_OpenUrl(HP_HttpSyncClient pClient, LPCSTR lpszMethod, LPCSTR lpszUrl, const HP_THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength, BOOL bForceReconnect) +{ + return C_HP_Object::ToFirst(pClient)->OpenUrl(lpszMethod, lpszUrl, lpHeaders, iHeaderCount, pBody, iLength, bForceReconnect); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpSyncClient_CleanupRequestResult(HP_HttpSyncClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->CleanupRequestResult(); +} + +/**************************************************************************/ +/********************** HTTP Sync Client 属性访问方法 **********************/ + +HPSOCKET_API void __HP_CALL HP_HttpSyncClient_SetConnectTimeout(HP_HttpSyncClient pClient, DWORD dwConnectTimeout) +{ + C_HP_Object::ToFirst(pClient)->SetConnectTimeout(dwConnectTimeout); +} + +HPSOCKET_API void __HP_CALL HP_HttpSyncClient_SetRequestTimeout(HP_HttpSyncClient pClient, DWORD dwRequestTimeout) +{ + C_HP_Object::ToFirst(pClient)->SetRequestTimeout(dwRequestTimeout); +} + +HPSOCKET_API DWORD __HP_CALL HP_HttpSyncClient_GetConnectTimeout(HP_HttpSyncClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->GetConnectTimeout(); +} + +HPSOCKET_API DWORD __HP_CALL HP_HttpSyncClient_GetRequestTimeout(HP_HttpSyncClient pClient) +{ + return C_HP_Object::ToFirst(pClient)->GetRequestTimeout(); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpSyncClient_GetResponseBody(HP_HttpSyncClient pClient, LPCBYTE* lpszBody, int* piLength) +{ + return C_HP_Object::ToFirst(pClient)->GetResponseBody(lpszBody, piLength); +} + +/**************************************************************************/ +/*************************** HTTP Cookie 管理方法 **************************/ + +HPSOCKET_API BOOL __HP_CALL HP_HttpCookie_MGR_LoadFromFile(LPCSTR lpszFile, BOOL bKeepExists) +{ + return g_CookieMgr.LoadFromFile(lpszFile, bKeepExists); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpCookie_MGR_SaveToFile(LPCSTR lpszFile, BOOL bKeepExists) +{ + return g_CookieMgr.SaveToFile(lpszFile, bKeepExists); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpCookie_MGR_ClearCookies(LPCSTR lpszDomain, LPCSTR lpszPath) +{ + return g_CookieMgr.ClearCookies(lpszDomain, lpszPath); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpCookie_MGR_RemoveExpiredCookies(LPCSTR lpszDomain, LPCSTR lpszPath) +{ + return g_CookieMgr.RemoveExpiredCookies(lpszDomain, lpszPath); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpCookie_MGR_SetCookie(LPCSTR lpszName, LPCSTR lpszValue, LPCSTR lpszDomain, LPCSTR lpszPath, int iMaxAge, BOOL bHttpOnly, BOOL bSecure, int enSameSite, BOOL bOnlyUpdateValueIfExists) +{ + return g_CookieMgr.SetCookie(lpszName, lpszValue, lpszDomain, lpszPath, iMaxAge, bHttpOnly, bSecure, (CCookie::EnSameSite)enSameSite, bOnlyUpdateValueIfExists); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpCookie_MGR_DeleteCookie(LPCSTR lpszDomain, LPCSTR lpszPath, LPCSTR lpszName) +{ + return g_CookieMgr.DeleteCookie(lpszDomain, lpszPath, lpszName); +} + +HPSOCKET_API void __HP_CALL HP_HttpCookie_MGR_SetEnableThirdPartyCookie(BOOL bEnableThirdPartyCookie) +{ + g_CookieMgr.SetEnableThirdPartyCookie(bEnableThirdPartyCookie); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpCookie_MGR_IsEnableThirdPartyCookie() +{ + return g_CookieMgr.IsEnableThirdPartyCookie(); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpCookie_HLP_ParseExpires(LPCSTR lpszExpires, __time64_t* ptmExpires) +{ + return CCookie::ParseExpires(lpszExpires, *ptmExpires); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpCookie_HLP_MakeExpiresStr(char lpszBuff[], int* piBuffLen, __time64_t tmExpires) +{ + return CCookie::MakeExpiresStr(lpszBuff, *piBuffLen, tmExpires); +} + +HPSOCKET_API BOOL __HP_CALL HP_HttpCookie_HLP_ToString(char lpszBuff[], int* piBuffLen, LPCSTR lpszName, LPCSTR lpszValue, LPCSTR lpszDomain, LPCSTR lpszPath, int iMaxAge, BOOL bHttpOnly, BOOL bSecure, int enSameSite) +{ + return CCookie::ToString(lpszBuff, *piBuffLen, lpszName, lpszValue, lpszDomain, lpszPath, iMaxAge, bHttpOnly, bSecure, (CCookie::EnSameSite)enSameSite); +} + +HPSOCKET_API __time64_t __HP_CALL HP_HttpCookie_HLP_CurrentUTCTime() +{ + return CCookie::CurrentUTCTime(); +} + +HPSOCKET_API __time64_t __HP_CALL HP_HttpCookie_HLP_MaxAgeToExpires(int iMaxAge) +{ + return CCookie::MaxAgeToExpires(iMaxAge); +} + +HPSOCKET_API int __HP_CALL HP_HttpCookie_HLP_ExpiresToMaxAge(__time64_t tmExpires) +{ + return CCookie::ExpiresToMaxAge(tmExpires); +} + +/*****************************************************************************************************************************************************/ +/************************************************************* HTTP Global Function Exports **********************************************************/ +/*****************************************************************************************************************************************************/ + +#endif + +/*****************************************************************************************************************************************************/ +/**************************************************************** Thread Pool Exports ****************************************************************/ +/*****************************************************************************************************************************************************/ + +/**********************************************************************************/ +/************************** Thread Pool 回调函数设置方法 ***************************/ + +HPSOCKET_API void __HP_CALL HP_Set_FN_ThreadPool_OnStartup(HP_ThreadPoolListener pListener, HP_FN_ThreadPool_OnStartup fn) +{ + ((C_HP_ThreadPoolListener*)pListener)->m_fnOnStartup = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_ThreadPool_OnShutdown(HP_ThreadPoolListener pListener, HP_FN_ThreadPool_OnShutdown fn) +{ + ((C_HP_ThreadPoolListener*)pListener)->m_fnOnShutdown = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_ThreadPool_OnWorkerThreadStart(HP_ThreadPoolListener pListener, HP_FN_ThreadPool_OnWorkerThreadStart fn) +{ + ((C_HP_ThreadPoolListener*)pListener)->m_fnOnWorkerThreadStart = fn; +} + +HPSOCKET_API void __HP_CALL HP_Set_FN_ThreadPool_OnWorkerThreadEnd(HP_ThreadPoolListener pListener, HP_FN_ThreadPool_OnWorkerThreadEnd fn) +{ + ((C_HP_ThreadPoolListener*)pListener)->m_fnOnWorkerThreadEnd = fn; +} + +/****************************************************/ +/******************* 对象创建函数 ********************/ + +HPSOCKET_API HP_ThreadPoolListener __HP_CALL Create_HP_ThreadPoolListener() +{ + return (HP_ThreadPoolListener)(new C_HP_ThreadPoolListener); +} + +HPSOCKET_API void __HP_CALL Destroy_HP_ThreadPoolListener(HP_ThreadPoolListener pListener) +{ + delete (IHPThreadPoolListener*)pListener; +} + +HPSOCKET_API HP_ThreadPool __HP_CALL Create_HP_ThreadPool(HP_ThreadPoolListener pListener) +{ + return (HP_ThreadPool)(new CHPThreadPool((IHPThreadPoolListener*)pListener)); +} + +HPSOCKET_API void __HP_CALL Destroy_HP_ThreadPool(HP_ThreadPool pThreadPool) +{ + delete (IHPThreadPool*)pThreadPool; +} + +HPSOCKET_API LPTSocketTask __HP_CALL Create_HP_SocketTaskObj(Fn_SocketTaskProc fnTaskProc, PVOID pSender, HP_CONNID dwConnID, LPCBYTE pBuffer, INT iBuffLen, En_HP_TaskBufferType enBuffType, WPARAM wParam, LPARAM lParam) +{ + return ::CreateSocketTaskObj(fnTaskProc, pSender, dwConnID, pBuffer, iBuffLen, enBuffType, wParam, lParam); +} + +HPSOCKET_API void __HP_CALL Destroy_HP_SocketTaskObj(LPTSocketTask pTask) +{ + ::DestroySocketTaskObj(pTask); +} + +/***********************************************************************/ +/***************************** 组件操作方法 *****************************/ + +HPSOCKET_API BOOL __HP_CALL HP_ThreadPool_Start(HP_ThreadPool pThreadPool, DWORD dwThreadCount, DWORD dwMaxQueueSize, En_HP_RejectedPolicy enRejectedPolicy, DWORD dwStackSize) +{ + return ((IHPThreadPool*)pThreadPool)->Start(dwThreadCount, dwMaxQueueSize, enRejectedPolicy, dwStackSize); +} + +HPSOCKET_API BOOL __HP_CALL HP_ThreadPool_Stop(HP_ThreadPool pThreadPool, DWORD dwMaxWait) +{ + return ((IHPThreadPool*)pThreadPool)->Stop(dwMaxWait); +} + +HPSOCKET_API BOOL __HP_CALL HP_ThreadPool_Submit(HP_ThreadPool pThreadPool, HP_Fn_TaskProc fnTaskProc, PVOID pvArg, DWORD dwMaxWait) +{ + return ((IHPThreadPool*)pThreadPool)->Submit(fnTaskProc, pvArg, dwMaxWait); +} + +HPSOCKET_API BOOL __HP_CALL HP_ThreadPool_Submit_Task(HP_ThreadPool pThreadPool, HP_LPTSocketTask pTask, DWORD dwMaxWait) +{ + return ((IHPThreadPool*)pThreadPool)->Submit(pTask, dwMaxWait); +} + +HPSOCKET_API BOOL __HP_CALL HP_ThreadPool_AdjustThreadCount(HP_ThreadPool pThreadPool, DWORD dwNewThreadCount) +{ + return ((IHPThreadPool*)pThreadPool)->AdjustThreadCount(dwNewThreadCount); +} + +HPSOCKET_API BOOL __HP_CALL HP_ThreadPool_Wait(HP_ThreadPool pThreadPool, DWORD dwMilliseconds) +{ + return ((IHPThreadPool*)pThreadPool)->Wait(dwMilliseconds); +} + +/***********************************************************************/ +/***************************** 属性访问方法 *****************************/ + +HPSOCKET_API BOOL __HP_CALL HP_ThreadPool_HasStarted(HP_ThreadPool pThreadPool) +{ + return ((IHPThreadPool*)pThreadPool)->HasStarted(); +} + +HPSOCKET_API En_HP_ServiceState __HP_CALL HP_ThreadPool_GetState(HP_ThreadPool pThreadPool) +{ + return ((IHPThreadPool*)pThreadPool)->GetState(); +} + +HPSOCKET_API DWORD __HP_CALL HP_ThreadPool_GetQueueSize(HP_ThreadPool pThreadPool) +{ + return ((IHPThreadPool*)pThreadPool)->GetQueueSize(); +} + +HPSOCKET_API DWORD __HP_CALL HP_ThreadPool_GetTaskCount(HP_ThreadPool pThreadPool) +{ + return ((IHPThreadPool*)pThreadPool)->GetTaskCount(); +} + +HPSOCKET_API DWORD __HP_CALL HP_ThreadPool_GetThreadCount(HP_ThreadPool pThreadPool) +{ + return ((IHPThreadPool*)pThreadPool)->GetThreadCount(); +} + +HPSOCKET_API DWORD __HP_CALL HP_ThreadPool_GetMaxQueueSize(HP_ThreadPool pThreadPool) +{ + return ((IHPThreadPool*)pThreadPool)->GetMaxQueueSize(); +} + +HPSOCKET_API En_HP_RejectedPolicy __HP_CALL HP_ThreadPool_GetRejectedPolicy(HP_ThreadPool pThreadPool) +{ + return ((IHPThreadPool*)pThreadPool)->GetRejectedPolicy(); +} + +/*****************************************************************************************************************************************************/ +/********************************************************* Compressor / Decompressor Exports *********************************************************/ +/*****************************************************************************************************************************************************/ + +/****************************************************/ +/******************* 对象创建函数 ********************/ + +HPSOCKET_API void __HP_CALL Destroy_HP_Compressor(HP_Compressor pCompressor) +{ + ::DestroyCompressor((IHPCompressor*)pCompressor); +} + +HPSOCKET_API void __HP_CALL Destroy_HP_Decompressor(HP_Decompressor pDecompressor) +{ + ::DestroyDecompressor((IHPDecompressor*)pDecompressor); +} + +#ifdef _ZLIB_SUPPORT + +HPSOCKET_API HP_Compressor __HP_CALL Create_HP_ZLibCompressor(HP_Fn_CompressDataCallback fnCallback) +{ + return ::CreateZLibCompressor(fnCallback); +} + +HPSOCKET_API HP_Compressor __HP_CALL Create_HP_ZLibCompressorEx(HP_Fn_CompressDataCallback fnCallback, int iWindowBits, int iLevel, int iMethod, int iMemLevel, int iStrategy, DWORD dwBuffSize) +{ + return ::CreateZLibCompressor(fnCallback, iWindowBits, iLevel, iMethod, iMemLevel, iStrategy, dwBuffSize); +} + +HPSOCKET_API HP_Compressor __HP_CALL Create_HP_GZipCompressor(HP_Fn_CompressDataCallback fnCallback) +{ + return ::CreateGZipCompressor(fnCallback); +} + +HPSOCKET_API HP_Compressor __HP_CALL Create_HP_GZipCompressorEx(HP_Fn_CompressDataCallback fnCallback, int iLevel, int iMethod, int iMemLevel, int iStrategy, DWORD dwBuffSize) +{ + return ::CreateGZipCompressor(fnCallback, iLevel, iMethod, iMemLevel, iStrategy, dwBuffSize); +} + +HPSOCKET_API HP_Decompressor __HP_CALL Create_HP_ZLibDecompressor(HP_Fn_DecompressDataCallback fnCallback) +{ + return ::CreateZLibDecompressor(fnCallback); +} + +HPSOCKET_API HP_Decompressor __HP_CALL Create_HP_ZLibDecompressorEx(HP_Fn_DecompressDataCallback fnCallback, int iWindowBits, DWORD dwBuffSize) +{ + return ::CreateZLibDecompressor(fnCallback, iWindowBits, dwBuffSize); +} + +HPSOCKET_API HP_Decompressor __HP_CALL Create_HP_GZipDecompressor(HP_Fn_DecompressDataCallback fnCallback) +{ + return ::CreateGZipDecompressor(fnCallback); +} + +HPSOCKET_API HP_Decompressor __HP_CALL Create_HP_GZipDecompressorEx(HP_Fn_DecompressDataCallback fnCallback, DWORD dwBuffSize) +{ + return ::CreateGZipDecompressor(fnCallback, dwBuffSize); +} + +#endif + +#ifdef _BROTLI_SUPPORT + +HPSOCKET_API HP_Compressor __HP_CALL Create_HP_BrotliCompressor(HP_Fn_CompressDataCallback fnCallback) +{ + return ::CreateBrotliCompressor(fnCallback); +} + +HPSOCKET_API HP_Compressor __HP_CALL Create_HP_BrotliCompressorEx(HP_Fn_CompressDataCallback fnCallback, int iQuality, int iWindow, int iMode, DWORD dwBuffSize) +{ + return ::CreateBrotliCompressor(fnCallback, iQuality, iWindow, iMode, dwBuffSize); +} + +HPSOCKET_API HP_Decompressor __HP_CALL Create_HP_BrotliDecompressor(HP_Fn_DecompressDataCallback fnCallback) +{ + return ::CreateBrotliDecompressor(fnCallback); +} + +HPSOCKET_API HP_Decompressor __HP_CALL Create_HP_BrotliDecompressorEx(HP_Fn_DecompressDataCallback fnCallback, DWORD dwBuffSize) +{ + return ::CreateBrotliDecompressor(fnCallback, dwBuffSize); +} + +#endif + +/***********************************************************************/ +/***************************** 组件操作方法 *****************************/ + +HPSOCKET_API BOOL __HP_CALL HP_Compressor_Process(HP_Compressor pCompressor, const BYTE* pData, int iLength, BOOL bLast, PVOID pContext) +{ + return ((IHPCompressor*)pCompressor)->Process(pData, iLength, bLast, pContext); +} + +HPSOCKET_API BOOL __HP_CALL HP_Compressor_ProcessEx(HP_Compressor pCompressor, const BYTE* pData, int iLength, BOOL bLast, BOOL bFlush, PVOID pContext) +{ + return ((IHPCompressor*)pCompressor)->ProcessEx(pData, iLength, bLast, bFlush, pContext); +} + +HPSOCKET_API BOOL __HP_CALL HP_Compressor_Reset(HP_Compressor pCompressor) +{ + return ((IHPCompressor*)pCompressor)->Reset(); +} + +HPSOCKET_API BOOL __HP_CALL HP_Decompressor_Process(HP_Decompressor pDecompressor, const BYTE* pData, int iLength, PVOID pContext) +{ + return ((IHPDecompressor*)pDecompressor)->Process(pData, iLength, pContext); +} + +HPSOCKET_API BOOL __HP_CALL HP_Decompressor_Reset(HP_Decompressor pDecompressor) +{ + return ((IHPDecompressor*)pDecompressor)->Reset(); +} + +/***********************************************************************/ +/***************************** 属性访问方法 *****************************/ + +HPSOCKET_API BOOL __HP_CALL HP_Compressor_IsValid(HP_Compressor pCompressor) +{ + return ((IHPCompressor*)pCompressor)->IsValid(); +} + +HPSOCKET_API BOOL __HP_CALL HP_Decompressor_IsValid(HP_Decompressor pDecompressor) +{ + return ((IHPDecompressor*)pDecompressor)->IsValid(); +} diff --git a/HPThreadPool.cpp b/HPThreadPool.cpp new file mode 100644 index 0000000..0cbed1a --- /dev/null +++ b/HPThreadPool.cpp @@ -0,0 +1,496 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "HPThreadPool.h" +#include "common/FuncHelper.h" + +#include + +LPTSocketTask CreateSocketTaskObj( Fn_SocketTaskProc fnTaskProc, + PVOID pSender, CONNID dwConnID, + LPCBYTE pBuffer, INT iBuffLen, EnTaskBufferType enBuffType, + WPARAM wParam, LPARAM lParam) +{ + ASSERT(fnTaskProc != nullptr); + ASSERT(iBuffLen >= 0); + + LPTSocketTask pTask = new TSocketTask; + + pTask->fn = fnTaskProc; + pTask->sender = pSender; + pTask->connID = dwConnID; + pTask->bufLen = iBuffLen; + pTask->bufType = enBuffType; + pTask->wparam = wParam; + pTask->lparam = lParam; + + if(enBuffType != TBT_COPY || !pBuffer) + pTask->buf = pBuffer; + else + { + pTask->buf = MALLOC(BYTE, iBuffLen); + ::CopyMemory((LPBYTE)pTask->buf, pBuffer, iBuffLen); + } + + return pTask; +} + +void DestroySocketTaskObj(LPTSocketTask pTask) +{ + if(pTask) + { + if(pTask->bufType != TBT_REFER && pTask->buf) + FREE(pTask->buf); + + delete pTask; + } +} + +volatile UINT CHPThreadPool::sm_uiNum = MAXUINT; +LPCTSTR CHPThreadPool::POOLED_THREAD_PREFIX = _T("hp-pool-"); + +BOOL CHPThreadPool::Start(DWORD dwThreadCount, DWORD dwMaxQueueSize, EnRejectedPolicy enRejectedPolicy, DWORD dwStackSize) +{ + if(!CheckStarting()) + return FALSE; + + m_dwStackSize = dwStackSize; + m_dwMaxQueueSize = dwMaxQueueSize; + m_enRejectedPolicy = enRejectedPolicy; + + FireStartup(); + + if(!InternalAdjustThreadCount(dwThreadCount)) + { + EXECUTE_RESTORE_ERROR(Stop()); + return FALSE; + } + + m_enState = SS_STARTED; + + return TRUE; +} + +BOOL CHPThreadPool::Stop(DWORD dwMaxWait) +{ + if(!CheckStoping()) + return FALSE; + + ::WaitFor(15); + + Shutdown(dwMaxWait); + + FireShutdown(); + + Reset(); + + return TRUE; +} + +BOOL CHPThreadPool::Shutdown(DWORD dwMaxWait) +{ + BOOL isOK = TRUE; + BOOL bLimited = (m_dwMaxQueueSize != 0); + BOOL bInfinite = (dwMaxWait == (DWORD)INFINITE || dwMaxWait == 0); + auto prdShutdown = [this]() {return m_stThreads.empty();}; + + if(m_enRejectedPolicy == TRP_WAIT_FOR && bLimited) + m_evQueue.SyncNotifyAll(); + + VERIFY(DoAdjustThreadCount(0)); + + if(bInfinite) + m_evShutdown.Wait(prdShutdown); + else + m_evShutdown.WaitFor(dwMaxWait, prdShutdown); + + ASSERT(m_lsTasks.Size() == 0); + ASSERT(m_stThreads.size() == 0); + + if(!m_lsTasks.IsEmpty()) + { + TTask* pTask = nullptr; + + while(m_lsTasks.PopFront(&pTask)) + { + if(pTask->freeArg) + ::DestroySocketTaskObj((LPTSocketTask)pTask->arg); + + TTask::Destruct(pTask); + } + + ::SetLastError(ERROR_CANCELLED); + isOK = FALSE; + } + + if(!m_stThreads.empty()) + { + CCriSecLock lock(m_csThread); + + if(!m_stThreads.empty()) + { +#if !defined(__ANDROID__) + for(auto it = m_stThreads.begin(), end = m_stThreads.end(); it != end; ++it) + pthread_cancel(*it); +#endif + m_stThreads.clear(); + + ::SetLastError(ERROR_CANCELLED); + isOK = FALSE; + } + } + + return isOK; +} + +BOOL CHPThreadPool::Submit(Fn_TaskProc fnTaskProc, PVOID pvArg, DWORD dwMaxWait) +{ + return DoSubmit(fnTaskProc, pvArg, FALSE, dwMaxWait); +} + +BOOL CHPThreadPool::Submit(LPTSocketTask pTask, DWORD dwMaxWait) +{ + return DoSubmit((Fn_TaskProc)pTask->fn, (PVOID)pTask, TRUE, dwMaxWait); +} + +BOOL CHPThreadPool::DoSubmit(Fn_TaskProc fnTaskProc, PVOID pvArg, BOOL bFreeArg, DWORD dwMaxWait) +{ + EnSubmitResult sr = DirectSubmit(fnTaskProc, pvArg, bFreeArg); + + if(sr != SUBMIT_FULL) + return (sr == SUBMIT_OK); + + if(m_enRejectedPolicy == TRP_CALL_FAIL) + { + ::SetLastError(ERROR_DESTINATION_ELEMENT_FULL); + return FALSE; + } + else if(m_enRejectedPolicy == TRP_WAIT_FOR) + { + return CycleWaitSubmit(fnTaskProc, pvArg, dwMaxWait, bFreeArg); + } + else if(m_enRejectedPolicy == TRP_CALLER_RUN) + { + DoRunTaskProc(fnTaskProc, pvArg, bFreeArg); + } + else + { + ASSERT(FALSE); + + ::SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + return TRUE; +} + +void CHPThreadPool::DoRunTaskProc(Fn_TaskProc fnTaskProc, PVOID pvArg, BOOL bFreeArg) +{ + ::InterlockedIncrement(&m_dwTaskCount); + fnTaskProc(pvArg); + ::InterlockedDecrement(&m_dwTaskCount); + + if(bFreeArg) + ::DestroySocketTaskObj((LPTSocketTask)pvArg); +} + +CHPThreadPool::EnSubmitResult CHPThreadPool::DirectSubmit(Fn_TaskProc fnTaskProc, PVOID pvArg, BOOL bFreeArg) +{ + if(!CheckStarted()) + return SUBMIT_ERROR; + + BOOL bLimited = (m_dwMaxQueueSize != 0); + + if(bLimited && m_lsTasks.Size() >= m_dwMaxQueueSize) + return SUBMIT_FULL; + else + { + TTask* pTask = TTask::Construct(fnTaskProc, pvArg, bFreeArg); + + m_lsTasks.PushBack(pTask); + m_evTask.SyncNotifyOne(); + } + + return SUBMIT_OK; +} + +BOOL CHPThreadPool::CycleWaitSubmit(Fn_TaskProc fnTaskProc, PVOID pvArg, DWORD dwMaxWait, BOOL bFreeArg) +{ + ASSERT(m_dwMaxQueueSize != 0); + + DWORD dwTime = ::TimeGetTime(); + BOOL bInfinite = (dwMaxWait == (DWORD)INFINITE || dwMaxWait == 0); + auto prdQueue = [this]() {return (m_lsTasks.Size() < m_dwMaxQueueSize);}; + + while(CheckStarted()) + { + EnSubmitResult sr = DirectSubmit(fnTaskProc, pvArg, bFreeArg); + + if(sr == SUBMIT_OK) + return TRUE; + if(sr == SUBMIT_ERROR) + return FALSE; + + if(bInfinite) + m_evQueue.Wait(prdQueue); + else + { + DWORD dwNow = ::GetTimeGap32(dwTime); + + if(dwNow > dwMaxWait || !m_evQueue.WaitFor(chrono::milliseconds(dwMaxWait - dwNow), prdQueue)) + { + ::SetLastError(ERROR_TIMEOUT); + break; + } + } + } + + return FALSE; +} + +BOOL CHPThreadPool::AdjustThreadCount(DWORD dwNewThreadCount) +{ + if(!CheckStarted()) + return FALSE; + + return InternalAdjustThreadCount(dwNewThreadCount); +} + +BOOL CHPThreadPool::InternalAdjustThreadCount(DWORD dwNewThreadCount) +{ + int iNewThreadCount = (int)dwNewThreadCount; + + if(iNewThreadCount == 0) + iNewThreadCount = ::GetDefaultWorkerThreadCount(); + else if(iNewThreadCount < 0) + iNewThreadCount = PROCESSOR_COUNT * (-iNewThreadCount); + + return DoAdjustThreadCount((DWORD)iNewThreadCount); +} + +BOOL CHPThreadPool::DoAdjustThreadCount(DWORD dwNewThreadCount) +{ + ASSERT((int)dwNewThreadCount >= 0); + + BOOL bRemove = FALSE; + DWORD dwThreadCount = 0; + + if(dwNewThreadCount > m_dwThreadCount) + { + dwThreadCount = dwNewThreadCount - m_dwThreadCount; + return CreateWorkerThreads(dwThreadCount); + } + else if(dwNewThreadCount < m_dwThreadCount) + { + bRemove = TRUE; + dwThreadCount = m_dwThreadCount - dwNewThreadCount; + + ::InterlockedSub(&m_dwThreadCount, dwThreadCount); + } + + if(bRemove) + { + for(DWORD i = 0; i < dwThreadCount; i++) + m_evTask.SyncNotifyOne(); + } + + return TRUE; +} + +BOOL CHPThreadPool::CreateWorkerThreads(DWORD dwThreadCount) +{ + unique_ptr pThreadAttr; + + if(m_dwStackSize != 0) + { + pThreadAttr = make_unique(); + VERIFY_IS_NO_ERROR(pthread_attr_init(pThreadAttr.get())); + + int rs = pthread_attr_setstacksize(pThreadAttr.get(), m_dwStackSize); + + if(!IS_NO_ERROR(rs)) + { + pthread_attr_destroy(pThreadAttr.get()); + ::SetLastError(rs); + + return FALSE; + } + } + + BOOL isOK = TRUE; + + for(DWORD i = 0; i < dwThreadCount; i++) + { + THR_ID dwThreadID; + int rs = pthread_create(&dwThreadID, pThreadAttr.get(), ThreadProc, (PVOID)this); + + if(!IS_NO_ERROR(rs)) + { + ::SetLastError(rs); + isOK = FALSE; + + break; + } + + ::InterlockedIncrement(&m_dwThreadCount); + + CCriSecLock lock(m_csThread); + m_stThreads.emplace(dwThreadID); + } + + if(pThreadAttr != nullptr) + pthread_attr_destroy(pThreadAttr.get()); + + return isOK; +} + +PVOID CHPThreadPool::ThreadProc(LPVOID pv) +{ + CHPThreadPool* pThis = (CHPThreadPool*)pv; + + ::SetSequenceThreadName(SELF_THREAD_ID, pThis->m_strPrefix, pThis->m_uiSeq); + + pThis->FireWorkerThreadStart(); + + PVOID rs = (PVOID)(UINT_PTR)(pThis->WorkerProc()); + + pThis->FireWorkerThreadEnd(); + + return rs; +} + +int CHPThreadPool::WorkerProc() +{ + BOOL bLimited = (m_dwMaxQueueSize != 0); + TTask* pTask = nullptr; + auto prdTask = [this]() {return (!m_lsTasks.IsEmpty()) || (m_dwThreadCount < m_stThreads.size());}; + + while(TRUE) + { + pTask = nullptr; + + while(m_lsTasks.PopFront(&pTask)) + { + if(m_enRejectedPolicy == TRP_WAIT_FOR && bLimited) + m_evQueue.SyncNotifyOne(); + + DoRunTaskProc(pTask->fn, pTask->arg, pTask->freeArg); + + TTask::Destruct(pTask); + } + + if(CheckWorkerThreadExit()) + break; + + m_evTask.Wait(prdTask); + } + + return 0; +} + +BOOL CHPThreadPool::CheckWorkerThreadExit() +{ + BOOL bExit = FALSE; + BOOL bShutdown = FALSE; + + if(m_dwThreadCount < m_stThreads.size()) + { + CCriSecLock lock(m_csThread); + + if(m_dwThreadCount < m_stThreads.size()) + { + VERIFY(m_stThreads.erase(SELF_THREAD_ID) == 1); + + bExit = TRUE; + bShutdown = m_stThreads.empty(); + } + } + + if(bExit) + { + pthread_detach(SELF_THREAD_ID); + + if(bShutdown) + m_evShutdown.SyncNotifyOne(); + } + + return bExit; +} + +BOOL CHPThreadPool::CheckStarting() +{ + if(::InterlockedCompareExchange(&m_enState, SS_STARTING, SS_STOPPED) != SS_STOPPED) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + return TRUE; +} + +BOOL CHPThreadPool::CheckStarted() +{ + if(m_enState != SS_STARTED) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + return TRUE; +} + +BOOL CHPThreadPool::CheckStoping() +{ + if( ::InterlockedCompareExchange(&m_enState, SS_STOPPING, SS_STARTED) != SS_STARTED && + ::InterlockedCompareExchange(&m_enState, SS_STOPPING, SS_STARTING) != SS_STARTING) + { + while(m_enState != SS_STOPPED) + ::WaitFor(5); + + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + return TRUE; +} + +void CHPThreadPool::Reset(BOOL bSetWaitEvent) +{ + m_uiSeq = MAXUINT; + m_dwStackSize = 0; + m_dwTaskCount = 0; + m_dwThreadCount = 0; + m_dwMaxQueueSize = 0; + m_enRejectedPolicy = TRP_CALL_FAIL; + m_enState = SS_STOPPED; + + if(bSetWaitEvent) + m_evWait.SyncNotifyAll(); +} + +void CHPThreadPool::MakePrefix() +{ + UINT uiNumber = ::InterlockedIncrement(&sm_uiNum); + + m_strPrefix.Format(_T("%s%u-"), POOLED_THREAD_PREFIX, uiNumber); +} diff --git a/HPThreadPool.h b/HPThreadPool.h new file mode 100644 index 0000000..60b6165 --- /dev/null +++ b/HPThreadPool.h @@ -0,0 +1,168 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#pragma once + +#include "hpsocket/SocketInterface.h" +#include "common/STLHelper.h" +#include "common/Semaphore.h" +#include "common/RingBuffer.h" +#include "InternalDef.h" + +LPTSocketTask CreateSocketTaskObj( Fn_SocketTaskProc fnTaskProc, + PVOID pSender, CONNID dwConnID, + LPCBYTE pBuffer, INT iBuffLen, EnTaskBufferType enBuffType = TBT_COPY, + WPARAM wParam = 0, LPARAM lParam = 0); + +void DestroySocketTaskObj(LPTSocketTask pTask); + +class CHPThreadPool : public IHPThreadPool +{ +private: + enum EnSubmitResult{SUBMIT_OK, SUBMIT_FULL, SUBMIT_ERROR}; + + struct TTask + { + Fn_TaskProc fn; + PVOID arg; + BOOL freeArg; + + public: + static TTask* Construct(Fn_TaskProc fnTaskProc, PVOID pvArg, BOOL bFreeArg) + { + return new TTask(fnTaskProc, pvArg, bFreeArg); + } + + static void Destruct(TTask* pTask) + { + if(pTask) + { + delete pTask; + } + } + + private: + TTask(Fn_TaskProc fnTaskProc, PVOID pvArg, BOOL bFreeArg) + : fn(fnTaskProc), arg(pvArg), freeArg(bFreeArg) + { + ASSERT(fn != nullptr); + } + }; + + using CTaskQueue = CCASQueue; + +public: + virtual BOOL Start(DWORD dwThreadCount = 0, DWORD dwMaxQueueSize = 0, EnRejectedPolicy enRejectedPolicy = TRP_CALL_FAIL, DWORD dwStackSize = 0); + virtual BOOL Stop(DWORD dwMaxWait = INFINITE); + virtual BOOL Wait(DWORD dwMilliseconds = INFINITE) {return m_evWait.WaitFor(dwMilliseconds, WAIT_FOR_STOP_PREDICATE);} + + virtual BOOL Submit(Fn_TaskProc fnTaskProc, PVOID pvArg, DWORD dwMaxWait = INFINITE); + virtual BOOL Submit(LPTSocketTask pTask, DWORD dwMaxWait = INFINITE); + virtual BOOL AdjustThreadCount(DWORD dwNewThreadCount); + +public: + virtual BOOL HasStarted() {return m_enState == SS_STARTED || m_enState == SS_STARTING;} + virtual EnServiceState GetState() {return m_enState;} + + virtual DWORD GetQueueSize() {return m_lsTasks.Size();} + virtual DWORD GetTaskCount() {return m_dwTaskCount;} + virtual DWORD GetThreadCount() {return m_dwThreadCount;} + virtual DWORD GetMaxQueueSize() {return m_dwMaxQueueSize;} + virtual EnRejectedPolicy GetRejectedPolicy() {return m_enRejectedPolicy;} + +private: + BOOL CheckStarting(); + BOOL CheckStarted(); + BOOL CheckStoping(); + + BOOL InternalAdjustThreadCount(DWORD dwNewThreadCount); + BOOL DoAdjustThreadCount(DWORD dwNewThreadCount); + + BOOL CreateWorkerThreads(DWORD dwThreadCount); + + BOOL Shutdown(DWORD dwMaxWait); + BOOL CheckWorkerThreadExit(); + + static PVOID ThreadProc(LPVOID pv); + int WorkerProc(); + + EnSubmitResult DirectSubmit(Fn_TaskProc fnTaskProc, PVOID pvArg, BOOL bFreeArg); + BOOL CycleWaitSubmit(Fn_TaskProc fnTaskProc, PVOID pvArg, DWORD dwMaxWait, BOOL bFreeArg); + BOOL DoSubmit(Fn_TaskProc fnTaskProc, PVOID pvArg, BOOL bFreeArg, DWORD dwMaxWait); + void DoRunTaskProc(Fn_TaskProc fnTaskProc, PVOID pvArg, BOOL bFreeArg); + + void FireStartup() + {if(m_pListener != nullptr) m_pListener->OnStartup(this);} + void FireShutdown() + {if(m_pListener != nullptr) m_pListener->OnShutdown(this);} + void FireWorkerThreadStart() + {if(m_pListener != nullptr) m_pListener->OnWorkerThreadStart(this, SELF_THREAD_ID);} + void FireWorkerThreadEnd() + {if(m_pListener != nullptr) m_pListener->OnWorkerThreadEnd(this, SELF_THREAD_ID);} + +public: + CHPThreadPool(IHPThreadPoolListener* pListener = nullptr) + : m_pListener(pListener) + { + MakePrefix(); + Reset(FALSE); + } + + virtual ~CHPThreadPool() + { + ENSURE_STOP(); + } + +private: + void Reset(BOOL bSetWaitEvent = TRUE); + void MakePrefix(); + +private: + static LPCTSTR POOLED_THREAD_PREFIX; + static volatile UINT sm_uiNum; + + volatile UINT m_uiSeq; + CString m_strPrefix; + +private: + IHPThreadPoolListener* m_pListener; + + DWORD m_dwStackSize; + DWORD m_dwMaxQueueSize; + EnRejectedPolicy m_enRejectedPolicy; + + volatile DWORD m_dwTaskCount; + volatile DWORD m_dwThreadCount; + volatile EnServiceState m_enState; + + CSEM m_evWait; + CSEM m_evShutdown; + CSEM m_evTask; + CSEM m_evQueue; + CCriSec m_csThread; + + CTaskQueue m_lsTasks; + unordered_set m_stThreads; + + DECLARE_NO_COPY_CLASS(CHPThreadPool) +}; diff --git a/HttpAgent.cpp b/HttpAgent.cpp new file mode 100644 index 0000000..a3daf33 --- /dev/null +++ b/HttpAgent.cpp @@ -0,0 +1,479 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "HttpAgent.h" + +#ifdef _HTTP_SUPPORT + +template BOOL CHttpAgentT::CheckParams() +{ + if(m_enLocalVersion != HV_1_1 && m_enLocalVersion != HV_1_0) + { + SetLastError(SE_INVALID_PARAM, __FUNCTION__, ERROR_INVALID_PARAMETER); + return FALSE; + } + + return __super::CheckParams(); +} + +template void CHttpAgentT::PrepareStart() +{ + __super::PrepareStart(); + + m_objPool.SetHttpObjLockTime(GetFreeSocketObjLockTime()); + m_objPool.SetHttpObjPoolSize(GetFreeSocketObjPool()); + m_objPool.SetHttpObjPoolHold(GetFreeSocketObjHold()); + + m_objPool.Prepare(); +} + +template BOOL CHttpAgentT::SendRequest(CONNID dwConnID, LPCSTR lpszMethod, LPCSTR lpszPath, const THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + WSABUF szBuffer[2]; + CStringA strHeader; + + LPCSTR lpszHost = nullptr; + USHORT usPort = 0; + BOOL bConnect = (stricmp(lpszMethod, HTTP_METHOD_CONNECT) == 0); + + if(!bConnect) + { + GetRemoteHost(dwConnID, &lpszHost, &usPort); + if(usPort == default_port) usPort = 0; + } + + CStringA strPath; + ::AdjustRequestPath(bConnect, lpszPath, strPath); + + pHttpObj->SetRequestPath(lpszMethod, strPath); + pHttpObj->ReloadCookies(); + + ::MakeRequestLine(lpszMethod, strPath, m_enLocalVersion, strHeader); + ::MakeHeaderLines(lpHeaders, iHeaderCount, &pHttpObj->GetCookieMap(), iLength, TRUE, -1, lpszHost, usPort, strHeader); + ::MakeHttpPacket(strHeader, pBody, iLength, szBuffer); + + return SendPackets(dwConnID, szBuffer, 2); +} + +template BOOL CHttpAgentT::SendLocalFile(CONNID dwConnID, LPCSTR lpszFileName, LPCSTR lpszMethod, LPCSTR lpszPath, const THeader lpHeaders[], int iHeaderCount) +{ + CFile file; + CFileMapping fmap; + + HRESULT hr = ::ReadSmallFile(CA2T(lpszFileName), file, fmap); + + if(FAILED(hr)) + { + ::SetLastError(hr); + return FALSE; + } + + return SendRequest(dwConnID, lpszMethod, lpszPath, lpHeaders, iHeaderCount, (BYTE*)fmap, (int)fmap.Size()); +} + +template BOOL CHttpAgentT::SendChunkData(CONNID dwConnID, const BYTE* pData, int iLength, LPCSTR lpszExtensions) +{ + char szLen[12]; + WSABUF bufs[5]; + + int iCount = MakeChunkPackage(pData, iLength, lpszExtensions, szLen, bufs); + + return SendPackets(dwConnID, bufs, iCount); +} + +template BOOL CHttpAgentT::SendWSMessage(CONNID dwConnID, BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], const BYTE* pData, int iLength, ULONGLONG ullBodyLen) +{ + ASSERT(lpszMask); + + WSABUF szBuffer[2]; + BYTE szHeader[HTTP_MAX_WS_HEADER_LEN]; + + unique_ptr szData = make_unique(iLength); + memcpy(szData.get(), pData, iLength); + + if(!::MakeWSPacket(bFinal, iReserved, iOperationCode, lpszMask, szData.get(), iLength, ullBodyLen, szHeader, szBuffer)) + return FALSE; + + return SendPackets(dwConnID, szBuffer, 2); +} + +template EnHandleResult CHttpAgentT::FireConnect(TAgentSocketObj* pSocketObj) +{ + return m_bHttpAutoStart ? __super::FireConnect(pSocketObj) : __super::DoFireConnect(pSocketObj); +} + +template EnHandleResult CHttpAgentT::DoFireConnect(TAgentSocketObj* pSocketObj) +{ + THttpObj* pHttpObj = DoStartHttp(pSocketObj); + EnHandleResult result = __super::DoFireConnect(pSocketObj); + + if(result == HR_ERROR) + { + m_objPool.PutFreeHttpObj(pHttpObj); + SetConnectionReserved(pSocketObj, nullptr); + } + + return result; +} + +template EnHandleResult CHttpAgentT::DoFireHandShake(TAgentSocketObj* pSocketObj) +{ + EnHandleResult result = __super::DoFireHandShake(pSocketObj); + + if(result == HR_ERROR) + { + THttpObj* pHttpObj = FindHttpObj(pSocketObj); + VERIFY(pHttpObj); + + m_objPool.PutFreeHttpObj(pHttpObj); + SetConnectionReserved(pSocketObj, nullptr); + } + + return result; +} + +template EnHandleResult CHttpAgentT::DoFireReceive(TAgentSocketObj* pSocketObj, const BYTE* pData, int iLength) +{ + THttpObj* pHttpObj = FindHttpObj(pSocketObj); + + if(pHttpObj != nullptr) + return pHttpObj->Execute(pData, iLength); + else + return DoFireSuperReceive(pSocketObj, pData, iLength); +} + +template EnHandleResult CHttpAgentT::DoFireClose(TAgentSocketObj* pSocketObj, EnSocketOperation enOperation, int iErrorCode) +{ + THttpObj* pHttpObj = FindHttpObj(pSocketObj); + + if(pHttpObj != nullptr) + pHttpObj->CheckBodyIdentityEof(); + + EnHandleResult result = __super::DoFireClose(pSocketObj, enOperation, iErrorCode); + + if(pHttpObj != nullptr) + { + m_objPool.PutFreeHttpObj(pHttpObj); + SetConnectionReserved(pSocketObj, nullptr); + } + + return result; +} + +template EnHandleResult CHttpAgentT::DoFireShutdown() +{ + EnHandleResult result = __super::DoFireShutdown(); + + m_objPool.Clear(); + + return result; +} + +template void CHttpAgentT::ReleaseGCSocketObj(BOOL bForce) +{ + __super::ReleaseGCSocketObj(bForce); + +#ifdef USE_EXTERNAL_GC + m_objPool.ReleaseGCHttpObj(bForce); +#endif +} + +template BOOL CHttpAgentT::IsUpgrade(CONNID dwConnID) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return FALSE; + + return pHttpObj->IsUpgrade(); +} + +template BOOL CHttpAgentT::IsKeepAlive(CONNID dwConnID) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return FALSE; + + return pHttpObj->IsKeepAlive(); +} + +template USHORT CHttpAgentT::GetVersion(CONNID dwConnID) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return 0; + + return pHttpObj->GetVersion(); +} + +template ULONGLONG CHttpAgentT::GetContentLength(CONNID dwConnID) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return 0; + + return pHttpObj->GetContentLength(); +} + +template LPCSTR CHttpAgentT::GetContentType(CONNID dwConnID) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return nullptr; + + return pHttpObj->GetContentType(); +} + +template LPCSTR CHttpAgentT::GetContentEncoding(CONNID dwConnID) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return nullptr; + + return pHttpObj->GetContentEncoding(); +} + +template LPCSTR CHttpAgentT::GetTransferEncoding(CONNID dwConnID) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return nullptr; + + return pHttpObj->GetTransferEncoding(); +} + +template EnHttpUpgradeType CHttpAgentT::GetUpgradeType(CONNID dwConnID) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return HUT_NONE; + + return pHttpObj->GetUpgradeType(); +} + +template USHORT CHttpAgentT::GetParseErrorCode(CONNID dwConnID, LPCSTR* lpszErrorDesc) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return 0; + + return pHttpObj->GetParseErrorCode(lpszErrorDesc); +} + +template BOOL CHttpAgentT::GetHeader(CONNID dwConnID, LPCSTR lpszName, LPCSTR* lpszValue) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return FALSE; + + return pHttpObj->GetHeader(lpszName, lpszValue); +} + +template BOOL CHttpAgentT::GetHeaders(CONNID dwConnID, LPCSTR lpszName, LPCSTR lpszValue[], DWORD& dwCount) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return FALSE; + + return pHttpObj->GetHeaders(lpszName, lpszValue, dwCount); +} + +template BOOL CHttpAgentT::GetAllHeaders(CONNID dwConnID, THeader lpHeaders[], DWORD& dwCount) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return FALSE; + + return pHttpObj->GetAllHeaders(lpHeaders, dwCount); +} + +template BOOL CHttpAgentT::GetAllHeaderNames(CONNID dwConnID, LPCSTR lpszName[], DWORD& dwCount) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return FALSE; + + return pHttpObj->GetAllHeaderNames(lpszName, dwCount); +} + +template BOOL CHttpAgentT::GetCookie(CONNID dwConnID, LPCSTR lpszName, LPCSTR* lpszValue) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return FALSE; + + return pHttpObj->GetCookie(lpszName, lpszValue); +} + +template BOOL CHttpAgentT::GetAllCookies(CONNID dwConnID, TCookie lpCookies[], DWORD& dwCount) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return FALSE; + + return pHttpObj->GetAllCookies(lpCookies, dwCount); +} + +template USHORT CHttpAgentT::GetStatusCode(CONNID dwConnID) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return 0; + + return pHttpObj->GetStatusCode(); +} + +template BOOL CHttpAgentT::GetWSMessageState(CONNID dwConnID, BOOL* lpbFinal, BYTE* lpiReserved, BYTE* lpiOperationCode, LPCBYTE* lpszMask, ULONGLONG* lpullBodyLen, ULONGLONG* lpullBodyRemain) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return FALSE; + + return pHttpObj->GetWSMessageState(lpbFinal, lpiReserved, lpiOperationCode, lpszMask, lpullBodyLen, lpullBodyRemain); +} + +template inline typename CHttpAgentT::THttpObj* CHttpAgentT::FindHttpObj(CONNID dwConnID) +{ + THttpObj* pHttpObj = nullptr; + GetConnectionReserved(dwConnID, (PVOID*)&pHttpObj); + + return pHttpObj; +} + +template inline typename CHttpAgentT::THttpObj* CHttpAgentT::FindHttpObj(TAgentSocketObj* pSocketObj) +{ + THttpObj* pHttpObj = nullptr; + GetConnectionReserved(pSocketObj, (PVOID*)&pHttpObj); + + return pHttpObj; +} + +template BOOL CHttpAgentT::StartHttp(CONNID dwConnID) +{ + if(IsHttpAutoStart()) + { + ::SetLastError(ERROR_INVALID_OPERATION); + return FALSE; + } + + TAgentSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TAgentSocketObj::IsValid(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + return StartHttp(pSocketObj); +} + +template BOOL CHttpAgentT::StartHttp(TAgentSocketObj* pSocketObj) +{ + if(!pSocketObj->HasConnected()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + CReentrantCriSecLock locallock(pSocketObj->csSend); + + if(!TAgentSocketObj::IsValid(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + if(!pSocketObj->HasConnected()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + THttpObj* pHttpObj = FindHttpObj(pSocketObj); + + if(pHttpObj != nullptr) + { + ::SetLastError(ERROR_ALREADY_INITIALIZED); + return FALSE; + } + + DoStartHttp(pSocketObj); + + if(!IsSecure()) + FireHandShake(pSocketObj); + else + { +#ifdef _SSL_SUPPORT + if(IsSSLAutoHandShake()) + StartSSLHandShake(pSocketObj); +#endif + } + + return TRUE; +} + +template typename CHttpAgentT::THttpObj* CHttpAgentT::DoStartHttp(TAgentSocketObj* pSocketObj) +{ + THttpObj* pHttpObj = m_objPool.PickFreeHttpObj(this, pSocketObj); + VERIFY(SetConnectionReserved(pSocketObj, pHttpObj)); + + return pHttpObj; +} + +// ------------------------------------------------------------------------------------------------------------- // + +template class CHttpAgentT; + +#ifdef _SSL_SUPPORT + +#include "SSLAgent.h" + +template class CHttpAgentT; + +#endif + +#endif \ No newline at end of file diff --git a/HttpAgent.h b/HttpAgent.h new file mode 100644 index 0000000..ab79bca --- /dev/null +++ b/HttpAgent.h @@ -0,0 +1,216 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "TcpAgent.h" +#include "HttpHelper.h" + +#ifdef _HTTP_SUPPORT + +template class CHttpAgentT : public IComplexHttpRequester, public T +{ + using __super = T; + using __super::GetConnectionReserved; + using __super::SetConnectionReserved; + using __super::SetLastError; + +public: + using __super::Stop; + using __super::Wait; + using __super::GetState; + using __super::SendPackets; + using __super::HasStarted; + using __super::GetRemoteHost; + using __super::GetFreeSocketObjLockTime; + using __super::GetFreeSocketObjPool; + using __super::GetFreeSocketObjHold; + + using __super::IsSecure; + using __super::FireHandShake; + using __super::FindSocketObj; + +#ifdef _SSL_SUPPORT + using __super::StartSSLHandShake; + using __super::IsSSLAutoHandShake; +#endif + +protected: + using CHttpObjPool = CHttpObjPoolT; + using THttpObj = THttpObjT; + + friend typename CHttpAgentT::THttpObj; + +public: + virtual BOOL SendRequest(CONNID dwConnID, LPCSTR lpszMethod, LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0, const BYTE* pBody = nullptr, int iLength = 0); + virtual BOOL SendLocalFile(CONNID dwConnID, LPCSTR lpszFileName, LPCSTR lpszMethod, LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0); + virtual BOOL SendChunkData(CONNID dwConnID, const BYTE* pData = nullptr, int iLength = 0, LPCSTR lpszExtensions = nullptr); + + virtual BOOL SendPost(CONNID dwConnID, LPCSTR lpszPath, const THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength) + {return SendRequest(dwConnID, HTTP_METHOD_POST, lpszPath, lpHeaders, iHeaderCount, pBody, iLength);} + virtual BOOL SendPut(CONNID dwConnID, LPCSTR lpszPath, const THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength) + {return SendRequest(dwConnID, HTTP_METHOD_PUT, lpszPath, lpHeaders, iHeaderCount, pBody, iLength);} + virtual BOOL SendPatch(CONNID dwConnID, LPCSTR lpszPath, const THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength) + {return SendRequest(dwConnID, HTTP_METHOD_PATCH, lpszPath, lpHeaders, iHeaderCount, pBody, iLength);} + virtual BOOL SendGet(CONNID dwConnID, LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0) + {return SendRequest(dwConnID, HTTP_METHOD_GET, lpszPath, lpHeaders, iHeaderCount);} + virtual BOOL SendDelete(CONNID dwConnID, LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0) + {return SendRequest(dwConnID, HTTP_METHOD_DELETE, lpszPath, lpHeaders, iHeaderCount);} + virtual BOOL SendHead(CONNID dwConnID, LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0) + {return SendRequest(dwConnID, HTTP_METHOD_HEAD, lpszPath, lpHeaders, iHeaderCount);} + virtual BOOL SendTrace(CONNID dwConnID, LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0) + {return SendRequest(dwConnID, HTTP_METHOD_TRACE, lpszPath, lpHeaders, iHeaderCount);} + virtual BOOL SendOptions(CONNID dwConnID, LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0) + {return SendRequest(dwConnID, HTTP_METHOD_OPTIONS, lpszPath, lpHeaders, iHeaderCount);} + virtual BOOL SendConnect(CONNID dwConnID, LPCSTR lpszHost, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0) + {return SendRequest(dwConnID, HTTP_METHOD_CONNECT, lpszHost, lpHeaders, iHeaderCount);} + + virtual BOOL SendWSMessage(CONNID dwConnID, BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], const BYTE* pData = nullptr, int iLength = 0, ULONGLONG ullBodyLen = 0); + + virtual BOOL StartHttp(CONNID dwConnID); + +public: + virtual void SetUseCookie(BOOL bUseCookie) {ENSURE_HAS_STOPPED(); m_pCookieMgr = bUseCookie ? &g_CookieMgr : nullptr;} + virtual void SetHttpAutoStart(BOOL bAutoStart) {ENSURE_HAS_STOPPED(); m_bHttpAutoStart = bAutoStart;} + virtual void SetLocalVersion(EnHttpVersion enLocalVersion) {ENSURE_HAS_STOPPED(); m_enLocalVersion = enLocalVersion;} + + virtual BOOL IsUseCookie() {return m_pCookieMgr != nullptr;} + virtual BOOL IsHttpAutoStart() {return m_bHttpAutoStart;} + virtual EnHttpVersion GetLocalVersion() {return m_enLocalVersion;} + + virtual BOOL IsUpgrade(CONNID dwConnID); + virtual BOOL IsKeepAlive(CONNID dwConnID); + virtual USHORT GetVersion(CONNID dwConnID); + virtual ULONGLONG GetContentLength(CONNID dwConnID); + virtual LPCSTR GetContentType(CONNID dwConnID); + virtual LPCSTR GetContentEncoding(CONNID dwConnID); + virtual LPCSTR GetTransferEncoding(CONNID dwConnID); + virtual EnHttpUpgradeType GetUpgradeType(CONNID dwConnID); + virtual USHORT GetParseErrorCode(CONNID dwConnID, LPCSTR* lpszErrorDesc = nullptr); + + virtual BOOL GetHeader(CONNID dwConnID, LPCSTR lpszName, LPCSTR* lpszValue); + virtual BOOL GetHeaders(CONNID dwConnID, LPCSTR lpszName, LPCSTR lpszValue[], DWORD& dwCount); + virtual BOOL GetAllHeaders(CONNID dwConnID, THeader lpHeaders[], DWORD& dwCount); + virtual BOOL GetAllHeaderNames(CONNID dwConnID, LPCSTR lpszName[], DWORD& dwCount); + + virtual BOOL GetCookie(CONNID dwConnID, LPCSTR lpszName, LPCSTR* lpszValue); + virtual BOOL GetAllCookies(CONNID dwConnID, TCookie lpCookies[], DWORD& dwCount); + + virtual USHORT GetStatusCode(CONNID dwConnID); + + virtual BOOL GetWSMessageState(CONNID dwConnID, BOOL* lpbFinal, BYTE* lpiReserved, BYTE* lpiOperationCode, LPCBYTE* lpszMask, ULONGLONG* lpullBodyLen, ULONGLONG* lpullBodyRemain); + +private: + BOOL StartHttp(TAgentSocketObj* pSocketObj); + THttpObj* DoStartHttp(TAgentSocketObj* pSocketObj); + +private: + virtual BOOL CheckParams(); + virtual void PrepareStart(); + virtual EnHandleResult FireConnect(TAgentSocketObj* pSocketObj); + virtual EnHandleResult DoFireConnect(TAgentSocketObj* pSocketObj); + virtual EnHandleResult DoFireHandShake(TAgentSocketObj* pSocketObj); + virtual EnHandleResult DoFireReceive(TAgentSocketObj* pSocketObj, const BYTE* pData, int iLength); + virtual EnHandleResult DoFireClose(TAgentSocketObj* pSocketObj, EnSocketOperation enOperation, int iErrorCode); + virtual EnHandleResult DoFireShutdown(); + + virtual void ReleaseGCSocketObj(BOOL bForce = FALSE); + + EnHandleResult DoFireSuperReceive(TAgentSocketObj* pSocketObj, const BYTE* pData, int iLength) + {return __super::DoFireReceive(pSocketObj, pData, iLength);} + + EnHttpParseResult FireMessageBegin(TAgentSocketObj* pSocketObj) + {return m_pListener->OnMessageBegin((IHttpAgent*)this, pSocketObj->connID);} + EnHttpParseResult FireRequestLine(TAgentSocketObj* pSocketObj, LPCSTR lpszMethod, LPCSTR lpszUrl) + {return m_pListener->OnRequestLine((IHttpAgent*)this, pSocketObj->connID, lpszMethod, lpszUrl);} + EnHttpParseResult FireStatusLine(TAgentSocketObj* pSocketObj, USHORT usStatusCode, LPCSTR lpszDesc) + {return m_pListener->OnStatusLine((IHttpAgent*)this, pSocketObj->connID, usStatusCode, lpszDesc);} + EnHttpParseResult FireHeader(TAgentSocketObj* pSocketObj, LPCSTR lpszName, LPCSTR lpszValue) + {return m_pListener->OnHeader((IHttpAgent*)this, pSocketObj->connID, lpszName, lpszValue);} + EnHttpParseResult FireHeadersComplete(TAgentSocketObj* pSocketObj) + {return m_pListener->OnHeadersComplete((IHttpAgent*)this, pSocketObj->connID);} + EnHttpParseResult FireBody(TAgentSocketObj* pSocketObj, const BYTE* pData, int iLength) + {return m_pListener->OnBody((IHttpAgent*)this, pSocketObj->connID, pData, iLength);} + EnHttpParseResult FireChunkHeader(TAgentSocketObj* pSocketObj, int iLength) + {return m_pListener->OnChunkHeader((IHttpAgent*)this, pSocketObj->connID, iLength);} + EnHttpParseResult FireChunkComplete(TAgentSocketObj* pSocketObj) + {return m_pListener->OnChunkComplete((IHttpAgent*)this, pSocketObj->connID);} + EnHttpParseResult FireMessageComplete(TAgentSocketObj* pSocketObj) + {return m_pListener->OnMessageComplete((IHttpAgent*)this, pSocketObj->connID);} + EnHttpParseResult FireUpgrade(TAgentSocketObj* pSocketObj, EnHttpUpgradeType enUpgradeType) + {return m_pListener->OnUpgrade((IHttpAgent*)this, pSocketObj->connID, enUpgradeType);} + EnHttpParseResult FireParseError(TAgentSocketObj* pSocketObj, int iErrorCode, LPCSTR lpszErrorDesc) + {return m_pListener->OnParseError((IHttpAgent*)this, pSocketObj->connID, iErrorCode, lpszErrorDesc);} + + EnHandleResult FireWSMessageHeader(TAgentSocketObj* pSocketObj, BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], ULONGLONG ullBodyLen) + {return m_pListener->OnWSMessageHeader((IHttpAgent*)this, pSocketObj->connID, bFinal, iReserved, iOperationCode, lpszMask, ullBodyLen);} + EnHandleResult FireWSMessageBody(TAgentSocketObj* pSocketObj, const BYTE* pData, int iLength) + {return m_pListener->OnWSMessageBody((IHttpAgent*)this, pSocketObj->connID, pData, iLength);} + EnHandleResult FireWSMessageComplete(TAgentSocketObj* pSocketObj) + {return m_pListener->OnWSMessageComplete((IHttpAgent*)this, pSocketObj->connID);} + + inline THttpObj* FindHttpObj(CONNID dwConnID); + inline THttpObj* FindHttpObj(TAgentSocketObj* pSocketObj); + + CCookieMgr* GetCookieMgr() {return m_pCookieMgr;} + LPCSTR GetRemoteDomain(TAgentSocketObj* pSocketObj) {LPCSTR lpszDomain; pSocketObj->GetRemoteHost(&lpszDomain); return lpszDomain;} + +public: + CHttpAgentT(IHttpAgentListener* pListener) + : T (pListener) + , m_pListener (pListener) + , m_pCookieMgr (&g_CookieMgr) + , m_bHttpAutoStart (TRUE) + , m_enLocalVersion (DEFAULT_HTTP_VERSION) + { + + } + + virtual ~CHttpAgentT() + { + ENSURE_STOP(); + } + +private: + IHttpAgentListener* m_pListener; + CCookieMgr* m_pCookieMgr; + EnHttpVersion m_enLocalVersion; + + BOOL m_bHttpAutoStart; + + CHttpObjPool m_objPool; +}; + +// ------------------------------------------------------------------------------------------------------------- // + +typedef CHttpAgentT CHttpAgent; + +#ifdef _SSL_SUPPORT + +#include "SSLAgent.h" + +typedef CHttpAgentT CHttpsAgent; + +#endif + +#endif \ No newline at end of file diff --git a/HttpClient.cpp b/HttpClient.cpp new file mode 100644 index 0000000..0da8ce6 --- /dev/null +++ b/HttpClient.cpp @@ -0,0 +1,598 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "HttpClient.h" + +#ifdef _HTTP_SUPPORT + +template BOOL CHttpClientT::CheckParams() +{ + if(m_enLocalVersion != HV_1_1 && m_enLocalVersion != HV_1_0) + { + SetLastError(SE_INVALID_PARAM, __FUNCTION__, ERROR_INVALID_PARAMETER); + return FALSE; + } + + return __super::CheckParams(); +} + +template BOOL CHttpClientT::SendRequest(LPCSTR lpszMethod, LPCSTR lpszPath, const THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength) +{ + USES_CONVERSION; + + WSABUF szBuffer[2]; + CStringA strHeader; + + LPCSTR lpszHost = nullptr; + USHORT usPort = 0; + BOOL bConnect = (stricmp(lpszMethod, HTTP_METHOD_CONNECT) == 0); + + if(!bConnect) + { + GetRemoteHost(&lpszHost, &usPort); + if(usPort == default_port) usPort = 0; + } + + CStringA strPath; + ::AdjustRequestPath(bConnect, lpszPath, strPath); + + m_objHttp.SetRequestPath(lpszMethod, strPath); + m_objHttp.ReloadCookies(); + + ::MakeRequestLine(lpszMethod, strPath, m_enLocalVersion, strHeader); + ::MakeHeaderLines(lpHeaders, iHeaderCount, &m_objHttp.GetCookieMap(), iLength, TRUE, -1, lpszHost, usPort, strHeader); + ::MakeHttpPacket(strHeader, pBody, iLength, szBuffer); + + return SendPackets(szBuffer, 2); +} + +template BOOL CHttpClientT::SendLocalFile(LPCSTR lpszFileName, LPCSTR lpszMethod, LPCSTR lpszPath, const THeader lpHeaders[], int iHeaderCount) +{ + CFile file; + CFileMapping fmap; + + HRESULT hr = ::ReadSmallFile(CA2T(lpszFileName), file, fmap); + + if(FAILED(hr)) + { + ::SetLastError(hr); + return FALSE; + } + + return SendRequest(lpszMethod, lpszPath, lpHeaders, iHeaderCount, (BYTE*)fmap, (int)fmap.Size()); +} + +template BOOL CHttpClientT::SendChunkData(const BYTE* pData, int iLength, LPCSTR lpszExtensions) +{ + char szLen[12]; + WSABUF bufs[5]; + + int iCount = MakeChunkPackage(pData, iLength, lpszExtensions, szLen, bufs); + + return SendPackets(bufs, iCount); +} + +template BOOL CHttpClientT::SendWSMessage(BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], const BYTE* pData, int iLength, ULONGLONG ullBodyLen) +{ + ASSERT(lpszMask); + + WSABUF szBuffer[2]; + BYTE szHeader[HTTP_MAX_WS_HEADER_LEN]; + + unique_ptr szData = make_unique(iLength); + memcpy(szData.get(), pData, iLength); + + if(!::MakeWSPacket(bFinal, iReserved, iOperationCode, lpszMask, szData.get(), iLength, ullBodyLen, szHeader, szBuffer)) + return FALSE; + + return SendPackets(szBuffer, 2); +} + +template BOOL CHttpClientT::StartHttp() +{ + if(IsHttpAutoStart()) + { + ::SetLastError(ERROR_INVALID_OPERATION); + return FALSE; + } + + if(!IsConnected()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + CReentrantCriSecLock locallock(m_csHttp); + + if(!IsConnected()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + if(m_objHttp.IsValid()) + { + ::SetLastError(ERROR_ALREADY_INITIALIZED); + return FALSE; + } + + DoStartHttp(); + + if(!IsSecure()) + FireHandShake(); + else + { +#ifdef _SSL_SUPPORT + if(IsSSLAutoHandShake()) + StartSSLHandShakeNoCheck(); +#endif + } + + return TRUE; +} + +// ------------------------------------------------------------------------------------------------------------- // + +template BOOL CHttpSyncClientT::Start(LPCTSTR lpszRemoteAddress, USHORT usPort, BOOL bAsyncConnect, LPCTSTR lpszBindAddress, USHORT usLocalPort) +{ + CleanupRequestResult(); + + if(!__super::Start(lpszRemoteAddress, usPort, TRUE, lpszBindAddress, usLocalPort)) + return FALSE; + + BOOL isOK = WaitForEvent(m_dwConnectTimeout); + + if(!isOK || m_enProgress != HSRP_DONE) + { + int ec = m_enProgress == HSRP_WAITING ? ERROR_TIMEDOUT : ERROR_CONNREFUSED; + + if(!isOK) Stop(); + + SetLastError(SE_CONNECT_SERVER, __FUNCTION__, ec); + return FALSE; + } + + return TRUE; +} + +template BOOL CHttpSyncClientT::SendRequest(LPCSTR lpszMethod, LPCSTR lpszPath, const THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength) +{ + CleanupRequestResult(); + + if(!__super::SendRequest(lpszMethod, lpszPath, lpHeaders, iHeaderCount, pBody, iLength)) + return FALSE; + + BOOL isOK = WaitForEvent(m_dwRequestTimeout); + + if(!isOK || m_enProgress != HSRP_DONE) + { + int ec = m_enProgress == HSRP_WAITING ? ERROR_TIMEDOUT : + (m_enProgress == HSRP_CLOSE ? ERROR_CONNABORTED : ERROR_INVALID_DATA); + + if(!isOK) Stop(); + + SetLastError(SE_DATA_SEND, __FUNCTION__, ec); + return FALSE; + } + + return TRUE; +} + +template BOOL CHttpSyncClientT::SendWSMessage(BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], const BYTE* pData, int iLength, ULONGLONG ullBodyLen) +{ + CleanupRequestResult(); + + if(!__super::SendWSMessage(bFinal, iReserved, iOperationCode, lpszMask, pData, iLength, ullBodyLen)) + return FALSE; + + BOOL isOK = WaitForEvent(m_dwRequestTimeout); + + if(!isOK || m_enProgress != HSRP_DONE) + { + int ec = m_enProgress == HSRP_WAITING ? ERROR_TIMEDOUT : + (m_enProgress == HSRP_CLOSE ? ERROR_CONNABORTED : ERROR_INVALID_DATA); + + if(!isOK) Stop(); + + SetLastError(SE_DATA_SEND, __FUNCTION__, ec); + return FALSE; + } + + return TRUE; +} + +template BOOL CHttpSyncClientT::OpenUrl(LPCSTR lpszMethod, LPCSTR lpszUrl, const THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength, BOOL bForceReconnect) +{ + BOOL bHttps; + USHORT usPort; + CStringA strHost; + CStringA strPath; + + if(!IsHttpAutoStart()) + { + SetLastError(SE_INVALID_PARAM, __FUNCTION__, ERROR_NOT_SUPPORTED); + return FALSE; + } + + if(!::ParseUrl(lpszUrl, bHttps, strHost, usPort, strPath)) + { + SetLastError(SE_CONNECT_SERVER, __FUNCTION__, ERROR_ADDRNOTAVAIL); + return FALSE; + } + + if((bHttps && default_port == HTTP_DEFAULT_PORT) || (!bHttps && default_port == HTTPS_DEFAULT_PORT)) + { + SetLastError(SE_CONNECT_SERVER, __FUNCTION__, ERROR_PROTO); + return FALSE; + } + + if(HasStarted()) + { + BOOL bNeedStop = bForceReconnect; + + if(!bNeedStop) + { + LPCSTR lpszHost = nullptr; + USHORT usPort2 = 0; + + GetRemoteHost(&lpszHost, &usPort2); + + if(usPort != usPort2) + bNeedStop = TRUE; + else + { + HP_SCOPE_HOST host(CA2T((LPCSTR)strHost)); + + if(lstricmp(host.name, CA2T(lpszHost)) != 0) + bNeedStop = TRUE; + } + } + + if(bNeedStop) Stop(); + } + + EnServiceState state = GetState(); + + if(state != SS_STARTED) + { + if(state == SS_STARTING) + { + do + { + ::WaitFor(10); + state = GetState(); + } while(state != SS_STARTED && state != SS_STOPPED); + } + else + { + while(state != SS_STOPPED) + { + ::WaitFor(10); + state = GetState(); + } + + Start(CA2T(strHost), usPort, FALSE, nullptr); + state = GetState(); + } + + if(state == SS_STOPPED) + return FALSE; + } + + if(iLength < 0 && !::IsStrEmptyA((LPCSTR)pBody)) + return SendLocalFile((LPCSTR)pBody, lpszMethod, strPath, lpHeaders, iHeaderCount); + + return SendRequest(lpszMethod, strPath, lpHeaders, iHeaderCount, pBody, iLength); +} + +template BOOL CHttpSyncClientT::CleanupRequestResult() +{ + m_pHttpObj = &m_objHttp; + m_enProgress = HSRP_WAITING; + + m_szBuffer.Free(); + m_objHttp2.Reset(); + m_evWait.Reset(); + + return TRUE; +} + +template void CHttpSyncClientT::SetRequestEvent(EnHttpSyncRequestProgress enProgress, BOOL bCopyHttpObj) +{ + if(m_enProgress != HSRP_WAITING) + return; + + m_enProgress = enProgress; + + if(bCopyHttpObj) + { + m_objHttp2.CopyData(m_objHttp); + m_objHttp2.CopyWSContext(m_objHttp); + + m_pHttpObj = &m_objHttp2; + } + + m_evWait.Set(); +} + +template BOOL CHttpSyncClientT::WaitForEvent(DWORD dwWait) +{ + LONG lTimeout = INFINITE; + + if(dwWait != 0) + lTimeout = (LONG)dwWait; + + return (m_evWait.Wait(lTimeout) > TIMEOUT); +} + +template BOOL CHttpSyncClientT::GetResponseBody(LPCBYTE* lpszBody, int* iLength) +{ + ASSERT(lpszBody && iLength); + + *lpszBody = m_szBuffer.Ptr(); + *iLength = (int)m_szBuffer.Size(); + + return TRUE; +} + +template EnHandleResult CHttpSyncClientT::OnHandShake(ITcpClient* pSender, CONNID dwConnID) +{ + EnHandleResult rs = HR_OK; + + if(m_pListener2 != nullptr) + rs = m_pListener2->OnHandShake(pSender, dwConnID); + + if(rs != HR_ERROR) + SetRequestEvent(HSRP_DONE, FALSE); + + return rs; +} + +template EnHandleResult CHttpSyncClientT::OnClose(ITcpClient* pSender, CONNID dwConnID, EnSocketOperation enOperation, int iErrorCode) +{ + EnHandleResult rs = HR_OK; + + if(m_pListener2 != nullptr) + rs = m_pListener2->OnClose(pSender, dwConnID, enOperation, iErrorCode); + + SetRequestEvent(HSRP_CLOSE); + + return rs; +} + +template EnHttpParseResult CHttpSyncClientT::OnBody(IHttpClient* pSender, CONNID dwConnID, const BYTE* pData, int iLength) +{ + EnHttpParseResult rs = HPR_OK; + + if(m_pListener2 != nullptr) + rs = m_pListener2->OnBody(pSender, dwConnID, pData, iLength); + + if(rs != HPR_ERROR) + m_szBuffer.Cat(pData, iLength); + + return rs; +} + +template EnHttpParseResult CHttpSyncClientT::OnMessageComplete(IHttpClient* pSender, CONNID dwConnID) +{ + EnHttpParseResult rs = HPR_OK; + + if(m_pListener2 != nullptr) + rs = m_pListener2->OnMessageComplete(pSender, dwConnID); + + if(rs != HPR_ERROR) + { + if(GetUpgradeType() == HUT_NONE) + SetRequestEvent(HSRP_DONE); + } + + return rs; +} + +template EnHttpParseResult CHttpSyncClientT::OnUpgrade(IHttpClient* pSender, CONNID dwConnID, EnHttpUpgradeType enUpgradeType) +{ + EnHttpParseResult rs = HPR_OK; + + if(m_pListener2 != nullptr) + rs = m_pListener2->OnUpgrade(pSender, dwConnID, enUpgradeType); + + if(rs != HPR_ERROR) + { + if(enUpgradeType == HUT_WEB_SOCKET) + { + SetRequestEvent(HSRP_DONE); + rs = HPR_OK; + } + else + { + SetRequestEvent(HSRP_ERROR); + rs = HPR_ERROR; + } + } + + return rs; +} + +template EnHttpParseResult CHttpSyncClientT::OnParseError(IHttpClient* pSender, CONNID dwConnID, int iErrorCode, LPCSTR lpszErrorDesc) +{ + EnHttpParseResult rs = HPR_OK; + + if(m_pListener2 != nullptr) + rs = m_pListener2->OnParseError(pSender, dwConnID, iErrorCode, lpszErrorDesc); + + SetRequestEvent(HSRP_ERROR); + + return rs; +} + +template EnHandleResult CHttpSyncClientT::OnWSMessageBody(IHttpClient* pSender, CONNID dwConnID, const BYTE* pData, int iLength) +{ + EnHandleResult rs = HR_OK; + + if(m_pListener2 != nullptr) + rs = m_pListener2->OnWSMessageBody(pSender, dwConnID, pData, iLength); + + if(rs != HR_ERROR) + m_szBuffer.Cat(pData, iLength); + + return rs; +} + +template EnHandleResult CHttpSyncClientT::OnWSMessageComplete(IHttpClient* pSender, CONNID dwConnID) +{ + EnHandleResult rs = HR_OK; + + if(m_pListener2 != nullptr) + rs = m_pListener2->OnWSMessageComplete(pSender, dwConnID); + + if(rs != HR_ERROR) + SetRequestEvent(HSRP_DONE); + + return rs; +} + +template EnHandleResult CHttpSyncClientT::OnPrepareConnect(ITcpClient* pSender, CONNID dwConnID, SOCKET socket) +{ + EnHandleResult rs = HR_OK; + + if(m_pListener2 != nullptr) + return m_pListener2->OnPrepareConnect(pSender, dwConnID, socket); + + return rs; +} + +template EnHandleResult CHttpSyncClientT::OnConnect(ITcpClient* pSender, CONNID dwConnID) +{ + EnHandleResult rs = HR_OK; + + if(m_pListener2 != nullptr) + return m_pListener2->OnConnect(pSender, dwConnID); + + return rs; +} + +template EnHandleResult CHttpSyncClientT::OnSend(ITcpClient* pSender, CONNID dwConnID, const BYTE* pData, int iLength) +{ + EnHandleResult rs = HR_OK; + + if(m_pListener2 != nullptr) + return m_pListener2->OnSend(pSender, dwConnID, pData, iLength); + + return rs; +} + +template EnHandleResult CHttpSyncClientT::OnReceive(ITcpClient* pSender, CONNID dwConnID, const BYTE* pData, int iLength) +{ + EnHandleResult rs = HR_OK; + + if(m_pListener2 != nullptr) + return m_pListener2->OnReceive(pSender, dwConnID, pData, iLength); + + return rs; +} + +template EnHttpParseResult CHttpSyncClientT::OnMessageBegin(IHttpClient* pSender, CONNID dwConnID) +{ + EnHttpParseResult rs = HPR_OK; + + if(m_pListener2 != nullptr) + rs = m_pListener2->OnMessageBegin(pSender, dwConnID); + + return rs; +} + +template EnHttpParseResult CHttpSyncClientT::OnStatusLine(IHttpClient* pSender, CONNID dwConnID, USHORT usStatusCode, LPCSTR lpszDesc) +{ + EnHttpParseResult rs = HPR_OK; + + if(m_pListener2 != nullptr) + rs = m_pListener2->OnStatusLine(pSender, dwConnID, usStatusCode, lpszDesc); + + return rs; +} + +template EnHttpParseResult CHttpSyncClientT::OnHeader(IHttpClient* pSender, CONNID dwConnID, LPCSTR lpszName, LPCSTR lpszValue) +{ + EnHttpParseResult rs = HPR_OK; + + if(m_pListener2 != nullptr) + rs = m_pListener2->OnHeader(pSender, dwConnID, lpszName, lpszValue); + + return rs; +} + +template EnHttpParseResult CHttpSyncClientT::OnHeadersComplete(IHttpClient* pSender, CONNID dwConnID) +{ + EnHttpParseResult rs = HPR_OK; + + if(m_pListener2 != nullptr) + rs = m_pListener2->OnHeadersComplete(pSender, dwConnID); + + return rs; +} + +template EnHttpParseResult CHttpSyncClientT::OnChunkHeader(IHttpClient* pSender, CONNID dwConnID, int iLength) +{ + EnHttpParseResult rs = HPR_OK; + + if(m_pListener2 != nullptr) + rs = m_pListener2->OnChunkHeader(pSender, dwConnID, iLength); + + return rs; +} + +template EnHttpParseResult CHttpSyncClientT::OnChunkComplete(IHttpClient* pSender, CONNID dwConnID) +{ + EnHttpParseResult rs = HPR_OK; + + if(m_pListener2 != nullptr) + rs = m_pListener2->OnChunkComplete(pSender, dwConnID); + + return rs; +} + +template EnHandleResult CHttpSyncClientT::OnWSMessageHeader(IHttpClient* pSender, CONNID dwConnID, BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], ULONGLONG ullBodyLen) +{ + EnHandleResult rs = HR_OK; + + if(m_pListener2 != nullptr) + rs = m_pListener2->OnWSMessageHeader(pSender, dwConnID, bFinal, iReserved, iOperationCode, lpszMask, ullBodyLen); + + return rs; +} + +// ------------------------------------------------------------------------------------------------------------- // + +template class CHttpClientT; +template class CHttpSyncClientT; + +#ifdef _SSL_SUPPORT + +#include "SSLClient.h" + +template class CHttpClientT; +template class CHttpSyncClientT; + +#endif + +#endif \ No newline at end of file diff --git a/HttpClient.h b/HttpClient.h new file mode 100644 index 0000000..9cb12a7 --- /dev/null +++ b/HttpClient.h @@ -0,0 +1,397 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "TcpClient.h" +#include "HttpHelper.h" + +#ifdef _HTTP_SUPPORT + +template class CHttpClientT : public R, public T +{ + using __super = T; + +public: + using __super::Stop; + using __super::Wait; + using __super::GetState; + using __super::SendPackets; + using __super::HasStarted; + using __super::GetRemoteHost; + + using __super::IsSecure; + using __super::IsConnected; + using __super::FireHandShake; + +#ifdef _SSL_SUPPORT + using __super::IsSSLAutoHandShake; + using __super::StartSSLHandShakeNoCheck; +#endif + +protected: + using __super::SetLastError; + + using THttpObj = THttpObjT; + friend typename CHttpClientT::THttpObj; + +public: + virtual BOOL SendRequest(LPCSTR lpszMethod, LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0, const BYTE* pBody = nullptr, int iLength = 0); + virtual BOOL SendLocalFile(LPCSTR lpszFileName, LPCSTR lpszMethod, LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0); + virtual BOOL SendChunkData(const BYTE* pData = nullptr, int iLength = 0, LPCSTR lpszExtensions = nullptr); + + virtual BOOL SendPost(LPCSTR lpszPath, const THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength) + {return SendRequest(HTTP_METHOD_POST, lpszPath, lpHeaders, iHeaderCount, pBody, iLength);} + virtual BOOL SendPut(LPCSTR lpszPath, const THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength) + {return SendRequest(HTTP_METHOD_PUT, lpszPath, lpHeaders, iHeaderCount, pBody, iLength);} + virtual BOOL SendPatch(LPCSTR lpszPath, const THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength) + {return SendRequest(HTTP_METHOD_PATCH, lpszPath, lpHeaders, iHeaderCount, pBody, iLength);} + virtual BOOL SendGet(LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0) + {return SendRequest(HTTP_METHOD_GET, lpszPath, lpHeaders, iHeaderCount);} + virtual BOOL SendDelete(LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0) + {return SendRequest(HTTP_METHOD_DELETE, lpszPath, lpHeaders, iHeaderCount);} + virtual BOOL SendHead(LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0) + {return SendRequest(HTTP_METHOD_HEAD, lpszPath, lpHeaders, iHeaderCount);} + virtual BOOL SendTrace(LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0) + {return SendRequest(HTTP_METHOD_TRACE, lpszPath, lpHeaders, iHeaderCount);} + virtual BOOL SendOptions(LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0) + {return SendRequest(HTTP_METHOD_OPTIONS, lpszPath, lpHeaders, iHeaderCount);} + virtual BOOL SendConnect(LPCSTR lpszHost, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0) + {return SendRequest(HTTP_METHOD_CONNECT, lpszHost, lpHeaders, iHeaderCount);} + + virtual BOOL SendWSMessage(BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], const BYTE* pData = nullptr, int iLength = 0, ULONGLONG ullBodyLen = 0); + + virtual BOOL StartHttp(); + +public: + virtual void SetUseCookie(BOOL bUseCookie) {ENSURE_HAS_STOPPED(); m_pCookieMgr = bUseCookie ? &g_CookieMgr : nullptr;} + virtual void SetHttpAutoStart(BOOL bAutoStart) {ENSURE_HAS_STOPPED(); m_bHttpAutoStart = bAutoStart;} + virtual void SetLocalVersion(EnHttpVersion enLocalVersion) {ENSURE_HAS_STOPPED(); m_enLocalVersion = enLocalVersion;} + + virtual BOOL IsUseCookie() {return m_pCookieMgr != nullptr;} + virtual BOOL IsHttpAutoStart() {return m_bHttpAutoStart;} + virtual EnHttpVersion GetLocalVersion() {return m_enLocalVersion;} + + virtual BOOL IsUpgrade() + {return m_objHttp.IsUpgrade();} + virtual BOOL IsKeepAlive() + {return m_objHttp.IsKeepAlive();} + virtual USHORT GetVersion() + {return m_objHttp.GetVersion();} + virtual ULONGLONG GetContentLength() + {return m_objHttp.GetContentLength();} + virtual LPCSTR GetContentType() + {return m_objHttp.GetContentType();} + virtual LPCSTR GetContentEncoding() + {return m_objHttp.GetContentEncoding();} + virtual LPCSTR GetTransferEncoding() + {return m_objHttp.GetTransferEncoding();} + virtual EnHttpUpgradeType GetUpgradeType() + {return m_objHttp.GetUpgradeType();} + virtual USHORT GetParseErrorCode(LPCSTR* lpszErrorDesc = nullptr) + {return m_objHttp.GetParseErrorCode(lpszErrorDesc);} + + virtual BOOL GetHeader(LPCSTR lpszName, LPCSTR* lpszValue) + {return m_objHttp.GetHeader(lpszName, lpszValue);} + virtual BOOL GetHeaders(LPCSTR lpszName, LPCSTR lpszValue[], DWORD& dwCount) + {return m_objHttp.GetHeaders(lpszName, lpszValue, dwCount);} + virtual BOOL GetAllHeaders(THeader lpHeaders[], DWORD& dwCount) + {return m_objHttp.GetAllHeaders(lpHeaders, dwCount);} + virtual BOOL GetAllHeaderNames(LPCSTR lpszName[], DWORD& dwCount) + {return m_objHttp.GetAllHeaderNames(lpszName, dwCount);} + + virtual BOOL GetCookie(LPCSTR lpszName, LPCSTR* lpszValue) + {return m_objHttp.GetCookie(lpszName, lpszValue);} + virtual BOOL GetAllCookies(TCookie lpCookies[], DWORD& dwCount) + {return m_objHttp.GetAllCookies(lpCookies, dwCount);} + + virtual USHORT GetStatusCode() + {return m_objHttp.GetStatusCode();} + + virtual BOOL GetWSMessageState(BOOL* lpbFinal, BYTE* lpiReserved, BYTE* lpiOperationCode, LPCBYTE* lpszMask, ULONGLONG* lpullBodyLen, ULONGLONG* lpullBodyRemain) + {return m_objHttp.GetWSMessageState(lpbFinal, lpiReserved, lpiOperationCode, lpszMask, lpullBodyLen, lpullBodyRemain);} + +private: + virtual BOOL CheckParams(); + + void DoStartHttp() + {m_objHttp.SetValid(TRUE);} + + virtual EnHandleResult FireConnect() + {return m_bHttpAutoStart ? __super::FireConnect() : __super::DoFireConnect(this);} + + virtual EnHandleResult DoFireConnect(ITcpClient* pSender) + { + ASSERT(pSender == this); + + DoStartHttp(); + + EnHandleResult result = __super::DoFireConnect(this); + + if(result == HR_ERROR) + m_objHttp.SetValid(FALSE); + + return result; + } + + virtual EnHandleResult DoFireReceive(ITcpClient* pSender, const BYTE* pData, int iLength) + {ASSERT(pSender == this); return m_objHttp.IsValid() ? m_objHttp.Execute(pData, iLength) : __super::DoFireReceive(pSender, pData, iLength);} + + EnHandleResult DoFireSuperReceive(IHttpClient* pSender, const BYTE* pData, int iLength) + {ASSERT(pSender == (IHttpClient*)this); return __super::DoFireReceive(pSender, pData, iLength);} + + virtual EnHandleResult DoFireClose(ITcpClient* pSender, EnSocketOperation enOperation, int iErrorCode) + { + ASSERT(pSender == (IHttpClient*)this); + + m_objHttp.CheckBodyIdentityEof(); + + return __super::DoFireClose(pSender, enOperation, iErrorCode); + } + + virtual void Reset() + { + m_objHttp.Reset(); + + __super::Reset(); + } + + EnHttpParseResult FireMessageBegin(IHttpClient* pSender) + {return m_pListener->OnMessageBegin(pSender, pSender->GetConnectionID());} + EnHttpParseResult FireRequestLine(IHttpClient* pSender, LPCSTR lpszMethod, LPCSTR lpszUrl) + {return m_pListener->OnRequestLine(pSender, pSender->GetConnectionID(), lpszMethod, lpszUrl);} + EnHttpParseResult FireStatusLine(IHttpClient* pSender, USHORT usStatusCode, LPCSTR lpszDesc) + {return m_pListener->OnStatusLine(pSender, pSender->GetConnectionID(), usStatusCode, lpszDesc);} + EnHttpParseResult FireHeader(IHttpClient* pSender, LPCSTR lpszName, LPCSTR lpszValue) + {return m_pListener->OnHeader(pSender, pSender->GetConnectionID(), lpszName, lpszValue);} + EnHttpParseResult FireHeadersComplete(IHttpClient* pSender) + {return m_pListener->OnHeadersComplete(pSender, pSender->GetConnectionID());} + EnHttpParseResult FireBody(IHttpClient* pSender, const BYTE* pData, int iLength) + {return m_pListener->OnBody(pSender, pSender->GetConnectionID(), pData, iLength);} + EnHttpParseResult FireChunkHeader(IHttpClient* pSender, int iLength) + {return m_pListener->OnChunkHeader(pSender, pSender->GetConnectionID(), iLength);} + EnHttpParseResult FireChunkComplete(IHttpClient* pSender) + {return m_pListener->OnChunkComplete(pSender, pSender->GetConnectionID());} + EnHttpParseResult FireMessageComplete(IHttpClient* pSender) + {return m_pListener->OnMessageComplete(pSender, pSender->GetConnectionID());} + EnHttpParseResult FireUpgrade(IHttpClient* pSender, EnHttpUpgradeType enUpgradeType) + {return m_pListener->OnUpgrade(pSender, pSender->GetConnectionID(), enUpgradeType);} + EnHttpParseResult FireParseError(IHttpClient* pSender, int iErrorCode, LPCSTR lpszErrorDesc) + {return m_pListener->OnParseError(pSender, pSender->GetConnectionID(), iErrorCode, lpszErrorDesc);} + + EnHandleResult FireWSMessageHeader(IHttpClient* pSender, BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], ULONGLONG ullBodyLen) + {return m_pListener->OnWSMessageHeader(pSender, pSender->GetConnectionID(), bFinal, iReserved, iOperationCode, lpszMask, ullBodyLen);} + EnHandleResult FireWSMessageBody(IHttpClient* pSender, const BYTE* pData, int iLength) + {return m_pListener->OnWSMessageBody(pSender, pSender->GetConnectionID(), pData, iLength);} + EnHandleResult FireWSMessageComplete(IHttpClient* pSender) + {return m_pListener->OnWSMessageComplete(pSender, pSender->GetConnectionID());} + + CCookieMgr* GetCookieMgr() {return m_pCookieMgr;} + LPCSTR GetRemoteDomain(IHttpClient* pSender) {LPCSTR lpszDomain; GetRemoteHost(&lpszDomain); return lpszDomain;} + +public: + CHttpClientT(IHttpClientListener* pListener) + : T (pListener) + , m_pListener (pListener) + , m_pCookieMgr (&g_CookieMgr) + , m_bHttpAutoStart (TRUE) + , m_enLocalVersion (DEFAULT_HTTP_VERSION) + , m_objHttp (FALSE, this, (IHttpClient*)this) + { + + } + + virtual ~CHttpClientT() + { + ENSURE_STOP(); + } + +private: + BOOL m_bHttpAutoStart; + + IHttpClientListener* m_pListener; + CCookieMgr* m_pCookieMgr; + EnHttpVersion m_enLocalVersion; + + CReentrantCriSec m_csHttp; + +protected: + THttpObj m_objHttp; +}; + +// ------------------------------------------------------------------------------------------------------------- // + +template class CHttpSyncClientT : public CHttpClientT, private CHttpClientListener +{ + using __super = CHttpClientT; + using __super::m_objHttp; + using __super::SetLastError; + + using typename __super::THttpObj; + +public: + using __super::Stop; + using __super::Wait; + using __super::GetState; + using __super::HasStarted; + using __super::GetRemoteHost; + using __super::SendLocalFile; + using __super::IsHttpAutoStart; + +public: + virtual BOOL Start(LPCTSTR lpszRemoteAddress, USHORT usPort, BOOL bAsyncConnect = TRUE, LPCTSTR lpszBindAddress = nullptr, USHORT usLocalPort = 0); +public: + virtual BOOL SendRequest(LPCSTR lpszMethod, LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0, const BYTE* pBody = nullptr, int iLength = 0); + virtual BOOL SendWSMessage(BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4] = nullptr, const BYTE* pData = nullptr, int iLength = 0, ULONGLONG ullBodyLen = 0); + +public: + virtual BOOL IsUpgrade() + {return m_pHttpObj->IsUpgrade();} + virtual BOOL IsKeepAlive() + {return m_pHttpObj->IsKeepAlive();} + virtual USHORT GetVersion() + {return m_pHttpObj->GetVersion();} + virtual ULONGLONG GetContentLength() + {return m_pHttpObj->GetContentLength();} + virtual LPCSTR GetContentType() + {return m_pHttpObj->GetContentType();} + virtual LPCSTR GetContentEncoding() + {return m_pHttpObj->GetContentEncoding();} + virtual LPCSTR GetTransferEncoding() + {return m_pHttpObj->GetTransferEncoding();} + virtual EnHttpUpgradeType GetUpgradeType() + {return m_pHttpObj->GetUpgradeType();} + virtual USHORT GetParseErrorCode(LPCSTR* lpszErrorDesc = nullptr) + {return m_pHttpObj->GetParseErrorCode(lpszErrorDesc);} + + virtual BOOL GetHeader(LPCSTR lpszName, LPCSTR* lpszValue) + {return m_pHttpObj->GetHeader(lpszName, lpszValue);} + virtual BOOL GetHeaders(LPCSTR lpszName, LPCSTR lpszValue[], DWORD& dwCount) + {return m_pHttpObj->GetHeaders(lpszName, lpszValue, dwCount);} + virtual BOOL GetAllHeaders(THeader lpHeaders[], DWORD& dwCount) + {return m_pHttpObj->GetAllHeaders(lpHeaders, dwCount);} + virtual BOOL GetAllHeaderNames(LPCSTR lpszName[], DWORD& dwCount) + {return m_pHttpObj->GetAllHeaderNames(lpszName, dwCount);} + + virtual BOOL GetCookie(LPCSTR lpszName, LPCSTR* lpszValue) + {return m_pHttpObj->GetCookie(lpszName, lpszValue);} + virtual BOOL GetAllCookies(TCookie lpCookies[], DWORD& dwCount) + {return m_pHttpObj->GetAllCookies(lpCookies, dwCount);} + + virtual USHORT GetStatusCode() + {return m_pHttpObj->GetStatusCode();} + + virtual BOOL GetWSMessageState(BOOL* lpbFinal, BYTE* lpiReserved, BYTE* lpiOperationCode, LPCBYTE* lpszMask, ULONGLONG* lpullBodyLen, ULONGLONG* lpullBodyRemain) + {return m_pHttpObj->GetWSMessageState(lpbFinal, lpiReserved, lpiOperationCode, lpszMask, lpullBodyLen, lpullBodyRemain);} + +public: + virtual BOOL OpenUrl(LPCSTR lpszMethod, LPCSTR lpszUrl, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0, const BYTE* pBody = nullptr, int iLength = 0, BOOL bForceReconnect = FALSE); + virtual BOOL CleanupRequestResult(); + +public: + virtual BOOL GetResponseBody (LPCBYTE* lpszBody, int* iLength); + + virtual void SetConnectTimeout (DWORD dwConnectTimeout) {ENSURE_HAS_STOPPED(); m_dwConnectTimeout = dwConnectTimeout;} + virtual void SetRequestTimeout (DWORD dwRequestTimeout) {ENSURE_HAS_STOPPED(); m_dwRequestTimeout = dwRequestTimeout;} + + virtual DWORD GetConnectTimeout () {return m_dwConnectTimeout;} + virtual DWORD GetRequestTimeout () {return m_dwRequestTimeout;} + +private: + virtual EnHandleResult OnHandShake(ITcpClient* pSender, CONNID dwConnID); + virtual EnHandleResult OnClose(ITcpClient* pSender, CONNID dwConnID, EnSocketOperation enOperation, int iErrorCode); + + virtual EnHttpParseResult OnBody(IHttpClient* pSender, CONNID dwConnID, const BYTE* pData, int iLength); + virtual EnHttpParseResult OnMessageComplete(IHttpClient* pSender, CONNID dwConnID); + virtual EnHttpParseResult OnUpgrade(IHttpClient* pSender, CONNID dwConnID, EnHttpUpgradeType enUpgradeType); + virtual EnHttpParseResult OnParseError(IHttpClient* pSender, CONNID dwConnID, int iErrorCode, LPCSTR lpszErrorDesc); + + virtual EnHandleResult OnWSMessageBody(IHttpClient* pSender, CONNID dwConnID, const BYTE* pData, int iLength); + virtual EnHandleResult OnWSMessageComplete(IHttpClient* pSender, CONNID dwConnID); + + virtual EnHandleResult OnPrepareConnect(ITcpClient* pSender, CONNID dwConnID, SOCKET socket); + virtual EnHandleResult OnConnect(ITcpClient* pSender, CONNID dwConnID); + virtual EnHandleResult OnSend(ITcpClient* pSender, CONNID dwConnID, const BYTE* pData, int iLength); + virtual EnHandleResult OnReceive(ITcpClient* pSender, CONNID dwConnID, const BYTE* pData, int iLength); + + virtual EnHttpParseResult OnMessageBegin(IHttpClient* pSender, CONNID dwConnID); + virtual EnHttpParseResult OnStatusLine(IHttpClient* pSender, CONNID dwConnID, USHORT usStatusCode, LPCSTR lpszDesc); + virtual EnHttpParseResult OnHeader(IHttpClient* pSender, CONNID dwConnID, LPCSTR lpszName, LPCSTR lpszValue); + virtual EnHttpParseResult OnHeadersComplete(IHttpClient* pSender, CONNID dwConnID); + virtual EnHttpParseResult OnChunkHeader(IHttpClient* pSender, CONNID dwConnID, int iLength); + virtual EnHttpParseResult OnChunkComplete(IHttpClient* pSender, CONNID dwConnID); + + virtual EnHandleResult OnWSMessageHeader(IHttpClient* pSender, CONNID dwConnID, BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], ULONGLONG ullBodyLen); + + +private: + void SetRequestEvent(EnHttpSyncRequestProgress enProgress, BOOL bCopyHttpObj = TRUE); + BOOL WaitForEvent(DWORD dwWait); + +public: + CHttpSyncClientT(IHttpClientListener* pListener = nullptr) + : __super (this) + , m_enProgress (HSRP_DONE) + , m_objHttp2 (FALSE, this, (IHttpClient*)this) + , m_pHttpObj (nullptr) + , m_pListener2 (pListener) + , m_dwConnectTimeout (DEFAULT_HTTP_SYNC_CONNECT_TIMEOUT) + , m_dwRequestTimeout (DEFAULT_HTTP_SYNC_REQUEST_TIMEOUT) + { + + } + + virtual ~CHttpSyncClientT() + { + ENSURE_STOP(); + } + +private: + DWORD m_dwConnectTimeout; + DWORD m_dwRequestTimeout; + + CEvt m_evWait; + THttpObj m_objHttp2; + + THttpObj* m_pHttpObj; + IHttpClientListener* m_pListener2; + + EnHttpSyncRequestProgress m_enProgress; + CBufferPtrT m_szBuffer; +}; + +// ------------------------------------------------------------------------------------------------------------- // + +typedef CHttpClientT CHttpClient; +typedef CHttpSyncClientT CHttpSyncClient; + +#ifdef _SSL_SUPPORT + +#include "SSLClient.h" + +typedef CHttpClientT CHttpsClient; +typedef CHttpSyncClientT CHttpsSyncClient; + +#endif + +#endif \ No newline at end of file diff --git a/HttpCookie.cpp b/HttpCookie.cpp new file mode 100644 index 0000000..4b6cf7b --- /dev/null +++ b/HttpCookie.cpp @@ -0,0 +1,927 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "HttpCookie.h" + +#ifdef _HTTP_SUPPORT + +static const char* s_short_week[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; +static const char* s_short_month[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + +CCookieMgr g_CookieMgr; + +CCookie* CCookie::FromString(const CStringA& strCookie, LPCSTR lpszDefaultDomain, LPCSTR lpszDefaultPath) +{ + CStringA strName; + CStringA strValue; + CStringA strDomain; + CStringA strPath; + + int iMaxAge = -1; + BOOL bHttpOnly = FALSE; + BOOL bSecure = FALSE; + EnSameSite enSameSite = SS_UNKNOWN; + + int i = 0; + int iStart = 0; + + while(TRUE) + { + CStringA strField = strCookie.Tokenize(COOKIE_FIELD_SEP, iStart); + strField.Trim(); + + if(i == 0) + { + ParseFieldKV(strField, strName, strValue, COOKIE_KV_SEP_CHAR); + + if(strName.IsEmpty()) + return nullptr; + } + else + { + if(strField.IsEmpty()) + break; + + CStringA strKey; + CStringA strVal; + + ParseFieldKV(strField, strKey, strVal, COOKIE_KV_SEP_CHAR); + + if(strKey.CompareNoCase(COOKIE_DOMAIN) == 0) + strDomain = strVal; + else if(strKey.CompareNoCase(COOKIE_PATH) == 0) + strPath = strVal; + else if(strKey.CompareNoCase(COOKIE_MAX_AGE) == 0 && !strVal.IsEmpty()) + iMaxAge = atoi(strVal); + else if(strKey.CompareNoCase(COOKIE_EXPIRES) == 0 && !strVal.IsEmpty() && iMaxAge == -1) + { + __time64_t tmExpires = -1; + + if(!ParseExpires(strVal, tmExpires)) + return nullptr; + + iMaxAge = ExpiresToMaxAge(tmExpires); + } + else if(strKey.CompareNoCase(COOKIE_HTTPONLY) == 0) + bHttpOnly = TRUE; + else if(strKey.CompareNoCase(COOKIE_SECURE) == 0) + bSecure = TRUE; + else if(strKey.CompareNoCase(COOKIE_SAMESITE) == 0) + { + if(strVal.IsEmpty() || strVal.CompareNoCase(COOKIE_SAMESITE_LAX) == 0) + enSameSite = SS_LAX; + else if(strVal.CompareNoCase(COOKIE_SAMESITE_STRICT) == 0) + enSameSite = SS_STRICT; + else if(strVal.CompareNoCase(COOKIE_SAMESITE_NONE) == 0) + enSameSite = SS_NONE; + } + } + + ++i; + } + + if(!AdjustDomain(strDomain, lpszDefaultDomain) || !AdjustPath(strPath, lpszDefaultPath)) + return nullptr; + + CCookie* pCookie = new CCookie(strName, strValue, strDomain, strPath, iMaxAge, bHttpOnly, bSecure, enSameSite); + ASSERT(pCookie->IsValid()); + + return pCookie; +} + +CStringA CCookie::ToString(LPCSTR lpszName, LPCSTR lpszValue, LPCSTR lpszDomain, LPCSTR lpszPath, int iMaxAge, BOOL bHttpOnly, BOOL bSecure, EnSameSite enSameSite) +{ + CCookie cookie(lpszName, lpszValue, lpszDomain, lpszPath, iMaxAge, bHttpOnly, bSecure, enSameSite); + return cookie.ToString(); +} + +BOOL CCookie::ToString(char lpszBuff[], int& iBuffLen, LPCSTR lpszName, LPCSTR lpszValue, LPCSTR lpszDomain, LPCSTR lpszPath, int iMaxAge, BOOL bHttpOnly, BOOL bSecure, EnSameSite enSameSite) +{ + BOOL isOK = FALSE; + CStringA str = ToString(lpszName, lpszValue, lpszDomain, lpszPath, iMaxAge, bHttpOnly, bSecure, enSameSite); + int iLength = str.GetLength() + 1; + + if(lpszBuff && iBuffLen >= iLength) + { + memcpy(lpszBuff, (LPCSTR)str, iLength * sizeof(char)); + isOK = TRUE; + } + + iBuffLen = iLength; + + return isOK; +} + +CStringA CCookie::ToString() +{ + ASSERT(!name.IsEmpty()); + + CStringA strCookie; + + strCookie.AppendFormat("%s=%s", (LPCSTR)name, (LPCSTR)value); + + if(!domain.IsEmpty()) + strCookie.AppendFormat("; %s=%s", COOKIE_DOMAIN, (LPCSTR)domain); + if(!path.IsEmpty()) + strCookie.AppendFormat("; %s=%s", COOKIE_PATH, (LPCSTR)path); + if(expires >= 0) + strCookie.AppendFormat("; %s=%s", COOKIE_EXPIRES, (LPCSTR)MakeExpiresStr(expires)); + if(httpOnly) + strCookie.AppendFormat("; %s", COOKIE_HTTPONLY); + if(secure) + strCookie.AppendFormat("; %s", COOKIE_SECURE); + if(sameSite != SS_UNKNOWN) + strCookie.AppendFormat("; %s=%s", COOKIE_SAMESITE, sameSite == SS_LAX ? COOKIE_SAMESITE_LAX : (sameSite == SS_STRICT ? COOKIE_SAMESITE_STRICT : COOKIE_SAMESITE_NONE)); + + return strCookie; +} + +BOOL CCookie::Match(LPCSTR lpszDomain, LPCSTR lpszPath, BOOL bHttp, BOOL bSecure) +{ + int iLen = (int)strlen(lpszDomain); + int iDiff = iLen - domain.GetLength(); + + if(iDiff < 0 || stricmp(lpszDomain + iDiff, domain) != 0) + return FALSE; + if(iDiff > 0 && *(lpszDomain + iDiff - 1) != COOKIE_DOMAIN_SEP_CHAR) + return FALSE; + if(strncmp(lpszPath, path, path.GetLength()) != 0) + return FALSE; + + return (bHttp || !httpOnly) && (bSecure || !secure); +} + +BOOL CCookie::IsSameDomain(LPCSTR lpszDomain) +{ + int iLen = (int)strlen(lpszDomain); + int iDiff = iLen - domain.GetLength(); + + LPCSTR lpszLong, lpszShort; + + if(iDiff < 0) + { + lpszLong = (LPCSTR)domain + iDiff; + lpszShort = lpszDomain; + } + else + { + lpszLong = lpszDomain + iDiff; + lpszShort = (LPCSTR)domain; + } + + if(stricmp(lpszLong, lpszShort) != 0) + return FALSE; + if(iDiff != 0 && *(lpszLong - 1) != COOKIE_DOMAIN_SEP_CHAR) + return FALSE; + + return TRUE; +} + +void CCookie::ParseFieldKV(const CStringA& strField, CStringA& strKey, CStringA& strVal, char chSep) +{ + int i = strField.Find(chSep); + + if(i < 0) + strKey = strField; + else + { + strKey = strField.Left(i); + strVal = strField.Mid(i + 1); + + strVal.Trim(); + } + + strKey.Trim(); +} + +BOOL CCookie::ParseExpires(LPCSTR lpszExpires, __time64_t& tmExpires) +{ + int iLength = (int)strlen(lpszExpires); + + if(iLength == 0 || iLength > 50) + return FALSE; + + char szMonth[10]; + char szZone[10]; + + tm t = {0}; + + if(sscanf( lpszExpires, "%*[^, ]%*[, ]%2d%*[-/ ]%8[^-/ ]%*[-/ ]%4d %2d:%2d:%2d %8s", + &t.tm_mday, szMonth, &t.tm_year, &t.tm_hour, &t.tm_min, &t.tm_sec, szZone) != 7) + return FALSE; + + if(t.tm_year < 70) + t.tm_year += 100; + else if (t.tm_year > 100) + t.tm_year -= 1900; + + int i = 0; + int size = _countof(s_short_month); + + for(; i < size; i++) + { + if(strnicmp(szMonth, s_short_month[i], 3) == 0) + break; + } + + if(i == size) + return FALSE; + + t.tm_mon = i; + + CStringA strZone = szZone; + + int iZone = 0; + int iMix = 0; + int iPos = strZone.Find('+'); + + if(iPos >= 0) + iMix = 1; + else + { + iPos = strZone.Find('-'); + + if(iPos >= 0) + iMix = -1; + } + + if(iPos >= 0) + { + strZone = strZone.Mid(iPos + 1); + strZone.Remove(':'); + + int val = atoi(strZone); + + if(val > 0) + { + int minutes = val % 100; + int hours = val / 100; + + iZone = iMix * (minutes * 60 + hours * 3600); + } + } + + tmExpires = GetUTCTime(t, iZone); + + return tmExpires >= 0; +} + +BOOL CCookie::AdjustDomain(CStringA& strDomain, LPCSTR lpszDefaultDomain) +{ + if(strDomain.IsEmpty() && lpszDefaultDomain) + strDomain = lpszDefaultDomain; + + strDomain.TrimLeft(COOKIE_DOMAIN_SEP_CHAR).MakeLower(); + + return !strDomain.IsEmpty(); +} + +BOOL CCookie::AdjustPath(CStringA& strPath, LPCSTR lpszDefaultPath) +{ + if(strPath.IsEmpty() && lpszDefaultPath) + strPath = lpszDefaultPath; + + int iLength = strPath.GetLength(); + + if(iLength == 0) + return FALSE; + + if(strPath.GetAt(iLength - 1) != COOKIE_PATH_SEP_CHAR) + { + int iPos = strPath.ReverseFind(COOKIE_PATH_SEP_CHAR); + + if(iPos >= 0) + strPath = strPath.Left(iPos + 1); + else + strPath.Empty(); + } + + if(!strPath.IsEmpty() && strPath.GetAt(0) != COOKIE_PATH_SEP_CHAR) + strPath.Insert(0, COOKIE_PATH_SEP_CHAR); + + return !strPath.IsEmpty(); +} + +__time64_t CCookie::GetUTCTime(tm& t, int iSecondOffsetTZ) +{ + __time64_t v = _mkgmtime64(&t); + if(v >= 0) v -= iSecondOffsetTZ; + + return v; +} + +CStringA CCookie::MakeExpiresStr(__time64_t tmExpires) +{ + ASSERT( tmExpires >= 0); + + if(tmExpires < 1) tmExpires = 1; + + tm t; + VERIFY(_gmtime64(&t, &tmExpires) != nullptr); + + CStringA str; + str.Format("%s, %02d-%s-%04d %02d:%02d:%02d GMT", + s_short_week[t.tm_wday], t.tm_mday, s_short_month[t.tm_mon], t.tm_year + 1900, t.tm_hour, t.tm_min, t.tm_sec); + + return str; +} + +BOOL CCookie::MakeExpiresStr(char lpszBuff[], int& iBuffLen, __time64_t tmExpires) +{ + BOOL isOK = FALSE; + CStringA str = MakeExpiresStr(tmExpires); + int iLength = str.GetLength() + 1; + + if(lpszBuff && iBuffLen >= iLength) + { + memcpy(lpszBuff, (LPCSTR)str, iLength * sizeof(char)); + isOK = TRUE; + } + + iBuffLen = iLength; + + return isOK; +} + +// ------------------------------------------------------------------------------------------------------------- // + +BOOL CCookieMgr::LoadFromFile(LPCSTR lpszFile, BOOL bKeepExists) +{ + BOOL isOK = FALSE; + FILE* pFile = nullptr; + + if((pFile = fopen(lpszFile, "r")) == nullptr) + goto _ERROR_END; + + { + CStringA strDomain; + CStringA strPath; + CCookie cookie; + + char szBuffer[8192]; + int iBufferSize = _countof(szBuffer); + __time64_t tmCurrent = _time64(nullptr); + CCookieSet* pCookieSet = nullptr; + + CWriteLock locallock(m_cs); + + if(!bKeepExists) + ClearDomainCookiesNoLock(); + + while(fgets(szBuffer, iBufferSize, pFile) != nullptr) + { + char c = szBuffer[0]; + + if(c == '\n' || c == '\r') + continue; + else if(c != '\t') + { + if(!LoadDomainAndPath(szBuffer, strDomain, strPath)) + goto _ERROR_END; + + pCookieSet = GetCookieSetNoLock(strDomain, strPath); + } + else + { + if(!LoadCookie(szBuffer, strDomain, strPath, cookie)) + goto _ERROR_END; + + if(cookie.expires <= tmCurrent) + continue; + + if(pCookieSet) + { + if(bKeepExists) + { + CCookieSetCI it = pCookieSet->find(cookie); + if(it != pCookieSet->end()) + continue; + } + + pCookieSet->emplace(move(cookie)); + } + else + { + SetCookieNoLock(cookie, FALSE); + pCookieSet = GetCookieSetNoLock(strDomain, strPath); + } + } + } + + if(!feof(pFile)) + goto _ERROR_END; + } + + isOK = TRUE; + +_ERROR_END: + + if(pFile) fclose(pFile); + + return isOK; +} + +BOOL CCookieMgr::SaveToFile(LPCSTR lpszFile, BOOL bKeepExists) +{ + if(bKeepExists) + { + if(!LoadFromFile(lpszFile, TRUE) && !IS_ERROR(ERROR_FILE_NOT_FOUND)) + return FALSE; + } + + BOOL isOK = FALSE; + FILE* pFile = nullptr; + + if((pFile = fopen(lpszFile, "w")) == nullptr) + goto _ERROR_END; + + { + __time64_t tmCurrent = _time64(nullptr); + + CReadLock locallock(m_cs); + + for(CCookieDomainMapCI it = m_cookies.begin(), end = m_cookies.end(); it != end; ++it) + { + const CStringA& strDomain = it->first; + const CCookiePathMap& paths = it->second; + + for(CCookiePathMapCI it2 = paths.begin(), end2 = paths.end(); it2 != end2; ++it2) + { + const CStringA& strPath = it2->first; + const CCookieSet& cookies = it2->second; + + if(fprintf(pFile, "%s %s\n", (LPCSTR)strDomain, (LPCSTR)strPath) < 0) + goto _ERROR_END; + + for(CCookieSetCI it3 = cookies.begin(), end3 = cookies.end(); it3 != end3; ++it3) + { + const CCookie& cookie = *it3; + + if(cookie.expires <= tmCurrent) + continue; + + LPCSTR lpszValue = (LPCSTR)cookie.value; + + if(lpszValue[0] == 0) + lpszValue = " "; + + if(fprintf(pFile, "\t%s;%s;%lld;%d;%d;%d\n", (LPCSTR)cookie.name, lpszValue, cookie.expires, cookie.httpOnly, cookie.secure, cookie.sameSite) < 0) + goto _ERROR_END; + } + } + } + } + + isOK = TRUE; + +_ERROR_END: + + if(pFile) fclose(pFile); + + return isOK; +} + +BOOL CCookieMgr::ClearCookies(LPCSTR lpszDomain, LPCSTR lpszPath) +{ + CStringA strDomain; + CStringA strPath; + + if(!AdjustDomainAndPath(lpszDomain, lpszPath, strDomain, strPath, TRUE)) + return FALSE; + + CWriteLock locallock(m_cs); + + ClearDomainCookiesNoLock(lpszDomain, lpszPath); + + return TRUE; +} + +BOOL CCookieMgr::RemoveExpiredCookies(LPCSTR lpszDomain, LPCSTR lpszPath) +{ + CStringA strDomain; + CStringA strPath; + + if(!AdjustDomainAndPath(lpszDomain, lpszPath, strDomain, strPath, TRUE)) + return FALSE; + + CWriteLock locallock(m_cs); + + RemoveExpiredCookiesNoLock(lpszDomain, lpszPath); + + return TRUE; +} + +BOOL CCookieMgr::GetCookies(CCookieSet& cookies, LPCSTR lpszDomain, LPCSTR lpszPath, BOOL bHttp, BOOL bSecure) +{ + ASSERT(lpszDomain && lpszPath); + + CStringA strDomain; + CStringA strPath; + + if(!AdjustDomainAndPath(lpszDomain, lpszPath, strDomain, strPath, FALSE)) + return FALSE; + + list lsDomains(1, lpszDomain); + list lsPaths(1, lpszPath); + + char c; + LPCSTR lpszTemp = lpszDomain; + + while((c = *(++lpszTemp)) != 0) + { + if(c == COOKIE_DOMAIN_SEP_CHAR) + { + if((c = *(++lpszTemp)) != 0) + lsDomains.push_back(lpszTemp); + else + break; + } + } + + lpszTemp = lpszPath + strlen(lpszPath) - 1; + + while(--lpszTemp >= lpszPath) + { + if((c = *lpszTemp) == COOKIE_PATH_SEP_CHAR) + { + *(LPSTR)(lpszTemp + 1) = 0; + lsPaths.push_back(lpszPath); + } + } + + CReadLock locallock(m_cs); + + for(list::const_iterator it = lsDomains.begin(), end = lsDomains.end(); it != end; ++it) + { + for(list::const_iterator it2 = lsPaths.begin(), end2 = lsPaths.end(); it2 != end2; ++it2) + MatchCookiesNoLock(cookies, *it, *it2, bHttp, bSecure); + } + + return TRUE; +} + +BOOL CCookieMgr::SetCookie(LPCSTR lpszName, LPCSTR lpszValue, LPCSTR lpszDomain, LPCSTR lpszPath, int iMaxAge, BOOL bHttpOnly, BOOL bSecure, CCookie::EnSameSite enSameSite, BOOL bOnlyUpdateValueIfExists) +{ + CCookie cookie(lpszName, lpszValue, lpszDomain, lpszPath, iMaxAge, bHttpOnly, bSecure, enSameSite); + + return SetCookie(cookie, bOnlyUpdateValueIfExists); +} + +BOOL CCookieMgr::SetCookie(const CStringA& strCookie, BOOL bOnlyUpdateValueIfExists) +{ + unique_ptr pCookie(CCookie::FromString(strCookie)); + if(!pCookie) return FALSE; + + return SetCookie(*pCookie, bOnlyUpdateValueIfExists); +} + +BOOL CCookieMgr::SetCookie(const CCookie& cookie, BOOL bOnlyUpdateValueIfExists) +{ + if(!cookie.IsValid()) return FALSE; + + CWriteLock locallock(m_cs); + + return SetCookieNoLock(cookie, bOnlyUpdateValueIfExists); +} + +BOOL CCookieMgr::DeleteCookie(LPCSTR lpszDomain, LPCSTR lpszPath, LPCSTR lpszName) +{ + CCookie cookie(lpszName, nullptr, lpszDomain, lpszPath); + + return DeleteCookie(cookie); +} + +BOOL CCookieMgr::DeleteCookie(const CCookie& cookie) +{ + if(!cookie.IsValid()) return FALSE; + + CWriteLock locallock(m_cs); + + return DeleteCookieNoLock(cookie); +} + +void CCookieMgr::ClearDomainCookiesNoLock(LPCSTR lpszDomain, LPCSTR lpszPath) +{ + if(!lpszDomain && !lpszPath) + m_cookies.clear(); + else if(!lpszPath) + m_cookies.erase(lpszDomain); + else + { + if(!lpszDomain) + { + for(CCookieDomainMapI it = m_cookies.begin(), end = m_cookies.end(); it != end; ++it) + ClearPathCookiesNoLock(it->second, lpszPath); + } + else + { + CCookieDomainMapI it = m_cookies.find(lpszDomain); + if(it != m_cookies.end()) + ClearPathCookiesNoLock(it->second, lpszPath); + } + } +} + +void CCookieMgr::ClearPathCookiesNoLock(CCookiePathMap& paths, LPCSTR lpszPath) +{ + if(!lpszPath) + paths.clear(); + else + { + CCookiePathMapI it = paths.find(lpszPath); + if(it != paths.end()) + paths.erase(it->first); + } +} + +void CCookieMgr::RemoveExpiredCookiesNoLock(LPCSTR lpszDomain, LPCSTR lpszPath) +{ + if(!lpszDomain) + { + for(CCookieDomainMapI it = m_cookies.begin(), end = m_cookies.end(); it != end; ++it) + RemoveDomainExpiredCookiesNoLock(it->second, lpszPath); + } + else + { + CCookieDomainMapI it = m_cookies.find(lpszDomain); + if(it != m_cookies.end()) + RemoveDomainExpiredCookiesNoLock(it->second, lpszPath); + } +} + +void CCookieMgr::RemoveDomainExpiredCookiesNoLock(CCookiePathMap& paths, LPCSTR lpszPath) +{ + if(!lpszPath) + { + for(CCookiePathMapI it = paths.begin(), end = paths.end(); it != end; ++it) + RemovePathExpiredCookiesNoLock(it->second); + } + else + { + CCookiePathMapI it = paths.find(lpszPath); + if(it != paths.end()) + RemovePathExpiredCookiesNoLock(it->second); + } +} + +void CCookieMgr::RemovePathExpiredCookiesNoLock(CCookieSet& cookies) +{ + CCookiePtrSet ptrs; + + for(CCookieSetI it = cookies.begin(), end = cookies.end(); it != end; ++it) + { + if(it->IsExpired()) + ptrs.emplace(&*it); + } + + if(!ptrs.empty()) + { + for(CCookiePtrSetI it = ptrs.begin(), end = ptrs.end(); it != end; ++it) + cookies.erase(**it); + } +} + +const CCookie* CCookieMgr::GetCookieNoLock(LPCSTR lpszDomain, LPCSTR lpszPath, LPCSTR lpszName) +{ + const CCookie cookie(lpszName, nullptr, lpszDomain, lpszPath); + return GetCookieNoLock(cookie); +} + +const CCookie* CCookieMgr::GetCookieNoLock(const CCookie& cookie) +{ + const CCookie* pCookie = nullptr; + + CCookieDomainMapCI it = m_cookies.find(cookie.domain); + if(it != m_cookies.end()) + { + CCookiePathMapCI it2 = it->second.find(cookie.path); + if(it2 != it->second.end()) + { + CCookieSetCI it3 = it2->second.find(cookie); + if(it3 != it2->second.end()) + pCookie = &*it3; + } + } + + return pCookie; +} + +void CCookieMgr::MatchCookiesNoLock(CCookieSet& cookies, LPCSTR lpszDomain, LPCSTR lpszPath, BOOL bHttp, BOOL bSecure) +{ + CCookieDomainMapCI it = m_cookies.find(lpszDomain); + if(it != m_cookies.end()) + { + CCookiePathMapCI it2 = it->second.find(lpszPath); + if(it2 != it->second.end()) + { + for(CCookieSetCI it3 = it2->second.begin(), end3 = it2->second.end(); it3 != end3; ++it3) + { + const CCookie& cookie = *it3; + + if(!cookie.IsExpired() && (bHttp || !cookie.httpOnly) && (bSecure || !cookie.secure)) + cookies.emplace(cookie); + } + } + } +} + +BOOL CCookieMgr::SetCookieNoLock(const CCookie& cookie, BOOL bOnlyUpdateValueIfExists) +{ + if(cookie.IsExpired()) + return DeleteCookieNoLock(cookie); + + CCookieDomainMapI it = m_cookies.find(cookie.domain); + + if(it == m_cookies.end()) + it = m_cookies.emplace(CCookieDomainMap::value_type(cookie.domain, CCookiePathMap())).first; + + CCookiePathMapI it2 = it->second.find(cookie.path); + + if(it2 == it->second.end()) + it2 = it->second.emplace(CCookiePathMap::value_type(cookie.path, CCookieSet())).first; + + CCookieSet& cookies = it2->second; + CCookieSetI it3 = cookies.find(cookie); + + if(it3 != cookies.end()) + { + if(bOnlyUpdateValueIfExists && !it3->IsExpired() && cookie.IsTransient()) + { + ((CCookie*)&*it3)->value = cookie.value; + return TRUE; + } + + cookies.erase(*it3); + } + + return cookies.emplace(cookie).second; +} + +BOOL CCookieMgr::DeleteCookieNoLock(const CCookie& cookie) +{ + BOOL isOK = FALSE; + + CCookieDomainMapI it = m_cookies.find(cookie.domain); + if(it != m_cookies.end()) + { + CCookiePathMapI it2 = it->second.find(cookie.path); + if(it2 != it->second.end()) + { + CCookieSetI it3 = it2->second.find(cookie); + if(it3 != it2->second.end()) + { + it2->second.erase(*it3); + isOK = TRUE; + } + } + } + + return isOK; +} + +CCookieSet* CCookieMgr::GetCookieSetNoLock(LPCSTR lpszDomain, LPCSTR lpszPath) +{ + CCookieSet* pCookieSet = nullptr; + CCookieDomainMapI it = m_cookies.find(lpszDomain); + + if(it != m_cookies.end()) + { + CCookiePathMapI it2 = it->second.find(lpszPath); + if(it2 != it->second.end()) + pCookieSet = &(it2->second); + } + + return pCookieSet; +} + +BOOL CCookieMgr::LoadDomainAndPath(LPSTR lpszBuff, CStringA& strDomain, CStringA& strPath) +{ + int i = 0; + char* lpszCtx = nullptr; + + for(; i < 2; i++) + { + char* lpszToken = strtok_r(lpszBuff, " \n\r", &lpszCtx); + + if(!lpszToken) + { + ::SetLastError(ERROR_BAD_FORMAT); + return FALSE; + } + + if(i == 0) + { + strDomain = lpszToken; + lpszBuff = nullptr; + } + else + strPath = lpszToken; + } + + if(!CCookie::AdjustDomain(strDomain)) + return FALSE; + if(!CCookie::AdjustPath(strPath)) + return FALSE; + + return TRUE; +} + +BOOL CCookieMgr::LoadCookie(LPSTR lpszBuff, LPCSTR lpszDomain, LPCSTR lpszPath, CCookie& cookie) +{ + cookie.domain = lpszDomain; + cookie.path = lpszPath; + + int i = 0; + char* lpszCtx = nullptr; + + for(; i < 6; i++) + { + char* lpszToken = strtok_r(lpszBuff, "\t;\n\r", &lpszCtx); + + if(!lpszToken) + { + ::SetLastError(ERROR_BAD_FORMAT); + return FALSE; + } + + if(i == 0) + { + cookie.name = lpszToken; + lpszBuff = nullptr; + } + else if(i == 1) + cookie.value = lpszToken; + else if(i == 2) + cookie.expires = atoll(lpszToken); + else if(i == 3) + cookie.httpOnly = (BOOL)atoi(lpszToken); + else if(i == 4) + cookie.secure = (BOOL)atoi(lpszToken); + else if(i == 5) + cookie.sameSite = (CCookie::EnSameSite)atoi(lpszToken); + } + + cookie.name.Trim(); + cookie.value.Trim(); + + if(cookie.name.IsEmpty() || cookie.expires <= 0 /*|| cookie.httpOnly < 0 || cookie.secure < 0*/ || cookie.sameSite < CCookie::SS_UNKNOWN || cookie.sameSite > CCookie::SS_NONE) + { + ::SetLastError(ERROR_INVALID_DATA); + return FALSE; + } + + return TRUE; +} + +BOOL CCookieMgr::AdjustDomainAndPath(LPCSTR& lpszDomain, LPCSTR& lpszPath, CStringA& strDomain, CStringA& strPath, BOOL bKeepNull) +{ + if(!bKeepNull || lpszDomain) + { + strDomain = lpszDomain; + + if(!CCookie::AdjustDomain(strDomain)) + return FALSE; + + lpszDomain = (LPCSTR)strDomain; + } + + if(!bKeepNull || lpszPath) + { + strPath = lpszPath; + + if(!CCookie::AdjustPath(strPath)) + return FALSE; + + lpszPath = (LPCSTR)strPath; + } + + return TRUE; +} + +CCookieMgr::CCookieMgr(BOOL bEnableThirdPartyCookie) +: m_bEnableThirdPartyCookie(bEnableThirdPartyCookie) +{ + +} + +#endif \ No newline at end of file diff --git a/HttpCookie.h b/HttpCookie.h new file mode 100644 index 0000000..681be36 --- /dev/null +++ b/HttpCookie.h @@ -0,0 +1,194 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "hpsocket/HPTypeDef.h" +#include "common/GeneralHelper.h" + +#ifdef _HTTP_SUPPORT + +#define COOKIE_DOMAIN "domain" +#define COOKIE_PATH "path" +#define COOKIE_EXPIRES "expires" +#define COOKIE_MAX_AGE "Max-Age" +#define COOKIE_HTTPONLY "HttpOnly" +#define COOKIE_SECURE "secure" +#define COOKIE_SAMESITE "SameSite" +#define COOKIE_SAMESITE_STRICT "Strict" +#define COOKIE_SAMESITE_LAX "Lax" +#define COOKIE_SAMESITE_NONE "None" +#define COOKIE_DEFAULT_PATH "/" +#define COOKIE_FIELD_SEP ";" +#define COOKIE_DOMAIN_SEP_CHAR '.' +#define COOKIE_PATH_SEP_CHAR '/' +#define COOKIE_KV_SEP_CHAR '=' + +class CCookie +{ +public: + enum EnSameSite + { + SS_UNKNOWN = 0, + SS_STRICT = 1, + SS_LAX = 2, + SS_NONE = 3 + }; + + CStringA name; + CStringA value; + CStringA domain; + CStringA path; + __time64_t expires; + BOOL httpOnly; + BOOL secure; + EnSameSite sameSite; + + CCookie(LPCSTR lpszName = nullptr, LPCSTR lpszValue = nullptr, LPCSTR lpszDomain = nullptr, LPCSTR lpszPath = nullptr, int iMaxAge = -1, BOOL bHttpOnly = FALSE, BOOL bSecure = FALSE, EnSameSite enSameSite = SS_UNKNOWN) + : name (lpszName) + , value (lpszValue) + , domain (lpszDomain) + , path (lpszPath) + , expires (MaxAgeToExpires(iMaxAge)) + , httpOnly (bHttpOnly) + , secure (bSecure) + , sameSite (enSameSite) + { + AdjustDomain(domain); + AdjustPath(path); + } + + static __time64_t CurrentUTCTime() {return _time64(nullptr);} + static __time64_t MaxAgeToExpires(int iMaxAge) {return iMaxAge > 0 ? _time64(nullptr) + iMaxAge : (iMaxAge < 0 ? -1 : 0);} + static int ExpiresToMaxAge(__time64_t tmExpires) {if(tmExpires < 0) return -1; __time64_t tmDiff = tmExpires - _time64(nullptr); return (tmDiff > 0 ? (int)tmDiff : 0);} + + static __time64_t GetUTCTime(tm& t, int iSecondOffsetTZ); + static void ParseFieldKV(const CStringA& strField, CStringA& strKey, CStringA& strVal, char chSep); + static BOOL ParseExpires(LPCSTR lpszExpires, __time64_t& tmExpires); + static BOOL AdjustDomain(CStringA& strDomain, LPCSTR lpszDefaultDomain = nullptr); + static BOOL AdjustPath(CStringA& strPath, LPCSTR lpszDefaultPath = nullptr); + static CStringA MakeExpiresStr(__time64_t tmExpires); + static BOOL MakeExpiresStr(char lpszBuff[], int& iBuffLen, __time64_t tmExpires); + static CCookie* FromString(const CStringA& strCookie, LPCSTR lpszDefaultDomain = nullptr, LPCSTR lpszDefaultPath = nullptr); + static CStringA ToString(LPCSTR lpszName, LPCSTR lpszValue, LPCSTR lpszDomain, LPCSTR lpszPath, int iMaxAge = -1, BOOL bHttpOnly = FALSE, BOOL bSecure = FALSE, EnSameSite enSameSite = SS_UNKNOWN); + static BOOL ToString(char lpszBuff[], int& iBuffLen, LPCSTR lpszName, LPCSTR lpszValue, LPCSTR lpszDomain, LPCSTR lpszPath, int iMaxAge = -1, BOOL bHttpOnly = FALSE, BOOL bSecure = FALSE, EnSameSite enSameSite = SS_UNKNOWN); + + CStringA ToString(); + BOOL Match(LPCSTR lpszDomain, LPCSTR lpszPath, BOOL bHttp, BOOL bSecure); + BOOL IsSameDomain(LPCSTR lpszDomain); + + BOOL IsTransient() const {return expires < 0;} + BOOL IsExpired() const {return !IsTransient() && expires <= _time64(nullptr);} + BOOL IsValid() const {return !name.IsEmpty() && !domain.IsEmpty() && !path.IsEmpty();} +}; + +struct ccookie_hash_func +{ + struct hash + { + size_t operator() (const CCookie& c) const + { + return hash_value((LPCSTR)(c.name)); + } + }; + + struct equal_to + { + bool operator () (const CCookie& cA, const CCookie& cB) const + { + return (cA.name == cB.name); + } + }; + +}; + +// ------------------------------------------------------------------------------------------------------------- // + +typedef unordered_set CCookiePtrSet; +typedef CCookiePtrSet::const_iterator CCookiePtrSetCI; +typedef CCookiePtrSet::iterator CCookiePtrSetI; + +typedef unordered_set CCookieSet; +typedef CCookieSet::const_iterator CCookieSetCI; +typedef CCookieSet::iterator CCookieSetI; + +typedef unordered_map CCookiePathMap; +typedef CCookiePathMap::const_iterator CCookiePathMapCI; +typedef CCookiePathMap::iterator CCookiePathMapI; + +typedef unordered_map CCookieDomainMap; +typedef CCookieDomainMap::const_iterator CCookieDomainMapCI; +typedef CCookieDomainMap::iterator CCookieDomainMapI; + +class CCookieMgr +{ +public: + BOOL LoadFromFile(LPCSTR lpszFile, BOOL bKeepExists = TRUE); + BOOL SaveToFile(LPCSTR lpszFile, BOOL bKeepExists = TRUE); + + BOOL ClearCookies(LPCSTR lpszDomain = nullptr, LPCSTR lpszPath = nullptr); + BOOL RemoveExpiredCookies(LPCSTR lpszDomain = nullptr, LPCSTR lpszPath = nullptr); + + BOOL GetCookies(CCookieSet& cookies, LPCSTR lpszDomain, LPCSTR lpszPath, BOOL bHttp, BOOL bSecure); + BOOL SetCookie(LPCSTR lpszName, LPCSTR lpszValue, LPCSTR lpszDomain, LPCSTR lpszPath, int iMaxAge = -1, BOOL bHttpOnly = FALSE, BOOL bSecure = FALSE, CCookie::EnSameSite enSameSite = CCookie::SS_UNKNOWN, BOOL bOnlyUpdateValueIfExists = TRUE); + BOOL SetCookie(const CStringA& strCookie, BOOL bOnlyUpdateValueIfExists = TRUE); + BOOL SetCookie(const CCookie& cookie, BOOL bOnlyUpdateValueIfExists = TRUE); + BOOL DeleteCookie(LPCSTR lpszDomain, LPCSTR lpszPath, LPCSTR lpszName); + BOOL DeleteCookie(const CCookie& cookie); + +private: + void ClearDomainCookiesNoLock(LPCSTR lpszDomain = nullptr, LPCSTR lpszPath = nullptr); + void ClearPathCookiesNoLock(CCookiePathMap& paths, LPCSTR lpszPath = nullptr); + void RemoveExpiredCookiesNoLock(LPCSTR lpszDomain = nullptr, LPCSTR lpszPath = nullptr); + void RemoveDomainExpiredCookiesNoLock(CCookiePathMap& paths, LPCSTR lpszPath = nullptr); + void RemovePathExpiredCookiesNoLock(CCookieSet& cookies); + const CCookie* GetCookieNoLock(LPCSTR lpszDomain, LPCSTR lpszPath, LPCSTR lpszName); + const CCookie* GetCookieNoLock(const CCookie& cookie); + void MatchCookiesNoLock(CCookieSet& cookies, LPCSTR lpszDomain, LPCSTR lpszPath, BOOL bHttp = TRUE, BOOL bSecure = FALSE); + BOOL SetCookieNoLock(const CCookie& cookie, BOOL bOnlyUpdateValueIfExists = TRUE); + BOOL DeleteCookieNoLock(const CCookie& cookie); + CCookieSet* GetCookieSetNoLock(LPCSTR lpszDomain, LPCSTR lpszPath); + +private: + static BOOL LoadDomainAndPath(LPSTR lpszBuff, CStringA& strDomain, CStringA& strPath); + static BOOL LoadCookie(LPSTR lpszBuff, LPCSTR lpszDomain, LPCSTR lpszPath, CCookie& cookie); + static BOOL AdjustDomainAndPath(LPCSTR& lpszDomain, LPCSTR& lpszPath, CStringA& strDomain, CStringA& strPath, BOOL bKeepNull = TRUE); + +public: + CCookieMgr(BOOL bEnableThirdPartyCookie = TRUE); + + void SetEnableThirdPartyCookie (BOOL bEnableThirdPartyCookie = TRUE) {m_bEnableThirdPartyCookie = bEnableThirdPartyCookie;} + BOOL IsEnableThirdPartyCookie () {return m_bEnableThirdPartyCookie;} + +private: + CSimpleRWLock m_cs; + CCookieDomainMap m_cookies; + BOOL m_bEnableThirdPartyCookie; +}; + +extern CCookieMgr g_CookieMgr; + +#endif \ No newline at end of file diff --git a/HttpHelper.cpp b/HttpHelper.cpp new file mode 100644 index 0000000..9787494 --- /dev/null +++ b/HttpHelper.cpp @@ -0,0 +1,469 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "HttpHelper.h" + +#ifdef _HTTP_SUPPORT + +/////////////////////////////////////////////////////////////////////////////////////////////////////// + +CStringA& GetHttpVersionStr(EnHttpVersion enVersion, CStringA& strResult) +{ + strResult.Format("HTTP/%d.%d", LOBYTE(enVersion), HIBYTE(enVersion)); + return strResult; +} + +CStringA& AdjustRequestPath(BOOL bConnect, LPCSTR lpszPath, CStringA& strPath) +{ + strPath = lpszPath; + + if(strPath.IsEmpty() || (!bConnect && strPath.GetAt(0) != HTTP_PATH_SEPARATOR_CHAR)) + strPath.Insert(0, HTTP_PATH_SEPARATOR_CHAR); + + return strPath; +} + +LPCSTR GetHttpDefaultStatusCodeDesc(EnHttpStatusCode enCode) +{ + switch(enCode) + { + case HSC_CONTINUE : return "Continue"; + case HSC_SWITCHING_PROTOCOLS : return "Switching Protocols"; + case HSC_PROCESSING : return "Processing"; + case HSC_EARLY_HINTS : return "Early Hints"; + case HSC_RESPONSE_IS_STALE : return "Response Is Stale"; + case HSC_REVALIDATION_FAILED : return "Revalidation Failed"; + case HSC_DISCONNECTED_OPERATION : return "Disconnected Operation"; + case HSC_HEURISTIC_EXPIRATION : return "Heuristic Expiration"; + case HSC_MISCELLANEOUS_WARNING : return "Miscellaneous Warning"; + + case HSC_OK : return "OK"; + case HSC_CREATED : return "Created"; + case HSC_ACCEPTED : return "Accepted"; + case HSC_NON_AUTHORITATIVE_INFORMATION : return "Non-Authoritative Information"; + case HSC_NO_CONTENT : return "No Content"; + case HSC_RESET_CONTENT : return "Reset Content"; + case HSC_PARTIAL_CONTENT : return "Partial Content"; + case HSC_MULTI_STATUS : return "Multi-Status"; + case HSC_ALREADY_REPORTED : return "Already Reported"; + case HSC_TRANSFORMATION_APPLIED : return "Transformation Applied"; + case HSC_IM_USED : return "IM Used"; + case HSC_MISCELLANEOUS_PERSISTENT_WARNING : return "Miscellaneous Persistent Warning"; + + case HSC_MULTIPLE_CHOICES : return "Multiple Choices"; + case HSC_MOVED_PERMANENTLY : return "Moved Permanently"; + case HSC_MOVED_TEMPORARILY : return "Move temporarily"; + case HSC_SEE_OTHER : return "See Other"; + case HSC_NOT_MODIFIED : return "Not Modified"; + case HSC_USE_PROXY : return "Use Proxy"; + case HSC_SWITCH_PROXY : return "Switch Proxy"; + case HSC_TEMPORARY_REDIRECT : return "Temporary Redirect"; + case HSC_PERMANENT_REDIRECT : return "Permanent Redirect"; + + case HSC_BAD_REQUEST : return "Bad Request"; + case HSC_UNAUTHORIZED : return "Unauthorized"; + case HSC_PAYMENT_REQUIRED : return "Payment Required"; + case HSC_FORBIDDEN : return "Forbidden"; + case HSC_NOT_FOUND : return "Not Found"; + case HSC_METHOD_NOT_ALLOWED : return "Method Not Allowed"; + case HSC_NOT_ACCEPTABLE : return "Not Acceptable"; + case HSC_PROXY_AUTHENTICATION_REQUIRED : return "Proxy Authentication Required"; + case HSC_REQUEST_TIMEOUT : return "Request Timeout"; + case HSC_CONFLICT : return "Conflict"; + case HSC_GONE : return "Gone"; + case HSC_LENGTH_REQUIRED : return "Length Required"; + case HSC_PRECONDITION_FAILED : return "Precondition Failed"; + case HSC_REQUEST_ENTITY_TOO_LARGE : return "Request Entity Too Large"; + case HSC_REQUEST_URI_TOO_LONG : return "Request-URI Too Long"; + case HSC_UNSUPPORTED_MEDIA_TYPE : return "Unsupported Media Type"; + case HSC_REQUESTED_RANGE_NOT_SATISFIABLE : return "Requested Range Not Satisfiable"; + case HSC_EXPECTATION_FAILED : return "Expectation Failed"; + case HSC_IM_A_TEAPOT : return "I'm a teapot"; + case HSC_PAGE_EXPIRED : return "Page Expired"; + case HSC_ENHANCE_YOUR_CALM : return "Enhance Your Calm"; + case HSC_MISDIRECTED_REQUEST : return "Misdirected Request"; + case HSC_UNPROCESSABLE_ENTITY : return "Unprocessable Entity"; + case HSC_LOCKED : return "Locked"; + case HSC_FAILED_DEPENDENCY : return "Failed Dependency"; + case HSC_UNORDERED_COLLECTION : return "Unordered Collection"; + case HSC_UPGRADE_REQUIRED : return "Upgrade Required"; + case HSC_PRECONDITION_REQUIRED : return "Precondition Required"; + case HSC_TOO_MANY_REQUESTS : return "Too Many Requests"; + case HSC_REQUEST_HEADER_FIELDS_TOO_LARGE_UNOFFICIAL : return "Request Header Fields Too Large Unofficial"; + case HSC_REQUEST_HEADER_FIELDS_TOO_LARGE : return "Request Header Fields Too Large"; + case HSC_LOGIN_TIMEOUT : return "Login Timeout"; + case HSC_NO_RESPONSE : return "No Response"; + case HSC_RETRY_WITH : return "Retry With"; + case HSC_BLOCKED_BY_PARENTAL_CONTROL : return "Blocked By Parental Control"; + case HSC_UNAVAILABLE_FOR_LEGAL_REASONS : return "Unavailable For Legal Reasons"; + case HSC_CLIENT_CLOSED_LOAD_BALANCED_REQUEST : return "Client Closed Load Balance Request"; + case HSC_INVALID_X_FORWARDED_FOR : return "Invalid X-Forwarded-For"; + case HSC_REQUEST_HEADER_TOO_LARGE : return "Request Header Too Large"; + case HSC_SSL_CERTIFICATE_ERROR : return "SSL Certificate Error"; + case HSC_SSL_CERTIFICATE_REQUIRED : return "SSL Certificate Required"; + case HSC_HTTP_REQUEST_SENT_TO_HTTPS_PORT : return "HTTP Request Sent To HTTPS Port"; + case HSC_INVALID_TOKEN : return "Invalid Token"; + case HSC_CLIENT_CLOSED_REQUEST : return "Client Closed Request"; + + case HSC_INTERNAL_SERVER_ERROR : return "Internal Server Error"; + case HSC_NOT_IMPLEMENTED : return "Not Implemented"; + case HSC_BAD_GATEWAY : return "Bad Gateway"; + case HSC_SERVICE_UNAVAILABLE : return "Service Unavailable"; + case HSC_GATEWAY_TIMEOUT : return "Gateway Timeout"; + case HSC_HTTP_VERSION_NOT_SUPPORTED : return "HTTP Version Not Supported"; + case HSC_VARIANT_ALSO_NEGOTIATES : return "Variant Also Negotiates"; + case HSC_INSUFFICIENT_STORAGE : return "Insufficient Storage"; + case HSC_LOOP_DETECTED : return "Loop Detected"; + case HSC_BANDWIDTH_LIMIT_EXCEEDED : return "Bandwidth Limit Exceeded"; + case HSC_NOT_EXTENDED : return "Not Extended"; + case HSC_NETWORK_AUTHENTICATION_REQUIRED : return "Network Authentication Required"; + case HSC_WEB_SERVER_UNKNOWN_ERROR : return "Web Server Unknown Error"; + case HSC_WEB_SERVER_IS_DOWN : return "Web Server Is Down"; + case HSC_CONNECTION_TIMEOUT : return "Connection Timeout"; + case HSC_ORIGIN_IS_UNREACHABLE : return "Origin Is Unreachable"; + case HSC_TIMEOUT_OCCURED : return "Timeout Occured"; + case HSC_SSL_HANDSHAKE_FAILED : return "SSL Handshake Failed"; + case HSC_INVALID_SSL_CERTIFICATE : return "Invalid SSL Certificate"; + case HSC_RAILGUN_ERROR : return "Railgun Error"; + case HSC_SITE_IS_OVERLOADED : return "Site Is Overloaded"; + case HSC_SITE_IS_FROZEN : return "Site Is Frozen"; + case HSC_IDENTITY_PROVIDER_AUTHENTICATION_ERROR : return "Identity Provider Authentication Error"; + case HSC_NETWORK_READ_TIMEOUT : return "Network Read Timeout"; + case HSC_NETWORK_CONNECT_TIMEOUT : return "Network Connect Timeout"; + + case HSC_UNPARSEABLE_RESPONSE_HEADERS : return "Unparseable Response Headers"; + + default : return "***"; + } +} + +static inline CStringA& AppendHeader(LPCSTR lpszName, LPCSTR lpszValue, CStringA& strValue) +{ + strValue.Append(lpszName); + strValue.Append(HTTP_HEADER_SEPARATOR); + strValue.Append(lpszValue); + strValue.Append(HTTP_CRLF); + + return strValue; +} + +void MakeRequestLine(LPCSTR lpszMethod, LPCSTR lpszPath, EnHttpVersion enVersion, CStringA& strValue) +{ + ASSERT(lpszMethod); + + strValue.Format("%s %s HTTP/%d.%d%s", (LPCSTR)(CStringA(lpszMethod).MakeUpper()), lpszPath, LOBYTE(enVersion), HIBYTE(enVersion), HTTP_CRLF); +} + +void MakeStatusLine(EnHttpVersion enVersion, USHORT usStatusCode, LPCSTR lpszDesc, CStringA& strValue) +{ + if(!lpszDesc) lpszDesc = ::GetHttpDefaultStatusCodeDesc((EnHttpStatusCode)usStatusCode); + strValue.Format("HTTP/%d.%d %d %s%s", LOBYTE(enVersion), HIBYTE(enVersion), usStatusCode, lpszDesc, HTTP_CRLF); +} + +void MakeHeaderLines(const THeader lpHeaders[], int iHeaderCount, const TCookieMap* pCookies, int iBodyLength, BOOL bRequest, int iConnFlag, LPCSTR lpszDefaultHost, USHORT usPort, CStringA& strValue) +{ + unordered_set szHeaderNames; + + if(iHeaderCount > 0) + { + ASSERT(lpHeaders); + + for(int i = 0; i < iHeaderCount; i++) + { + const THeader& header = lpHeaders[i]; + + ASSERT(!::IsStrEmptyA(header.name)); + + if(!::IsStrEmptyA(header.name)) + { + szHeaderNames.emplace(header.name); + AppendHeader(header.name, header.value, strValue); + } + } + } + + if( (!bRequest || iBodyLength > 0) && + (szHeaderNames.empty() || + (szHeaderNames.find(HTTP_HEADER_CONTENT_LENGTH) == szHeaderNames.end() && + szHeaderNames.find(HTTP_HEADER_TRANSFER_ENCODING) == szHeaderNames.end()))) + { + char szBodyLength[16]; + itoa(iBodyLength, szBodyLength, 10); + + AppendHeader(HTTP_HEADER_CONTENT_LENGTH, szBodyLength, strValue); + } + + if( (iConnFlag == 0 || iConnFlag == 1) && + (szHeaderNames.empty() || + szHeaderNames.find(HTTP_HEADER_CONNECTION) == szHeaderNames.end() )) + { + LPCSTR lpszValue = iConnFlag == 0 ? HTTP_CONNECTION_CLOSE_VALUE : HTTP_CONNECTION_KEEPALIVE_VALUE; + AppendHeader(HTTP_HEADER_CONNECTION, lpszValue, strValue); + } + + if( bRequest && !::IsStrEmptyA(lpszDefaultHost) && + (szHeaderNames.empty() || + (szHeaderNames.find(HTTP_HEADER_HOST) == szHeaderNames.end()) )) + { + CStringA strHost(lpszDefaultHost); + if(usPort != 0) strHost.AppendFormat(":%u", usPort); + + AppendHeader(HTTP_HEADER_HOST, strHost, strValue); + } + + szHeaderNames.clear(); + + if(pCookies != nullptr) + { + DWORD dwSize = (DWORD)pCookies->size(); + + if(dwSize > 0) + { + strValue.Append(HTTP_HEADER_COOKIE); + strValue.Append(HTTP_HEADER_SEPARATOR); + + DWORD dwIndex = 0; + + for(TCookieMapCI it = pCookies->begin(), end = pCookies->end(); it != end; ++it, ++dwIndex) + { + strValue.Append(it->first); + strValue.AppendChar(COOKIE_KV_SEP_CHAR); + strValue.Append(it->second); + + if(dwIndex < dwSize - 1) + strValue.Append(HTTP_COOKIE_SEPARATOR); + } + + strValue.Append(HTTP_CRLF); + } + } + + strValue.Append(HTTP_CRLF); +} + +void MakeHttpPacket(const CStringA& strHeader, const BYTE* pBody, int iLength, WSABUF szBuffer[2]) +{ + ASSERT(pBody != nullptr || iLength == 0); + + szBuffer[0].buf = (LPBYTE)(LPCSTR)strHeader; + szBuffer[0].len = strHeader.GetLength(); + szBuffer[1].buf = (LPBYTE)pBody; + szBuffer[1].len = iLength; +} + +int MakeChunkPackage(const BYTE* pData, int iLength, LPCSTR lpszExtensions, char szLen[12], WSABUF bufs[5]) +{ + ASSERT(iLength == 0 || pData != nullptr); + + int i = 0; + + if(::IsStrEmptyA(lpszExtensions)) + { + sprintf(szLen, "%x" HTTP_CRLF, iLength); + + bufs[i].buf = (LPBYTE)szLen; + bufs[i].len = (int)strlen(szLen); + ++i; + } + else + { + LPCSTR lpszSep = lpszExtensions[0] == ';' ? " " : " ;"; + + sprintf(szLen, "%x%s", iLength, lpszSep); + + bufs[i].buf = (LPBYTE)szLen; + bufs[i].len = (int)strlen(szLen); + ++i; + + bufs[i].buf = (LPBYTE)lpszExtensions; + bufs[i].len = (int)strlen(lpszExtensions); + ++i; + + bufs[i].buf = (LPBYTE)HTTP_CRLF; + bufs[i].len = 2; + ++i; + } + + if(iLength > 0) + { + bufs[i].buf = (LPBYTE)pData; + bufs[i].len = iLength; + ++i; + } + + bufs[i].buf = (LPBYTE)HTTP_CRLF; + bufs[i].len = 2; + ++i; + + return i; +} + +BOOL MakeWSPacket(BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], BYTE* pData, int iLength, ULONGLONG ullBodyLen, BYTE szHeader[HTTP_MAX_WS_HEADER_LEN], WSABUF szBuffer[2]) +{ + ULONGLONG ullLength = (ULONGLONG)iLength; + + ASSERT(pData != nullptr || iLength == 0); + ASSERT(ullBodyLen == 0 || ullBodyLen >= ullLength); + + if(ullBodyLen == 0) + ullBodyLen = ullLength; + else if(ullBodyLen < ullLength) + { + ::SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + TBaseWSHeader bh(szHeader, TRUE); + + int iHeaderLen = HTTP_MIN_WS_HEADER_LEN; + + bh.set_fin(bFinal); + bh.set_rsv(iReserved); + bh.set_code(iOperationCode); + bh.set_mask(lpszMask ? TRUE : FALSE); + + if(ullBodyLen < 126) + bh.set_len((BYTE)ullBodyLen); + else if(ullBodyLen <= 0xFFFF) + { + bh.set_len(126); + bh.set_extlen((USHORT)ullBodyLen); + + iHeaderLen += 2; + } + else + { + bh.set_len(127); + *(ULONGLONG*)(szHeader + HTTP_MIN_WS_HEADER_LEN) = ::HToN64(ullBodyLen); + + iHeaderLen += 8; + } + + if(lpszMask) + { + memcpy(szHeader + iHeaderLen, lpszMask, 4); + + for(int i = 0; i < iLength; i++) + pData[i] = pData[i] ^ lpszMask[i & 0x03]; + + iHeaderLen += 4; + } + + szBuffer[0].buf = szHeader; + szBuffer[0].len = iHeaderLen; + szBuffer[1].buf = (LPBYTE)pData; + szBuffer[1].len = iLength; + + return TRUE; +} + +BOOL ParseUrl(const CStringA& strUrl, BOOL& bHttps, CStringA& strHost, USHORT& usPort, CStringA& strPath) +{ + int iSchemaLength = (int)strlen(HTTP_SCHEMA); + + if(strnicmp(strUrl, HTTP_SCHEMA, iSchemaLength) == 0) + bHttps = FALSE; + else + { + iSchemaLength = (int)strlen(HTTPS_SCHEMA); + + if(strnicmp(strUrl, HTTPS_SCHEMA, iSchemaLength) == 0) + bHttps = TRUE; + else + return FALSE; + } + + CStringA strFullHost; + int i = strUrl.Find(HTTP_PATH_SEPARATOR_CHAR, iSchemaLength); + + if(i > 0) + { + strFullHost = strUrl.Mid(iSchemaLength, i - iSchemaLength); + strPath = strUrl.Mid(i); + } + else + { + strFullHost = strUrl.Mid(iSchemaLength); + strPath = HTTP_PATH_SEPARATOR; + } + + if(strFullHost.IsEmpty()) + return FALSE; + + CStringA strPort; + + char c = strFullHost.GetAt(0); + + if(!::isalnum(c)) + { + if(c != IPV6_ADDR_BEGIN_CHAR) + return FALSE; + else + { + i = strFullHost.ReverseFind(IPV6_ADDR_END_CHAR); + + if(i < 0) + return FALSE; + else + { + if(strFullHost.GetLength() > i + 1) + { + if(strFullHost.GetAt(i + 1) != PORT_SEPARATOR_CHAR) + return FALSE; + + strPort = strFullHost.Mid(i + 2); + + if(strPort.IsEmpty()) + return FALSE; + } + + strHost = strFullHost.Mid(1, i - 1); + } + } + } + else + { + i = strFullHost.Find(PORT_SEPARATOR_CHAR); + + if(i < 0) + strHost = strFullHost; + else + { + strPort = strFullHost.Mid(i + 1); + + if(strPort.IsEmpty()) + return FALSE; + + strHost = strFullHost.Mid(0, i); + } + } + + if(strPort.IsEmpty()) + usPort = bHttps ? HTTPS_DEFAULT_PORT : HTTP_DEFAULT_PORT; + else + usPort = (USHORT)::atoi(strPort); + + return TRUE; +} + +#endif \ No newline at end of file diff --git a/HttpHelper.h b/HttpHelper.h new file mode 100644 index 0000000..c4d375f --- /dev/null +++ b/HttpHelper.h @@ -0,0 +1,1385 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "SocketHelper.h" +#include "HttpCookie.h" + +#ifdef _HTTP_SUPPORT + +#include "./common/http/llhttp.h" +#include "./common/http/llhttp_url.h" + +typedef llhttp_t http_parser; +typedef llhttp_settings_t http_parser_settings; + +#define LLHTTP_MAX_ERROR_NUM 23 +#define LLHTTP_MAX_METHOD_NUM 45 + + +/************************************************************************ +名称:HTTP 全局常量 +描述:声明 HTTP 组件的公共全局常量 +************************************************************************/ + +#define HTTP_DEFAULT_PORT 80 +#define HTTPS_DEFAULT_PORT 443 + +#define HTTP_SCHEMA "http://" +#define HTTPS_SCHEMA "https://" + +#define HTTP_CRLF "\r\n" +#define HTTP_PATH_SEPARATOR_CHAR '/' +#define HTTP_PATH_SEPARATOR "/" +#define HTTP_HEADER_SEPARATOR ": " +#define HTTP_COOKIE_SEPARATOR "; " +#define HTTP_1_0_STR "HTTP/1.0" +#define HTTP_1_1_STR "HTTP/1.1" + +#define HTTP_HEADER_HOST "Host" +#define HTTP_HEADER_COOKIE "Cookie" +#define HTTP_HEADER_SET_COOKIE "Set-Cookie" +#define HTTP_HEADER_CONTENT_TYPE "Content-Type" +#define HTTP_HEADER_CONTENT_LENGTH "Content-Length" +#define HTTP_HEADER_CONTENT_ENCODING "Content-Encoding" +#define HTTP_HEADER_TRANSFER_ENCODING "Transfer-Encoding" +#define HTTP_HEADER_CONNECTION "Connection" +#define HTTP_HEADER_UPGRADE "Upgrade" +#define HTTP_HEADER_VALUE_WEB_SOCKET "WebSocket" + +#define HTTP_CONNECTION_CLOSE_VALUE "close" +#define HTTP_CONNECTION_KEEPALIVE_VALUE "keep-alive" + +#define HTTP_METHOD_POST "POST" +#define HTTP_METHOD_PUT "PUT" +#define HTTP_METHOD_PATCH "PATCH" +#define HTTP_METHOD_GET "GET" +#define HTTP_METHOD_DELETE "DELETE" +#define HTTP_METHOD_HEAD "HEAD" +#define HTTP_METHOD_TRACE "TRACE" +#define HTTP_METHOD_OPTIONS "OPTIONS" +#define HTTP_METHOD_CONNECT "CONNECT" + +#define HTTP_MIN_WS_HEADER_LEN 2 +#define HTTP_MAX_WS_HEADER_LEN 14 + +#define MIN_HTTP_RELEASE_CHECK_INTERVAL ((DWORD)1000) +#define MIN_HTTP_RELEASE_DELAY 100 +#define MAX_HTTP_RELEASE_DELAY (60 * 1000) +#define DEFAULT_HTTP_RELEASE_DELAY (5 * 1000) +#define DEFAULT_HTTP_VERSION HV_1_1 + +#define DEFAULT_HTTP_SYNC_CONNECT_TIMEOUT 5000 +#define DEFAULT_HTTP_SYNC_REQUEST_TIMEOUT 10000 + +// ------------------------------------------------------------------------------------------------------------- // + +enum EnHttpSyncRequestProgress +{ + HSRP_DONE, + HSRP_WAITING, + HSRP_ERROR, + HSRP_CLOSE +}; + +struct TDyingConnection +{ + CONNID connID; + DWORD killTime; + + TDyingConnection(CONNID id, DWORD kt = 0) + : connID (id) + , killTime (kt == 0 ? ::TimeGetTime() : kt) + { + + } + + static TDyingConnection* Construct(CONNID id, DWORD kt = 0) {return new TDyingConnection(id, kt);} + static void Destruct(TDyingConnection* pObj) {if(pObj) delete pObj;} + +}; + +typedef unordered_multimap THeaderMap; +typedef THeaderMap::const_iterator THeaderMapCI; +typedef THeaderMap::iterator THeaderMapI; + +typedef unordered_map TCookieMap; +typedef TCookieMap::const_iterator TCookieMapCI; +typedef TCookieMap::iterator TCookieMapI; + +// ------------------------------------------------------------------------------------------------------------- // + +struct TBaseWSHeader +{ +public: + BOOL fin() + { + return (data >> 7) & 0x1; + } + + void set_fin(BOOL v) + { + data |= ((v ? 1 : 0) << 7); + } + + BYTE rsv() + { + return (data >> 4) & 0x7; + } + + void set_rsv(BYTE v) + { + data |= ((v & 0x7) << 4); + } + + BYTE code() + { + return data & 0xF; + } + + void set_code(BYTE v) + { + data |= (v & 0xF); + } + + BOOL mask() + { + return (data >> 15) & 0x1; + } + + void set_mask(BOOL v) + { + data |= ((v ? 1 : 0) << 15); + } + + BYTE len() + { + return (data >> 8) & 0x7F; + } + + void set_len(BYTE v) + { + data |= ((v & 0x7F) << 8); + } + + USHORT extlen() + { + return ntohs((USHORT)(data >> 16)); + } + + void set_extlen(USHORT v) + { + data |= (htons(v) << 16); + } + + TBaseWSHeader(const BYTE* p, BOOL bZero = FALSE) + : data(*(UINT*)p) + { + if(bZero) data = 0; + } + +private: + UINT& data; +}; + +template struct TWSContext +{ +public: + EnHandleResult Parse(const BYTE* pData, int iLength) + { + ASSERT(pData != nullptr && iLength > 0); + + EnHandleResult hr = HR_OK; + BYTE* pTemp = (BYTE*)pData; + int iRemain = iLength; + int iMin = 0; + + while(iRemain > 0) + { + if(m_bHeader) + { + iMin = MIN(m_iHeaderRemain, iRemain); + memcpy(m_szHeader + m_iHeaderLen - m_iHeaderRemain, pTemp, iMin); + + m_iHeaderRemain -= iMin; + + if(m_iHeaderRemain == 0) + { + TBaseWSHeader bh(m_szHeader); + + int iLen = bh.len(); + int iExtLen = iLen < 126 ? 0 : (iLen == 126 ? 2 : 8); + int iMaskLen = bh.mask() ? 4 : 0; + int iRealHeaderLen = HTTP_MIN_WS_HEADER_LEN + iExtLen + iMaskLen; + + if(m_iHeaderLen < iRealHeaderLen) + { + m_iHeaderRemain = iRealHeaderLen - m_iHeaderLen; + m_iHeaderLen = iRealHeaderLen; + } + else + { + if((m_pHttpObj->IsRequest() && iMaskLen == 0) || (!m_pHttpObj->IsRequest() && iMaskLen > 0)) + { + ::SetLastError(ERROR_INVALID_DATA); + hr = HR_ERROR; + + break; + } + + m_ullBodyLen = iExtLen == 0 ? iLen : (iExtLen == 2 ? bh.extlen() : NToH64(*(ULONGLONG*)(m_szHeader + HTTP_MIN_WS_HEADER_LEN))); + m_ullBodyRemain = m_ullBodyLen; + m_lpszMask = iMaskLen > 0 ? m_szHeader + HTTP_MIN_WS_HEADER_LEN + iExtLen : nullptr; + + hr = m_pHttpObj->on_ws_message_header(bh.fin(), bh.rsv(), bh.code(), m_lpszMask, m_ullBodyLen); + + if(hr == HR_ERROR) + break; + + if(m_ullBodyLen > 0) + m_bHeader = FALSE; + else + { + hr = CompleteMessage(); + + if(hr == HR_ERROR) + break; + } + } + } + } + else + { + iMin = (int)MIN(m_ullBodyRemain, (ULONGLONG)iRemain); + + if(m_lpszMask) + { + int iFactor = (m_ullBodyLen - m_ullBodyRemain) & 0x03; + + for(int i = 0; i < iMin; i++) + pTemp[i] = pTemp[i] ^ m_lpszMask[(i + iFactor) & 0x03]; + } + + m_ullBodyRemain -= iMin; + + hr = m_pHttpObj->on_ws_message_body(pTemp, iMin); + + if(hr == HR_ERROR) + break; + + if(m_ullBodyRemain == 0) + { + hr = CompleteMessage(); + + if(hr == HR_ERROR) + break; + } + } + + pTemp += iMin; + iRemain -= iMin; + } + + return hr; + } + + BOOL GetMessageState(BOOL* lpbFinal, BYTE* lpiReserved, BYTE* lpiOperationCode, LPCBYTE* lpszMask, ULONGLONG* lpullBodyLen, ULONGLONG* lpullBodyRemain) + { + TBaseWSHeader bh(m_szHeader); + + if(lpbFinal) *lpbFinal = bh.fin(); + if(lpiReserved) *lpiReserved = bh.rsv(); + if(lpiOperationCode) *lpiOperationCode = bh.code(); + if(lpszMask) *lpszMask = m_lpszMask; + if(lpullBodyLen) *lpullBodyLen = m_ullBodyLen; + if(lpullBodyRemain) *lpullBodyRemain = m_ullBodyRemain; + + return TRUE; + } + + BOOL CopyData(const TWSContext& src) + { + if(&src == this) + return FALSE; + + memcpy(m_szHeader, src.m_szHeader, HTTP_MAX_WS_HEADER_LEN); + + if(src.m_lpszMask) + m_lpszMask = m_szHeader + (src.m_lpszMask - src.m_szHeader); + else + m_lpszMask = nullptr; + + m_ullBodyLen = src.m_ullBodyLen; + m_ullBodyRemain = src.m_ullBodyRemain; + + return TRUE; + } + +public: + TWSContext(T* pHttpObj) : m_pHttpObj(pHttpObj) + { + Reset(); + } + +private: + EnHandleResult CompleteMessage() + { + EnHandleResult hr = m_pHttpObj->on_ws_message_complete(); + + Reset(); + + return hr; + } + + void Reset() + { + m_bHeader = TRUE; + m_lpszMask = nullptr; + m_iHeaderLen = HTTP_MIN_WS_HEADER_LEN; + m_iHeaderRemain = HTTP_MIN_WS_HEADER_LEN; + m_ullBodyLen = 0; + m_ullBodyRemain = 0; + } + +private: + T* m_pHttpObj; + + BYTE m_szHeader[HTTP_MAX_WS_HEADER_LEN]; + const BYTE* m_lpszMask; + + BOOL m_bHeader; + int m_iHeaderLen; + int m_iHeaderRemain; + ULONGLONG m_ullBodyLen; + ULONGLONG m_ullBodyRemain; +}; + +// ------------------------------------------------------------------------------------------------------------- // + +/* Http 上下文结构 */ +template struct THttpObjT +{ +public: + EnHandleResult Execute(const BYTE* pData, int iLength) + { + ASSERT(pData != nullptr && iLength > 0); + + if(m_parser.upgrade) + { + if(m_enUpgrade == HUT_WEB_SOCKET) + { + if(m_pwsContext != nullptr) + return m_pwsContext->Parse(pData, iLength); + } + else + { + return m_pContext->DoFireSuperReceive(m_pSocket, pData, iLength); + } + } + + EnHandleResult hr = HR_OK; + llhttp_errno rs = ::llhttp_execute(&m_parser, (LPCSTR)pData, iLength); + + if(rs == HPE_OK) + ASSERT(m_parser.error_pos == nullptr); + else if(rs == HPE_PAUSED_UPGRADE) + { + int iPased = (m_parser.error_pos == nullptr) ? iLength : (int)((const BYTE*)(m_parser.error_pos) - pData); + hr = Upgrade(pData, iLength, iPased); + } + else + { + m_pContext->FireParseError(m_pSocket, m_parser.error, ::llhttp_get_error_reason(&m_parser)); + hr = HR_ERROR; + } + + return hr; + } + + void CheckBodyIdentityEof() + { + if(!m_parser.upgrade) + ::llhttp_finish(&m_parser); + } + + static int on_message_begin(http_parser* p) + { + THttpObjT* pSelf = Self(p); + + pSelf->ResetHeaderState(FALSE, FALSE); + + return pSelf->m_pContext->FireMessageBegin(pSelf->m_pSocket); + } + + static int on_url(http_parser* p, const char* at, size_t length) + { + Self(p)->AppendBuffer(at, length); + + return HPR_OK; + } + + static int on_url_complete(llhttp_t* p) + { + THttpObjT* pSelf = Self(p); + EnHttpParseResult hpr = pSelf->ParseUrl(); + + if(hpr == HPR_OK) + hpr = pSelf->m_pContext->FireRequestLine(pSelf->m_pSocket, ::llhttp_method_name((llhttp_method_t)p->method), pSelf->GetBuffer()); + + pSelf->ResetBuffer(); + + return hpr; + } + + static int on_status(http_parser* p, const char* at, size_t length) + { + Self(p)->AppendBuffer(at, length); + + return HPR_OK; + } + + static int on_status_complete(llhttp_t* p) + { + THttpObjT* pSelf = Self(p); + EnHttpParseResult hpr = pSelf->m_pContext->FireStatusLine(pSelf->m_pSocket, p->status_code, pSelf->GetBuffer()); + + pSelf->ResetBuffer(); + + return hpr; + } + + static int on_header_field(http_parser* p, const char* at, size_t length) + { + Self(p)->AppendBuffer(at, length); + + return HPR_OK; + } + + static int on_header_field_complete(llhttp_t* p) + { + THttpObjT* pSelf = Self(p); + pSelf->m_strCurHeader = pSelf->GetBuffer(); + pSelf->ResetBuffer(); + + return HPR_OK; + } + + static int on_header_value(http_parser* p, const char* at, size_t length) + { + Self(p)->AppendBuffer(at, length); + + return HPR_OK; + } + + static int on_header_value_complete(llhttp_t* p) + { + THttpObjT* pSelf = Self(p); + + pSelf->m_headers.emplace(move(THeaderMap::value_type(pSelf->m_strCurHeader, pSelf->GetBuffer()))); + + EnHttpParseResult hpr = pSelf->m_pContext->FireHeader(pSelf->m_pSocket, pSelf->m_strCurHeader, pSelf->GetBuffer()); + + if(hpr != HPR_ERROR && !pSelf->GetBufferRef().IsEmpty()) + { + if(pSelf->m_bRequest && pSelf->m_strCurHeader.CompareNoCase(HTTP_HEADER_COOKIE) == 0) + hpr = pSelf->ParseCookie(); + else if(!pSelf->m_bRequest && pSelf->m_strCurHeader.CompareNoCase(HTTP_HEADER_SET_COOKIE) == 0) + hpr = pSelf->ParseSetCookie(); + } + + pSelf->ResetBuffer(); + + return hpr; + } + + static int on_headers_complete(http_parser* p) + { + THttpObjT* pSelf = Self(p); + + pSelf->CheckUpgrade(); + pSelf->ResetHeaderBuffer(); + + EnHttpParseResult rs = pSelf->m_pContext->FireHeadersComplete(pSelf->m_pSocket); + + if(!pSelf->m_bRequest && pSelf->GetMethodInt() == HTTP_HEAD && rs == HPR_OK) + rs = HPR_SKIP_BODY; + + return rs; + } + + static int on_body(http_parser* p, const char* at, size_t length) + { + THttpObjT* pSelf = Self(p); + + return pSelf->m_pContext->FireBody(pSelf->m_pSocket, (const BYTE*)at, (int)length); + } + + static int on_chunk_header(http_parser* p) + { + THttpObjT* pSelf = Self(p); + + return pSelf->m_pContext->FireChunkHeader(pSelf->m_pSocket, (int)p->content_length); + } + + static int on_chunk_complete(http_parser* p) + { + THttpObjT* pSelf = Self(p); + + return pSelf->m_pContext->FireChunkComplete(pSelf->m_pSocket); + } + + static int on_message_complete(http_parser* p) + { + THttpObjT* pSelf = Self(p); + + return pSelf->m_pContext->FireMessageComplete(pSelf->m_pSocket); + } + + EnHandleResult on_ws_message_header(BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], ULONGLONG ullBodyLen) + { + return m_pContext->FireWSMessageHeader(m_pSocket, bFinal, iReserved, iOperationCode, lpszMask, ullBodyLen); + } + + EnHandleResult on_ws_message_body(const BYTE* pData, int iLength) + { + return m_pContext->FireWSMessageBody(m_pSocket, pData, iLength); + } + + EnHandleResult on_ws_message_complete() + { + return m_pContext->FireWSMessageComplete(m_pSocket); + } + +private: + + EnHandleResult Upgrade(const BYTE* pData, int iLength, int iPased) + { + ASSERT(m_parser.upgrade); + + if(m_pContext->FireUpgrade(m_pSocket, m_enUpgrade) != HPR_OK) + return HR_ERROR; + + ResetHeaderState(); + + if(m_enUpgrade == HUT_WEB_SOCKET) + m_pwsContext = new TWSContext>(this); + + if(iPased < iLength) + return Execute(pData + iPased, iLength - iPased); + + return HR_OK; + } + + void CheckUpgrade() + { + if(!m_parser.upgrade) + return; + + if(m_bRequest && m_parser.method == HTTP_CONNECT) + m_enUpgrade = HUT_HTTP_TUNNEL; + else + { + LPCSTR lpszValue; + if(GetHeader(HTTP_HEADER_UPGRADE, &lpszValue) && stricmp(HTTP_HEADER_VALUE_WEB_SOCKET, lpszValue) == 0) + m_enUpgrade = HUT_WEB_SOCKET; + else + m_enUpgrade = HUT_UNKNOWN; + } + } + + EnHttpParseResult ParseUrl() + { + http_parser_url url = {0}; + + BOOL isConnect = m_parser.method == HTTP_CONNECT; + int rs = ::http_parser_parse_url(m_strBuffer, m_strBuffer.GetLength(), isConnect, &url); + + if(rs != HPE_OK) + { + /* + m_parser.error = HPE_INVALID_URL; + m_parser.reason = "Parse url fail"; + */ + + return HPR_ERROR; + } + + m_usUrlFieldSet = url.field_set; + LPCSTR lpszBuffer = m_strBuffer; + + for(int i = 0; i < UF_MAX; i++) + { + if((url.field_set & (1 << i)) != 0) + m_pstrUrlFileds[i].SetString((lpszBuffer + url.field_data[i].off), url.field_data[i].len); + } + + return HPR_OK; + } + + EnHttpParseResult ParseCookie() + { + int i = 0; + + do + { + CStringA tk = m_strBuffer.Tokenize(COOKIE_FIELD_SEP, i); + + if(i == -1) + break; + + int j = tk.Trim().Find(COOKIE_KV_SEP_CHAR); + + if(j <= 0) + continue; + /* + { + m_parser.http_errno = HPE_INVALID_HEADER_TOKEN; + return HPR_ERROR; + } + */ + + AddCookie(tk.Left(j), tk.Mid(j + 1)); + + } while(TRUE); + + return HPR_OK; + } + + EnHttpParseResult ParseSetCookie() + { + CCookieMgr* pCookieMgr = m_pContext->GetCookieMgr(); + + if(pCookieMgr == nullptr) + return HPR_OK; + + LPCSTR lpszDomain = GetDomain(); + LPCSTR lpszPath = GetPath(); + + unique_ptr pCookie(CCookie::FromString(m_strBuffer, lpszDomain, lpszPath)); + + if(pCookie == nullptr) + return HPR_ERROR; + + if(pCookie->Match(lpszDomain, lpszPath, TRUE, m_pContext->IsSecure())) + { + if(pCookie->IsExpired()) + DeleteCookie(pCookie->name); + else + AddCookie(pCookie->name, pCookie->value); + } + + if(pCookieMgr->IsEnableThirdPartyCookie() || pCookie->IsSameDomain(lpszDomain)) + pCookieMgr->SetCookie(*pCookie); + + return HPR_OK; + } + +public: + int GetCount() const {return 0;} + DWORD GetFreeTime() const {return m_dwFreeTime;} + void SetFree() {m_dwFreeTime = ::TimeGetTime();} + + BOOL IsRequest() {return m_bRequest;} + BOOL IsUpgrade() {return m_parser.upgrade;} + BOOL IsKeepAlive() {return ::llhttp_should_keep_alive(&m_parser);} + USHORT GetVersion() {return MAKEWORD(m_parser.http_major, m_parser.http_minor);} + ULONGLONG GetContentLength() {return m_parser.content_length;} + + int GetMethodInt() {return m_bRequest ? m_parser.method : m_sRequestMethod;} + USHORT GetUrlFieldSet() {return m_usUrlFieldSet;} + USHORT GetStatusCode() {return m_parser.status_code;} + + EnHttpUpgradeType GetUpgradeType() {return m_enUpgrade;} + + THeaderMap& GetHeaderMap() {return m_headers;} + TCookieMap& GetCookieMap() {return m_cookies;} + + BOOL HasReleased() {return m_bReleased;} + void Release() {m_bReleased = TRUE;} + + LPCSTR GetMethod() + { + int iMethod = GetMethodInt(); + + if(iMethod >= 0 && iMethod <= LLHTTP_MAX_METHOD_NUM) + return ::llhttp_method_name((llhttp_method)iMethod); + + return nullptr; + } + + LPCSTR GetContentType() + { + LPCSTR lpszValue = nullptr; + GetHeader(HTTP_HEADER_CONTENT_TYPE, &lpszValue); + + return lpszValue; + } + + LPCSTR GetContentEncoding() + { + LPCSTR lpszValue = nullptr; + GetHeader(HTTP_HEADER_CONTENT_ENCODING, &lpszValue); + + return lpszValue; + } + + LPCSTR GetTransferEncoding() + { + LPCSTR lpszValue = nullptr; + GetHeader(HTTP_HEADER_TRANSFER_ENCODING, &lpszValue); + + return lpszValue; + } + + LPCSTR GetHost() + { + LPCSTR lpszValue = nullptr; + GetHeader(HTTP_HEADER_HOST, &lpszValue); + + return lpszValue; + } + + USHORT GetParseErrorCode(LPCSTR* lpszErrorDesc = nullptr) + { + USHORT usError = (USHORT)m_parser.error; + + if(lpszErrorDesc) + { + if(usError == HPE_OK) + *lpszErrorDesc = ::llhttp_errno_name((llhttp_errno_t)usError); + else + { + *lpszErrorDesc = ::llhttp_get_error_reason(&m_parser); + + if(::IsStrEmptyA(*lpszErrorDesc) && usError <= LLHTTP_MAX_ERROR_NUM) + *lpszErrorDesc = ::llhttp_errno_name((llhttp_errno_t)usError); + } + } + + return usError; + } + + LPCSTR GetUrlField(EnHttpUrlField enField) + { + ASSERT(m_bRequest && enField < HUF_MAX); + + if(!m_bRequest || enField >= HUF_MAX) + return nullptr; + + return m_pstrUrlFileds[enField]; + } + + LPCSTR GetPath() + { + if(m_bRequest) + return GetUrlField(HUF_PATH); + else + return *m_pstrRequestPath; + } + + LPCSTR GetDomain() + { + ASSERT(!m_bRequest); + + return m_pContext->GetRemoteDomain(m_pSocket); + } + + LPCSTR GetRequestPath() + { + if(m_bRequest) + return nullptr; + + return *m_pstrRequestPath; + } + + void SetRequestPath(LPCSTR lpszMethod, LPCSTR lpszPath) + { + ASSERT(!m_bRequest); + + if(m_bRequest) + return; + + *m_pstrRequestPath = lpszPath; + +#define HTTP_METHOD_GEN(NUM, NAME, STRING) else if(stricmp(lpszMethod, #STRING) == 0) m_sRequestMethod = HTTP_##NAME; + + if(::IsStrEmptyA(lpszMethod)) m_sRequestMethod = -1; + HTTP_METHOD_MAP(HTTP_METHOD_GEN) + else + m_sRequestMethod = -1; + +#undef HTTP_METHOD_GEN + } + + BOOL GetHeader(LPCSTR lpszName, LPCSTR* lpszValue) + { + ASSERT(lpszName); + + BOOL isOK = FALSE; + THeaderMapCI it = m_headers.find(lpszName); + + if(it != m_headers.end()) + { + *lpszValue = it->second; + isOK = TRUE; + } + + return isOK; + } + + BOOL GetHeaders(LPCSTR lpszName, LPCSTR lpszValue[], DWORD& dwCount) + { + ASSERT(lpszName); + + if(lpszValue == nullptr || dwCount == 0) + { + dwCount = (DWORD)m_headers.count(lpszName); + return FALSE; + } + + pair range = m_headers.equal_range(lpszName); + + THeaderMapCI it = range.first; + DWORD dwIndex = 0; + + while(it != range.second) + { + if(dwIndex < dwCount) + lpszValue[dwIndex] = it->second; + + ++dwIndex; + ++it; + } + + BOOL isOK = (dwIndex > 0 && dwIndex <= dwCount); + dwCount = dwIndex; + + return isOK; + } + + BOOL GetAllHeaders(THeader lpHeaders[], DWORD& dwCount) + { + DWORD dwSize = (DWORD)m_headers.size(); + + if(lpHeaders == nullptr || dwCount == 0 || dwSize == 0 || dwSize > dwCount) + { + dwCount = dwSize; + return FALSE; + } + + DWORD dwIndex = 0; + + for(THeaderMapCI it = m_headers.begin(), end = m_headers.end(); it != end; ++it, ++dwIndex) + { + lpHeaders[dwIndex].name = it->first; + lpHeaders[dwIndex].value = it->second; + } + + dwCount = dwSize; + return TRUE; + } + + BOOL GetAllHeaderNames(LPCSTR lpszName[], DWORD& dwCount) + { + DWORD dwSize = (DWORD)m_headers.size(); + + if(lpszName == nullptr || dwCount == 0 || dwSize == 0 || dwSize > dwCount) + { + dwCount = dwSize; + return FALSE; + } + + DWORD dwIndex = 0; + + for(THeaderMapCI it = m_headers.begin(), end = m_headers.end(); it != end; ++it, ++dwIndex) + lpszName[dwIndex] = it->first; + + dwCount = dwSize; + return TRUE; + } + + BOOL AddCookie(LPCSTR lpszName, LPCSTR lpszValue, BOOL bRelpace = TRUE) + { + ASSERT(lpszName); + + TCookieMapI it = m_cookies.find(lpszName); + + if(it == m_cookies.end()) + return m_cookies.emplace(move(TCookieMap::value_type(lpszName, lpszValue))).second; + + BOOL isOK = FALSE; + + if(bRelpace) + { + it->second = lpszValue; + isOK = TRUE; + } + + return isOK; + } + + BOOL DeleteCookie(LPCSTR lpszName) + { + ASSERT(lpszName); + + return m_cookies.erase(lpszName) > 0; + } + + void DeleteAllCookies() + { + m_cookies.clear(); + } + + BOOL GetCookie(LPCSTR lpszName, LPCSTR* lpszValue) + { + ASSERT(lpszName); + + BOOL isOK = FALSE; + TCookieMapCI it = m_cookies.find(lpszName); + + if(it != m_cookies.end()) + { + *lpszValue = it->second; + isOK = TRUE; + } + + return isOK; + } + + BOOL GetAllCookies(TCookie lpCookies[], DWORD& dwCount) + { + DWORD dwSize = (DWORD)m_cookies.size(); + + if(lpCookies == nullptr || dwCount == 0 || dwSize == 0 || dwSize > dwCount) + { + dwCount = dwSize; + return FALSE; + } + + DWORD dwIndex = 0; + + for(TCookieMapCI it = m_cookies.begin(), end = m_cookies.end(); it != end; ++it, ++dwIndex) + { + lpCookies[dwIndex].name = it->first; + lpCookies[dwIndex].value = it->second; + } + + dwCount = dwSize; + return TRUE; + } + + BOOL ReloadCookies() + { + CCookieMgr* pCookieMgr = m_pContext->GetCookieMgr(); + + if(pCookieMgr == nullptr) + return TRUE; + + DeleteAllCookies(); + + CCookieSet cookies; + + if(!pCookieMgr->GetCookies(cookies, GetDomain(), GetPath(), TRUE, m_pContext->IsSecure())) + return FALSE; + + for(CCookieSetCI it = cookies.begin(), end = cookies.end(); it != end; ++it) + AddCookie(it->name, it->value); + + return TRUE; + } + + BOOL GetWSMessageState(BOOL* lpbFinal, BYTE* lpiReserved, BYTE* lpiOperationCode, LPCBYTE* lpszMask, ULONGLONG* lpullBodyLen, ULONGLONG* lpullBodyRemain) + { + if(!m_pwsContext) + return FALSE; + + return m_pwsContext->GetMessageState(lpbFinal, lpiReserved, lpiOperationCode, lpszMask, lpullBodyLen, lpullBodyRemain); + } + +public: + THttpObjT (BOOL bRequest, T* pContext, S* pSocket) + : m_pContext (pContext) + , m_pSocket (pSocket) + , m_bRequest (bRequest) + , m_bValid (FALSE) + , m_bReleased (FALSE) + , m_dwFreeTime (0) + , m_usUrlFieldSet (m_bRequest ? 0 : -1) + , m_pstrUrlFileds (nullptr) + , m_enUpgrade (HUT_NONE) + , m_pwsContext (nullptr) + { + if(m_bRequest) + m_pstrUrlFileds = new CStringA[HUF_MAX]; + else + m_pstrRequestPath = new CStringA; + + ResetParser(); + } + + ~THttpObjT() + { + if(m_bRequest) + delete[] m_pstrUrlFileds; + else + delete m_pstrRequestPath; + + ReleaseWSContext(); + } + + static THttpObjT* Construct(BOOL bRequest, T* pContext, S* pSocket) + {return new THttpObjT(bRequest, pContext, pSocket);} + + static void Destruct(THttpObjT* pHttpObj) + {if(pHttpObj) delete pHttpObj;} + + void Reset(BOOL bValid = FALSE) + { + ResetParser(); + ResetHeaderState(); + ReleaseWSContext(); + + m_bValid = bValid; + m_bReleased = FALSE; + m_enUpgrade = HUT_NONE; + m_dwFreeTime = 0; + } + + void Renew(T* pContext, S* pSocket) + { + Reset(TRUE); + + m_pContext = pContext; + m_pSocket = pSocket; + } + + void SetValid(BOOL bValid = TRUE) + { + m_bValid = bValid; + } + + BOOL IsValid() + { + return m_bValid; + } + + BOOL CopyData(const THttpObjT& src) + { + if(&src == this) + return FALSE; + if(m_bRequest != src.m_bRequest) + return FALSE; + + void* p = m_parser.data; + m_parser = src.m_parser; + m_parser.data = p; + + m_headers = src.m_headers; + m_cookies = src.m_cookies; + + if(m_bRequest) + { + m_usUrlFieldSet = src.m_usUrlFieldSet; + + for(int i = 0;i < HUF_MAX; i++) + m_pstrUrlFileds[i] = src.m_pstrUrlFileds[i]; + } + else + { + m_sRequestMethod = src.m_sRequestMethod; + *m_pstrRequestPath = *src.m_pstrRequestPath; + } + + m_enUpgrade = src.m_enUpgrade; + + return TRUE; + } + + BOOL CopyWSContext(const THttpObjT& src) + { + if(&src == this) + return FALSE; + if(m_bRequest != src.m_bRequest) + return FALSE; + + if(!src.m_pwsContext && !m_pwsContext) + ; + else if(!src.m_pwsContext && m_pwsContext) + { + delete m_pwsContext; + m_pwsContext = nullptr; + } + else + { + if(!m_pwsContext) + m_pwsContext = new TWSContext>(this); + + m_pwsContext->CopyData(*src.m_pwsContext); + } + + return TRUE; + } + +private: + + void ResetParser() + { + ::llhttp_init(&m_parser, m_bRequest ? HTTP_REQUEST : HTTP_RESPONSE, &sm_settings); + m_parser.data = this; + } + + void ResetHeaderState(BOOL bClearCookies = TRUE, BOOL bResetRequestData = TRUE) + { + if(m_bRequest) + { + if(m_usUrlFieldSet != 0) + { + m_usUrlFieldSet = 0; + + for(int i = 0; i < HUF_MAX; i++) + m_pstrUrlFileds[i].Empty(); + } + } + else + { + if(bResetRequestData) + { + m_sRequestMethod = -1; + m_pstrRequestPath->Empty(); + } + } + + if(m_bRequest || bClearCookies) + DeleteAllCookies(); + + m_headers.clear(); + ResetHeaderBuffer(); + } + + void ResetHeaderBuffer() + { + ResetBuffer(); + m_strCurHeader.Empty(); + } + + void ReleaseWSContext() + { + if(m_pwsContext) + { + delete m_pwsContext; + m_pwsContext = nullptr; + } + } + + void AppendBuffer(const char* at, size_t length) {m_strBuffer.Append(at, (int)length);} + void ResetBuffer() {m_strBuffer.Empty();} + LPCSTR GetBuffer() {return m_strBuffer;} + CStringA& GetBufferRef() {return m_strBuffer;} + + static THttpObjT* Self(http_parser* p) {return (THttpObjT*)(p->data);} + static T* SelfContext(http_parser* p) {return Self(p)->m_pContext;} + static S* SelfSocketObj(http_parser* p) {return Self(p)->m_pSocket;} + +private: + BOOL m_bValid; + BOOL m_bRequest; + BOOL m_bReleased; + T* m_pContext; + S* m_pSocket; + http_parser m_parser; + THeaderMap m_headers; + TCookieMap m_cookies; + CStringA m_strBuffer; + CStringA m_strCurHeader; + + union + { + USHORT m_usUrlFieldSet; + short m_sRequestMethod; + }; + + union + { + CStringA* m_pstrUrlFileds; + CStringA* m_pstrRequestPath; + }; + + EnHttpUpgradeType m_enUpgrade; + DWORD m_dwFreeTime; + + TWSContext>* m_pwsContext; + + static http_parser_settings sm_settings; +}; + +template http_parser_settings THttpObjT::sm_settings = +{ + on_message_begin, + on_url, + on_status, + nullptr, // on_method + nullptr, // on_version + on_header_field, + on_header_value, + nullptr, // on_chunk_extension_name + nullptr, // on_chunk_extension_value + on_headers_complete, + on_body, + on_message_complete, + on_url_complete, + on_status_complete, + nullptr, // on_method_complete + nullptr, // on_version_complete + on_header_field_complete, + on_header_value_complete, + nullptr, // on_chunk_extension_name_complete + nullptr, // on_chunk_extension_value_complete + on_chunk_header, + on_chunk_complete, + nullptr // on_reset +}; + +// ------------------------------------------------------------------------------------------------------------- // + +template class CHttpObjPoolT +{ + typedef THttpObjT THttpObj; + typedef CRingPool TSSLHttpObjList; + typedef CCASQueue TSSLHttpObjQueue; + +public: + THttpObj* PickFreeHttpObj(T* pContext, S* pSocket) + { + DWORD dwIndex; + THttpObj* pHttpObj = nullptr; + + if(m_lsFreeHttpObj.TryLock(&pHttpObj, dwIndex)) + { + if(::GetTimeGap32(pHttpObj->GetFreeTime()) >= m_dwHttpObjLockTime) + VERIFY(m_lsFreeHttpObj.ReleaseLock(nullptr, dwIndex)); + else + { + VERIFY(m_lsFreeHttpObj.ReleaseLock(pHttpObj, dwIndex)); + pHttpObj = nullptr; + } + } + + if(pHttpObj) + pHttpObj->Renew(pContext, pSocket); + else + { + pHttpObj = THttpObj::Construct(is_request, pContext, pSocket); + ASSERT(pHttpObj); + } + + return pHttpObj; + } + + void PutFreeHttpObj(THttpObj* pHttpObj) + { + pHttpObj->SetFree(); + +#ifndef USE_EXTERNAL_GC + ReleaseGCHttpObj(); +#endif + if(!m_lsFreeHttpObj.TryPut(pHttpObj)) + m_lsGCHttpObj.PushBack(pHttpObj); + } + + void Prepare() + { + m_lsFreeHttpObj.Reset(m_dwHttpObjPoolSize); + } + + void Clear() + { + m_lsFreeHttpObj.Clear(); + + ReleaseGCHttpObj(TRUE); + VERIFY(m_lsGCHttpObj.IsEmpty()); + } + + void ReleaseGCHttpObj(BOOL bForce = FALSE) + { + ::ReleaseGCObj(m_lsGCHttpObj, m_dwHttpObjLockTime, bForce); + } + +public: + void SetHttpObjLockTime (DWORD dwHttpObjLockTime) {m_dwHttpObjLockTime = dwHttpObjLockTime;} + void SetHttpObjPoolSize (DWORD dwHttpObjPoolSize) {m_dwHttpObjPoolSize = dwHttpObjPoolSize;} + void SetHttpObjPoolHold (DWORD dwHttpObjPoolHold) {m_dwHttpObjPoolHold = dwHttpObjPoolHold;} + + DWORD GetHttpObjLockTime() {return m_dwHttpObjLockTime;} + DWORD GetHttpObjPoolSize() {return m_dwHttpObjPoolSize;} + DWORD GetHttpObjPoolHold() {return m_dwHttpObjPoolHold;} + +public: + CHttpObjPoolT( DWORD dwPoolSize = DEFAULT_HTTPOBJ_POOL_SIZE, + DWORD dwPoolHold = DEFAULT_HTTPOBJ_POOL_HOLD, + DWORD dwLockTime = DEFAULT_HTTPOBJ_LOCK_TIME) + : m_dwHttpObjPoolSize(dwPoolSize) + , m_dwHttpObjPoolHold(dwPoolHold) + , m_dwHttpObjLockTime(dwLockTime) + { + + } + + ~CHttpObjPoolT() {Clear();} + + DECLARE_NO_COPY_CLASS(CHttpObjPoolT) + +public: + static const DWORD DEFAULT_HTTPOBJ_LOCK_TIME; + static const DWORD DEFAULT_HTTPOBJ_POOL_SIZE; + static const DWORD DEFAULT_HTTPOBJ_POOL_HOLD; + +private: + DWORD m_dwHttpObjLockTime; + DWORD m_dwHttpObjPoolSize; + DWORD m_dwHttpObjPoolHold; + + TSSLHttpObjList m_lsFreeHttpObj; + TSSLHttpObjQueue m_lsGCHttpObj; +}; + +template const DWORD CHttpObjPoolT::DEFAULT_HTTPOBJ_LOCK_TIME = DEFAULT_OBJECT_CACHE_LOCK_TIME; +template const DWORD CHttpObjPoolT::DEFAULT_HTTPOBJ_POOL_SIZE = DEFAULT_OBJECT_CACHE_POOL_SIZE; +template const DWORD CHttpObjPoolT::DEFAULT_HTTPOBJ_POOL_HOLD = DEFAULT_OBJECT_CACHE_POOL_HOLD; + +// ------------------------------------------------------------------------------------------------------------- // + +extern CStringA& GetHttpVersionStr(EnHttpVersion enVersion, CStringA& strResult); +extern CStringA& AdjustRequestPath(BOOL bConnect, LPCSTR lpszPath, CStringA& strPath); +extern LPCSTR GetHttpDefaultStatusCodeDesc(EnHttpStatusCode enCode); +extern void MakeRequestLine(LPCSTR lpszMethod, LPCSTR lpszPath, EnHttpVersion enVersion, CStringA& strValue); +extern void MakeStatusLine(EnHttpVersion enVersion, USHORT usStatusCode, LPCSTR lpszDesc, CStringA& strValue); +extern void MakeHeaderLines(const THeader lpHeaders[], int iHeaderCount, const TCookieMap* pCookies, int iBodyLength, BOOL bRequest, int iConnFlag, LPCSTR lpszDefaultHost, USHORT usPort, CStringA& strValue); +extern void MakeHttpPacket(const CStringA& strHeader, const BYTE* pBody, int iLength, WSABUF szBuffer[2]); +extern int MakeChunkPackage(const BYTE* pData, int iLength, LPCSTR lpszExtensions, char szLen[12], WSABUF bufs[5]); +extern BOOL MakeWSPacket(BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], BYTE* pData, int iLength, ULONGLONG ullBodyLen, BYTE szHeader[HTTP_MAX_WS_HEADER_LEN], WSABUF szBuffer[2]); +extern BOOL ParseUrl(const CStringA& strUrl, BOOL& bHttps, CStringA& strHost, USHORT& usPort, CStringA& strPath); + +#endif \ No newline at end of file diff --git a/HttpServer.cpp b/HttpServer.cpp new file mode 100644 index 0000000..1f5cc4c --- /dev/null +++ b/HttpServer.cpp @@ -0,0 +1,615 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "HttpServer.h" + +#ifdef _HTTP_SUPPORT + +template BOOL CHttpServerT::Start(LPCTSTR lpszBindAddress, USHORT usPort) +{ + BOOL isOK = __super::Start(lpszBindAddress, usPort); + + if(isOK) VERIFY(m_thCleaner.Start(this, &CHttpServerT::CleanerThreadProc)); + + return isOK; +} + +template BOOL CHttpServerT::CheckParams() +{ + if ((m_enLocalVersion != HV_1_1 && m_enLocalVersion != HV_1_0) || + (m_dwReleaseDelay < MIN_HTTP_RELEASE_DELAY || m_dwReleaseDelay > MAX_HTTP_RELEASE_DELAY)) + { + SetLastError(SE_INVALID_PARAM, __FUNCTION__, ERROR_INVALID_PARAMETER); + return FALSE; + } + + return __super::CheckParams(); +} + +template void CHttpServerT::PrepareStart() +{ + __super::PrepareStart(); + + m_objPool.SetHttpObjLockTime(GetFreeSocketObjLockTime()); + m_objPool.SetHttpObjPoolSize(GetFreeSocketObjPool()); + m_objPool.SetHttpObjPoolHold(GetFreeSocketObjHold()); + + m_objPool.Prepare(); +} + +template BOOL CHttpServerT::SendResponse(CONNID dwConnID, USHORT usStatusCode, LPCSTR lpszDesc, const THeader lpHeaders[], int iHeaderCount, const BYTE* pData, int iLength) +{ + WSABUF szBuffer[2]; + CStringA strHeader; + + ::MakeStatusLine(m_enLocalVersion, usStatusCode, lpszDesc, strHeader); + ::MakeHeaderLines(lpHeaders, iHeaderCount, nullptr, iLength, FALSE, IsKeepAlive(dwConnID), nullptr, 0, strHeader); + ::MakeHttpPacket(strHeader, pData, iLength, szBuffer); + + return SendPackets(dwConnID, szBuffer, 2); +} + +template BOOL CHttpServerT::SendLocalFile(CONNID dwConnID, LPCSTR lpszFileName, USHORT usStatusCode, LPCSTR lpszDesc, const THeader lpHeaders[], int iHeaderCount) +{ + CFile file; + CFileMapping fmap; + + HRESULT hr = ::ReadSmallFile(CA2T(lpszFileName), file, fmap); + + if(FAILED(hr)) + { + ::SetLastError(hr); + return FALSE; + } + + return SendResponse(dwConnID, usStatusCode, lpszDesc, lpHeaders, iHeaderCount, (BYTE*)fmap, (int)fmap.Size()); +} + +template BOOL CHttpServerT::SendChunkData(CONNID dwConnID, const BYTE* pData, int iLength, LPCSTR lpszExtensions) +{ + char szLen[12]; + WSABUF bufs[5]; + + int iCount = MakeChunkPackage(pData, iLength, lpszExtensions, szLen, bufs); + + return SendPackets(dwConnID, bufs, iCount); +} + +template BOOL CHttpServerT::Release(CONNID dwConnID) +{ + if(!HasStarted()) + return FALSE; + + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr || pHttpObj->HasReleased()) + return FALSE; + + pHttpObj->Release(); + + m_lsDyingQueue.PushBack(TDyingConnection::Construct(dwConnID)); + + return TRUE; +} + +template BOOL CHttpServerT::SendWSMessage(CONNID dwConnID, BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE* pData, int iLength, ULONGLONG ullBodyLen) +{ + WSABUF szBuffer[2]; + BYTE szHeader[HTTP_MAX_WS_HEADER_LEN]; + + if(!::MakeWSPacket(bFinal, iReserved, iOperationCode, nullptr, (BYTE*)pData, iLength, ullBodyLen, szHeader, szBuffer)) + return FALSE; + + return SendPackets(dwConnID, szBuffer, 2); +} + +template UINT CHttpServerT::CleanerThreadProc(PVOID pv) +{ + TRACE("---------------> Connection Cleaner Thread 0x%08X started <---------------", SELF_THREAD_ID); + + pollfd pfd = {m_evCleaner.GetFD(), POLLIN}; + DWORD dwInterval = MAX(MIN_HTTP_RELEASE_CHECK_INTERVAL, (m_dwReleaseDelay - MIN_HTTP_RELEASE_DELAY / 2)); + + while(HasStarted()) + { + int rs = (int)::PollForSingleObject(pfd, dwInterval); + ASSERT(rs >= TIMEOUT); + + if(rs < TIMEOUT) + ERROR_ABORT(); + + if(rs == TIMEOUT) + KillDyingConnection(); + else if(rs == 1) + { + m_evCleaner.Reset(); + goto END_DETECTOR; + } + else + ASSERT(FALSE); + } + +END_DETECTOR: + + ReleaseDyingConnection(); + VERIFY(!HasStarted()); + + TRACE("---------------> Connection Cleaner Thread 0x%08X stoped <---------------", SELF_THREAD_ID); + + return 0; +} + +template void CHttpServerT::KillDyingConnection() +{ + TDyingConnection* pDyingConn = nullptr; + TDyingConnection* pFirstDyingConn = nullptr; + DWORD now = ::TimeGetTime(); + + while(m_lsDyingQueue.UnsafePeekFront(&pDyingConn)) + { + if((int)(now - pDyingConn->killTime) < (int)m_dwReleaseDelay) + break; + + BOOL bDisconnect = TRUE; + BOOL bDestruct = TRUE; + + VERIFY(m_lsDyingQueue.UnsafePopFront(&pDyingConn)); + + int iPending; + if(!GetPendingDataLength(pDyingConn->connID, iPending)) + bDisconnect = FALSE; + else if(iPending > 0) + { + bDisconnect = FALSE; + bDestruct = FALSE; + } + + if(bDisconnect) + Disconnect(pDyingConn->connID, TRUE); + + if(bDestruct) + { + TDyingConnection::Destruct(pDyingConn); + + if(pFirstDyingConn == pDyingConn) + pFirstDyingConn = nullptr; + } + else + { + m_lsDyingQueue.PushBack(pDyingConn); + + if(pFirstDyingConn == nullptr) + pFirstDyingConn = pDyingConn; + else if(pFirstDyingConn == pDyingConn) + break; + } + } +} + +template void CHttpServerT::ReleaseDyingConnection() +{ + TDyingConnection* pDyingConn = nullptr; + + while(m_lsDyingQueue.UnsafePopFront(&pDyingConn)) + TDyingConnection::Destruct(pDyingConn); + + VERIFY(m_lsDyingQueue.IsEmpty()); +} + +template EnHandleResult CHttpServerT::FireAccept(TSocketObj* pSocketObj) +{ + return m_bHttpAutoStart ? __super::FireAccept(pSocketObj) : __super::DoFireAccept(pSocketObj); +} + +template EnHandleResult CHttpServerT::DoFireAccept(TSocketObj* pSocketObj) +{ + THttpObj* pHttpObj = DoStartHttp(pSocketObj); + EnHandleResult result = __super::DoFireAccept(pSocketObj); + + if(result == HR_ERROR) + { + m_objPool.PutFreeHttpObj(pHttpObj); + SetConnectionReserved(pSocketObj, nullptr); + } + + return result; +} + +template EnHandleResult CHttpServerT::DoFireHandShake(TSocketObj* pSocketObj) +{ + EnHandleResult result = __super::DoFireHandShake(pSocketObj); + + if(result == HR_ERROR) + { + THttpObj* pHttpObj = FindHttpObj(pSocketObj); + + if(pHttpObj != nullptr) + { + m_objPool.PutFreeHttpObj(pHttpObj); + SetConnectionReserved(pSocketObj, nullptr); + } + } + + return result; +} + +template EnHandleResult CHttpServerT::DoFireReceive(TSocketObj* pSocketObj, const BYTE* pData, int iLength) +{ + THttpObj* pHttpObj = FindHttpObj(pSocketObj); + + if(pHttpObj == nullptr) + return DoFireSuperReceive(pSocketObj, pData, iLength); + else + { + if(pHttpObj->HasReleased()) + return HR_ERROR; + + return pHttpObj->Execute(pData, iLength); + } +} + +template EnHandleResult CHttpServerT::DoFireClose(TSocketObj* pSocketObj, EnSocketOperation enOperation, int iErrorCode) +{ + EnHandleResult result = __super::DoFireClose(pSocketObj, enOperation, iErrorCode); + + THttpObj* pHttpObj = FindHttpObj(pSocketObj); + + if(pHttpObj != nullptr) + { + m_objPool.PutFreeHttpObj(pHttpObj); + SetConnectionReserved(pSocketObj, nullptr); + } + + return result; +} + +template EnHandleResult CHttpServerT::DoFireShutdown() +{ + EnHandleResult result = __super::DoFireShutdown(); + + m_objPool.Clear(); + WaitForCleanerThreadEnd(); + + return result; +} + +template void CHttpServerT::ReleaseGCSocketObj(BOOL bForce) +{ + __super::ReleaseGCSocketObj(bForce); + +#ifdef USE_EXTERNAL_GC + m_objPool.ReleaseGCHttpObj(bForce); +#endif +} + +template void CHttpServerT::WaitForCleanerThreadEnd() +{ + if(m_thCleaner.IsRunning()) + { + m_evCleaner.Set(); + m_thCleaner.Join(); + m_evCleaner.Reset(); + } +} + +template BOOL CHttpServerT::IsUpgrade(CONNID dwConnID) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return FALSE; + + return pHttpObj->IsUpgrade(); +} + +template BOOL CHttpServerT::IsKeepAlive(CONNID dwConnID) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return FALSE; + + return pHttpObj->IsKeepAlive(); +} + +template USHORT CHttpServerT::GetVersion(CONNID dwConnID) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return 0; + + return pHttpObj->GetVersion(); +} + +template LPCSTR CHttpServerT::GetHost(CONNID dwConnID) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return nullptr; + + return pHttpObj->GetHost(); +} + +template ULONGLONG CHttpServerT::GetContentLength(CONNID dwConnID) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return 0; + + return pHttpObj->GetContentLength(); +} + +template LPCSTR CHttpServerT::GetContentType(CONNID dwConnID) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return nullptr; + + return pHttpObj->GetContentType(); +} + +template LPCSTR CHttpServerT::GetContentEncoding(CONNID dwConnID) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return nullptr; + + return pHttpObj->GetContentEncoding(); +} + +template LPCSTR CHttpServerT::GetTransferEncoding(CONNID dwConnID) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return nullptr; + + return pHttpObj->GetTransferEncoding(); +} + +template EnHttpUpgradeType CHttpServerT::GetUpgradeType(CONNID dwConnID) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return HUT_NONE; + + return pHttpObj->GetUpgradeType(); +} + +template USHORT CHttpServerT::GetParseErrorCode(CONNID dwConnID, LPCSTR* lpszErrorDesc) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return 0; + + return pHttpObj->GetParseErrorCode(lpszErrorDesc); +} + +template BOOL CHttpServerT::GetHeader(CONNID dwConnID, LPCSTR lpszName, LPCSTR* lpszValue) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return FALSE; + + return pHttpObj->GetHeader(lpszName, lpszValue); +} + +template BOOL CHttpServerT::GetHeaders(CONNID dwConnID, LPCSTR lpszName, LPCSTR lpszValue[], DWORD& dwCount) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return FALSE; + + return pHttpObj->GetHeaders(lpszName, lpszValue, dwCount); +} + +template BOOL CHttpServerT::GetAllHeaders(CONNID dwConnID, THeader lpHeaders[], DWORD& dwCount) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return FALSE; + + return pHttpObj->GetAllHeaders(lpHeaders, dwCount); +} + +template BOOL CHttpServerT::GetAllHeaderNames(CONNID dwConnID, LPCSTR lpszName[], DWORD& dwCount) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return FALSE; + + return pHttpObj->GetAllHeaderNames(lpszName, dwCount); +} + +template BOOL CHttpServerT::GetCookie(CONNID dwConnID, LPCSTR lpszName, LPCSTR* lpszValue) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return FALSE; + + return pHttpObj->GetCookie(lpszName, lpszValue); +} + +template BOOL CHttpServerT::GetAllCookies(CONNID dwConnID, TCookie lpCookies[], DWORD& dwCount) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return FALSE; + + return pHttpObj->GetAllCookies(lpCookies, dwCount); +} + +template USHORT CHttpServerT::GetUrlFieldSet(CONNID dwConnID) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return 0; + + return pHttpObj->GetUrlFieldSet(); +} + +template LPCSTR CHttpServerT::GetUrlField(CONNID dwConnID, EnHttpUrlField enField) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return nullptr; + + return pHttpObj->GetUrlField(enField); +} + +template LPCSTR CHttpServerT::GetMethod(CONNID dwConnID) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return nullptr; + + return pHttpObj->GetMethod(); +} + +template BOOL CHttpServerT::GetWSMessageState(CONNID dwConnID, BOOL* lpbFinal, BYTE* lpiReserved, BYTE* lpiOperationCode, LPCBYTE* lpszMask, ULONGLONG* lpullBodyLen, ULONGLONG* lpullBodyRemain) +{ + THttpObj* pHttpObj = FindHttpObj(dwConnID); + + if(pHttpObj == nullptr) + return FALSE; + + return pHttpObj->GetWSMessageState(lpbFinal, lpiReserved, lpiOperationCode, lpszMask, lpullBodyLen, lpullBodyRemain); +} + +template inline typename CHttpServerT::THttpObj* CHttpServerT::FindHttpObj(CONNID dwConnID) +{ + THttpObj* pHttpObj = nullptr; + GetConnectionReserved(dwConnID, (PVOID*)&pHttpObj); + + return pHttpObj; +} + +template inline typename CHttpServerT::THttpObj* CHttpServerT::FindHttpObj(TSocketObj* pSocketObj) +{ + THttpObj* pHttpObj = nullptr; + GetConnectionReserved(pSocketObj, (PVOID*)&pHttpObj); + + return pHttpObj; +} + +template BOOL CHttpServerT::StartHttp(CONNID dwConnID) +{ + if(IsHttpAutoStart()) + { + ::SetLastError(ERROR_INVALID_OPERATION); + return FALSE; + } + + TSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TSocketObj::IsValid(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + return StartHttp(pSocketObj); +} + +template BOOL CHttpServerT::StartHttp(TSocketObj* pSocketObj) +{ + if(!pSocketObj->HasConnected()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + CReentrantCriSecLock locallock(pSocketObj->csSend); + + if(!TSocketObj::IsValid(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + if(!pSocketObj->HasConnected()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + THttpObj* pHttpObj = FindHttpObj(pSocketObj); + + if(pHttpObj != nullptr) + { + ::SetLastError(ERROR_ALREADY_INITIALIZED); + return FALSE; + } + + DoStartHttp(pSocketObj); + + if(!IsSecure()) + FireHandShake(pSocketObj); + else + { +#ifdef _SSL_SUPPORT + if(IsSSLAutoHandShake()) + StartSSLHandShake(pSocketObj); +#endif + } + + return TRUE; +} + +template typename CHttpServerT::THttpObj* CHttpServerT::DoStartHttp(TSocketObj* pSocketObj) +{ + THttpObj* pHttpObj = m_objPool.PickFreeHttpObj(this, pSocketObj); + VERIFY(SetConnectionReserved(pSocketObj, pHttpObj)); + + return pHttpObj; +} + +// ------------------------------------------------------------------------------------------------------------- // + +template class CHttpServerT; + +#ifdef _SSL_SUPPORT + +#include "SSLServer.h" + +template class CHttpServerT; + +#endif + +#endif \ No newline at end of file diff --git a/HttpServer.h b/HttpServer.h new file mode 100644 index 0000000..6decd32 --- /dev/null +++ b/HttpServer.h @@ -0,0 +1,223 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "TcpServer.h" +#include "HttpHelper.h" +#include "common/Thread.h" + +#ifdef _HTTP_SUPPORT + +template class CHttpServerT : public IComplexHttpResponder, public T +{ + using __super = T; + using __super::GetConnectionReserved; + using __super::SetConnectionReserved; + using __super::SetLastError; + +public: + using __super::Stop; + using __super::Wait; + using __super::GetState; + using __super::SendPackets; + using __super::Disconnect; + using __super::HasStarted; + using __super::GetFreeSocketObjLockTime; + using __super::GetFreeSocketObjPool; + using __super::GetFreeSocketObjHold; + using __super::GetPendingDataLength; + + using __super::IsSecure; + using __super::FireHandShake; + using __super::FindSocketObj; + +#ifdef _SSL_SUPPORT + using __super::StartSSLHandShake; + using __super::IsSSLAutoHandShake; +#endif + +protected: + using CCleanThread = CThread; + using CHttpObjPool = CHttpObjPoolT; + using THttpObj = THttpObjT; + + friend typename CHttpServerT::CCleanThread; + friend typename CHttpServerT::THttpObj; + +public: + + virtual BOOL Start(LPCTSTR lpszBindAddress, USHORT usPort); + + virtual BOOL SendResponse(CONNID dwConnID, USHORT usStatusCode, LPCSTR lpszDesc = nullptr, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0, const BYTE* pData = nullptr, int iLength = 0); + virtual BOOL SendLocalFile(CONNID dwConnID, LPCSTR lpszFileName, USHORT usStatusCode = HSC_OK, LPCSTR lpszDesc = nullptr, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0); + virtual BOOL SendChunkData(CONNID dwConnID, const BYTE* pData = nullptr, int iLength = 0, LPCSTR lpszExtensions = nullptr); + + virtual BOOL Release(CONNID dwConnID); + + virtual BOOL SendWSMessage(CONNID dwConnID, BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE* pData = nullptr, int iLength = 0, ULONGLONG ullBodyLen = 0); + + virtual BOOL StartHttp(CONNID dwConnID); + +public: + + virtual void SetHttpAutoStart(BOOL bAutoStart) {ENSURE_HAS_STOPPED(); m_bHttpAutoStart = bAutoStart;} + virtual void SetLocalVersion(EnHttpVersion enLocalVersion) {ENSURE_HAS_STOPPED(); m_enLocalVersion = enLocalVersion;} + virtual void SetReleaseDelay(DWORD dwReleaseDelay) {ENSURE_HAS_STOPPED(); m_dwReleaseDelay = dwReleaseDelay;} + + virtual BOOL IsHttpAutoStart () {return m_bHttpAutoStart;} + virtual EnHttpVersion GetLocalVersion () {return m_enLocalVersion;} + virtual DWORD GetReleaseDelay () {return m_dwReleaseDelay;} + + virtual BOOL IsUpgrade(CONNID dwConnID); + virtual BOOL IsKeepAlive(CONNID dwConnID); + virtual USHORT GetVersion(CONNID dwConnID); + virtual LPCSTR GetHost(CONNID dwConnID); + virtual ULONGLONG GetContentLength(CONNID dwConnID); + virtual LPCSTR GetContentType(CONNID dwConnID); + virtual LPCSTR GetContentEncoding(CONNID dwConnID); + virtual LPCSTR GetTransferEncoding(CONNID dwConnID); + virtual EnHttpUpgradeType GetUpgradeType(CONNID dwConnID); + virtual USHORT GetParseErrorCode(CONNID dwConnID, LPCSTR* lpszErrorDesc = nullptr); + + virtual BOOL GetHeader(CONNID dwConnID, LPCSTR lpszName, LPCSTR* lpszValue); + virtual BOOL GetHeaders(CONNID dwConnID, LPCSTR lpszName, LPCSTR lpszValue[], DWORD& dwCount); + virtual BOOL GetAllHeaders(CONNID dwConnID, THeader lpHeaders[], DWORD& dwCount); + virtual BOOL GetAllHeaderNames(CONNID dwConnID, LPCSTR lpszName[], DWORD& dwCount); + + virtual BOOL GetCookie(CONNID dwConnID, LPCSTR lpszName, LPCSTR* lpszValue); + virtual BOOL GetAllCookies(CONNID dwConnID, TCookie lpCookies[], DWORD& dwCount); + + virtual USHORT GetUrlFieldSet(CONNID dwConnID); + virtual LPCSTR GetUrlField(CONNID dwConnID, EnHttpUrlField enField); + virtual LPCSTR GetMethod(CONNID dwConnID); + + virtual BOOL GetWSMessageState(CONNID dwConnID, BOOL* lpbFinal, BYTE* lpiReserved, BYTE* lpiOperationCode, LPCBYTE* lpszMask, ULONGLONG* lpullBodyLen, ULONGLONG* lpullBodyRemain); + +private: + BOOL StartHttp(TSocketObj* pSocketObj); + THttpObj* DoStartHttp(TSocketObj* pSocketObj); + +private: + virtual BOOL CheckParams(); + virtual void PrepareStart(); + virtual EnHandleResult FireAccept(TSocketObj* pSocketObj); + virtual EnHandleResult DoFireAccept(TSocketObj* pSocketObj); + virtual EnHandleResult DoFireHandShake(TSocketObj* pSocketObj); + virtual EnHandleResult DoFireReceive(TSocketObj* pSocketObj, const BYTE* pData, int iLength); + virtual EnHandleResult DoFireClose(TSocketObj* pSocketObj, EnSocketOperation enOperation, int iErrorCode); + virtual EnHandleResult DoFireShutdown(); + + virtual void ReleaseGCSocketObj(BOOL bForce = FALSE); + + EnHandleResult DoFireSuperReceive(TSocketObj* pSocketObj, const BYTE* pData, int iLength) + {return __super::DoFireReceive(pSocketObj, pData, iLength);} + + EnHttpParseResult FireMessageBegin(TSocketObj* pSocketObj) + {return m_pListener->OnMessageBegin((IHttpServer*)this, pSocketObj->connID);} + EnHttpParseResult FireRequestLine(TSocketObj* pSocketObj, LPCSTR lpszMethod, LPCSTR lpszUrl) + {return m_pListener->OnRequestLine((IHttpServer*)this, pSocketObj->connID, lpszMethod, lpszUrl);} + EnHttpParseResult FireStatusLine(TSocketObj* pSocketObj, USHORT usStatusCode, LPCSTR lpszDesc) + {return m_pListener->OnStatusLine((IHttpServer*)this, pSocketObj->connID, usStatusCode, lpszDesc);} + EnHttpParseResult FireHeader(TSocketObj* pSocketObj, LPCSTR lpszName, LPCSTR lpszValue) + {return m_pListener->OnHeader((IHttpServer*)this, pSocketObj->connID, lpszName, lpszValue);} + EnHttpParseResult FireHeadersComplete(TSocketObj* pSocketObj) + {return m_pListener->OnHeadersComplete((IHttpServer*)this, pSocketObj->connID);} + EnHttpParseResult FireBody(TSocketObj* pSocketObj, const BYTE* pData, int iLength) + {return m_pListener->OnBody((IHttpServer*)this, pSocketObj->connID, pData, iLength);} + EnHttpParseResult FireChunkHeader(TSocketObj* pSocketObj, int iLength) + {return m_pListener->OnChunkHeader((IHttpServer*)this, pSocketObj->connID, iLength);} + EnHttpParseResult FireChunkComplete(TSocketObj* pSocketObj) + {return m_pListener->OnChunkComplete((IHttpServer*)this, pSocketObj->connID);} + EnHttpParseResult FireMessageComplete(TSocketObj* pSocketObj) + {return m_pListener->OnMessageComplete((IHttpServer*)this, pSocketObj->connID);} + EnHttpParseResult FireUpgrade(TSocketObj* pSocketObj, EnHttpUpgradeType enUpgradeType) + {return m_pListener->OnUpgrade((IHttpServer*)this, pSocketObj->connID, enUpgradeType);} + EnHttpParseResult FireParseError(TSocketObj* pSocketObj, int iErrorCode, LPCSTR lpszErrorDesc) + {return m_pListener->OnParseError((IHttpServer*)this, pSocketObj->connID, iErrorCode, lpszErrorDesc);} + + EnHandleResult FireWSMessageHeader(TSocketObj* pSocketObj, BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], ULONGLONG ullBodyLen) + {return m_pListener->OnWSMessageHeader((IHttpServer*)this, pSocketObj->connID, bFinal, iReserved, iOperationCode, lpszMask, ullBodyLen);} + EnHandleResult FireWSMessageBody(TSocketObj* pSocketObj, const BYTE* pData, int iLength) + {return m_pListener->OnWSMessageBody((IHttpServer*)this, pSocketObj->connID, pData, iLength);} + EnHandleResult FireWSMessageComplete(TSocketObj* pSocketObj) + {return m_pListener->OnWSMessageComplete((IHttpServer*)this, pSocketObj->connID);} + + inline THttpObj* FindHttpObj(CONNID dwConnID); + inline THttpObj* FindHttpObj(TSocketObj* pSocketObj); + + CCookieMgr* GetCookieMgr() {return nullptr;} + LPCSTR GetRemoteDomain(TSocketObj* pSocketObj) {return nullptr;} + +private: + void KillDyingConnection(); + void ReleaseDyingConnection(); + + UINT CleanerThreadProc(PVOID pv = nullptr); + void WaitForCleanerThreadEnd(); + +public: + CHttpServerT(IHttpServerListener* pListener) + : T (pListener) + , m_pListener (pListener) + , m_bHttpAutoStart (TRUE) + , m_enLocalVersion (DEFAULT_HTTP_VERSION) + , m_dwReleaseDelay (DEFAULT_HTTP_RELEASE_DELAY) + { + + } + + virtual ~CHttpServerT() + { + ENSURE_STOP(); + } + +private: + IHttpServerListener* m_pListener; + + CEvt m_evCleaner; + CCleanThread m_thCleaner; + + EnHttpVersion m_enLocalVersion; + DWORD m_dwReleaseDelay; + + BOOL m_bHttpAutoStart; + + CCASQueue m_lsDyingQueue; + + CHttpObjPool m_objPool; +}; + +// ------------------------------------------------------------------------------------------------------------- // + +typedef CHttpServerT CHttpServer; + +#ifdef _SSL_SUPPORT + +#include "SSLServer.h" + +typedef CHttpServerT CHttpsServer; + +#endif + +#endif \ No newline at end of file diff --git a/InternalDef.h b/InternalDef.h new file mode 100644 index 0000000..ee4b4ca --- /dev/null +++ b/InternalDef.h @@ -0,0 +1,132 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "hpsocket/HPTypeDef.h" + +/************************************************************************ +ƣȫֳ +Ĺȫֳ +************************************************************************/ + +/* Socket Сֵ */ +#define MIN_SOCKET_BUFFER_SIZE 8 +/* Сļֽ */ +#define MAX_SMALL_FILE_SIZE 0x3FFFFF +/* ʱ */ +#define MAX_CONNECTION_PERIOD (MAXINT / 2) +/* ¼ʱȡ */ +#define MAX_CONTINUE_READS 100 +/* ¼ʱд */ +#define MAX_CONTINUE_WRITES 100 + +/* ĬϹеȴ¼ */ +#define DEFAULT_WORKER_MAX_EVENT_COUNT CIODispatcher::DEF_WORKER_MAX_EVENTS + +/* Server/Agent */ +#define MAX_CONNECTION_COUNT (5 * 1000 * 1000) +/* Server/Agent Ĭ */ +#define DEFAULT_CONNECTION_COUNT 10000 +/* Server/Agent Ĭ Socket 󻺴ʱ */ +#define DEFAULT_FREE_SOCKETOBJ_LOCK_TIME DEFAULT_OBJECT_CACHE_LOCK_TIME +/* Server/Agent Ĭ Socket شС */ +#define DEFAULT_FREE_SOCKETOBJ_POOL DEFAULT_OBJECT_CACHE_POOL_SIZE +/* Server/Agent Ĭ Socket ػշֵ */ +#define DEFAULT_FREE_SOCKETOBJ_HOLD DEFAULT_OBJECT_CACHE_POOL_HOLD +/* Server/Agent Ĭڴ黺شС */ +#define DEFAULT_FREE_BUFFEROBJ_POOL DEFAULT_BUFFER_CACHE_POOL_SIZE +/* Server/Agent Ĭڴ黺ػշֵ */ +#define DEFAULT_FREE_BUFFEROBJ_HOLD DEFAULT_BUFFER_CACHE_POOL_HOLD +/* Client Ĭڴ黺شС */ +#define DEFAULT_CLIENT_FREE_BUFFER_POOL_SIZE 60 +/* Client Ĭڴ黺ػշֵ */ +#define DEFAULT_CLIENT_FREE_BUFFER_POOL_HOLD 60 +/* Client/Agent Ĭͬӳʱʱ */ +#define DEFAULT_SYNC_CONNECT_TIMEOUT 10000 +/* IPv4 Ĭϰ󶨵ַ */ +#define DEFAULT_IPV4_BIND_ADDRESS _T("0.0.0.0") +/* IPv6 Ĭϰ󶨵ַ */ +#define DEFAULT_IPV6_BIND_ADDRESS _T("::") +/* IPv4 㲥ַ */ +#define DEFAULT_IPV4_BROAD_CAST_ADDRESS _T("255.255.255.255") + +/* TCP ĬͨݻС */ +#define DEFAULT_TCP_SOCKET_BUFFER_SIZE DEFAULT_BUFFER_CACHE_CAPACITY +/* TCP Ĭ */ +#define DEFALUT_TCP_KEEPALIVE_TIME (60 * 1000) +/* TCP Ĭȷϰ */ +#define DEFALUT_TCP_KEEPALIVE_INTERVAL (20 * 1000) +/* TCP Server Ĭ Listen дС */ +#define DEFAULT_TCP_SERVER_SOCKET_LISTEN_QUEUE SOMAXCONN + +/* UDP ݱ󳤶 */ +#define MAXIMUM_UDP_MAX_DATAGRAM_SIZE (16 * DEFAULT_BUFFER_CACHE_CAPACITY) +/* UDP Ĭݱ󳤶 */ +#define DEFAULT_UDP_MAX_DATAGRAM_SIZE 1432 +/* UDP Ĭ Receive ԤͶ */ +#define DEFAULT_UDP_POST_RECEIVE_COUNT DEFAULT_WORKER_MAX_EVENT_COUNT +/* UDP ĬϼԴ */ +#define DEFAULT_UDP_DETECT_ATTEMPTS 3 +/* UDP Ĭϼͼ */ +#define DEFAULT_UDP_DETECT_INTERVAL (60 * 1000) + +/* TCP Pack λ */ +#define TCP_PACK_LENGTH_BITS 22 +/* TCP Pack */ +#define TCP_PACK_LENGTH_MASK 0x3FFFFF +/* TCP Pack 󳤶Ӳ */ +#define TCP_PACK_MAX_SIZE_LIMIT 0x3FFFFF +/* TCP Pack Ĭ󳤶 */ +#define TCP_PACK_DEFAULT_MAX_SIZE 0x040000 +/* TCP Pack ͷʶֵӲ */ +#define TCP_PACK_HEADER_FLAG_LIMIT 0x0003FF +/* TCP Pack ͷĬϱʶֵ */ +#define TCP_PACK_DEFAULT_HEADER_FLAG 0x000000 + +/* Ĭѹ/ѹݻ */ +#define DEFAULT_COMPRESS_BUFFER_SIZE (16 * 1024) + +/* ռ룩 */ +#define GC_CHECK_INTERVAL (15 * 1000) + +#define HOST_SEPARATOR_CHAR '^' +#define PORT_SEPARATOR_CHAR ':' +#define IPV6_ADDR_BEGIN_CHAR '[' +#define IPV6_ADDR_END_CHAR ']' +#define IPV4_ADDR_SEPARATOR_CHAR '.' +#define IPV6_ADDR_SEPARATOR_CHAR ':' +#define IPV6_ZONE_INDEX_CHAR '%' + +#define CST_CONNECTING (-1) +#define INVALID_SOCKET INVALID_FD +#define SOCKET_ERROR HAS_ERROR +#define WSASetLastError SetLastError +#define WSAGetLastError GetLastError +#define InetPton inet_pton +#define InetNtop inet_ntop +#define closesocket close + +#define ENSURE_STOP() {if(GetState() != SS_STOPPED) {Stop();} Wait();} +#define ENSURE_HAS_STOPPED() {ASSERT(GetState() == SS_STOPPED); if(GetState() != SS_STOPPED) return;} +#define WAIT_FOR_STOP_PREDICATE [this]() {return GetState() == SS_STOPPED;} diff --git a/MiscHelper.cpp b/MiscHelper.cpp new file mode 100644 index 0000000..5a238ec --- /dev/null +++ b/MiscHelper.cpp @@ -0,0 +1,51 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MiscHelper.h" + +BOOL AddPackHeader(const WSABUF * pBuffers, int iCount, unique_ptr& buffers, DWORD dwMaxPackSize, USHORT usPackHeaderFlag, DWORD& dwHeader) +{ + ASSERT(pBuffers && iCount > 0); + + DWORD iLength = 0; + + for(int i = 0; i < iCount; i++) + { + const WSABUF& buf = pBuffers[i]; + buffers[i + 1] = buf; + iLength += buf.len; + } + + if(iLength == 0 || iLength > dwMaxPackSize) + { + ::SetLastError(ERROR_BAD_LENGTH); + return FALSE; + } + + dwHeader = ::HToLE32((usPackHeaderFlag << TCP_PACK_LENGTH_BITS) | iLength); + + buffers[0].len = sizeof(dwHeader); + buffers[0].buf = (LPBYTE)&dwHeader; + + return TRUE; +} diff --git a/MiscHelper.h b/MiscHelper.h new file mode 100644 index 0000000..4f6beae --- /dev/null +++ b/MiscHelper.h @@ -0,0 +1,157 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "SocketHelper.h" + +/* Pack Data Info */ +template struct TPackInfo +{ + bool header; + DWORD length; + B* pBuffer; + + static TPackInfo* Construct(B* pbuf = nullptr, bool head = true, DWORD len = sizeof(DWORD)) + { + return new TPackInfo(pbuf, head, len); + } + + static void Destruct(TPackInfo* pPackInfo) + { + if(pPackInfo) + delete pPackInfo; + } + + TPackInfo(B* pbuf = nullptr, bool head = true, DWORD len = sizeof(DWORD)) + : header(head), length(len), pBuffer(pbuf) + { + } + + void Reset() + { + header = true; + length = sizeof(DWORD); + pBuffer = nullptr; + } +}; + +typedef TPackInfo TBufferPackInfo; + +BOOL AddPackHeader(const WSABUF * pBuffers, int iCount, unique_ptr& buffers, DWORD dwMaxPackSize, USHORT usPackHeaderFlag, DWORD& dwHeader); + +template EnFetchResult FetchBuffer(B* pBuffer, BYTE* pData, int iLength) +{ + ASSERT(pBuffer != nullptr); + ASSERT(pData != nullptr && iLength > 0); + + EnFetchResult result = FR_OK; + + if(pBuffer->Length() >= iLength) + pBuffer->Fetch(pData, iLength); + else + result = FR_LENGTH_TOO_LONG; + + return result; +} + +template EnFetchResult PeekBuffer(B* pBuffer, BYTE* pData, int iLength) +{ + ASSERT(pBuffer != nullptr); + ASSERT(pData != nullptr && iLength > 0); + + EnFetchResult result = FR_OK; + + if(pBuffer->Length() >= iLength) + pBuffer->Peek(pData, iLength); + else + result = FR_LENGTH_TOO_LONG; + + return result; +} + +template EnHandleResult ParsePack(T* pThis, TPackInfo* pInfo, B* pBuffer, S* pSocket, DWORD dwMaxPackSize, USHORT usPackHeaderFlag) +{ + EnHandleResult rs = HR_OK; + + int required = pInfo->length; + int remain = pBuffer->Length(); + + while(remain >= required) + { + if(pSocket->IsPaused()) + break; + + remain -= required; + CBufferPtr buffer(required); + + pBuffer->Fetch(buffer, (int)buffer.Size()); + + if(pInfo->header) + { + DWORD header = ::HToLE32(*((DWORD*)(BYTE*)buffer)); + + if(usPackHeaderFlag != 0) + { + USHORT flag = (USHORT)(header >> TCP_PACK_LENGTH_BITS); + + if(flag != usPackHeaderFlag) + { + ::SetLastError(ERROR_INVALID_DATA); + return HR_ERROR; + } + } + + DWORD len = header & TCP_PACK_LENGTH_MASK; + + if(len == 0 || len > dwMaxPackSize) + { + ::SetLastError(ERROR_BAD_LENGTH); + return HR_ERROR; + } + + required = len; + } + else + { + rs = pThis->DoFireSuperReceive(pSocket, (const BYTE*)buffer, (int)buffer.Size()); + + if(rs == HR_ERROR) + return rs; + + required = sizeof(DWORD); + } + + pInfo->header = !pInfo->header; + pInfo->length = required; + } + + return rs; +} + +template EnHandleResult ParsePack(T* pThis, TPackInfo* pInfo, B* pBuffer, S* pSocket, DWORD dwMaxPackSize, USHORT usPackHeaderFlag, const BYTE* pData, int iLength) +{ + pBuffer->Cat(pData, iLength); + + return ParsePack(pThis, pInfo, pBuffer, pSocket, dwMaxPackSize, usPackHeaderFlag); +} diff --git a/SSLAgent.cpp b/SSLAgent.cpp new file mode 100644 index 0000000..4b3dd7d --- /dev/null +++ b/SSLAgent.cpp @@ -0,0 +1,229 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SSLAgent.h" +#include "SSLHelper.h" + +#ifdef _SSL_SUPPORT + +BOOL CSSLAgent::CheckParams() +{ + if(!m_sslCtx.IsValid()) + { + SetLastError(SE_SSL_ENV_NOT_READY, __FUNCTION__, ERROR_NOT_READY); + return FALSE; + } + + return __super::CheckParams(); +} + +void CSSLAgent::PrepareStart() +{ + __super::PrepareStart(); + + m_sslPool.SetItemCapacity (GetSocketBufferSize()); + m_sslPool.SetItemPoolSize (GetFreeBufferObjPool()); + m_sslPool.SetItemPoolHold (GetFreeBufferObjHold()); + m_sslPool.SetSessionLockTime(GetFreeSocketObjLockTime()); + m_sslPool.SetSessionPoolSize(GetFreeSocketObjPool()); + m_sslPool.SetSessionPoolHold(GetFreeSocketObjHold()); + + m_sslPool.Prepare(); +} + +void CSSLAgent::Reset() +{ + m_sslPool.Clear(); + m_sslCtx.RemoveThreadLocalState(); + + __super::Reset(); +} + +void CSSLAgent::OnWorkerThreadEnd(THR_ID dwThreadID) +{ + m_sslCtx.RemoveThreadLocalState(); + + __super::OnWorkerThreadEnd(dwThreadID); +} + +void CSSLAgent::ReleaseGCSocketObj(BOOL bForce) +{ + __super::ReleaseGCSocketObj(bForce); + +#ifdef USE_EXTERNAL_GC + m_sslPool.ReleaseGCSession(bForce); +#endif +} + +BOOL CSSLAgent::SendPackets(CONNID dwConnID, const WSABUF pBuffers[], int iCount) +{ + ASSERT(pBuffers && iCount > 0); + + TAgentSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TAgentSocketObj::IsValid(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + CSSLSession* pSession = nullptr; + GetConnectionReserved2(pSocketObj, (PVOID*)&pSession); + + if(pSession != nullptr) + { + CLocalSafeCounter localcounter(*pSession); + return ::ProcessSend(this, pSocketObj, pSession, pBuffers, iCount); + } + + return DoSendPackets(pSocketObj, pBuffers, iCount); +} + +EnHandleResult CSSLAgent::FireConnect(TAgentSocketObj* pSocketObj) +{ + EnHandleResult result = DoFireConnect(pSocketObj); + + if(result != HR_ERROR && m_bSSLAutoHandShake) + DoSSLHandShake(pSocketObj); + + return result; +} + +EnHandleResult CSSLAgent::FireReceive(TAgentSocketObj* pSocketObj, const BYTE* pData, int iLength) +{ + CSSLSession* pSession = nullptr; + GetConnectionReserved2(pSocketObj, (PVOID*)&pSession); + + if(pSession != nullptr) + { + CLocalSafeCounter localcounter(*pSession); + return ::ProcessReceive(this, pSocketObj, pSession, pData, iLength); + } + + return DoFireReceive(pSocketObj, pData, iLength); +} + +EnHandleResult CSSLAgent::FireClose(TAgentSocketObj* pSocketObj, EnSocketOperation enOperation, int iErrorCode) +{ + EnHandleResult result = DoFireClose(pSocketObj, enOperation, iErrorCode); + + CSSLSession* pSession = nullptr; + GetConnectionReserved2(pSocketObj, (PVOID*)&pSession); + + if(pSession != nullptr) + m_sslPool.PutFreeSession(pSession); + + return result; +} + +BOOL CSSLAgent::StartSSLHandShake(CONNID dwConnID) +{ + if(IsSSLAutoHandShake()) + { + ::SetLastError(ERROR_INVALID_OPERATION); + return FALSE; + } + + TAgentSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TAgentSocketObj::IsValid(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + return StartSSLHandShake(pSocketObj); +} + +BOOL CSSLAgent::StartSSLHandShake(TAgentSocketObj* pSocketObj) +{ + if(!pSocketObj->HasConnected()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + CReentrantCriSecLock locallock(pSocketObj->csSend); + + if(!TAgentSocketObj::IsValid(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + if(!pSocketObj->HasConnected()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + CSSLSession* pSession = nullptr; + GetConnectionReserved2(pSocketObj, (PVOID*)&pSession); + + if(pSession != nullptr) + { + ::SetLastError(ERROR_ALREADY_INITIALIZED); + return FALSE; + } + + DoSSLHandShake(pSocketObj); + + return TRUE; +} + +void CSSLAgent::DoSSLHandShake(TAgentSocketObj* pSocketObj) +{ + CSSLSession* pSession = m_sslPool.PickFreeSession(pSocketObj->host); + + CLocalSafeCounter localcounter(*pSession); + + ENSURE(SetConnectionReserved2(pSocketObj, pSession)); + ENSURE(::ProcessHandShake(this, pSocketObj, pSession) == HR_OK); +} + +BOOL CSSLAgent::GetSSLSessionInfo(CONNID dwConnID, EnSSLSessionInfo enInfo, LPVOID* lppInfo) +{ + ASSERT(lppInfo != nullptr); + + *lppInfo = nullptr; + TAgentSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TAgentSocketObj::IsValid(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + CSSLSession* pSession = nullptr; + GetConnectionReserved2(pSocketObj, (PVOID*)&pSession); + + if(pSession == nullptr || !pSession->IsValid()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + return pSession->GetSessionInfo(enInfo, lppInfo); +} + +#endif \ No newline at end of file diff --git a/SSLAgent.h b/SSLAgent.h new file mode 100644 index 0000000..901c9ba --- /dev/null +++ b/SSLAgent.h @@ -0,0 +1,107 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "TcpAgent.h" +#include "SSLHelper.h" + +#ifdef _SSL_SUPPORT + +class CSSLAgent : public CTcpAgent +{ + using __super = CTcpAgent; + +public: + using __super::Wait; + +public: + virtual BOOL IsSecure() {return TRUE;} + virtual BOOL SendPackets(CONNID dwConnID, const WSABUF pBuffers[], int iCount); + + virtual BOOL SetupSSLContext(int iVerifyMode = SSL_VM_NONE, LPCTSTR lpszPemCertFile = nullptr, LPCTSTR lpszPemKeyFile = nullptr, LPCTSTR lpszKeyPassword = nullptr, LPCTSTR lpszCAPemCertFileOrPath = nullptr) + {return m_sslCtx.Initialize(SSL_SM_CLIENT, iVerifyMode, FALSE, (LPVOID)lpszPemCertFile, (LPVOID)lpszPemKeyFile, (LPVOID)lpszKeyPassword, (LPVOID)lpszCAPemCertFileOrPath, nullptr);} + + virtual BOOL SetupSSLContextByMemory(int iVerifyMode = SSL_VM_NONE, LPCSTR lpszPemCert = nullptr, LPCSTR lpszPemKey = nullptr, LPCSTR lpszKeyPassword = nullptr, LPCSTR lpszCAPemCert = nullptr) + {return m_sslCtx.Initialize(SSL_SM_CLIENT, iVerifyMode, TRUE, (LPVOID)lpszPemCert, (LPVOID)lpszPemKey, (LPVOID)lpszKeyPassword, (LPVOID)lpszCAPemCert, nullptr);} + + + virtual void CleanupSSLContext() + {m_sslCtx.Cleanup();} + + virtual BOOL StartSSLHandShake(CONNID dwConnID); + +public: + virtual void SetSSLAutoHandShake(BOOL bAutoHandShake) {ENSURE_HAS_STOPPED(); m_bSSLAutoHandShake = bAutoHandShake;} + virtual void SetSSLCipherList (LPCTSTR lpszCipherList){ENSURE_HAS_STOPPED(); m_sslCtx.SetCipherList(lpszCipherList);} + virtual BOOL IsSSLAutoHandShake () {return m_bSSLAutoHandShake;} + virtual LPCTSTR GetSSLCipherList() {return m_sslCtx.GetCipherList();} + + virtual BOOL GetSSLSessionInfo(CONNID dwConnID, EnSSLSessionInfo enInfo, LPVOID* lppInfo); + +protected: + virtual EnHandleResult FireConnect(TAgentSocketObj* pSocketObj); + virtual EnHandleResult FireReceive(TAgentSocketObj* pSocketObj, const BYTE* pData, int iLength); + virtual EnHandleResult FireClose(TAgentSocketObj* pSocketObj, EnSocketOperation enOperation, int iErrorCode); + + virtual BOOL CheckParams(); + virtual void PrepareStart(); + virtual void Reset(); + + virtual void OnWorkerThreadEnd(THR_ID dwThreadID); + + virtual void ReleaseGCSocketObj(BOOL bForce = FALSE); + +protected: + virtual BOOL StartSSLHandShake(TAgentSocketObj* pSocketObj); + +private: + void DoSSLHandShake(TAgentSocketObj* pSocketObj); + +private: + friend EnHandleResult ProcessHandShake<>(CSSLAgent* pThis, TAgentSocketObj* pSocketObj, CSSLSession* pSession); + friend EnHandleResult ProcessReceive<>(CSSLAgent* pThis, TAgentSocketObj* pSocketObj, CSSLSession* pSession, const BYTE* pData, int iLength); + friend BOOL ProcessSend<>(CSSLAgent* pThis, TAgentSocketObj* pSocketObj, CSSLSession* pSession, const WSABUF * pBuffers, int iCount); + +public: + CSSLAgent(ITcpAgentListener* pListener) + : CTcpAgent(pListener) + , m_sslPool(m_sslCtx) + , m_bSSLAutoHandShake(TRUE) + { + + } + + virtual ~CSSLAgent() + { + ENSURE_STOP(); + } + +private: + BOOL m_bSSLAutoHandShake; + + CSSLContext m_sslCtx; + CSSLSessionPool m_sslPool; +}; + +#endif \ No newline at end of file diff --git a/SSLClient.cpp b/SSLClient.cpp new file mode 100644 index 0000000..7dc2ece --- /dev/null +++ b/SSLClient.cpp @@ -0,0 +1,150 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SSLClient.h" +#include "SSLHelper.h" + +#ifdef _SSL_SUPPORT + +BOOL CSSLClient::CheckParams() +{ + if(!m_sslCtx.IsValid()) + { + SetLastError(SE_SSL_ENV_NOT_READY, __FUNCTION__, ERROR_NOT_READY); + return FALSE; + } + + return __super::CheckParams(); +} + +void CSSLClient::PrepareStart() +{ + m_dwMainThreadID = SELF_THREAD_ID; + + __super::PrepareStart(); +} + +void CSSLClient::Reset() +{ + m_sslSession.Reset(); + + if(m_dwMainThreadID != 0) + { + m_sslCtx.RemoveThreadLocalState(m_dwMainThreadID); + m_dwMainThreadID = 0; + } + + __super::Reset(); +} + +void CSSLClient::OnWorkerThreadEnd(THR_ID dwThreadID) +{ + m_sslCtx.RemoveThreadLocalState(); + + __super::OnWorkerThreadEnd(dwThreadID); +} + +BOOL CSSLClient::SendPackets(const WSABUF pBuffers[], int iCount) +{ + ASSERT(pBuffers && iCount > 0); + + if(m_sslSession.IsValid()) + return ::ProcessSend(this, this, &m_sslSession, pBuffers, iCount); + else + return DoSendPackets(this, pBuffers, iCount); +} + +EnHandleResult CSSLClient::FireConnect() +{ + EnHandleResult result = DoFireConnect(this); + + if(result != HR_ERROR && m_bSSLAutoHandShake) + DoSSLHandShake(); + + return result; +} + +EnHandleResult CSSLClient::FireReceive(const BYTE* pData, int iLength) +{ + if(m_sslSession.IsValid()) + return ::ProcessReceive(this, this, &m_sslSession, pData, iLength); + else + return DoFireReceive(this, pData, iLength); +} + +BOOL CSSLClient::StartSSLHandShake() +{ + if(IsSSLAutoHandShake()) + { + ::SetLastError(ERROR_INVALID_OPERATION); + return FALSE; + } + + return StartSSLHandShakeNoCheck(); +} + +BOOL CSSLClient::StartSSLHandShakeNoCheck() +{ + if(!IsConnected()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + CCriSecLock locallock(m_sslSession.GetSendLock()); + + if(!IsConnected()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + if(m_sslSession.IsValid()) + { + ::SetLastError(ERROR_ALREADY_INITIALIZED); + return FALSE; + } + + DoSSLHandShake(); + + return TRUE; +} + +void CSSLClient::DoSSLHandShake() +{ + m_sslSession.Renew(m_sslCtx, m_strHost); + ENSURE(::ProcessHandShake(this, this, &m_sslSession) == HR_OK); +} + +BOOL CSSLClient::GetSSLSessionInfo(EnSSLSessionInfo enInfo, LPVOID* lppInfo) +{ + if(!m_sslSession.IsValid()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + return m_sslSession.GetSessionInfo(enInfo, lppInfo); +} + +#endif \ No newline at end of file diff --git a/SSLClient.h b/SSLClient.h new file mode 100644 index 0000000..0f68fd8 --- /dev/null +++ b/SSLClient.h @@ -0,0 +1,105 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "TcpClient.h" +#include "SSLHelper.h" + +#ifdef _SSL_SUPPORT + +class CSSLClient : public CTcpClient +{ + using __super = CTcpClient; + +public: + using __super::Wait; + +public: + virtual BOOL IsSecure() {return TRUE;} + virtual BOOL SendPackets(const WSABUF pBuffers[], int iCount); + + virtual BOOL SetupSSLContext(int iVerifyMode = SSL_VM_NONE, LPCTSTR lpszPemCertFile = nullptr, LPCTSTR lpszPemKeyFile = nullptr, LPCTSTR lpszKeyPassword = nullptr, LPCTSTR lpszCAPemCertFileOrPath = nullptr) + {return m_sslCtx.Initialize(SSL_SM_CLIENT, iVerifyMode, FALSE, (LPVOID)lpszPemCertFile, (LPVOID)lpszPemKeyFile, (LPVOID)lpszKeyPassword, (LPVOID)lpszCAPemCertFileOrPath, nullptr);} + + virtual BOOL SetupSSLContextByMemory(int iVerifyMode = SSL_VM_NONE, LPCSTR lpszPemCert = nullptr, LPCSTR lpszPemKey = nullptr, LPCSTR lpszKeyPassword = nullptr, LPCSTR lpszCAPemCert = nullptr) + {return m_sslCtx.Initialize(SSL_SM_CLIENT, iVerifyMode, TRUE, (LPVOID)lpszPemCert, (LPVOID)lpszPemKey, (LPVOID)lpszKeyPassword, (LPVOID)lpszCAPemCert, nullptr);} + + virtual void CleanupSSLContext() + {m_sslCtx.Cleanup();} + + virtual BOOL StartSSLHandShake(); + +public: + virtual void SetSSLAutoHandShake(BOOL bAutoHandShake) {ENSURE_HAS_STOPPED(); m_bSSLAutoHandShake = bAutoHandShake;} + virtual void SetSSLCipherList (LPCTSTR lpszCipherList){ENSURE_HAS_STOPPED(); m_sslCtx.SetCipherList(lpszCipherList);} + virtual BOOL IsSSLAutoHandShake () {return m_bSSLAutoHandShake;} + virtual LPCTSTR GetSSLCipherList() {return m_sslCtx.GetCipherList();} + + virtual BOOL GetSSLSessionInfo(EnSSLSessionInfo enInfo, LPVOID* lppInfo); + +protected: + virtual EnHandleResult FireConnect(); + virtual EnHandleResult FireReceive(const BYTE* pData, int iLength); + + virtual BOOL CheckParams(); + virtual void PrepareStart(); + virtual void Reset(); + + virtual void OnWorkerThreadEnd(THR_ID dwThreadID); + +protected: + virtual BOOL StartSSLHandShakeNoCheck(); + +private: + void DoSSLHandShake(); + +private: + friend EnHandleResult ProcessHandShake<>(CSSLClient* pThis, CSSLClient* pSocketObj, CSSLSession* pSession); + friend EnHandleResult ProcessReceive<>(CSSLClient* pThis, CSSLClient* pSocketObj, CSSLSession* pSession, const BYTE* pData, int iLength); + friend BOOL ProcessSend<>(CSSLClient* pThis, CSSLClient* pSocketObj, CSSLSession* pSession, const WSABUF * pBuffers, int iCount); + +public: + CSSLClient(ITcpClientListener* pListener) + : CTcpClient (pListener) + , m_sslSession (m_itPool) + , m_dwMainThreadID (0) + , m_bSSLAutoHandShake (TRUE) + { + + } + + virtual ~CSSLClient() + { + ENSURE_STOP(); + } + +private: + THR_ID m_dwMainThreadID; + BOOL m_bSSLAutoHandShake; + + CSSLContext m_sslCtx; + CSSLSession m_sslSession; +}; + +#endif \ No newline at end of file diff --git a/SSLHelper.cpp b/SSLHelper.cpp new file mode 100644 index 0000000..27be3a6 --- /dev/null +++ b/SSLHelper.cpp @@ -0,0 +1,1200 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SSLHelper.h" + +#ifdef _SSL_SUPPORT + +#include "SocketHelper.h" +#include "common/FileHelper.h" + +#include "openssl/ssl.h" +#include "openssl/err.h" +#include "openssl/engine.h" +#include "openssl/x509v3.h" + +#if OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_1_1_0 + int CSSLInitializer::sm_iLockNum = 0; + CSimpleRWLock* CSSLInitializer::sm_pcsLocks = nullptr; +#endif + +CSSLInitializer CSSLInitializer::sm_instance; + +const DWORD CSSLSessionPool::DEFAULT_ITEM_CAPACITY = CItemPool::DEFAULT_ITEM_CAPACITY; +const DWORD CSSLSessionPool::DEFAULT_ITEM_POOL_SIZE = CItemPool::DEFAULT_POOL_SIZE; +const DWORD CSSLSessionPool::DEFAULT_ITEM_POOL_HOLD = CItemPool::DEFAULT_POOL_HOLD; +const DWORD CSSLSessionPool::DEFAULT_SESSION_LOCK_TIME = DEFAULT_OBJECT_CACHE_LOCK_TIME; +const DWORD CSSLSessionPool::DEFAULT_SESSION_POOL_SIZE = DEFAULT_OBJECT_CACHE_POOL_SIZE; +const DWORD CSSLSessionPool::DEFAULT_SESSION_POOL_HOLD = DEFAULT_OBJECT_CACHE_POOL_HOLD; + +CSSLInitializer::CSSLInitializer() +{ +#if OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_1_1_0 + sm_iLockNum = CRYPTO_num_locks(); + + if(sm_iLockNum > 0) + sm_pcsLocks = new CSimpleRWLock[sm_iLockNum]; +/* +#ifdef _DEBUG + CRYPTO_malloc_debug_init(); + CRYPTO_dbg_set_options(V_CRYPTO_MDEBUG_ALL); + CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON); +#endif +*/ + CRYPTO_set_locking_callback (&ssl_lock_callback); + CRYPTO_set_dynlock_create_callback (&ssl_lock_dyn_create_callback); + CRYPTO_set_dynlock_destroy_callback (&ssl_lock_dyn_destroy_callback); + CRYPTO_set_dynlock_lock_callback (&ssl_lock_dyn_callback); + + SSL_library_init(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); +#else + OPENSSL_init_ssl(OPENSSL_INIT_SSL_DEFAULT, nullptr); +#endif +} + +CSSLInitializer::~CSSLInitializer() +{ + CleanupThreadState(); + +#if OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_1_1_0 + CONF_modules_free(); + ENGINE_cleanup(); + EVP_cleanup(); + CRYPTO_cleanup_all_ex_data(); + ERR_free_strings(); +#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_1_0_2 + SSL_COMP_free_compression_methods(); +#endif + + CRYPTO_set_locking_callback (nullptr); + CRYPTO_set_dynlock_create_callback (nullptr); + CRYPTO_set_dynlock_destroy_callback (nullptr); + CRYPTO_set_dynlock_lock_callback (nullptr); + + if(sm_iLockNum > 0) + { + delete[] sm_pcsLocks; + + sm_pcsLocks = nullptr; + sm_iLockNum = 0; + } +#endif +} + +void CSSLInitializer::CleanupThreadState(THR_ID dwThreadID) +{ +#if OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_1_1_0 + CRYPTO_THREADID tid = {nullptr, dwThreadID}; + + CRYPTO_THREADID_current(&tid); + ERR_remove_thread_state(&tid); +#else + OPENSSL_thread_stop(); +#endif +} + +#if OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_1_1_0 + +void CSSLInitializer::ssl_lock_callback(int mode, int n, const char *file, int line) +{ + mode & CRYPTO_LOCK + ? (mode & CRYPTO_READ + ? sm_pcsLocks[n].lock_shared() + : sm_pcsLocks[n].lock()) + : (mode & CRYPTO_READ + ? sm_pcsLocks[n].unlock_shared() + : sm_pcsLocks[n].unlock()); +} + +CRYPTO_dynlock_value* CSSLInitializer::ssl_lock_dyn_create_callback(const char *file, int line) + { + return new DynamicLock; + } + +void CSSLInitializer::ssl_lock_dyn_callback(int mode, CRYPTO_dynlock_value* l, const char *file, int line) +{ + mode & CRYPTO_LOCK + ? (mode & CRYPTO_READ + ? l->cs.lock_shared() + : l->cs.lock()) + : (mode & CRYPTO_READ + ? l->cs.unlock_shared() + : l->cs.unlock()); +} + +void CSSLInitializer::ssl_lock_dyn_destroy_callback(CRYPTO_dynlock_value* l, const char *file, int line) +{ + delete l; +} + +#endif + +BOOL CSSLContext::Initialize(EnSSLSessionMode enSessionMode, int iVerifyMode, BOOL bMemory, LPVOID lpPemCert, LPVOID lpPemKey, LPVOID lpKeyPasswod, LPVOID lpCAPemCert, HP_Fn_SNI_ServerNameCallback fnServerNameCallback) +{ + ASSERT(!IsValid()); + + if(IsValid()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + m_enSessionMode = enSessionMode; + + if(AddContext(iVerifyMode, bMemory, lpPemCert, lpPemKey, lpKeyPasswod, lpCAPemCert) == 0) + m_sslCtx = GetContext(0); + else + { + EXECUTE_RESTORE_ERROR(Cleanup()); + return FALSE; + } + + SetServerNameCallback(fnServerNameCallback); + + return TRUE; +} + +int CSSLContext::AddServerContext(int iVerifyMode, BOOL bMemory, LPVOID lpPemCert, LPVOID lpPemKey, LPVOID lpKeyPasswod, LPVOID lpCAPemCert) +{ + ASSERT(IsValid()); + + if(!IsValid()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + if(m_enSessionMode != SSL_SM_SERVER) + { + ::SetLastError(ERROR_INVALID_OPERATION); + return FALSE; + } + + return AddContext(iVerifyMode, bMemory, lpPemCert, lpPemKey, lpKeyPasswod, lpCAPemCert); +} + +BOOL CSSLContext::BindServerName(LPCTSTR lpszServerName, int iContextIndex) +{ + ASSERT(lpszServerName && iContextIndex >= 0 && !::IsIPAddress(lpszServerName)); + + if(!lpszServerName || iContextIndex < 0 || ::IsIPAddress(lpszServerName)) + { + ::SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + int iLen = lstrlen(lpszServerName); + LPCTSTR lpszSep = ::StrChr(lpszServerName, SSL_DOMAIN_SEP_CHAR); + + if(lpszSep == nullptr || lpszSep == lpszServerName || lpszSep == (lpszServerName + iLen - 1)) + { + ::SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + int iSize = (int)m_lsSslCtxs.size(); + + if(iSize <= iContextIndex) + { + ::SetLastError(ERROR_INVALID_INDEX); + return FALSE; + } + + m_sslServerNames[lpszServerName] = iContextIndex; + + return TRUE; +} + +int CSSLContext::AddContext(int iVerifyMode, BOOL bMemory, LPVOID lpPemCert, LPVOID lpPemKey, LPVOID lpKeyPasswod, LPVOID lpCAPemCert) +{ + USES_CONVERSION; + + int iIndex = -1; + +#if OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_1_1_0 + SSL_CTX* sslCtx = SSL_CTX_new(SSLv23_method()); +#else + SSL_CTX* sslCtx = SSL_CTX_new(TLS_method()); +#endif + + SSL_CTX_set_quiet_shutdown(sslCtx, 1); + SSL_CTX_set_verify(sslCtx, iVerifyMode, nullptr); + + if(!SSL_CTX_set_cipher_list(sslCtx, T2CA(m_strCipherList))) + ::SetLastError(ERROR_EMPTY); + else + { + if(m_enSessionMode == SSL_SM_SERVER) + { + static volatile ULONG s_session_id_context = 0; + ULONG session_id_context = ::InterlockedIncrement(&s_session_id_context); + + SSL_CTX_set_session_id_context(sslCtx, (BYTE*)&session_id_context, sizeof(session_id_context)); + } + + if(LoadCertAndKey(sslCtx, iVerifyMode, bMemory, lpPemCert, lpPemKey, lpKeyPasswod, lpCAPemCert)) + { + iIndex = (int)m_lsSslCtxs.size(); + m_lsSslCtxs.push_back(sslCtx); + } + } + + if(iIndex < 0) + EXECUTE_RESTORE_ERROR(SSL_CTX_free(sslCtx)); + + return iIndex; +} + +BOOL CSSLContext::LoadCertAndKey(SSL_CTX* sslCtx, int iVerifyMode, BOOL bMemory, LPVOID lpPemCert, LPVOID lpPemKey, LPVOID lpKeyPasswod, LPVOID lpCAPemCert) +{ + if(bMemory) + return LoadCertAndKeyByMemory(sslCtx, iVerifyMode, (LPCSTR)lpPemCert, (LPCSTR)lpPemKey, (LPCSTR)lpKeyPasswod, (LPCSTR)lpCAPemCert); + else + return LoadCertAndKeyByFile(sslCtx, iVerifyMode, (LPCTSTR)lpPemCert, (LPCTSTR)lpPemKey, (LPCTSTR)lpKeyPasswod, (LPCTSTR)lpCAPemCert); +} + +BOOL CSSLContext::LoadCertAndKeyByFile(SSL_CTX* sslCtx, int iVerifyMode, LPCTSTR lpszPemCertFile, LPCTSTR lpszPemKeyFile, LPCTSTR lpszKeyPassword, LPCTSTR lpszCAPemCertFileOrPath) +{ + USES_CONVERSION; + + if(::IsStrNotEmpty(lpszCAPemCertFileOrPath)) + { + LPCTSTR lpszCAPemCertFile = nullptr; + LPCTSTR lpszCAPemCertPath = nullptr; + + CFile fCAPemCertFile(lpszCAPemCertFileOrPath, O_RDONLY | O_CLOEXEC); + + if(!fCAPemCertFile.IsExist()) + { + ::SetLastError(ERROR_FILE_NOT_FOUND); + return FALSE; + } + + if(fCAPemCertFile.IsFile()) + lpszCAPemCertFile = lpszCAPemCertFileOrPath; + else if(fCAPemCertFile.IsDirectory()) + lpszCAPemCertPath = lpszCAPemCertFileOrPath; + else + { + ::SetLastError(ERROR_BAD_FILE_TYPE); + return FALSE; + } + + if(!SSL_CTX_load_verify_locations(sslCtx, T2CA(lpszCAPemCertFile), T2CA(lpszCAPemCertPath))) + { + ::SetLastError(ERROR_INVALID_DATA); + return FALSE; + } + + if(!SSL_CTX_set_default_verify_paths(sslCtx)) + { + ::SetLastError(ERROR_FUNCTION_FAILED); + return FALSE; + } + + if(m_enSessionMode == SSL_SM_SERVER && (iVerifyMode & SSL_VM_PEER) && lpszCAPemCertFile != nullptr) + { + STACK_OF(X509_NAME)* caCertNames = SSL_load_client_CA_file(T2CA(lpszCAPemCertFile)); + + if(caCertNames == nullptr) + { + ::SetLastError(ERROR_EMPTY); + return FALSE; + } + + SSL_CTX_set_client_CA_list(sslCtx, caCertNames); + } + } + + if(::IsStrNotEmpty(lpszPemCertFile)) + { + CFile fPemCertFile(lpszPemCertFile, O_RDONLY | O_CLOEXEC); + + if(!fPemCertFile.IsFile()) + { + ::SetLastError(ERROR_FILE_NOT_FOUND); + return FALSE; + } + + if(::IsStrEmpty(lpszPemKeyFile)) + { + ::SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + CFile fPemKeyFile(lpszPemKeyFile, O_RDONLY | O_CLOEXEC); + + if(!fPemKeyFile.IsFile()) + { + ::SetLastError(ERROR_FILE_NOT_FOUND); + return FALSE; + } + + if(::IsStrNotEmpty(lpszKeyPassword)) + SSL_CTX_set_default_passwd_cb_userdata(sslCtx, (void*)T2CA(lpszKeyPassword)); + + if(!SSL_CTX_use_PrivateKey_file(sslCtx, T2CA(lpszPemKeyFile), SSL_FILETYPE_PEM)) + { + ::SetLastError(ERROR_INVALID_PASSWORD); + return FALSE; + } + + if(!SSL_CTX_use_certificate_chain_file(sslCtx, T2CA(lpszPemCertFile))) + { + ::SetLastError(ERROR_INVALID_DATA); + return FALSE; + } + + if(!SSL_CTX_check_private_key(sslCtx)) + { + ::SetLastError(ERROR_INVALID_ACCESS); + return FALSE; + } + } + + return TRUE; +} + +BOOL CSSLContext::LoadCertAndKeyByMemory(SSL_CTX* sslCtx, int iVerifyMode, LPCSTR lpszPemCert, LPCSTR lpszPemKey, LPCSTR lpszKeyPassword, LPCSTR lpszCAPemCert) +{ + if(!LoadCAPemCertByMemory(sslCtx, iVerifyMode, lpszCAPemCert)) + return FALSE; + if(!LoadPemCertAndKeyByMemory(sslCtx, lpszPemCert, lpszPemKey, lpszKeyPassword)) + return FALSE; + + return TRUE; +} + +BOOL CSSLContext::LoadCAPemCertByMemory(SSL_CTX* sslCtx, int iVerifyMode, LPCSTR lpszCAPemCert) +{ + if(::IsStrEmptyA(lpszCAPemCert)) + return TRUE; + + if(!AddCAPemCertToStoreByMemory(sslCtx, lpszCAPemCert)) + return FALSE; + + if(!SSL_CTX_set_default_verify_paths(sslCtx)) + { + ::SetLastError(ERROR_FUNCTION_FAILED); + return FALSE; + } + + if(m_enSessionMode == SSL_SM_SERVER && (iVerifyMode & SSL_VM_PEER)) + { + if(!SetClientCAListByMemory(sslCtx, lpszCAPemCert)) + return FALSE; + } + + return TRUE; +} + +BOOL CSSLContext::LoadPemCertAndKeyByMemory(SSL_CTX* sslCtx, LPCSTR lpszPemCert, LPCSTR lpszPemKey, LPCSTR lpszKeyPassword) +{ + if(::IsStrEmptyA(lpszPemCert)) + return TRUE; + + if(::IsStrEmptyA(lpszPemKey)) + { + ::SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + if(::IsStrNotEmptyA(lpszKeyPassword)) + SSL_CTX_set_default_passwd_cb_userdata(sslCtx, (void*)(lpszKeyPassword)); + + if(!SetPrivateKeyByMemory(sslCtx, lpszPemKey)) + return FALSE; + + if(!SetCertChainByMemory(sslCtx, lpszPemCert)) + return FALSE; + + if(!SSL_CTX_check_private_key(sslCtx)) + { + ::SetLastError(ERROR_INVALID_ACCESS); + return FALSE; + } + + return TRUE; +} + +BOOL CSSLContext::AddCAPemCertToStoreByMemory(SSL_CTX* sslCtx, LPCSTR lpszPemCert) +{ + BOOL isOK = FALSE; + int iCount = 0; + BIO* pBIO = BIO_new_mem_buf(lpszPemCert, -1); + X509_STORE* pStore = SSL_CTX_get_cert_store(sslCtx); + STACK_OF(X509_INFO) * pStack = nullptr; + + if(pBIO == nullptr) + { + ::SetLastError(ERROR_CREATE_FAILED); + goto _END; + } + + if(pStore == nullptr) + { + ::SetLastError(ERROR_NOT_FOUND); + goto _END; + } + + pStack = PEM_X509_INFO_read_bio(pBIO, nullptr, nullptr, nullptr); + + if(pStack == nullptr) + { + ::SetLastError(ERROR_NO_DATA); + goto _END; + } + + for(int i = 0; i < sk_X509_INFO_num(pStack); i++) + { + X509_INFO* pInfo = sk_X509_INFO_value(pStack, i); + + if(pInfo->x509) + { + if(!X509_STORE_add_cert(pStore, pInfo->x509)) + { + ::SetLastError(ERROR_INVALID_DATA); + goto _END; + } + + ++iCount; + } + + if(pInfo->crl) + { + if(!X509_STORE_add_crl(pStore, pInfo->crl)) + { + ::SetLastError(ERROR_INVALID_DATA); + goto _END; + } + + ++iCount; + } + } + + if(iCount > 0) + isOK = TRUE; + else + ::SetLastError(ERROR_EMPTY); + +_END: + + if(pStack != nullptr) + sk_X509_INFO_pop_free(pStack, X509_INFO_free); + + if(pBIO != nullptr) + BIO_free(pBIO); + + return isOK; +} + +BOOL CSSLContext::SetClientCAListByMemory(SSL_CTX* sslCtx, LPCSTR lpszPemCert) +{ + BOOL isOK = FALSE; + X509* pX509 = nullptr; + X509_NAME* pName = nullptr; + STACK_OF(X509_NAME)* pStack = nullptr; + BIO* pBIO = BIO_new_mem_buf(lpszPemCert, -1); + OPENSSL_LHASH* pNameHash = (OPENSSL_LHASH*)OPENSSL_LH_new((OPENSSL_LH_HASHFUNC)FN_X509_NAME_HASH, (OPENSSL_LH_COMPFUNC)X509_NAME_cmp); + + if(pBIO == nullptr || pNameHash == nullptr) + { + ::SetLastError(ERROR_CREATE_FAILED); + goto _ERR; + } + + while(TRUE) + { + if(PEM_read_bio_X509(pBIO, &pX509, nullptr, nullptr) == nullptr) + break; + + if(pStack == nullptr) + { + pStack = sk_X509_NAME_new_null(); + + if(pStack == nullptr) + { + ::SetLastError(ERROR_CREATE_FAILED); + goto _ERR; + } + } + + if((pName = X509_get_subject_name(pX509)) == nullptr) + { + ::SetLastError(ERROR_NO_DATA); + goto _ERR; + } + + if((pName = X509_NAME_dup(pName)) == nullptr) + { + ::SetLastError(ERROR_CREATE_FAILED); + goto _ERR; + } + + if(OPENSSL_LH_retrieve(pNameHash, pName) != nullptr) + { + X509_NAME_free(pName); + pName = nullptr; + } + else + { + OPENSSL_LH_insert(pNameHash, pName); + + if(!sk_X509_NAME_push(pStack, pName)) + { + ::SetLastError(ERROR_WRITE_FAULT); + goto _ERR; + } + } + } + + if(pStack == nullptr) + { + ::SetLastError(ERROR_EMPTY); + goto _ERR; + } + + SSL_CTX_set_client_CA_list(sslCtx, pStack); + + isOK = TRUE; + goto _END; + +_ERR: + + if(pName != nullptr) + X509_NAME_free(pName); + if(pStack != nullptr) + { + sk_X509_NAME_pop_free(pStack, X509_NAME_free); + pStack = nullptr; + } + +_END: + + if(pX509 != nullptr) + X509_free(pX509); + + if(pNameHash != nullptr) + OPENSSL_LH_free(pNameHash); + + if(pBIO != nullptr) + BIO_free(pBIO); + + if(pStack != nullptr) + ERR_clear_error(); + + return isOK; +} + +BOOL CSSLContext::SetPrivateKeyByMemory(SSL_CTX* sslCtx, LPCSTR lpszPemKey) +{ + BOOL isOK = FALSE; + BIO* pBIO = BIO_new_mem_buf(lpszPemKey, -1); + EVP_PKEY* pKey = nullptr; + + if(pBIO == nullptr) + { + ::SetLastError(ERROR_CREATE_FAILED); + goto _END; + } + + pKey = PEM_read_bio_PrivateKey(pBIO, nullptr, SSL_CTX_get_default_passwd_cb(sslCtx), SSL_CTX_get_default_passwd_cb_userdata(sslCtx)); + + if(pKey == nullptr) + { + ::SetLastError(ERROR_INVALID_PASSWORD); + goto _END; + } + + if(!SSL_CTX_use_PrivateKey(sslCtx, pKey)) + { + ::SetLastError(ERROR_INVALID_DATA); + goto _END; + } + + isOK = TRUE; + +_END: + + if(pKey != nullptr) + EVP_PKEY_free(pKey); + + if(pBIO != nullptr) + BIO_free(pBIO); + + return isOK; +} + +BOOL CSSLContext::SetCertChainByMemory(SSL_CTX* sslCtx, LPCSTR lpszPemCert) +{ + BOOL isOK = FALSE; + ULONG err = 0; + BIO* pBIO = BIO_new_mem_buf(lpszPemCert, -1); + X509* pX509 = nullptr; + + pem_password_cb* cb = SSL_CTX_get_default_passwd_cb(sslCtx); + LPVOID userdata = SSL_CTX_get_default_passwd_cb_userdata(sslCtx); + + if(pBIO == nullptr) + { + ::SetLastError(ERROR_CREATE_FAILED); + goto _END; + } + + pX509 = PEM_read_bio_X509_AUX(pBIO, nullptr, cb, userdata); + + if(pX509 == nullptr) + { + ::SetLastError(ERROR_NO_DATA); + goto _END; + } + + if(!SSL_CTX_use_certificate(sslCtx, pX509) || (ERR_peek_error() != 0)) + { + ::SetLastError(ERROR_INVALID_DATA); + goto _END; + } + + if(!SSL_CTX_clear_chain_certs(sslCtx)) + { + ::SetLastError(ERROR_FUNCTION_FAILED); + goto _END; + } + + X509* pCA; + while((pCA = PEM_read_bio_X509(pBIO, nullptr, cb, userdata)) != nullptr) + { + if(!SSL_CTX_add0_chain_cert(sslCtx, pCA)) + { + X509_free(pCA); + + ::SetLastError(ERROR_FUNCTION_FAILED); + goto _END; + } + } + + err = ERR_peek_last_error(); + + if(ERR_GET_LIB(err) == ERR_LIB_PEM && ERR_GET_REASON(err) == PEM_R_NO_START_LINE) + ERR_clear_error(); + else + { + ::SetLastError(ERROR_FUNCTION_FAILED); + goto _END; + } + + isOK = TRUE; + +_END: + + if(pX509 != nullptr) + X509_free(pX509); + + if(pBIO != nullptr) + BIO_free(pBIO); + + return isOK; +} + +void CSSLContext::Cleanup() +{ + if(IsValid()) + { + int iCount = (int)m_lsSslCtxs.size(); + + for(int i = 0; i < iCount; i++) + SSL_CTX_free(m_lsSslCtxs[i]); + + m_lsSslCtxs.clear(); + m_sslServerNames.clear(); + + m_sslCtx = nullptr; + } + + m_fnServerNameCallback = nullptr; + + RemoveThreadLocalState(); +} + +void CSSLContext::SetServerNameCallback(Fn_SNI_ServerNameCallback fn) +{ + if(m_enSessionMode != SSL_SM_SERVER) + return; + + if(fn == nullptr) + m_fnServerNameCallback = DefaultServerNameCallback; + else + m_fnServerNameCallback = fn; + + VERIFY(SSL_CTX_set_tlsext_servername_callback(m_sslCtx, InternalServerNameCallback)); + VERIFY(SSL_CTX_set_tlsext_servername_arg(m_sslCtx, this)); +} + +int CSSLContext::InternalServerNameCallback(SSL* ssl, int* ad, void* arg) +{ + USES_CONVERSION; + + CSSLContext* pThis = (CSSLContext*)arg; + ASSERT(pThis->m_fnServerNameCallback != nullptr); + + const char* lpszServerName = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + + if(lpszServerName == nullptr) + return SSL_TLSEXT_ERR_NOACK; + + int iIndex = pThis->m_fnServerNameCallback(A2CT(lpszServerName), pThis); + + if(iIndex == 0) + return SSL_TLSEXT_ERR_OK; + + if(iIndex < 0) + { + ::SetLastError(ERROR_INVALID_NAME); + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + + SSL_CTX* sslCtx = pThis->GetContext(iIndex); + + if(sslCtx == nullptr) + { + ::SetLastError(ERROR_INVALID_INDEX); + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + + SSL_set_SSL_CTX(ssl, sslCtx); + + return SSL_TLSEXT_ERR_OK; +} + +int __HP_CALL CSSLContext::DefaultServerNameCallback(LPCTSTR lpszServerName, PVOID pContext) +{ + CSSLContext* pThis = (CSSLContext*)pContext; + + if(pThis->m_sslServerNames.empty()) + return 0; + + LPCTSTR lpszTmp = lpszServerName; + LPCTSTR lpszSep = ::StrChr(lpszTmp, SSL_DOMAIN_SEP_CHAR); + + while(lpszSep != nullptr) + { + CServerNameMap::const_iterator it = pThis->m_sslServerNames.find(lpszTmp); + + if(it != pThis->m_sslServerNames.end()) + return it->second; + + lpszTmp = (lpszSep + 1); + lpszSep = ::StrChr(lpszTmp, SSL_DOMAIN_SEP_CHAR); + } + + return 0; +} + +SSL_CTX* CSSLContext::GetContext(int i) const +{ + SSL_CTX* sslCtx = nullptr; + + if(i >= 0 && i < (int)m_lsSslCtxs.size()) + sslCtx = m_lsSslCtxs[i]; + + return sslCtx; +} + +BOOL CSSLSession::WriteRecvChannel(const BYTE* pData, int iLength) +{ + ASSERT(pData && iLength > 0); + + BOOL isOK = TRUE; + int bytes = BIO_write(m_bioRecv, pData, iLength); + + if(bytes > 0) + ASSERT(bytes == iLength); + else if(!BIO_should_retry(m_bioRecv)) + { + ::SetLastError(ERROR_INVALID_DATA); + isOK = FALSE; + } + + return isOK; +} + +BOOL CSSLSession::ReadRecvChannel() +{ + BOOL isOK = TRUE; + int bytes = SSL_read(m_ssl, m_bufRecv.buf, m_pitRecv->Capacity()); + + if(bytes > 0) + m_bufRecv.len = bytes; + else if(!IsFatalError(bytes)) + m_bufRecv.len = 0; + else + isOK = FALSE; + + if(isOK && m_enStatus == SSL_HSS_PROC && SSL_is_init_finished(m_ssl)) + m_enStatus = SSL_HSS_SUCC; + + return isOK; +} + +BOOL CSSLSession::WriteSendChannel(const BYTE* pData, int iLength) +{ + ASSERT(IsReady()); + ASSERT(pData && iLength > 0); + + BOOL isOK = TRUE; + int bytes = SSL_write(m_ssl, pData, iLength); + + if(bytes > 0) + ASSERT(bytes == iLength); + else if(IsFatalError(bytes)) + isOK = FALSE; + + return isOK; +} + +BOOL CSSLSession::WriteSendChannel(const WSABUF pBuffers[], int iCount) +{ + ASSERT(pBuffers && iCount > 0); + + BOOL isOK = TRUE; + + for(int i = 0; i < iCount; i++) + { + const WSABUF& buffer = pBuffers[i]; + + if(buffer.len > 0) + { + if(!WriteSendChannel((const BYTE*)buffer.buf, buffer.len)) + { + isOK = FALSE; + break; + } + } + } + + return isOK; +} + +BOOL CSSLSession::ReadSendChannel() +{ + if(BIO_pending(m_bioSend) == 0) + { + m_bufSend.len = 0; + return TRUE; + } + + BOOL isOK = TRUE; + int bytes = BIO_read(m_bioSend, m_bufSend.buf, m_pitSend->Capacity()); + + if(bytes > 0) + m_bufSend.len = bytes; + else if(BIO_should_retry(m_bioSend)) + m_bufSend.len = 0; + else + { + ::SetLastError(ERROR_INVALID_DATA); + isOK = FALSE; + } + + return isOK; +} + +CSSLSession* CSSLSession::Renew(const CSSLContext& sslCtx, LPCSTR lpszHostName) +{ + ASSERT(!IsValid()); + + ResetCount(); + + m_ssl = SSL_new(sslCtx.GetDefaultContext()); + m_bioSend = BIO_new(BIO_s_mem()); + m_bioRecv = BIO_new(BIO_s_mem()); + + SSL_set_bio(m_ssl, m_bioRecv, m_bioSend); + + if(sslCtx.GetSessionMode() == SSL_SM_SERVER) + SSL_accept(m_ssl); + else + { + USES_CONVERSION; + + if(lpszHostName && lpszHostName[0] != 0 && !::IsIPAddress(A2CT(lpszHostName))) + SSL_set_tlsext_host_name(m_ssl, lpszHostName); + + SSL_connect(m_ssl); + } + + m_pitSend = m_itPool.PickFreeItem(); + m_pitRecv = m_itPool.PickFreeItem(); + m_bufSend.buf = m_pitSend->Ptr(); + m_bufRecv.buf = m_pitRecv->Ptr(); + m_enStatus = SSL_HSS_PROC; + + return this; +} + +BOOL CSSLSession::Reset() +{ + BOOL isOK = FALSE; + + if(IsValid()) + { + CCriSecLock locallock(m_csSend); + + if(IsValid()) + { + m_enStatus = SSL_HSS_INIT; + + SSL_shutdown(m_ssl); + SSL_free(m_ssl); + + m_itPool.PutFreeItem(m_pitSend); + m_itPool.PutFreeItem(m_pitRecv); + + m_pitSend = nullptr; + m_pitRecv = nullptr; + m_ssl = nullptr; + m_bioSend = nullptr; + m_bioRecv = nullptr; + m_dwFreeTime= ::TimeGetTime(); + + isOK = TRUE; + } + } + + ERR_clear_error(); + + return isOK; +} + +inline BOOL CSSLSession::IsFatalError(int iBytes) +{ + int iErrorCode = SSL_get_error(m_ssl, iBytes); + + if( iErrorCode == SSL_ERROR_NONE || + iErrorCode == SSL_ERROR_WANT_READ || + iErrorCode == SSL_ERROR_WANT_WRITE || + iErrorCode == SSL_ERROR_WANT_CONNECT || + iErrorCode == SSL_ERROR_WANT_ACCEPT ) + return FALSE; + +#ifdef _DEBUG + char szBuffer[512]; +#endif + + int i = 0; + ULONG iCode = iErrorCode; + + for(; iCode != SSL_ERROR_NONE; i++) + { +#ifdef _DEBUG + ERR_error_string_n(iCode, szBuffer, sizeof(szBuffer)); + TRACE(" > SSL Error: %ld - %s\n", iCode, szBuffer); +#endif + + iCode = ERR_get_error(); + } + + if(iErrorCode == SSL_ERROR_SYSCALL && i == 1) + return FALSE; + + ::SetLastError(ERROR_INVALID_DATA); + + return TRUE; +} + +BOOL CSSLSession::GetSessionInfo(EnSSLSessionInfo enInfo, LPVOID* lppInfo) +{ + ASSERT(lppInfo != nullptr); + + *lppInfo = nullptr; + + if(enInfo < SSL_SSI_MIN || enInfo > SSL_SSI_MAX) + { + ::SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + if(!IsValid()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + SSL_CTX* pContext = SSL_get_SSL_CTX(m_ssl); + + switch(enInfo) + { + case SSL_SSI_CTX: + { + *lppInfo = (LPVOID)(pContext); + } + break; + case SSL_SSI_CTX_METHOD: + { + if(pContext != nullptr) + *lppInfo = (LPVOID)(SSL_CTX_get_ssl_method(pContext)); + } + break; + case SSL_SSI_CTX_CIPHERS: + { + if(pContext != nullptr) + *lppInfo = (LPVOID)(SSL_CTX_get_ciphers(pContext)); + } + break; + case SSL_SSI_CTX_CERT_STORE: + { + if(pContext != nullptr) + *lppInfo = (LPVOID)(SSL_CTX_get_cert_store(pContext)); + } + break; + case SSL_SSI_SERVER_NAME_TYPE: + { + *lppInfo = (LPVOID)(UINT_PTR)(SSL_get_servername_type(m_ssl)); + } + break; + case SSL_SSI_SERVER_NAME: + { + int type = SSL_get_servername_type(m_ssl); + + if(type != -1) + *lppInfo = (LPVOID)(SSL_get_servername(m_ssl, type)); + } + break; + case SSL_SSI_VERSION: + { + *lppInfo = (LPVOID)(SSL_get_version(m_ssl)); + } + break; + case SSL_SSI_METHOD: + { + *lppInfo = (LPVOID)(SSL_get_ssl_method(m_ssl)); + } + break; + case SSL_SSI_CERT: + { + *lppInfo = (LPVOID)(SSL_get_certificate(m_ssl)); + } + break; + case SSL_SSI_PKEY: + { + *lppInfo = (LPVOID)(SSL_get_privatekey(m_ssl)); + } + break; + case SSL_SSI_CURRENT_CIPHER: + { + *lppInfo = (LPVOID)(SSL_get_current_cipher(m_ssl)); + } + break; + case SSL_SSI_CIPHERS: + { + *lppInfo = (LPVOID)(SSL_get_ciphers(m_ssl)); + } + break; + case SSL_SSI_CLIENT_CIPHERS: + { + *lppInfo = (LPVOID)(SSL_get_client_ciphers(m_ssl)); + } + break; + case SSL_SSI_PEER_CERT: + { + X509* pCert = SSL_get_peer_certificate(m_ssl); + + if(pCert != nullptr) + { + *lppInfo = (LPVOID*)pCert; + X509_free(pCert); + } + } + break; + case SSL_SSI_PEER_CERT_CHAIN: + { + *lppInfo = (LPVOID)(SSL_get_peer_cert_chain(m_ssl)); + } + break; + case SSL_SSI_VERIFIED_CHAIN: + { + *lppInfo = (LPVOID)(SSL_get0_verified_chain(m_ssl)); + } + break; + } + + return TRUE; +} + +CSSLSession* CSSLSessionPool::PickFreeSession(LPCSTR lpszHostName) +{ + DWORD dwIndex; + CSSLSession* pSession = nullptr; + + if(m_lsFreeSession.TryLock(&pSession, dwIndex)) + { + if(::GetTimeGap32(pSession->GetFreeTime()) >= m_dwSessionLockTime) + VERIFY(m_lsFreeSession.ReleaseLock(nullptr, dwIndex)); + else + { + VERIFY(m_lsFreeSession.ReleaseLock(pSession, dwIndex)); + pSession = nullptr; + } + } + + if(!pSession) pSession = CSSLSession::Construct(m_itPool); + + ASSERT(pSession); + return pSession->Renew(m_sslCtx, lpszHostName); +} + +void CSSLSessionPool::PutFreeSession(CSSLSession* pSession) +{ + if(pSession->Reset()) + { +#ifndef USE_EXTERNAL_GC + ReleaseGCSession(); +#endif + if(!m_lsFreeSession.TryPut(pSession)) + m_lsGCSession.PushBack(pSession); + } +} + +void CSSLSessionPool::Prepare() +{ + m_itPool.Prepare(); + m_lsFreeSession.Reset(m_dwSessionPoolSize); +} + +void CSSLSessionPool::Clear() +{ + m_lsFreeSession.Clear(); + + ReleaseGCSession(TRUE); + VERIFY(m_lsGCSession.IsEmpty()); + + m_itPool.Clear(); +} + +void CSSLSessionPool::ReleaseGCSession(BOOL bForce) +{ + ::ReleaseGCObj(m_lsGCSession, m_dwSessionLockTime, bForce); +} + +#endif \ No newline at end of file diff --git a/SSLHelper.h b/SSLHelper.h new file mode 100644 index 0000000..ced52d4 --- /dev/null +++ b/SSLHelper.h @@ -0,0 +1,496 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "hpsocket/HPTypeDef.h" +#include "common/BufferPool.h" + +#ifdef _SSL_SUPPORT + +#include "openssl/ssl.h" + +#define OPENSSL_VERSION_1_0_2 0x10002000L +#define OPENSSL_VERSION_1_1_0 0x10100000L +#define OPENSSL_VERSION_3_0_0 0x30000000L + +#if OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_1_1_0 + #define DEFAULT_CIPHER_LIST _T("DEFAULT:!aNULL:!eNULL:!SSLv2") +#else + #define DEFAULT_CIPHER_LIST _T("DEFAULT:!aNULL:!eNULL:!SSLv2:!SSLv3") +#endif + +#if OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_3_0_0 + #define FN_X509_NAME_HASH X509_NAME_hash +#else + inline unsigned long FN_X509_NAME_HASH(const X509_NAME *x) {return X509_NAME_hash_ex(x, nullptr, nullptr, nullptr);} +#endif + +/************************************************************************ +名称:SSL 全局常量 +描述:声明 SSL 组件的公共全局常量 +************************************************************************/ + +#define SSL_DOMAIN_SEP_CHAR '.' + + /************************************************************************ +名称:SSL 握手状态 +描述:标识当前连接的 SSL 握手状态 +************************************************************************/ +enum EnSSLHandShakeStatus +{ + SSL_HSS_INIT = 0, // 初始状态 + SSL_HSS_PROC = 1, // 正在握手 + SSL_HSS_SUCC = 2, // 握手成功 +}; + +#if OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_1_1_0 + +/* SSL CRYPTO DYNLOCK 结构 */ +typedef struct CRYPTO_dynlock_value +{ + CSimpleRWLock cs; +} DynamicLock; + +#endif + +class CSSLInitializer +{ +public: + + static void CleanupThreadState(THR_ID dwThreadID = 0); + +private: + + CSSLInitializer(); + ~CSSLInitializer(); + +private: + +#if OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_1_1_0 + static void ssl_lock_callback(int mode, int n, const char *file, int line); + static CRYPTO_dynlock_value* ssl_lock_dyn_create_callback(const char *file, int line); + static void ssl_lock_dyn_callback(int mode, CRYPTO_dynlock_value* l, const char *file, int line); + static void ssl_lock_dyn_destroy_callback(CRYPTO_dynlock_value* l, const char *file, int line); +#endif + +private: + +#if OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_1_1_0 + static int sm_iLockNum; + static CSimpleRWLock* sm_pcsLocks; +#endif + + static CSSLInitializer sm_instance; + +}; + +/************************************************************************ +名称:SSL Context +描述:初始化和清理 SSL 运行环境 +************************************************************************/ +class CSSLContext +{ + typedef unordered_map CServerNameMap; + +public: + + /* + * 名称:初始化 SSL 环境参数 + * 描述:SSL 环境参数必须在 SSL 通信组件启动前完成初始化,否则启动失败 + * + * 参数: enSessionMode -- SSL 工作模式(参考 EnSSLSessionMode) + * iVerifyMode -- SSL 验证模式(参考 EnSSLVerifyMode) + * lpPemCert -- 证书文件(客户端可选) + * lpPemKey -- 私钥文件(客户端可选) + * lpKeyPasswod -- 私钥密码(没有密码则为空) + * lpCAPemCert -- CA 证书文件或目录(单向验证或客户端可选) + * fnServerNameCallback -- SNI 回调函数指针(可选,只用于服务端,如果为 nullptr 则使用 SNI 默认回调函数) + * + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 ::GetLastError() 获取失败原因 + */ + BOOL Initialize(EnSSLSessionMode enSessionMode, int iVerifyMode = SSL_VM_NONE, BOOL bMemory = FALSE, LPVOID lpPemCert = nullptr, LPVOID lpPemKey = nullptr, LPVOID lpKeyPasswod = nullptr, LPVOID lpCAPemCert = nullptr, Fn_SNI_ServerNameCallback fnServerNameCallback = nullptr); + + /* + * 名称:增加 SNI 主机证书(只用于服务端) + * 描述:SSL 服务端在 Initialize() 成功后可以调用本方法增加多个 SNI 主机证书 + * + * 参数: iVerifyMode -- SSL 验证模式(参考 EnSSLVerifyMode) + * lpPemCert -- 证书文件 + * lpPemKey -- 私钥文件 + * lpKeyPasswod -- 私钥密码(没有密码则为空) + * lpCAPemCert -- CA 证书文件或目录(单向验证可选) + * + * 返回值: 正数 -- 成功,并返回 SNI 主机证书对应的索引,该索引用于在 SNI 回调函数中定位 SNI 主机 + * 负数 -- 失败,可通过 ::GetLastError() 获取失败原因 + */ + int AddServerContext(int iVerifyMode, BOOL bMemory, LPVOID lpPemCert, LPVOID lpPemKey, LPVOID lpKeyPasswod = nullptr, LPVOID lpCAPemCert = nullptr); + + /* + * 名称:绑定 SNI 主机域名 + * 描述:SSL 服务端在 AddServerContext() 成功后可以调用本方法绑定主机域名到 SNI 主机证书 + * + * 参数: lpszServerName -- 主机域名 + * iContextIndex -- SNI 主机证书对应的索引 + * + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 ::GetLastError() 获取失败原因 + */ + virtual BOOL BindServerName(LPCTSTR lpszServerName, int iContextIndex); + + /* + * 名称:清理 SSL 运行环境 + * 描述:清理 SSL 运行环境,回收 SSL 相关内存 + * 1、CSSLContext 的析构函数会自动调用本方法 + * 2、当要重新设置 SSL 环境参数时,需要先调用本方法清理原先的环境参数 + * + * 参数: 无 + * + * 返回值:无 + */ + void Cleanup(); + + /* 获取 SSL 运行环境 SSL_CTX 对象 */ + SSL_CTX* GetContext (int i) const; + /* 获取 SSL 运行环境默认 SSL_CTX 对象 */ + SSL_CTX* GetDefaultContext () const {return m_sslCtx;} + /* 获取 SSL 运行环境的配置模式,配置模式参考:EnSSLSessionMode */ + EnSSLSessionMode GetSessionMode () const {return m_enSessionMode;} + /* 检查 SSL 运行环境是否初始化完成 */ + BOOL IsValid () const {return m_sslCtx != nullptr;} + + /* 设置 SSL 加密算法列表 */ + void SetCipherList(LPCTSTR lpszCipherList) {m_strCipherList = lpszCipherList;} + /* 获取 SSL 加密算法列表 */ + LPCTSTR GetCipherList() {return m_strCipherList;} + +public: + + /* + * 名称:清理线程局部环境 SSL 资源 + * 描述:任何一个操作 SSL 的线程,在通信结束时都需要清理线程局部环境 SSL 资源 + * 1、主线程和 HP-Socket 工作线程在通信结束时会自动清理线程局部环境 SSL 资源。因此,一般情况下不必手工调用本方法 + * 2、特殊情况下,当自定义线程参与 HP-Socket 通信操作并检查到 SSL 内存泄漏时,需在每次通信结束时自定义线程调用本方法 + * + * 参数: dwThreadID -- 线程 ID(0:当前线程) + * + * 返回值:无 + */ + static void RemoveThreadLocalState(THR_ID dwThreadID = 0) {CSSLInitializer::CleanupThreadState(dwThreadID);} + +public: + + CSSLContext() + : m_strCipherList (DEFAULT_CIPHER_LIST) + , m_enSessionMode (SSL_SM_SERVER) + , m_sslCtx (nullptr) + , m_fnServerNameCallback(nullptr) + { + + } + + ~CSSLContext() {Cleanup();} + +private: + + void SetServerNameCallback(Fn_SNI_ServerNameCallback fn); + int AddContext(int iVerifyMode, BOOL bMemory, LPVOID lpPemCert, LPVOID lpPemKey, LPVOID lpKeyPasswod, LPVOID lpCAPemCert); + BOOL LoadCertAndKey(SSL_CTX* sslCtx, int iVerifyMode, BOOL bMemory, LPVOID lpPemCert, LPVOID lpPemKey, LPVOID lpKeyPasswod, LPVOID lpCAPemCert); + BOOL LoadCertAndKeyByFile(SSL_CTX* sslCtx, int iVerifyMode, LPCTSTR lpszPemCertFile, LPCTSTR lpszPemKeyFile, LPCTSTR lpszKeyPassword, LPCTSTR lpszCAPemCertFileOrPath); + BOOL LoadCertAndKeyByMemory(SSL_CTX* sslCtx, int iVerifyMode, LPCSTR lpszPemCert, LPCSTR lpszPemKey, LPCSTR lpszKeyPassword, LPCSTR lpszCAPemCert); + BOOL LoadCAPemCertByMemory(SSL_CTX* sslCtx, int iVerifyMode, LPCSTR lpszCAPemCert); + BOOL LoadPemCertAndKeyByMemory(SSL_CTX* sslCtx, LPCSTR lpszPemCert, LPCSTR lpszPemKey, LPCSTR lpszKeyPassword); + BOOL AddCAPemCertToStoreByMemory(SSL_CTX* sslCtx, LPCSTR lpszPemCert); + BOOL SetClientCAListByMemory(SSL_CTX* sslCtx, LPCSTR lpszPemCert); + BOOL SetPrivateKeyByMemory(SSL_CTX* sslCtx, LPCSTR lpszPemKey); + BOOL SetCertChainByMemory(SSL_CTX* sslCtx, LPCSTR lpszPemCert); + +private: + + static int InternalServerNameCallback(SSL* ssl, int* ad, void* arg); + +public: + + /* + * 名称:SNI 默认回调函数 + * 描述:Initialize 方法中如果不指定 SNI 回调函数则使用此 SNI 默认回调函数 + * + * 参数: lpszServerName -- 请求域名 + * pContext -- SSL Context 对象 + * + * 返回值:SNI 主机证书对应的索引 + */ + static int __HP_CALL DefaultServerNameCallback(LPCTSTR lpszServerName, PVOID pContext); + +private: + + CString m_strCipherList; + EnSSLSessionMode m_enSessionMode; + CServerNameMap m_sslServerNames; + vector m_lsSslCtxs; + SSL_CTX* m_sslCtx; + + Fn_SNI_ServerNameCallback m_fnServerNameCallback; +}; + +class CSSLSession : public CSafeCounter +{ +public: + + BOOL WriteRecvChannel(const BYTE* pData, int iLength); + BOOL ReadRecvChannel(); + + BOOL WriteSendChannel(const BYTE* pData, int iLength); + BOOL WriteSendChannel(const WSABUF pBuffers[], int iCount); + BOOL ReadSendChannel(); + + const WSABUF& GetRecvBuffer() const {return m_bufRecv;} + const WSABUF& GetSendBuffer() const {return m_bufSend;} + + CSSLSession* Renew(const CSSLContext& sslCtx, LPCSTR lpszHostName = nullptr); + BOOL Reset(); + BOOL IsValid() const {return GetStatus() != SSL_HSS_INIT;} + BOOL IsHandShaking() const {return GetStatus() == SSL_HSS_PROC;} + BOOL IsReady() const {return GetStatus() == SSL_HSS_SUCC;} + EnSSLHandShakeStatus GetStatus() const {return m_enStatus;} + DWORD GetFreeTime() const {return m_dwFreeTime;} + CCriSec& GetSendLock() {return m_csSend;} + BOOL GetSessionInfo(EnSSLSessionInfo enInfo, LPVOID* lppInfo); + +private: + + BOOL IsFatalError(int iBytes); + +public: + + CSSLSession(CItemPool& itPool) + : m_enStatus(SSL_HSS_INIT) + , m_itPool (itPool) + , m_ssl (nullptr) + , m_bioSend (nullptr) + , m_bioRecv (nullptr) + , m_pitSend (nullptr) + , m_pitRecv (nullptr) + { + + } + + ~CSSLSession() + { + Reset(); + } + + static CSSLSession* Construct(CItemPool& itPool) + {return new CSSLSession(itPool);} + + static void Destruct(CSSLSession* pSession) + {if(pSession) delete pSession;} + +private: + CItemPool& m_itPool; + CCriSec m_csSend; + + DWORD m_dwFreeTime; + EnSSLHandShakeStatus m_enStatus; + + SSL* m_ssl; + BIO* m_bioSend; + BIO* m_bioRecv; + + TItem* m_pitSend; + TItem* m_pitRecv; + WSABUF m_bufSend; + WSABUF m_bufRecv; +}; + +class CSSLSessionPool +{ + typedef CRingPool TSSLSessionList; + typedef CCASQueue TSSLSessionQueue; + +public: + CSSLSession* PickFreeSession (LPCSTR lpszHostName = nullptr); + void PutFreeSession (CSSLSession* pSession); + + void Prepare (); + void Clear (); + + void ReleaseGCSession (BOOL bForce = FALSE); + +public: + void SetItemCapacity (DWORD dwItemCapacity) {m_itPool.SetItemCapacity(dwItemCapacity);} + void SetItemPoolSize (DWORD dwItemPoolSize) {m_itPool.SetPoolSize(dwItemPoolSize);} + void SetItemPoolHold (DWORD dwItemPoolHold) {m_itPool.SetPoolHold(dwItemPoolHold);} + + void SetSessionLockTime (DWORD dwSessionLockTime) {m_dwSessionLockTime = dwSessionLockTime;} + void SetSessionPoolSize (DWORD dwSessionPoolSize) {m_dwSessionPoolSize = dwSessionPoolSize;} + void SetSessionPoolHold (DWORD dwSessionPoolHold) {m_dwSessionPoolHold = dwSessionPoolHold;} + + DWORD GetItemCapacity () {return m_itPool.GetItemCapacity();} + DWORD GetItemPoolSize () {return m_itPool.GetPoolSize();} + DWORD GetItemPoolHold () {return m_itPool.GetPoolHold();} + + DWORD GetSessionLockTime() {return m_dwSessionLockTime;} + DWORD GetSessionPoolSize() {return m_dwSessionPoolSize;} + DWORD GetSessionPoolHold() {return m_dwSessionPoolHold;} + +public: + CSSLSessionPool(const CSSLContext& sslCtx, + DWORD dwPoolSize = DEFAULT_SESSION_POOL_SIZE, + DWORD dwPoolHold = DEFAULT_SESSION_POOL_HOLD, + DWORD dwLockTime = DEFAULT_SESSION_LOCK_TIME) + : m_sslCtx(sslCtx) + , m_dwSessionPoolSize(dwPoolSize) + , m_dwSessionPoolHold(dwPoolHold) + , m_dwSessionLockTime(dwLockTime) + { + + } + + ~CSSLSessionPool() {Clear();} + + DECLARE_NO_COPY_CLASS(CSSLSessionPool) + +public: + static const DWORD DEFAULT_ITEM_CAPACITY; + static const DWORD DEFAULT_ITEM_POOL_SIZE; + static const DWORD DEFAULT_ITEM_POOL_HOLD; + static const DWORD DEFAULT_SESSION_LOCK_TIME; + static const DWORD DEFAULT_SESSION_POOL_SIZE; + static const DWORD DEFAULT_SESSION_POOL_HOLD; + +private: + CItemPool m_itPool; + const CSSLContext& m_sslCtx; + + DWORD m_dwSessionLockTime; + DWORD m_dwSessionPoolSize; + DWORD m_dwSessionPoolHold; + + TSSLSessionList m_lsFreeSession; + TSSLSessionQueue m_lsGCSession; +}; + +template EnHandleResult ProcessHandShake(T* pThis, S* pSocketObj, CSSLSession* pSession) +{ + EnHandleResult result = HR_OK; + + CCriSecLock locallock(pSession->GetSendLock()); + + while(TRUE) + { + VERIFY(pSession->ReadSendChannel()); + const WSABUF& buffer = pSession->GetSendBuffer(); + + if(buffer.len == 0) + break; + + if(!pThis->DoSendPackets(pSocketObj, &buffer, 1)) + { + result = HR_ERROR; + break; + } + } + + return result; +} + +template EnHandleResult ProcessReceive(T* pThis, S* pSocketObj, CSSLSession* pSession, const BYTE* pData, int iLength) +{ + if(!pSession->WriteRecvChannel(pData, iLength)) + return HR_ERROR; + + EnHandleResult result = HR_OK; + EnSSLHandShakeStatus enStatus = pSession->GetStatus(); + + while(TRUE) + { + if(!pSession->ReadRecvChannel()) + return HR_ERROR; + + if(enStatus == SSL_HSS_PROC && pSession->IsReady()) + { + result = ProcessHandShake(pThis, pSocketObj, pSession); + + if(result == HR_ERROR) + break; + + enStatus = SSL_HSS_SUCC; + result = pThis->DoFireHandShake(pSocketObj); + + if(result == HR_ERROR) + break; + } + + const WSABUF& buffer = pSession->GetRecvBuffer(); + + if(buffer.len == 0) + break; + + result = pThis->DoFireReceive(pSocketObj, (const BYTE*)buffer.buf, buffer.len); + + if(result == HR_ERROR) + break; + } + + if(result != HR_ERROR && pSession->IsHandShaking()) + result = ::ProcessHandShake(pThis, pSocketObj, pSession); + + return result; +} + +template BOOL ProcessSend(T* pThis, S* pSocketObj, CSSLSession* pSession, const WSABUF * pBuffers, int iCount) +{ + if(pSession == nullptr || !pSession->IsReady()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + CCriSecLock locallock(pSession->GetSendLock()); + + if(!pSession->IsReady()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + VERIFY(pSession->WriteSendChannel(pBuffers, iCount)); + + while(TRUE) + { + VERIFY(pSession->ReadSendChannel()); + const WSABUF& buffer = pSession->GetSendBuffer(); + + if(buffer.len == 0) + break; + + if(!pThis->DoSendPackets(pSocketObj, &buffer, 1)) + return FALSE; + } + + return TRUE; +} + +#endif \ No newline at end of file diff --git a/SSLServer.cpp b/SSLServer.cpp new file mode 100644 index 0000000..66dbafc --- /dev/null +++ b/SSLServer.cpp @@ -0,0 +1,229 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SSLServer.h" +#include "SSLHelper.h" + +#ifdef _SSL_SUPPORT + +BOOL CSSLServer::CheckParams() +{ + if(!m_sslCtx.IsValid()) + { + SetLastError(SE_SSL_ENV_NOT_READY, __FUNCTION__, ERROR_NOT_READY); + return FALSE; + } + + return __super::CheckParams(); +} + +void CSSLServer::PrepareStart() +{ + __super::PrepareStart(); + + m_sslPool.SetItemCapacity (GetSocketBufferSize()); + m_sslPool.SetItemPoolSize (GetFreeBufferObjPool()); + m_sslPool.SetItemPoolHold (GetFreeBufferObjHold()); + m_sslPool.SetSessionLockTime(GetFreeSocketObjLockTime()); + m_sslPool.SetSessionPoolSize(GetFreeSocketObjPool()); + m_sslPool.SetSessionPoolHold(GetFreeSocketObjHold()); + + m_sslPool.Prepare(); +} + +void CSSLServer::Reset() +{ + m_sslPool.Clear(); + m_sslCtx.RemoveThreadLocalState(); + + __super::Reset(); +} + +void CSSLServer::OnWorkerThreadEnd(THR_ID dwThreadID) +{ + m_sslCtx.RemoveThreadLocalState(); + + __super::OnWorkerThreadEnd(dwThreadID); +} + +void CSSLServer::ReleaseGCSocketObj(BOOL bForce) +{ + __super::ReleaseGCSocketObj(bForce); + +#ifdef USE_EXTERNAL_GC + m_sslPool.ReleaseGCSession(bForce); +#endif +} + +BOOL CSSLServer::SendPackets(CONNID dwConnID, const WSABUF pBuffers[], int iCount) +{ + ASSERT(pBuffers && iCount > 0); + + TSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TSocketObj::IsValid(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + CSSLSession* pSession = nullptr; + GetConnectionReserved2(pSocketObj, (PVOID*)&pSession); + + if(pSession != nullptr) + { + CLocalSafeCounter localcounter(*pSession); + return ::ProcessSend(this, pSocketObj, pSession, pBuffers, iCount); + } + + return DoSendPackets(pSocketObj, pBuffers, iCount); +} + +EnHandleResult CSSLServer::FireAccept(TSocketObj* pSocketObj) +{ + EnHandleResult result = DoFireAccept(pSocketObj); + + if(result != HR_ERROR && m_bSSLAutoHandShake) + DoSSLHandShake(pSocketObj); + + return result; +} + +EnHandleResult CSSLServer::FireReceive(TSocketObj* pSocketObj, const BYTE* pData, int iLength) +{ + CSSLSession* pSession = nullptr; + GetConnectionReserved2(pSocketObj, (PVOID*)&pSession); + + if(pSession != nullptr) + { + CLocalSafeCounter localcounter(*pSession); + return ::ProcessReceive(this, pSocketObj, pSession, pData, iLength); + } + + return DoFireReceive(pSocketObj, pData, iLength); +} + +EnHandleResult CSSLServer::FireClose(TSocketObj* pSocketObj, EnSocketOperation enOperation, int iErrorCode) +{ + EnHandleResult result = DoFireClose(pSocketObj, enOperation, iErrorCode); + + CSSLSession* pSession = nullptr; + GetConnectionReserved2(pSocketObj, (PVOID*)&pSession); + + if(pSession != nullptr) + m_sslPool.PutFreeSession(pSession); + + return result; +} + +BOOL CSSLServer::StartSSLHandShake(CONNID dwConnID) +{ + if(IsSSLAutoHandShake()) + { + ::SetLastError(ERROR_INVALID_OPERATION); + return FALSE; + } + + TSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TSocketObj::IsValid(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + return StartSSLHandShake(pSocketObj); +} + +BOOL CSSLServer::StartSSLHandShake(TSocketObj* pSocketObj) +{ + if(!pSocketObj->HasConnected()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + CReentrantCriSecLock locallock(pSocketObj->csSend); + + if(!TSocketObj::IsValid(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + if(!pSocketObj->HasConnected()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + CSSLSession* pSession = nullptr; + GetConnectionReserved2(pSocketObj, (PVOID*)&pSession); + + if(pSession != nullptr) + { + ::SetLastError(ERROR_ALREADY_INITIALIZED); + return FALSE; + } + + DoSSLHandShake(pSocketObj); + + return TRUE; +} + +void CSSLServer::DoSSLHandShake(TSocketObj* pSocketObj) +{ + CSSLSession* pSession = m_sslPool.PickFreeSession(); + + CLocalSafeCounter localcounter(*pSession); + + ENSURE(SetConnectionReserved2(pSocketObj, pSession)); + ENSURE(::ProcessHandShake(this, pSocketObj, pSession) == HR_OK); +} + +BOOL CSSLServer::GetSSLSessionInfo(CONNID dwConnID, EnSSLSessionInfo enInfo, LPVOID* lppInfo) +{ + ASSERT(lppInfo != nullptr); + + *lppInfo = nullptr; + TSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TSocketObj::IsValid(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + CSSLSession* pSession = nullptr; + GetConnectionReserved2(pSocketObj, (PVOID*)&pSession); + + if(pSession == nullptr || !pSession->IsValid()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + return pSession->GetSessionInfo(enInfo, lppInfo); +} + +#endif \ No newline at end of file diff --git a/SSLServer.h b/SSLServer.h new file mode 100644 index 0000000..70cb89b --- /dev/null +++ b/SSLServer.h @@ -0,0 +1,115 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "TcpServer.h" +#include "SSLHelper.h" + +#ifdef _SSL_SUPPORT + +class CSSLServer : public CTcpServer +{ + using __super = CTcpServer; + +public: + using __super::Wait; + +public: + virtual BOOL IsSecure() {return TRUE;} + virtual BOOL SendPackets(CONNID dwConnID, const WSABUF pBuffers[], int iCount); + + virtual BOOL SetupSSLContext(int iVerifyMode = SSL_VM_NONE, LPCTSTR lpszPemCertFile = nullptr, LPCTSTR lpszPemKeyFile = nullptr, LPCTSTR lpszKeyPassword = nullptr, LPCTSTR lpszCAPemCertFileOrPath = nullptr, Fn_SNI_ServerNameCallback fnServerNameCallback = nullptr) + {return m_sslCtx.Initialize(SSL_SM_SERVER, iVerifyMode, FALSE, (LPVOID)lpszPemCertFile, (LPVOID)lpszPemKeyFile, (LPVOID)lpszKeyPassword, (LPVOID)lpszCAPemCertFileOrPath, fnServerNameCallback);} + + virtual BOOL SetupSSLContextByMemory(int iVerifyMode = SSL_VM_NONE, LPCSTR lpszPemCert = nullptr, LPCSTR lpszPemKey = nullptr, LPCSTR lpszKeyPassword = nullptr, LPCSTR lpszCAPemCert = nullptr, Fn_SNI_ServerNameCallback fnServerNameCallback = nullptr) + {return m_sslCtx.Initialize(SSL_SM_SERVER, iVerifyMode, TRUE, (LPVOID)lpszPemCert, (LPVOID)lpszPemKey, (LPVOID)lpszKeyPassword, (LPVOID)lpszCAPemCert, fnServerNameCallback);} + + virtual int AddSSLContext(int iVerifyMode = SSL_VM_NONE, LPCTSTR lpszPemCertFile = nullptr, LPCTSTR lpszPemKeyFile = nullptr, LPCTSTR lpszKeyPassword = nullptr, LPCTSTR lpszCAPemCertFileOrPath = nullptr) + {return m_sslCtx.AddServerContext(iVerifyMode, FALSE, (LPVOID)lpszPemCertFile, (LPVOID)lpszPemKeyFile, (LPVOID)lpszKeyPassword, (LPVOID)lpszCAPemCertFileOrPath);} + + virtual int AddSSLContextByMemory(int iVerifyMode = SSL_VM_NONE, LPCSTR lpszPemCert = nullptr, LPCSTR lpszPemKey = nullptr, LPCSTR lpszKeyPassword = nullptr, LPCSTR lpszCAPemCert = nullptr) + {return m_sslCtx.AddServerContext(iVerifyMode, TRUE, (LPVOID)lpszPemCert, (LPVOID)lpszPemKey, (LPVOID)lpszKeyPassword, (LPVOID)lpszCAPemCert);} + + virtual BOOL BindSSLServerName(LPCTSTR lpszServerName, int iContextIndex) + {return m_sslCtx.BindServerName(lpszServerName, iContextIndex);} + + virtual void CleanupSSLContext() + {m_sslCtx.Cleanup();} + + virtual BOOL StartSSLHandShake(CONNID dwConnID); + +public: + virtual void SetSSLAutoHandShake(BOOL bAutoHandShake) {ENSURE_HAS_STOPPED(); m_bSSLAutoHandShake = bAutoHandShake;} + virtual void SetSSLCipherList (LPCTSTR lpszCipherList){ENSURE_HAS_STOPPED(); m_sslCtx.SetCipherList(lpszCipherList);} + virtual BOOL IsSSLAutoHandShake () {return m_bSSLAutoHandShake;} + virtual LPCTSTR GetSSLCipherList() {return m_sslCtx.GetCipherList();} + + virtual BOOL GetSSLSessionInfo(CONNID dwConnID, EnSSLSessionInfo enInfo, LPVOID* lppInfo); + +protected: + virtual EnHandleResult FireAccept(TSocketObj* pSocketObj); + virtual EnHandleResult FireReceive(TSocketObj* pSocketObj, const BYTE* pData, int iLength); + virtual EnHandleResult FireClose(TSocketObj* pSocketObj, EnSocketOperation enOperation, int iErrorCode); + + virtual BOOL CheckParams(); + virtual void PrepareStart(); + virtual void Reset(); + + virtual void OnWorkerThreadEnd(THR_ID dwThreadID); + + virtual void ReleaseGCSocketObj(BOOL bForce = FALSE); + +protected: + virtual BOOL StartSSLHandShake(TSocketObj* pSocketObj); + +private: + void DoSSLHandShake(TSocketObj* pSocketObj); + +private: + friend EnHandleResult ProcessHandShake<>(CSSLServer* pThis, TSocketObj* pSocketObj, CSSLSession* pSession); + friend EnHandleResult ProcessReceive<>(CSSLServer* pThis, TSocketObj* pSocketObj, CSSLSession* pSession, const BYTE* pData, int iLength); + friend BOOL ProcessSend<>(CSSLServer* pThis, TSocketObj* pSocketObj, CSSLSession* pSession, const WSABUF * pBuffers, int iCount); + +public: + CSSLServer(ITcpServerListener* pListener) + : CTcpServer(pListener) + , m_sslPool(m_sslCtx) + , m_bSSLAutoHandShake(TRUE) + { + + } + + virtual ~CSSLServer() + { + ENSURE_STOP(); + } + +private: + BOOL m_bSSLAutoHandShake; + + CSSLContext m_sslCtx; + CSSLSessionPool m_sslPool; +}; + +#endif \ No newline at end of file diff --git a/SocketHelper.cpp b/SocketHelper.cpp new file mode 100644 index 0000000..05b16f4 --- /dev/null +++ b/SocketHelper.cpp @@ -0,0 +1,1669 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "SocketHelper.h" + +#include +#include +#include +#include + +#ifdef _ICONV_SUPPORT +#include +#endif + +#ifndef SO_REUSEPORT + #define SO_REUSEPORT 15 +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////////////////////////////// + +static const BYTE s_szUdpCloseNotify[] = {0xBE, 0xB6, 0x1F, 0xEB, 0xDA, 0x52, 0x46, 0xBA, 0x92, 0x33, 0x59, 0xDB, 0xBF, 0xE6, 0xC8, 0xE4}; +static const int s_iUdpCloseNotifySize = ARRAY_SIZE(s_szUdpCloseNotify); + +const hp_addr hp_addr::ANY_ADDR4(AF_INET, TRUE); +const hp_addr hp_addr::ANY_ADDR6(AF_INET6, TRUE); + +BOOL SetCurrentWorkerThreadName() +{ + return SetWorkerThreadDefaultName(0); +} + +BOOL SetWorkerThreadDefaultName(THR_ID tid) +{ + static volatile UINT _s_uiSeq = MAXUINT; + + return ::SetSequenceThreadName(tid, DEFAULT_WORKER_THREAD_PREFIX, _s_uiSeq); +} + +LPCTSTR GetSocketErrorDesc(EnSocketError enCode) +{ + switch(enCode) + { + case SE_OK: return _T("SUCCESS"); + case SE_ILLEGAL_STATE: return _T("Illegal State"); + case SE_INVALID_PARAM: return _T("Invalid Parameter"); + case SE_SOCKET_CREATE: return _T("Create SOCKET Fail"); + case SE_SOCKET_BIND: return _T("Bind SOCKET Fail"); + case SE_SOCKET_PREPARE: return _T("Prepare SOCKET Fail"); + case SE_SOCKET_LISTEN: return _T("Listen SOCKET Fail"); + case SE_CP_CREATE: return _T("Create IOCP Fail"); + case SE_WORKER_THREAD_CREATE: return _T("Create Worker Thread Fail"); + case SE_DETECT_THREAD_CREATE: return _T("Create Detector Thread Fail"); + case SE_SOCKE_ATTACH_TO_CP: return _T("Attach SOCKET to IOCP Fail"); + case SE_CONNECT_SERVER: return _T("Connect to Server Fail"); + case SE_NETWORK: return _T("Network Error"); + case SE_DATA_PROC: return _T("Process Data Error"); + case SE_DATA_SEND: return _T("Send Data Fail"); + case SE_GC_START: return _T("Start GC Fail"); + + case SE_SSL_ENV_NOT_READY: return _T("SSL environment not ready"); + + default: ASSERT(FALSE); return _T("UNKNOWN ERROR"); + } +} + +ADDRESS_FAMILY DetermineAddrFamily(LPCTSTR lpszAddress) +{ + if (!lpszAddress || lpszAddress[0] == 0) + return AF_UNSPEC; + + if(::StrChr(lpszAddress, IPV6_ADDR_SEPARATOR_CHAR)) + return AF_INET6; + + TCHAR c; + int arr[4]; + + if(stscanf(lpszAddress, _T("%d.%d.%d.%d%c"), &arr[0], &arr[1], &arr[2], &arr[3], &c) != 4) + return AF_UNSPEC; + + for(int i = 0; i < 4; i++) + { + if(arr[i] < 0 || arr[i] > 255) + return AF_UNSPEC; + } + + return AF_INET; +} + +BOOL GetInAddr(LPCTSTR lpszAddress, HP_ADDR& addr) +{ + addr.family = DetermineAddrFamily(lpszAddress); + + if (addr.family == AF_UNSPEC) + return FALSE; + + return (::InetPton(addr.family, lpszAddress, addr.Addr()) == TRUE); +} + +BOOL GetSockAddr(LPCTSTR lpszAddress, USHORT usPort, HP_SOCKADDR& addr) +{ + if(addr.family != AF_INET && addr.family != AF_INET6) + { + ::WSASetLastError(ERROR_ADDRNOTAVAIL); + return FALSE; + } + + if(addr.family == AF_INET6 && StrChr(lpszAddress, IPV6_ZONE_INDEX_CHAR)) + return GetSockAddrByHostNameDirectly(lpszAddress, usPort, addr); + + addr.ZeroAddr(); + + int rs = ::InetPton(addr.family, lpszAddress, addr.SinAddr()); + + if(rs != 1) + { + if(rs == 0) ::WSASetLastError(ERROR_INVALID_PARAMETER); + + return FALSE; + } + + if(usPort != 0) + addr.SetPort(usPort); + + return TRUE; +} + +BOOL IsIPAddress(LPCTSTR lpszAddress, EnIPAddrType* penType) +{ + HP_ADDR addr; + + BOOL isOK = GetInAddr(lpszAddress, addr); + + if(isOK && penType) + *penType = addr.IsIPv4() ? IPT_IPV4 : IPT_IPV6; + + return isOK; +} + +BOOL GetIPAddress(LPCTSTR lpszHost, LPTSTR lpszIP, int& iIPLen, EnIPAddrType& enType) +{ + HP_SOCKADDR addr; + + if(!GetSockAddrByHostName(lpszHost, 0, addr)) + return FALSE; + + enType = addr.IsIPv4() ? IPT_IPV4 : IPT_IPV6; + + USHORT usPort; + ADDRESS_FAMILY usFamily; + return sockaddr_IN_2_A(addr, usFamily, lpszIP, iIPLen, usPort); +} + +BOOL GetSockAddrByHostName(LPCTSTR lpszHost, USHORT usPort, HP_SOCKADDR& addr) +{ + addr.family = DetermineAddrFamily(lpszHost); + + if(addr.family != AF_UNSPEC) + return GetSockAddr(lpszHost, usPort, addr); + + return GetSockAddrByHostNameDirectly(lpszHost, usPort, addr); +} + +BOOL GetSockAddrByHostNameDirectly(LPCTSTR lpszHost, USHORT usPort, HP_SOCKADDR& addr) +{ + addr.ZeroAddr(); + + addrinfo* pInfo = nullptr; + addrinfo hints = {0}; + +#if defined(__ANDROID__) + hints.ai_flags = 0; +#else + hints.ai_flags = (AI_V4MAPPED | AI_ADDRCONFIG); +#endif + hints.ai_family = addr.family; + hints.ai_socktype = SOCK_STREAM; + + int rs = ::getaddrinfo(CT2A(lpszHost), nullptr, &hints, &pInfo); + + if(!IS_NO_ERROR(rs)) + { + ::WSASetLastError(ERROR_HOSTUNREACH); + return FALSE; + } + + BOOL isOK = FALSE; + + for(addrinfo* pCur = pInfo; pCur != nullptr; pCur = pCur->ai_next) + { + if(pCur->ai_family == AF_INET || pCur->ai_family == AF_INET6) + { + memcpy(addr.Addr(), pCur->ai_addr, pCur->ai_addrlen); + isOK = TRUE; + + break; + } + } + + EXECUTE_RESTORE_ERROR(::freeaddrinfo(pInfo)); + + if(isOK) + addr.SetPort(usPort); + else + ::WSASetLastError(ERROR_HOSTUNREACH); + + return isOK; +} + +BOOL EnumHostIPAddresses(LPCTSTR lpszHost, EnIPAddrType enType, LPTIPAddr** lpppIPAddr, int& iIPAddrCount) +{ + *lpppIPAddr = nullptr; + iIPAddrCount = 0; + + ADDRESS_FAMILY usFamily = (enType == IPT_ALL ? + AF_UNSPEC : (enType == IPT_IPV4 ? + AF_INET : (enType == IPT_IPV6 ? + AF_INET6 : 0xFF))); + + if(usFamily == 0xFF) + { + ::WSASetLastError(ERROR_AFNOSUPPORT); + return FALSE; + } + + vector vt; + + ADDRESS_FAMILY usFamily2 = DetermineAddrFamily(lpszHost); + + if(usFamily2 != AF_UNSPEC) + { + if(usFamily != AF_UNSPEC && usFamily != usFamily2) + { + ::WSASetLastError(ERROR_HOSTUNREACH); + return FALSE; + } + + HP_SOCKADDR addr(usFamily2); + + if(!GetSockAddr(lpszHost, 0, addr)) + return FALSE; + + vt.emplace_back(&addr); + + return RetrieveSockAddrIPAddresses(vt, lpppIPAddr, iIPAddrCount); + } + + addrinfo* pInfo = nullptr; + addrinfo hints = {0}; + +#if defined(__ANDROID__) + hints.ai_flags = 0; +#else + hints.ai_flags = AI_ALL; +#endif + hints.ai_family = usFamily; + hints.ai_socktype = SOCK_STREAM; + + int rs = ::getaddrinfo(CT2A(lpszHost), nullptr, &hints, &pInfo); + + if(rs != NO_ERROR) + { + ::WSASetLastError(rs); + return FALSE; + } + + for(addrinfo* pCur = pInfo; pCur != nullptr; pCur = pCur->ai_next) + { + if(pCur->ai_family == AF_INET || pCur->ai_family == AF_INET6) + vt.emplace_back((HP_PSOCKADDR)pCur->ai_addr); + } + + BOOL isOK = RetrieveSockAddrIPAddresses(vt, lpppIPAddr, iIPAddrCount); + + ::freeaddrinfo(pInfo); + + if(!isOK) ::WSASetLastError(EHOSTUNREACH); + + return isOK; +} + +BOOL RetrieveSockAddrIPAddresses(const vector& vt, LPTIPAddr** lpppIPAddr, int& iIPAddrCount) +{ + iIPAddrCount = (int)vt.size(); + + if(iIPAddrCount == 0) return FALSE; + + HP_PSOCKADDR pSockAddr; + ADDRESS_FAMILY usFamily; + USHORT usPort; + int iAddrLength; + LPTSTR lpszAddr; + LPTIPAddr lpItem; + + (*lpppIPAddr) = new LPTIPAddr[iIPAddrCount + 1]; + (*lpppIPAddr)[iIPAddrCount] = nullptr; + + for(int i = 0; i < iIPAddrCount; i++) + { + pSockAddr = vt[i]; + iAddrLength = HP_SOCKADDR::AddrMinStrLength(pSockAddr->family); + lpszAddr = new TCHAR[iAddrLength]; + + VERIFY(sockaddr_IN_2_A(*vt[i], usFamily, lpszAddr, iAddrLength, usPort)); + + lpItem = new TIPAddr; + lpItem->type = pSockAddr->IsIPv4() ? IPT_IPV4 : IPT_IPV6; + lpItem->address = lpszAddr; + + (*lpppIPAddr)[i] = lpItem; + } + + return TRUE; +} + +BOOL FreeHostIPAddresses(LPTIPAddr* lppIPAddr) +{ + if(!lppIPAddr) return FALSE; + + LPTIPAddr p; + LPTIPAddr* lppCur = lppIPAddr; + + while((p = *lppCur++) != nullptr) + { + delete[] p->address; + delete p; + } + + delete[] lppIPAddr; + + return TRUE; +} + +BOOL sockaddr_IN_2_A(const HP_SOCKADDR& addr, ADDRESS_FAMILY& usFamily, LPTSTR lpszAddress, int& iAddressLen, USHORT& usPort) +{ + BOOL isOK = FALSE; + + usFamily = addr.family; + usPort = addr.Port(); + + if(::InetNtop(addr.family, addr.SinAddr(), lpszAddress, iAddressLen)) + { + iAddressLen = (int)lstrlen(lpszAddress) + 1; + isOK = TRUE; + } + else + { + if(::WSAGetLastError() == ENOSPC) + iAddressLen = HP_SOCKADDR::AddrMinStrLength(usFamily); + } + + return isOK; +} + +BOOL sockaddr_A_2_IN(LPCTSTR lpszAddress, USHORT usPort, HP_SOCKADDR& addr) +{ + addr.family = DetermineAddrFamily(lpszAddress); + return GetSockAddr(lpszAddress, usPort, addr); +} + +BOOL GetSocketAddress(SOCKET socket, LPTSTR lpszAddress, int& iAddressLen, USHORT& usPort, BOOL bLocal) +{ + HP_SOCKADDR addr; + + int addr_len = addr.AddrSize(); + int result = bLocal ? getsockname(socket, addr.Addr(), (socklen_t*)&addr_len) : getpeername(socket, addr.Addr(), (socklen_t*)&addr_len); + + if(result != NO_ERROR) + return FALSE; + + ADDRESS_FAMILY usFamily; + return sockaddr_IN_2_A(addr, usFamily, lpszAddress, iAddressLen, usPort); +} + +BOOL GetSocketLocalAddress(SOCKET socket, LPTSTR lpszAddress, int& iAddressLen, USHORT& usPort) +{ + return GetSocketAddress(socket, lpszAddress, iAddressLen, usPort, TRUE); +} + +BOOL GetSocketRemoteAddress(SOCKET socket, LPTSTR lpszAddress, int& iAddressLen, USHORT& usPort) +{ + return GetSocketAddress(socket, lpszAddress, iAddressLen, usPort, FALSE); +} + +BOOL SetMultiCastSocketOptions(SOCKET sock, const HP_SOCKADDR& bindAddr, const HP_SOCKADDR& castAddr, int iMCTtl, BOOL bMCLoop) +{ + if(castAddr.IsIPv4()) + { + BYTE ttl = (BYTE)iMCTtl; + BYTE loop = (BYTE)bMCLoop; + + VERIFY(::SSO_SetSocketOption(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) != SOCKET_ERROR); + VERIFY(::SSO_SetSocketOption(sock, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) != SOCKET_ERROR); + + ip_mreq mcast; + ::ZeroMemory(&mcast, sizeof(mcast)); + + mcast.imr_multiaddr = castAddr.addr4.sin_addr; + mcast.imr_interface = bindAddr.addr4.sin_addr; + + if(::SSO_SetSocketOption(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mcast, sizeof(mcast)) == SOCKET_ERROR) + return FALSE; + if(::SSO_SetSocketOption(sock, IPPROTO_IP, IP_MULTICAST_IF, bindAddr.SinAddr(), sizeof(IN_ADDR)) == SOCKET_ERROR) + return FALSE; + } + else + { + INT ttl = (INT)iMCTtl; + UINT loop = (UINT)bMCLoop; + + VERIFY(::SSO_SetSocketOption(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)) != SOCKET_ERROR); + VERIFY(::SSO_SetSocketOption(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &loop, sizeof(loop)) != SOCKET_ERROR); + + ipv6_mreq mcast; + ::ZeroMemory(&mcast, sizeof(mcast)); + + mcast.ipv6mr_multiaddr = castAddr.addr6.sin6_addr; + mcast.ipv6mr_interface = bindAddr.addr6.sin6_scope_id; + + if(::SSO_SetSocketOption(sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mcast, sizeof(mcast)) == SOCKET_ERROR) + return FALSE; + if(::SSO_SetSocketOption(sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, (PVOID)(&bindAddr.addr6.sin6_scope_id), sizeof(UINT)) == SOCKET_ERROR) + return FALSE; + } + + return TRUE; +} + +int WaitForSocketWrite(SOCKET sock, DWORD dwTimeout) +{ + timeval tv = {(__time_t)(dwTimeout / 1000), (__suseconds_t)((dwTimeout % 1000) * 1000)}; + + fd_set wfds, efds; + FD_ZERO(&wfds); + FD_ZERO(&efds); + FD_SET(sock, &wfds); + FD_SET(sock, &efds); + + int rs = NO_EINTR_INT(select(sock + 1, nullptr, &wfds, &efds, &tv)); + + if(rs <= 0) return ((rs == 0) ? ERROR_TIMEOUT : ENSURE_ERROR(ERROR_CANT_WAIT)); + + if(FD_ISSET(sock, &efds)) + { + rs = SSO_GetError(sock); + return ((rs != NO_ERROR && rs != SOCKET_ERROR) ? rs : ENSURE_ERROR(ERROR_CANT_WAIT)); + } + + VERIFY(FD_ISSET(sock, &wfds)); + + rs = SSO_GetError(sock); + + if(!IS_NO_ERROR(rs)) + return ((rs != SOCKET_ERROR) ? rs : ENSURE_ERROR(ERROR_CANT_WAIT)); + + return NO_ERROR; +} + +ULONGLONG NToH64(ULONGLONG value) +{ + return (((ULONGLONG)ntohl((UINT)((value << 32) >> 32))) << 32) | ntohl((UINT)(value >> 32)); +} + +ULONGLONG HToN64(ULONGLONG value) +{ + return (((ULONGLONG)htonl((UINT)((value << 32) >> 32))) << 32) | htonl((UINT)(value >> 32)); +} + +BOOL IsLittleEndian() +{ + static const USHORT _s_endian_test_value = 0x0102; + static const BOOL _s_bLE = (*((BYTE*)&_s_endian_test_value) == 0x02); + + return _s_bLE; +} + +USHORT HToLE16(USHORT value) +{ + return IsLittleEndian() ? value : ENDIAN_SWAP_16(value); +} + +USHORT HToBE16(USHORT value) +{ + return IsLittleEndian() ? ENDIAN_SWAP_16(value) : value; +} + +DWORD HToLE32(DWORD value) +{ + return IsLittleEndian() ? value : ENDIAN_SWAP_32(value); +} + +DWORD HToBE32(DWORD value) +{ + return IsLittleEndian() ? ENDIAN_SWAP_32(value) : value; +} + +HRESULT ReadSmallFile(LPCTSTR lpszFileName, CFile& file, CFileMapping& fmap, DWORD dwMaxFileSize) +{ + ASSERT(lpszFileName != nullptr); + + if(file.Open(lpszFileName, O_RDONLY)) + { + SIZE_T dwSize; + if(file.GetSize(dwSize)) + { + if(dwSize > 0 && dwSize <= dwMaxFileSize) + { + if(fmap.Map(file, dwSize)) + return NO_ERROR; + } + else if(dwSize == 0) + ::SetLastError(ERROR_EMPTY); + else + ::SetLastError(ERROR_FILE_TOO_LARGE); + } + } + + HRESULT rs = ::GetLastError(); + + return (!IS_NO_ERROR(rs) ? rs : ERROR_UNKNOWN); +} + +HRESULT MakeSmallFilePackage(LPCTSTR lpszFileName, CFile& file, CFileMapping& fmap, WSABUF szBuf[3], const LPWSABUF pHead, const LPWSABUF pTail) +{ + DWORD dwMaxFileSize = MAX_SMALL_FILE_SIZE - (pHead ? pHead->len : 0) - (pTail ? pTail->len : 0); + ASSERT(dwMaxFileSize <= MAX_SMALL_FILE_SIZE); + + HRESULT hr = ReadSmallFile(lpszFileName, file, fmap, dwMaxFileSize); + + if(IS_NO_ERROR(hr)) + { + szBuf[1].len = (UINT)fmap.Size(); + szBuf[1].buf = fmap; + + if(pHead) memcpy(&szBuf[0], pHead, sizeof(WSABUF)); + else memset(&szBuf[0], 0, sizeof(WSABUF)); + + if(pTail) memcpy(&szBuf[2], pTail, sizeof(WSABUF)); + else memset(&szBuf[2], 0, sizeof(WSABUF)); + } + + return hr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////// + +int SSO_SetSocketOption(SOCKET sock, int level, int name, LPVOID val, int len) +{ + return setsockopt(sock, level, name, val, (socklen_t)len); +} + +int SSO_GetSocketOption(SOCKET sock, int level, int name, LPVOID val, int* len) +{ + return getsockopt(sock, level, name, val, (socklen_t*)len); +} + +int SSO_IoctlSocket(SOCKET sock, long cmd, PVOID arg) +{ + return ioctl(sock, cmd, arg); +} + +int SSO_NoBlock(SOCKET sock, BOOL bNoBlock) +{ + return fcntl_SETFL(sock, O_NONBLOCK, bNoBlock) ? NO_ERROR : SOCKET_ERROR; +} + +int SSO_NoDelay(SOCKET sock, BOOL bNoDelay) +{ + int val = bNoDelay ? 1 : 0; + return setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(int)); +} + +int SSO_DontLinger(SOCKET sock, BOOL bDont) +{ + return SSO_Linger(sock, 0, 0); +} + +int SSO_Linger(SOCKET sock, int l_onoff, int l_linger) +{ + linger ln = {l_onoff, l_linger}; + return setsockopt(sock, SOL_SOCKET, SO_LINGER, &ln, sizeof(linger)); +} + +int SSO_KeepAlive(SOCKET sock, BOOL bKeepAlive) +{ + int val = bKeepAlive ? 1 : 0; + return setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(int)); +} + +int SSO_KeepAliveVals(SOCKET sock, BOOL bOnOff, DWORD dwIdle, DWORD dwInterval, DWORD dwCount) +{ + if(bOnOff) + { + dwIdle /= 1000; + dwInterval /= 1000; + + if(dwIdle == 0 || dwInterval == 0 || dwCount == 0) + { + ::WSASetLastError(ERROR_INVALID_PARAMETER); + return SOCKET_ERROR; + } + } + + BOOL isOK = IS_NO_ERROR(SSO_KeepAlive(sock, bOnOff)); + + if(isOK && bOnOff) + { + isOK &= IS_NO_ERROR(setsockopt(sock, SOL_TCP, TCP_KEEPIDLE, &dwIdle, sizeof(DWORD))); + isOK &= IS_NO_ERROR(setsockopt(sock, SOL_TCP, TCP_KEEPINTVL, &dwInterval, sizeof(DWORD))); + isOK &= IS_NO_ERROR(setsockopt(sock, SOL_TCP, TCP_KEEPCNT, &dwCount, sizeof(DWORD))); + } + + return isOK ? NO_ERROR : SOCKET_ERROR; +} + +int SSO_ReuseAddress(SOCKET sock, EnReuseAddressPolicy opt) +{ + int iSet = 1; + int iUnSet = 0; + int rs = NO_ERROR; + + BOOL bReusePortSupported = +#if defined(__linux) || defined(__linux__) + ::IsKernelVersionAbove(2, 6, 32); +#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) || defined(__APPLE__) || defined(__MACH__) + TRUE; +#else + FALSE; +#endif + + if(opt == RAP_NONE) + { + rs = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &iUnSet, sizeof(int)); + if(bReusePortSupported) + rs |= setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &iUnSet, sizeof(int)); + } + else if(opt == RAP_ADDR_ONLY) + { + rs = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &iSet, sizeof(int)); + if(bReusePortSupported) + rs |= setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &iUnSet, sizeof(int)); + } + else if(opt == RAP_ADDR_AND_PORT) + { + rs = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &iSet, sizeof(int)); + if(bReusePortSupported) + rs |= setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &iSet, sizeof(int)); + } + else + { + ::SetLastError(ERROR_INVALID_PARAMETER); + rs = -1; + } + + return rs; +} + +int SSO_RecvBuffSize(SOCKET sock, int size) +{ + return setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &size, sizeof(int)); +} + +int SSO_SendBuffSize(SOCKET sock, int size) +{ + return setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &size, sizeof(int)); +} + +int SSO_RecvTimeOut(SOCKET sock, int ms) +{ + timeval tv; + ::MillisecondToTimeval(ms, tv); + + return setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(timeval)); +} + +int SSO_SendTimeOut(SOCKET sock, int ms) +{ + timeval tv; + ::MillisecondToTimeval(ms, tv); + + return setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(timeval)); +} + +int SSO_GetError(SOCKET sock) +{ + int e; + socklen_t len = sizeof(e); + + if(IS_NO_ERROR(getsockopt(sock, SOL_SOCKET, SO_ERROR, &e, &len))) + return e; + + return SOCKET_ERROR; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////// + +CONNID GenerateConnectionID() +{ + static volatile CONNID s_dwConnID = 0; + + CONNID dwConnID = ::InterlockedIncrement(&s_dwConnID); + + if(dwConnID == 0) + dwConnID = ::InterlockedIncrement(&s_dwConnID); + + return dwConnID; +} + +int IsUdpCloseNotify(const BYTE* pData, int iLength) +{ + return (iLength == s_iUdpCloseNotifySize && + memcmp(pData, s_szUdpCloseNotify, s_iUdpCloseNotifySize) == 0) ; +} + +int SendUdpCloseNotify(SOCKET sock) +{ + return (int)send(sock, (LPCSTR)s_szUdpCloseNotify, s_iUdpCloseNotifySize, 0); +} + +int SendUdpCloseNotify(SOCKET sock, const HP_SOCKADDR& remoteAddr) +{ + return (int)sendto(sock, (LPCSTR)s_szUdpCloseNotify, s_iUdpCloseNotifySize, 0, remoteAddr.Addr(), remoteAddr.AddrSize()); +} + +int ManualCloseSocket(SOCKET sock, int iShutdownFlag, BOOL bGraceful) +{ + if(!bGraceful) + SSO_Linger(sock, 1, 0); + + if(iShutdownFlag != 0xFF) + shutdown(sock, iShutdownFlag); + + return closesocket(sock); +} + +DWORD GuessBase64EncodeBound(DWORD dwSrcLen) +{ + return 4 * ((dwSrcLen + 2) / 3); +} + +DWORD GuessBase64DecodeBound(const BYTE* lpszSrc, DWORD dwSrcLen) +{ + if(dwSrcLen < 2) + return 0; + + if(lpszSrc[dwSrcLen - 2] == '=') + dwSrcLen -= 2; + else if(lpszSrc[dwSrcLen - 1] == '=') + --dwSrcLen; + + DWORD dwMod = dwSrcLen % 4; + DWORD dwAdd = dwMod == 2 ? 1 : (dwMod == 3 ? 2 : 0); + + return 3 * (dwSrcLen / 4) + dwAdd; +} + +int Base64Encode(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen) +{ + static const BYTE CODES[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + DWORD dwRealLen = GuessBase64EncodeBound(dwSrcLen); + + if(lpszDest == nullptr || dwDestLen < dwRealLen) + { + dwDestLen = dwRealLen; + return -5; + } + + BYTE* p = lpszDest; + DWORD leven = 3 * (dwSrcLen / 3); + DWORD i = 0; + + for (; i < leven; i += 3) + { + *p++ = CODES[lpszSrc[0] >> 2]; + *p++ = CODES[((lpszSrc[0] & 3) << 4) + (lpszSrc[1] >> 4)]; + *p++ = CODES[((lpszSrc[1] & 0xf) << 2) + (lpszSrc[2] >> 6)]; + *p++ = CODES[lpszSrc[2] & 0x3f]; + + lpszSrc += 3; + } + + if(i < dwSrcLen) + { + BYTE a = lpszSrc[0]; + BYTE b = (i + 1 < dwSrcLen) ? lpszSrc[1] : 0; + + *p++ = CODES[a >> 2]; + *p++ = CODES[((a & 3) << 4) + (b >> 4)]; + *p++ = (i + 1 < dwSrcLen) ? CODES[((b & 0xf) << 2)] : '='; + *p++ = '='; + } + + ASSERT(dwRealLen == (DWORD)(p - lpszDest)); + + if(dwDestLen > dwRealLen) + { + *p = 0; + dwDestLen = dwRealLen; + } + + return 0; +} + +int Base64Decode(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen) +{ + static const BYTE MAP[256] = + { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 253, 255, + 255, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 253, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, + 255, 254, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255, + 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, + 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255 + }; + + DWORD dwRealLen = GuessBase64DecodeBound(lpszSrc, dwSrcLen); + + if(lpszDest == nullptr || dwDestLen < dwRealLen) + { + dwDestLen = dwRealLen; + return -5; + } + + BYTE c; + int g = 3; + DWORD i, x, y, z; + + for(i = x = y = z = 0; i < dwSrcLen || x != 0;) + { + c = i < dwSrcLen ? MAP[lpszSrc[i++]] : 254; + + if(c == 255) {dwDestLen = 0; return -3;} + else if(c == 254) {c = 0; g--;} + else if(c == 253) continue; + + z = (z << 6) | c; + + if(++x == 4) + { + lpszDest[y++] = (BYTE)((z >> 16) & 255); + if (g > 1) lpszDest[y++] = (BYTE)((z >> 8) & 255); + if (g > 2) lpszDest[y++] = (BYTE)(z & 255); + + x = z = 0; + } + } + + BOOL isOK = (y == dwRealLen); + + if(!isOK) + dwDestLen = 0; + else + { + if(dwDestLen > dwRealLen) + { + lpszDest[dwRealLen] = 0; + dwDestLen = dwRealLen; + } + } + + return isOK ? 0 : -3; +} + +DWORD GuessUrlEncodeBound(const BYTE* lpszSrc, DWORD dwSrcLen) +{ + DWORD dwAdd = 0; + + for(DWORD i = 0; i < dwSrcLen; i++) + { + BYTE c = lpszSrc[i]; + + if(!(isalnum(c) || c == ' ' || c == '.' || c == '-' || c == '_' || c == '*')) + dwAdd += 2; + } + + return dwSrcLen + dwAdd; +} + +DWORD GuessUrlDecodeBound(const BYTE* lpszSrc, DWORD dwSrcLen) +{ + DWORD dwPercent = 0; + + for(DWORD i = 0; i < dwSrcLen; i++) + { + if(lpszSrc[i] == '%') + { + ++dwPercent; + i += 2; + } + } + + DWORD dwSub = dwPercent * 2; + + if(dwSrcLen < dwSub) + return 0; + + return dwSrcLen - dwSub; +} + +int UrlEncode(BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen) +{ + BYTE c; + DWORD j = 0; + + if(lpszDest == nullptr || dwDestLen == 0) + goto ERROR_DEST_LEN; + + for(DWORD i = 0; i < dwSrcLen; i++) + { + if(j >= dwDestLen) + goto ERROR_DEST_LEN; + + c = lpszSrc[i]; + + if (isalnum(c) || c == '.' || c == '-' || c == '_' || c == '*') + lpszDest[j++] = c; + else if(c == ' ') + lpszDest[j++] = '+'; + else + { + if(j + 3 >= dwDestLen) + goto ERROR_DEST_LEN; + + lpszDest[j++] = '%'; + HEX_VALUE_TO_DOUBLE_CHAR(lpszDest + j, c); + j += 2; + + } + } + + if(dwDestLen > j) + { + lpszDest[j] = 0; + dwDestLen = j; + } + + return 0; + +ERROR_DEST_LEN: + dwDestLen = GuessUrlEncodeBound(lpszSrc, dwSrcLen); + return -5; +} + +int UrlDecode(BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen) +{ + char c; + DWORD j = 0; + + if(lpszDest == nullptr || dwDestLen == 0) + goto ERROR_DEST_LEN; + + for(DWORD i = 0; i < dwSrcLen; i++) + { + if(j >= dwDestLen) + goto ERROR_DEST_LEN; + + c = lpszSrc[i]; + + if(c == '+') + lpszDest[j++] = ' '; + else if(c != '%') + lpszDest[j++] = c; + else + { + if(i + 2 >= dwSrcLen) + goto ERROR_SRC_DATA; + + lpszDest[j++] = HEX_DOUBLE_CHAR_TO_VALUE(lpszSrc + i + 1); + i += 2; + } + } + + if(dwDestLen > j) + { + lpszDest[j] = 0; + dwDestLen = j; + } + + return 0; + +ERROR_SRC_DATA: + dwDestLen = 0; + return -3; + +ERROR_DEST_LEN: + dwDestLen = GuessUrlDecodeBound(lpszSrc, dwSrcLen); + return -5; +} + +void DestroyCompressor(IHPCompressor* pCompressor) +{ + delete pCompressor; +} + +void DestroyDecompressor(IHPDecompressor* pDecompressor) +{ + delete pDecompressor; +} + +#ifdef _ZLIB_SUPPORT + +CHPZLibCompressor::CHPZLibCompressor(Fn_CompressDataCallback fnCallback, int iWindowBits, int iLevel, int iMethod, int iMemLevel, int iStrategy, DWORD dwBuffSize) +: m_fnCallback (fnCallback) +, m_dwBuffSize (dwBuffSize) +, m_bValid (FALSE) +{ + ASSERT(m_fnCallback != nullptr); + + ::ZeroObject(m_Stream); + + m_bValid = (::deflateInit2(&m_Stream, iLevel, iMethod, iWindowBits, iMemLevel, iStrategy) == Z_OK); +} +CHPZLibCompressor::~CHPZLibCompressor() +{ + if(m_bValid) ::deflateEnd(&m_Stream); +} + +BOOL CHPZLibCompressor::Reset() +{ + return (m_bValid = (::deflateReset(&m_Stream) == Z_OK)); +} + +BOOL CHPZLibCompressor::Process(const BYTE* pData, int iLength, BOOL bLast, PVOID pContext) +{ + return ProcessEx(pData, iLength, bLast, FALSE, pContext); +} + +BOOL CHPZLibCompressor::ProcessEx(const BYTE* pData, int iLength, BOOL bLast, BOOL bFlush, PVOID pContext) +{ + ASSERT(IsValid() && iLength > 0); + + if(!IsValid()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + unique_ptr szBuff = make_unique(m_dwBuffSize); + + m_Stream.next_in = (z_const Bytef*)pData; + m_Stream.avail_in = iLength; + + BOOL isOK = TRUE; + int rs = Z_OK; + int flush = bLast ? Z_FINISH : (bFlush ? Z_SYNC_FLUSH : Z_NO_FLUSH); + + while(m_Stream.avail_in > 0) + { + do + { + m_Stream.next_out = szBuff.get(); + m_Stream.avail_out = m_dwBuffSize; + + rs = ::deflate(&m_Stream, flush); + + if(rs == Z_STREAM_ERROR) + { + ::SetLastError(ERROR_INVALID_DATA); + isOK = FALSE; + + goto ZLIB_COMPRESS_END; + } + + int iRead = (int)(m_dwBuffSize - m_Stream.avail_out); + + if(iRead == 0) + break; + + if(!m_fnCallback(szBuff.get(), iRead, pContext)) + { + ::SetLastError(ERROR_CANCELLED); + isOK = FALSE; + + goto ZLIB_COMPRESS_END; + } + } while(m_Stream.avail_out == 0); + } + +ZLIB_COMPRESS_END: + + ASSERT(!isOK || (rs == Z_OK && !bLast) || (rs == Z_STREAM_END && bLast)); + + if(!isOK || bLast) Reset(); + + return isOK; +} + +CHPZLibDecompressor::CHPZLibDecompressor(Fn_DecompressDataCallback fnCallback, int iWindowBits, DWORD dwBuffSize) +: m_fnCallback (fnCallback) +, m_dwBuffSize (dwBuffSize) +, m_bValid (FALSE) +{ + ASSERT(m_fnCallback != nullptr); + + ::ZeroObject(m_Stream); + + m_bValid = (::inflateInit2(&m_Stream, iWindowBits) == Z_OK); +} +CHPZLibDecompressor::~CHPZLibDecompressor() +{ + if(m_bValid) ::inflateEnd(&m_Stream); +} + +BOOL CHPZLibDecompressor::Reset() +{ + return (m_bValid = (::inflateReset(&m_Stream) == Z_OK)); +} + +BOOL CHPZLibDecompressor::Process(const BYTE* pData, int iLength, PVOID pContext) +{ + ASSERT(IsValid() && iLength > 0); + + if(!IsValid()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + unique_ptr szBuff = make_unique(m_dwBuffSize); + + m_Stream.next_in = (z_const Bytef*)pData; + m_Stream.avail_in = iLength; + + BOOL isOK = TRUE; + int rs = Z_OK; + + while(m_Stream.avail_in > 0) + { + do + { + m_Stream.next_out = szBuff.get(); + m_Stream.avail_out = m_dwBuffSize; + + rs = ::inflate(&m_Stream, Z_NO_FLUSH); + + if(rs != Z_OK && rs != Z_STREAM_END) + { + ::SetLastError(ERROR_INVALID_DATA); + isOK = FALSE; + + goto ZLIB_DECOMPRESS_END; + } + + int iRead = (int)(m_dwBuffSize - m_Stream.avail_out); + + if(iRead == 0) + break; + + if(!m_fnCallback(szBuff.get(), iRead, pContext)) + { + ::SetLastError(ERROR_CANCELLED); + isOK = FALSE; + + goto ZLIB_DECOMPRESS_END; + } + } while(m_Stream.avail_out == 0); + + if(rs == Z_STREAM_END) + break; + } + +ZLIB_DECOMPRESS_END: + + ASSERT(!isOK || rs == Z_OK || rs == Z_STREAM_END); + + if(!isOK || rs == Z_STREAM_END) Reset(); + + return isOK; +} + +IHPCompressor* CreateZLibCompressor(Fn_CompressDataCallback fnCallback, int iWindowBits, int iLevel, int iMethod, int iMemLevel, int iStrategy, DWORD dwBuffSize) +{ + return new CHPZLibCompressor(fnCallback, iWindowBits, iLevel, iMethod, iMemLevel, iStrategy, dwBuffSize); +} + +IHPCompressor* CreateGZipCompressor(Fn_CompressDataCallback fnCallback, int iLevel, int iMethod, int iMemLevel, int iStrategy, DWORD dwBuffSize) +{ + return new CHPZLibCompressor(fnCallback, MAX_WBITS + 16, iLevel, iMethod, iMemLevel, iStrategy, dwBuffSize); +} + +IHPDecompressor* CreateZLibDecompressor(Fn_DecompressDataCallback fnCallback, int iWindowBits, DWORD dwBuffSize) +{ + return new CHPZLibDecompressor(fnCallback, iWindowBits, dwBuffSize); +} + +IHPDecompressor* CreateGZipDecompressor(Fn_DecompressDataCallback fnCallback, DWORD dwBuffSize) +{ + return new CHPZLibDecompressor(fnCallback, MAX_WBITS + 32, dwBuffSize); +} + +int Compress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen) +{ + return CompressEx(lpszSrc, dwSrcLen, lpszDest, dwDestLen); +} + +int CompressEx(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen, int iLevel, int iMethod, int iWindowBits, int iMemLevel, int iStrategy) +{ + z_stream stream; + + stream.next_in = (z_const Bytef*)lpszSrc; + stream.avail_in = dwSrcLen; + stream.next_out = lpszDest; + stream.avail_out = dwDestLen; + stream.zalloc = nullptr; + stream.zfree = nullptr; + stream.opaque = nullptr; + + int err = ::deflateInit2(&stream, iLevel, iMethod, iWindowBits, iMemLevel, iStrategy); + + if(err != Z_OK) return err; + + err = ::deflate(&stream, Z_FINISH); + + if(err != Z_STREAM_END) + { + ::deflateEnd(&stream); + return err == Z_OK ? Z_BUF_ERROR : err; + } + + if(dwDestLen > stream.total_out) + { + lpszDest[stream.total_out] = 0; + dwDestLen = (DWORD)stream.total_out; + } + + return ::deflateEnd(&stream); +} + +int Uncompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen) +{ + return UncompressEx(lpszSrc, dwSrcLen, lpszDest, dwDestLen); +} + +int UncompressEx(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen, int iWindowBits) +{ + z_stream stream; + + stream.next_in = (z_const Bytef*)lpszSrc; + stream.avail_in = (uInt)dwSrcLen; + stream.next_out = lpszDest; + stream.avail_out = dwDestLen; + stream.zalloc = nullptr; + stream.zfree = nullptr; + + int err = ::inflateInit2(&stream, iWindowBits); + + if(err != Z_OK) return err; + + err = ::inflate(&stream, Z_FINISH); + + if(err != Z_STREAM_END) + { + ::inflateEnd(&stream); + return (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0)) ? Z_DATA_ERROR : err; + } + + if(dwDestLen > stream.total_out) + { + lpszDest[stream.total_out] = 0; + dwDestLen = (DWORD)stream.total_out; + } + + return inflateEnd(&stream); +} + +DWORD GuessCompressBound(DWORD dwSrcLen, BOOL bGZip) +{ + DWORD dwBound = (DWORD)::compressBound(dwSrcLen); + + if(bGZip) dwBound += 16; + + return dwBound; +} + +int GZipCompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen) +{ + return CompressEx(lpszSrc, dwSrcLen, lpszDest, dwDestLen, Z_DEFAULT_COMPRESSION, Z_DEFLATED, MAX_WBITS + 16); +} + +int GZipUncompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen) +{ + return UncompressEx(lpszSrc, dwSrcLen, lpszDest, dwDestLen, MAX_WBITS + 32); +} + +DWORD GZipGuessUncompressBound(const BYTE* lpszSrc, DWORD dwSrcLen) +{ + if(dwSrcLen < 20 || *(USHORT*)lpszSrc != 0x8B1F) + return 0; + + return *(DWORD*)(lpszSrc + dwSrcLen - 4); +} + +#endif + +#ifdef _BROTLI_SUPPORT + +CHPBrotliCompressor::CHPBrotliCompressor(Fn_CompressDataCallback fnCallback, int iQuality, int iWindow, int iMode, DWORD dwBuffSize) +: m_fnCallback (fnCallback) +, m_iQuality (iQuality) +, m_iWindow (iWindow) +, m_iMode (iMode) +, m_dwBuffSize (dwBuffSize) +, m_bValid (FALSE) +{ + ASSERT(m_fnCallback != nullptr); + + Reset(); +} + +CHPBrotliCompressor::~CHPBrotliCompressor() +{ + if(m_bValid) ::BrotliEncoderDestroyInstance(m_pState); +} + +BOOL CHPBrotliCompressor::Reset() +{ + if(m_bValid) ::BrotliEncoderDestroyInstance(m_pState); + m_pState = ::BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); + + if(m_pState != nullptr) + { + ::BrotliEncoderSetParameter(m_pState, BROTLI_PARAM_QUALITY , (UINT)m_iQuality); + ::BrotliEncoderSetParameter(m_pState, BROTLI_PARAM_LGWIN , (UINT)m_iWindow); + ::BrotliEncoderSetParameter(m_pState, BROTLI_PARAM_MODE , (UINT)m_iMode); + + if (m_iWindow > BROTLI_MAX_WINDOW_BITS) + ::BrotliEncoderSetParameter(m_pState, BROTLI_PARAM_LARGE_WINDOW, BROTLI_TRUE); + } + + return (m_bValid = (m_pState != nullptr)); +} + +BOOL CHPBrotliCompressor::Process(const BYTE* pData, int iLength, BOOL bLast, PVOID pContext) +{ + return ProcessEx(pData, iLength, bLast, FALSE, pContext); +} + +BOOL CHPBrotliCompressor::ProcessEx(const BYTE* pData, int iLength, BOOL bLast, BOOL bFlush, PVOID pContext) +{ + ASSERT(IsValid() && iLength > 0); + + if(!IsValid()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + unique_ptr szBuff = make_unique(m_dwBuffSize); + + const BYTE* pNextInData = pData; + size_t iAvlInLen = (SIZE_T)iLength; + BYTE* pNextOutData = nullptr; + size_t iAvlOutLen = 0; + + BOOL isOK = TRUE; + BrotliEncoderOperation op = bLast ? BROTLI_OPERATION_FINISH : (bFlush ? BROTLI_OPERATION_FLUSH : BROTLI_OPERATION_PROCESS); + + while(iAvlInLen > 0) + { + do + { + pNextOutData = szBuff.get(); + iAvlOutLen = m_dwBuffSize; + + if(!::BrotliEncoderCompressStream(m_pState, op, &iAvlInLen, &pNextInData, &iAvlOutLen, &pNextOutData, nullptr)) + { + ::SetLastError(ERROR_INVALID_DATA); + isOK = FALSE; + + goto BROTLI_COMPRESS_END; + } + + int iRead = (int)(m_dwBuffSize - iAvlOutLen); + + if(iRead == 0) + break; + + if(!m_fnCallback(szBuff.get(), iRead, pContext)) + { + ::SetLastError(ERROR_CANCELLED); + isOK = FALSE; + + goto BROTLI_COMPRESS_END; + } + } while (iAvlOutLen == 0); + } + +BROTLI_COMPRESS_END: + + if(!isOK || bLast) Reset(); + + return isOK; +} + +CHPBrotliDecompressor::CHPBrotliDecompressor(Fn_DecompressDataCallback fnCallback, DWORD dwBuffSize) +: m_fnCallback (fnCallback) +, m_dwBuffSize (dwBuffSize) +, m_bValid (FALSE) +{ + ASSERT(m_fnCallback != nullptr); + + Reset(); +} + +CHPBrotliDecompressor::~CHPBrotliDecompressor() +{ + if(m_bValid) ::BrotliDecoderDestroyInstance(m_pState); +} + +BOOL CHPBrotliDecompressor::Reset() +{ + if(m_bValid) ::BrotliDecoderDestroyInstance(m_pState); + m_pState = ::BrotliDecoderCreateInstance(nullptr, nullptr, nullptr); + + return (m_bValid = (m_pState != nullptr)); +} + +BOOL CHPBrotliDecompressor::Process(const BYTE* pData, int iLength, PVOID pContext) +{ + ASSERT(IsValid() && iLength > 0); + + if(!IsValid()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + unique_ptr szBuff = make_unique(m_dwBuffSize); + + const BYTE* pNextInData = pData; + size_t iAvlInLen = (SIZE_T)iLength; + BYTE* pNextOutData = nullptr; + size_t iAvlOutLen = 0; + + BOOL isOK = TRUE; + BrotliDecoderResult rs = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT; + + do + { + do + { + pNextOutData = szBuff.get(); + iAvlOutLen = m_dwBuffSize; + + rs = ::BrotliDecoderDecompressStream(m_pState, &iAvlInLen, &pNextInData, &iAvlOutLen, &pNextOutData, nullptr); + + if(rs == BROTLI_DECODER_RESULT_ERROR) + { + ::SetLastError(ERROR_INVALID_DATA); + isOK = FALSE; + + goto BROTLI_DECOMPRESS_END; + } + + int iRead = (int)(m_dwBuffSize - iAvlOutLen); + + if(iRead == 0) + break; + + if(!m_fnCallback(szBuff.get(), iRead, pContext)) + { + ::SetLastError(ERROR_CANCELLED); + isOK = FALSE; + + goto BROTLI_DECOMPRESS_END; + } + } while (iAvlOutLen == 0); + + if(rs == BROTLI_DECODER_RESULT_SUCCESS) + break; + + } while(rs == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT); + +BROTLI_DECOMPRESS_END: + + if(!isOK || rs == BROTLI_DECODER_RESULT_SUCCESS) Reset(); + + return isOK; +} + +IHPCompressor* CreateBrotliCompressor(Fn_CompressDataCallback fnCallback, int iQuality, int iWindow, int iMode, DWORD dwBuffSize) +{ + return new CHPBrotliCompressor(fnCallback, iQuality, iWindow, iMode, dwBuffSize); +} + +IHPDecompressor* CreateBrotliDecompressor(Fn_DecompressDataCallback fnCallback, DWORD dwBuffSize) +{ + return new CHPBrotliDecompressor(fnCallback, dwBuffSize); +} + +int BrotliCompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen) +{ + return BrotliCompressEx(lpszSrc, dwSrcLen, lpszDest, dwDestLen, BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, BROTLI_DEFAULT_MODE); +} + +int BrotliCompressEx(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen, int iQuality, int iWindow, int iMode) +{ + size_t stDestLen = (size_t)dwDestLen; + int rs = ::BrotliEncoderCompress(iQuality, iWindow, (BrotliEncoderMode)iMode, (size_t)dwSrcLen, lpszSrc, &stDestLen, lpszDest); + dwDestLen = (DWORD)stDestLen; + + return (rs == 1) ? 0 : ((rs == 3) ? -5 : -3); +} + +int BrotliUncompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen) +{ + size_t stDestLen = (size_t)dwDestLen; + BrotliDecoderResult rs = ::BrotliDecoderDecompress((size_t)dwSrcLen, lpszSrc, &stDestLen, lpszDest); + dwDestLen = (DWORD)stDestLen; + + return (rs == BROTLI_DECODER_RESULT_SUCCESS) ? 0 : ((rs == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) ? -5 : -3); +} + +DWORD BrotliGuessCompressBound(DWORD dwSrcLen) +{ + return (DWORD)::BrotliEncoderMaxCompressedSize((size_t)dwSrcLen); +} + +#endif + +#ifdef _ICONV_SUPPORT + +BOOL CharsetConvert(LPCSTR lpszFromCharset, LPCSTR lpszToCharset, LPCSTR lpszInBuf, int iInBufLen, LPSTR lpszOutBuf, int& iOutBufLen) +{ + ASSERT(lpszInBuf != nullptr); + + SIZE_T nInBufLeft = iInBufLen; + SIZE_T nOutBufLeft = iOutBufLen; + int iOutBufSize = iOutBufLen; + iOutBufLen = 0; + + if(lpszInBuf == nullptr) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + iconv_t ic = iconv_open(lpszToCharset, lpszFromCharset); + + if(IS_INVALID_PVOID(ic)) + return FALSE; + + SIZE_T rs = iconv(ic, (LPSTR*)&lpszInBuf, &nInBufLeft, &lpszOutBuf, &nOutBufLeft); + iOutBufLen = iOutBufSize - (int)nOutBufLeft; + + EXECUTE_RESTORE_ERROR(iconv_close(ic)); + + return !IS_HAS_ERROR(rs); +} + +BOOL GbkToUnicodeEx(const char szSrc[], int iSrcLength, WCHAR szDest[], int& iDestLength) +{ + int iInBufLen = (int)((iSrcLength > 0) ? iSrcLength : ((szSrc != nullptr) ? strlen(szSrc) + 1 : 0)); + int iOutBufLen = (int)(iDestLength * sizeof(WCHAR)); + + BOOL isOK = CharsetConvert(CHARSET_GBK, SYSTEM_CHARSET_UNICODE, szSrc, iInBufLen, (char*)szDest, iOutBufLen); + iDestLength = (int)(iOutBufLen / sizeof(WCHAR)); + + return isOK; +} + +BOOL UnicodeToGbkEx(const WCHAR szSrc[], int iSrcLength, char szDest[], int& iDestLength) +{ + int iInBufLen = (int)(((iSrcLength > 0) ? iSrcLength : ((szSrc != nullptr) ? wcslen(szSrc) + 1 : 0)) * sizeof(WCHAR)); + + return CharsetConvert(SYSTEM_CHARSET_UNICODE, CHARSET_GBK, (LPCSTR)szSrc, iInBufLen, szDest, iDestLength); +} + +BOOL Utf8ToUnicodeEx(const char szSrc[], int iSrcLength, WCHAR szDest[], int& iDestLength) +{ + int iInBufLen = (int)((iSrcLength > 0) ? iSrcLength : ((szSrc != nullptr) ? strlen(szSrc) + 1 : 0)); + int iOutBufLen = (int)(iDestLength * sizeof(WCHAR)); + + BOOL isOK = CharsetConvert(CHARSET_UTF_8, SYSTEM_CHARSET_UNICODE, szSrc, iInBufLen, (char*)szDest, iOutBufLen); + iDestLength = (int)(iOutBufLen / sizeof(WCHAR)); + + return isOK; +} + +BOOL UnicodeToUtf8Ex(const WCHAR szSrc[], int iSrcLength, char szDest[], int& iDestLength) +{ + int iInBufLen = (int)(((iSrcLength > 0) ? iSrcLength : ((szSrc != nullptr) ? wcslen(szSrc) + 1 : 0)) * sizeof(WCHAR)); + + return CharsetConvert(SYSTEM_CHARSET_UNICODE, CHARSET_UTF_8, (LPCSTR)szSrc, iInBufLen, szDest, iDestLength); +} + +BOOL GbkToUtf8Ex(const char szSrc[], int iSrcLength, char szDest[], int& iDestLength) +{ + int iInBufLen = (int)((iSrcLength > 0) ? iSrcLength : ((szSrc != nullptr) ? strlen(szSrc) + 1 : 0)); + + return CharsetConvert(CHARSET_GBK, CHARSET_UTF_8, szSrc, iInBufLen, szDest, iDestLength); +} + +BOOL Utf8ToGbkEx(const char szSrc[], int iSrcLength, char szDest[], int& iDestLength) +{ + int iInBufLen = (int)((iSrcLength > 0) ? iSrcLength : ((szSrc != nullptr) ? strlen(szSrc) + 1 : 0)); + + return CharsetConvert(CHARSET_UTF_8, CHARSET_GBK, szSrc, iInBufLen, szDest, iDestLength); +} + +BOOL GbkToUnicode(const char szSrc[], WCHAR szDest[], int& iDestLength) +{ + return GbkToUnicodeEx(szSrc, -1, szDest, iDestLength); +} + +BOOL UnicodeToGbk(const WCHAR szSrc[], char szDest[], int& iDestLength) +{ + return UnicodeToGbkEx(szSrc, -1, szDest, iDestLength); +} + +BOOL Utf8ToUnicode(const char szSrc[], WCHAR szDest[], int& iDestLength) +{ + return Utf8ToUnicodeEx(szSrc, -1, szDest, iDestLength); +} + +BOOL UnicodeToUtf8(const WCHAR szSrc[], char szDest[], int& iDestLength) +{ + return UnicodeToUtf8Ex(szSrc, -1, szDest, iDestLength); +} + +BOOL GbkToUtf8(const char szSrc[], char szDest[], int& iDestLength) +{ + return GbkToUtf8Ex(szSrc, -1, szDest, iDestLength); +} + +BOOL Utf8ToGbk(const char szSrc[], char szDest[], int& iDestLength) +{ + return Utf8ToGbkEx(szSrc, -1, szDest, iDestLength); +} + +#endif diff --git a/SocketHelper.h b/SocketHelper.h new file mode 100644 index 0000000..28e72bb --- /dev/null +++ b/SocketHelper.h @@ -0,0 +1,959 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#pragma once + +#include "hpsocket/HPTypeDef.h" +#include "hpsocket/SocketInterface.h" +#include "common/StringT.h" +#include "common/SysHelper.h" +#include "common/BufferPtr.h" +#include "common/BufferPool.h" +#include "common/RingBuffer.h" +#include "common/FileHelper.h" +#include "InternalDef.h" + +#include +#include +#include +#include + +#ifdef _ZLIB_SUPPORT +#include +#endif + +#ifdef _BROTLI_SUPPORT +#include +#include +#endif + +using ADDRESS_FAMILY = sa_family_t; +using IN_ADDR = in_addr; +using IN6_ADDR = in6_addr; +using SOCKADDR = sockaddr; +using SOCKADDR_IN = sockaddr_in; +using SOCKADDR_IN6 = sockaddr_in6; + +typedef struct hp_addr +{ + ADDRESS_FAMILY family; + + union + { + ULONG_PTR addr; + IN_ADDR addr4; + IN6_ADDR addr6; + }; + + static const hp_addr ANY_ADDR4; + static const hp_addr ANY_ADDR6; + + inline int AddrSize() const + { + return AddrSize(family); + } + + inline static int AddrSize(ADDRESS_FAMILY f) + { + if(f == AF_INET) + return sizeof(IN_ADDR); + + return sizeof(IN6_ADDR); + } + + inline static const hp_addr& AnyAddr(ADDRESS_FAMILY f) + { + if(f == AF_INET) + return ANY_ADDR4; + + return ANY_ADDR6; + } + + inline const ULONG_PTR* Addr() const {return &addr;} + inline ULONG_PTR* Addr() {return &addr;} + + inline BOOL IsIPv4() const {return family == AF_INET;} + inline BOOL IsIPv6() const {return family == AF_INET6;} + inline BOOL IsSpecified() const {return IsIPv4() || IsIPv6();} + inline void ZeroAddr() {::ZeroMemory(&addr6, sizeof(addr6));} + inline void Reset() {::ZeroMemory(this, sizeof(*this));} + + inline hp_addr& Copy(hp_addr& other) const + { + if(this != &other) + memcpy(&other, this, offsetof(hp_addr, addr) + AddrSize()); + + return other; + } + + hp_addr(ADDRESS_FAMILY f = AF_UNSPEC, BOOL bZeroAddr = FALSE) + { + family = f; + + if(bZeroAddr) ZeroAddr(); + } + +} HP_ADDR, *HP_PADDR; + +typedef struct hp_sockaddr +{ + union + { + ADDRESS_FAMILY family; + SOCKADDR addr; + SOCKADDR_IN addr4; + SOCKADDR_IN6 addr6; + }; + + inline int AddrSize() const + { + return AddrSize(family); + } + + inline static int AddrSize(ADDRESS_FAMILY f) + { + if(f == AF_INET) + return sizeof(SOCKADDR_IN); + + return sizeof(SOCKADDR_IN6); + } + + inline int EffectAddrSize() const + { + return EffectAddrSize(family); + } + + inline static int EffectAddrSize(ADDRESS_FAMILY f) + { + return (f == AF_INET) ? offsetof(SOCKADDR_IN, sin_zero) : sizeof(SOCKADDR_IN6); + } + + inline static const hp_sockaddr& AnyAddr(ADDRESS_FAMILY f) + { + static const hp_sockaddr s_any_addr4(AF_INET, TRUE); + static const hp_sockaddr s_any_addr6(AF_INET6, TRUE); + + if(f == AF_INET) + return s_any_addr4; + + return s_any_addr6; + } + + inline static int AddrMinStrLength(ADDRESS_FAMILY f) + { + if(f == AF_INET) + return INET_ADDRSTRLEN; + + return INET6_ADDRSTRLEN; + } + + inline BOOL IsIPv4() const {return family == AF_INET;} + inline BOOL IsIPv6() const {return family == AF_INET6;} + inline BOOL IsSpecified() const {return IsIPv4() || IsIPv6();} + inline USHORT Port() const {return ntohs(addr4.sin_port);} + inline void SetPort(USHORT usPort) {addr4.sin_port = htons(usPort);} + inline void* SinAddr() const {return IsIPv4() ? (void*)&addr4.sin_addr : (void*)&addr6.sin6_addr;} + inline void* SinAddr() {return IsIPv4() ? (void*)&addr4.sin_addr : (void*)&addr6.sin6_addr;} + + inline const SOCKADDR* Addr() const {return &addr;} + inline SOCKADDR* Addr() {return &addr;} + inline void ZeroAddr() {::ZeroMemory(((char*)this) + sizeof(family), sizeof(*this) - sizeof(family));} + inline void Reset() {::ZeroMemory(this, sizeof(*this));} + + inline hp_sockaddr& Copy(hp_sockaddr& other) const + { + if(this != &other) + memcpy(&other, this, AddrSize()); + + return other; + } + + size_t Hash() const + { + ASSERT(IsSpecified()); + + size_t _Val = 2166136261U; + const int size = EffectAddrSize(); + const BYTE* pAddr = (const BYTE*)Addr(); + + for(int i = 0; i < size; i++) + _Val = 16777619U * _Val ^ (size_t)pAddr[i]; + + return (_Val); + } + + bool EqualTo(const hp_sockaddr& other) const + { + ASSERT(IsSpecified() && other.IsSpecified()); + + return EqualMemory(this, &other, EffectAddrSize()); + } + + hp_sockaddr(ADDRESS_FAMILY f = AF_UNSPEC, BOOL bZeroAddr = FALSE) + { + family = f; + + if(bZeroAddr) ZeroAddr(); + } + +} HP_SOCKADDR, *HP_PSOCKADDR; + +typedef struct hp_scope_host +{ + LPCTSTR addr; + LPCTSTR name; + + BOOL bNeedFree; + + hp_scope_host(LPCTSTR lpszOriginAddress) + { + ASSERT(lpszOriginAddress != nullptr); + + LPCTSTR lpszFind = ::StrChr(lpszOriginAddress, HOST_SEPARATOR_CHAR); + + if(lpszFind == nullptr) + { + addr = lpszOriginAddress; + name = lpszOriginAddress; + bNeedFree = FALSE; + } + else + { + int i = (int)(lpszFind - lpszOriginAddress); + int iSize = (int)lstrlen(lpszOriginAddress) + 1; + LPTSTR lpszCopy = new TCHAR[iSize]; + + ::memcpy((PVOID)lpszCopy, (PVOID)lpszOriginAddress, iSize * sizeof(TCHAR)); + + lpszCopy[i] = 0; + addr = lpszCopy; + name = lpszCopy + i + 1; + bNeedFree = TRUE; + + if(::IsStrEmpty(name)) + name = addr; + } + } + + ~hp_scope_host() + { + if(bNeedFree) + delete[] addr; + } + +} HP_SCOPE_HOST, *HP_PSCOPE_HOST; + +struct TNodeBufferObj : public TItem +{ + using __super = TItem; + + HP_SOCKADDR remoteAddr; + +public: + void Reset(int first = 0, int last = 0) + { + __super::Reset(first, last); + remoteAddr.Reset(); + } + +public: + static TNodeBufferObj* Construct(CPrivateHeap& heap, + int capacity = DEFAULT_ITEM_CAPACITY, + BYTE* pData = nullptr, + int length = 0) + { + return ::ConstructItemT((TNodeBufferObj*)(nullptr), heap, capacity, pData, length); + } + + static void Destruct(TNodeBufferObj* pBufferObj) + { + ::DestructItemT(pBufferObj); + } + + TNodeBufferObj(CPrivateHeap& hp, BYTE* pHead, int cap = DEFAULT_ITEM_CAPACITY, BYTE* pData = nullptr, int length = 0) + : TItem(hp, pHead, cap, pData, length) + { + + } + + ~TNodeBufferObj() + { + } + + DECLARE_NO_COPY_CLASS(TNodeBufferObj) +}; + +typedef TItemPtrT TNodeBufferObjPtr; +typedef CNodePoolT CNodeBufferObjPool; +typedef CCASQueue CNodeRecvQueue; +typedef TItemListExT TNodeBufferObjList; + +typedef unique_ptr CNodeCriSecs; +typedef unique_ptr TNodeBufferObjLists; + +/* Server 组件和 Agent 组件内部使用的事件处理结果常量 */ + +// 连接已关闭 +#define HR_CLOSED 0xFF + +/* 命令类型 */ +enum EnDispCmdType +{ + DISP_CMD_SEND = 0x01, // 发送数据 + DISP_CMD_RECEIVE = 0x02, // 接收数据 + DISP_CMD_UNPAUSE = 0x03, // 恢复接收数据 + DISP_CMD_DISCONNECT = 0x04, // 断开连接 + DISP_CMD_TIMEOUT = 0x05, // 保活超时 +}; + +/* 关闭连接标识 */ +enum EnSocketCloseFlag +{ + SCF_NONE = 0, // 不触发事件 + SCF_CLOSE = 1, // 触发 正常关闭 OnClose 事件 + SCF_ERROR = 2 // 触发 异常关闭 OnClose 事件 +}; + +/* 监听 Socket 数组智能指针 */ +typedef unique_ptr ListenSocketsPtr; + +/* 数据缓冲节点 */ +typedef TItem TBufferObj; +/* 数据缓冲节点智能指针 */ +typedef TItemPtr TBufferObjPtr; +/* 数据缓冲区对象池 */ +typedef CItemPool CBufferObjPool; +/* 数据缓冲区链表模板 */ +typedef TItemListExV TBufferObjList; + +/* 接收缓冲区数组智能指针 */ +typedef unique_ptr CReceiveBuffersPtr; + +/* 线程 ID - 接收缓冲区哈希表 */ +typedef unordered_map TReceiveBufferMap; +/* 线程 ID - 接收缓冲区哈希表迭代器 */ +typedef TReceiveBufferMap::iterator TReceiveBufferMapI; +/* 线程 ID - 接收缓冲区哈希表 const 迭代器 */ +typedef TReceiveBufferMap::const_iterator TReceiveBufferMapCI; + +/* Socket 缓冲区基础结构 */ +struct TSocketObjBase : public CSafeCounter +{ + CPrivateHeap& heap; + CReentrantCriSec csSend; + TBufferObjList sndBuff; + + + CONNID connID; + HP_SOCKADDR remoteAddr; + PVOID extra; + PVOID reserved; + PVOID reserved2; + DWORD activeTime; + + union + { + DWORD freeTime; + DWORD connTime; + }; + + volatile BOOL valid; + volatile BOOL connected; + volatile BOOL paused; + + TSocketObjBase(CPrivateHeap& hp, CBufferObjPool& bfPool) : heap(hp), sndBuff(bfPool) {} + + static BOOL IsExist(TSocketObjBase* pSocketObj) + {return pSocketObj != nullptr;} + + static BOOL IsValid(TSocketObjBase* pSocketObj) + {return (IsExist(pSocketObj) && pSocketObj->valid == TRUE);} + + static void Invalid(TSocketObjBase* pSocketObj) + {ASSERT(IsExist(pSocketObj)); pSocketObj->valid = FALSE;} + + static void Release(TSocketObjBase* pSocketObj) + { + ASSERT(IsExist(pSocketObj)); + + pSocketObj->freeTime = ::TimeGetTime(); + pSocketObj->sndBuff.Release(); + } + + static BOOL InvalidSocketObj(TSocketObjBase* pSocketObj) + { + BOOL bDone = FALSE; + + if(TSocketObjBase::IsValid(pSocketObj)) + { + pSocketObj->SetConnected(FALSE); + + CReentrantCriSecLock locallock(pSocketObj->csSend); + + if(TSocketObjBase::IsValid(pSocketObj)) + { + TSocketObjBase::Invalid(pSocketObj); + bDone = TRUE; + } + } + + return bDone; + } + + DWORD GetConnTime () const {return connTime;} + DWORD GetFreeTime () const {return freeTime;} + DWORD GetActiveTime () const {return activeTime;} + BOOL IsPaused () const {return paused;} + + int Pending () const {return sndBuff.Length();} + BOOL IsPending () const {return Pending() > 0;} + + BOOL HasConnected() {return connected == TRUE;} + BOOL IsConnecting() {return connected == CST_CONNECTING;} + void SetConnected(BOOL bConnected = TRUE) {connected = bConnected;} + + void Reset(CONNID dwConnID) + { + ResetCount(); + + connID = dwConnID; + connected = FALSE; + valid = TRUE; + paused = FALSE; + extra = nullptr; + reserved = nullptr; + reserved2 = nullptr; + } +}; + +/* 数据缓冲区结构 */ +struct TSocketObj : public TSocketObjBase +{ + using __super = TSocketObjBase; + + SOCKET socket; + + static TSocketObj* Construct(CPrivateHeap& hp, CBufferObjPool& bfPool) + { + TSocketObj* pSocketObj = (TSocketObj*)hp.Alloc(sizeof(TSocketObj)); + ASSERT(pSocketObj); + + return new (pSocketObj) TSocketObj(hp, bfPool); + } + + static void Destruct(TSocketObj* pSocketObj) + { + ASSERT(pSocketObj); + + CPrivateHeap& heap = pSocketObj->heap; + pSocketObj->TSocketObj::~TSocketObj(); + heap.Free(pSocketObj); + } + + TSocketObj(CPrivateHeap& hp, CBufferObjPool& bfPool) + : __super(hp, bfPool) + { + + } + + void Reset(CONNID dwConnID, SOCKET soClient) + { + __super::Reset(dwConnID); + + socket = soClient; + } +}; + +/* Agent 数据缓冲区结构 */ +struct TAgentSocketObj : public TSocketObj +{ + using __super = TSocketObj; + + CStringA host; + + static TAgentSocketObj* Construct(CPrivateHeap& hp, CBufferObjPool& bfPool) + { + TAgentSocketObj* pSocketObj = (TAgentSocketObj*)hp.Alloc(sizeof(TAgentSocketObj)); + ASSERT(pSocketObj); + + return new (pSocketObj) TAgentSocketObj(hp, bfPool); + } + + static void Destruct(TAgentSocketObj* pSocketObj) + { + ASSERT(pSocketObj); + + CPrivateHeap& heap = pSocketObj->heap; + pSocketObj->TAgentSocketObj::~TAgentSocketObj(); + heap.Free(pSocketObj); + } + + TAgentSocketObj(CPrivateHeap& hp, CBufferObjPool& bfPool) + : __super(hp, bfPool) + { + + } + + void Reset(CONNID dwConnID, SOCKET soClient) + { + __super::Reset(dwConnID, soClient); + + host.Empty(); + } + + BOOL GetRemoteHost(LPCSTR* lpszHost, USHORT* pusPort = nullptr) + { + *lpszHost = host; + + if(pusPort) + *pusPort = remoteAddr.Port(); + + return (!host.IsEmpty()); + } +}; + +/* UDP 数据缓冲区结构 */ +struct TUdpSocketObj : public TSocketObjBase +{ + using __super = TSocketObjBase; + + int index; + PVOID pHolder; + FD fdTimer; + + volatile DWORD detectFails; + + static TUdpSocketObj* Construct(CPrivateHeap& hp, CBufferObjPool& bfPool) + { + TUdpSocketObj* pSocketObj = (TUdpSocketObj*)hp.Alloc(sizeof(TUdpSocketObj)); + ASSERT(pSocketObj); + + return new (pSocketObj) TUdpSocketObj(hp, bfPool); + } + + static void Destruct(TUdpSocketObj* pSocketObj) + { + ASSERT(pSocketObj); + + CPrivateHeap& heap = pSocketObj->heap; + pSocketObj->TUdpSocketObj::~TUdpSocketObj(); + heap.Free(pSocketObj); + } + + TUdpSocketObj(CPrivateHeap& hp, CBufferObjPool& bfPool) + : __super(hp, bfPool) + { + + } + + void Reset(CONNID dwConnID) + { + __super::Reset(dwConnID); + + index = -1; + detectFails = 0; + fdTimer = INVALID_FD; + pHolder = nullptr; + } +}; + +/* 有效 TSocketObj 缓存 */ +typedef CRingCache2 TSocketObjPtrPool; +/* 失效 TSocketObj 缓存 */ +typedef CRingPool TSocketObjPtrList; +/* 失效 TSocketObj 垃圾回收结构链表 */ +typedef CCASQueue TSocketObjPtrQueue; + +/* 有效 TSocketObj 缓存 */ +typedef CRingCache2 TAgentSocketObjPtrPool; +/* 失效 TSocketObj 缓存 */ +typedef CRingPool TAgentSocketObjPtrList; +/* 失效 TSocketObj 垃圾回收结构链表 */ +typedef CCASQueue TAgentSocketObjPtrQueue; + +/* 有效 TUdpSocketObj 缓存 */ +typedef CRingCache2 TUdpSocketObjPtrPool; +/* 失效 TUdpSocketObj 缓存 */ +typedef CRingPool TUdpSocketObjPtrList; +/* 失效 TUdpSocketObj 垃圾回收结构链表 */ +typedef CCASQueue TUdpSocketObjPtrQueue; + +/* HP_SOCKADDR 比较器 */ +struct hp_sockaddr_func +{ + struct hash + { + size_t operator() (const HP_SOCKADDR* pA) const + { + return pA->Hash(); + } + }; + + struct equal_to + { + bool operator () (const HP_SOCKADDR* pA, const HP_SOCKADDR* pB) const + { + return pA->EqualTo(*pB); + } + }; + +}; + +/* 地址-连接 ID 哈希表 */ +typedef unordered_map + TSockAddrMap; +/* 地址-连接 ID 哈希表迭代器 */ +typedef TSockAddrMap::iterator TSockAddrMapI; +/* 地址-连接 ID 哈希表 const 迭代器 */ +typedef TSockAddrMap::const_iterator TSockAddrMapCI; + +/* IClient 组件关闭上下文 */ +struct TClientCloseContext +{ + BOOL bFireOnClose; + EnSocketOperation enOperation; + int iErrorCode; + BOOL bNotify; + + TClientCloseContext(BOOL bFire = TRUE, EnSocketOperation enOp = SO_CLOSE, int iCode = SE_OK, BOOL bNtf = TRUE) + { + Reset(bFire, enOp, iCode, bNtf); + } + + void Reset(BOOL bFire = TRUE, EnSocketOperation enOp = SO_CLOSE, int iCode = SE_OK, BOOL bNtf = TRUE) + { + bFireOnClose = bFire; + enOperation = enOp; + iErrorCode = iCode; + bNotify = bNtf; + } + +}; + +/*****************************************************************************************************/ +/******************************************** 公共帮助方法 ********************************************/ +/*****************************************************************************************************/ + +/* 默认工作线程前缀 */ +#define DEFAULT_WORKER_THREAD_PREFIX "hp-worker-" + +/* 设置当前工作线程名称 */ +BOOL SetCurrentWorkerThreadName(); +/* 设置工作线程默认名称 */ +BOOL SetWorkerThreadDefaultName(THR_ID tid); + +/* 获取错误描述文本 */ +LPCTSTR GetSocketErrorDesc(EnSocketError enCode); +/* 确定地址簇 */ +ADDRESS_FAMILY DetermineAddrFamily(LPCTSTR lpszAddress); +/* 地址字符串地址转换为 HP_ADDR */ +BOOL GetInAddr(LPCTSTR lpszAddress, HP_ADDR& addr); +/* 地址字符串地址转换为 HP_SOCKADDR */ +BOOL GetSockAddr(LPCTSTR lpszAddress, USHORT usPort, HP_SOCKADDR& addr); +/* 检查字符串是否符合 IP 地址格式 */ +BOOL IsIPAddress(LPCTSTR lpszAddress, EnIPAddrType* penType = nullptr); +/* 通过主机名获取 IP 地址 */ +BOOL GetIPAddress(LPCTSTR lpszHost, LPTSTR lpszIP, int& iIPLenth, EnIPAddrType& enType); +/* 通过主机名获取 HP_SOCKADDR */ +BOOL GetSockAddrByHostName(LPCTSTR lpszHost, USHORT usPort, HP_SOCKADDR& addr); +/* 通过主机名获取 HP_SOCKADDR */ +BOOL GetSockAddrByHostNameDirectly(LPCTSTR lpszHost, USHORT usPort, HP_SOCKADDR &addr); +/* 枚举主机 IP 地址 */ +BOOL EnumHostIPAddresses(LPCTSTR lpszHost, EnIPAddrType enType, LPTIPAddr** lpppIPAddr, int& iIPAddrCount); +/* 填充 LPTIPAddr* */ +BOOL RetrieveSockAddrIPAddresses(const vector& vt, LPTIPAddr** lpppIPAddr, int& iIPAddrCount); +/* 释放 LPTIPAddr* */ +BOOL FreeHostIPAddresses(LPTIPAddr* lppIPAddr); +/* 把 HP_SOCKADDR 结构转换为地址字符串 */ +BOOL sockaddr_IN_2_A(const HP_SOCKADDR& addr, ADDRESS_FAMILY& usFamily, LPTSTR lpszAddress, int& iAddressLen, USHORT& usPort); +/* 把地址字符串转换为 HP_SOCKADDR 结构 */ +BOOL sockaddr_A_2_IN(LPCTSTR lpszAddress, USHORT usPort, HP_SOCKADDR& addr); +/* 获取 Socket 的本地或远程地址信息 */ +BOOL GetSocketAddress(SOCKET socket, LPTSTR lpszAddress, int& iAddressLen, USHORT& usPort, BOOL bLocal = TRUE); +/* 获取 Socket 的本地地址信息 */ +BOOL GetSocketLocalAddress(SOCKET socket, LPTSTR lpszAddress, int& iAddressLen, USHORT& usPort); +/* 获取 Socket 的远程地址信息 */ +BOOL GetSocketRemoteAddress(SOCKET socket, LPTSTR lpszAddress, int& iAddressLen, USHORT& usPort); +/* 设置组播选项 */ +BOOL SetMultiCastSocketOptions(SOCKET sock, const HP_SOCKADDR& bindAddr, const HP_SOCKADDR& castAddr, int iMCTtl, BOOL bMCLoop); +/* 等待连接 */ +int WaitForSocketWrite(SOCKET sock, DWORD dwTimeout); + +/* 64 位网络字节序转主机字节序 */ +ULONGLONG NToH64(ULONGLONG value); +/* 64 位主机字节序转网络字节序 */ +ULONGLONG HToN64(ULONGLONG value); + +/* 短整型高低字节交换 */ +#define ENDIAN_SWAP_16(A) ((USHORT)((((USHORT)(A) & 0xff00) >> 8) | (((USHORT)(A) & 0x00ff) << 8))) +/* 长整型高低字节交换 */ +#define ENDIAN_SWAP_32(A) ((((DWORD)(A) & 0xff000000) >> 24) | \ + (((DWORD)(A) & 0x00ff0000) >> 8) | \ + (((DWORD)(A) & 0x0000ff00) << 8) | \ + (((DWORD)(A) & 0x000000ff) << 24) ) + +/* 检查是否小端字节序 */ +BOOL IsLittleEndian(); +/* 短整型主机字节序转小端字节序 */ +USHORT HToLE16(USHORT value); +/* 短整型主机字节序转大端字节序 */ +USHORT HToBE16(USHORT value); +/* 长整型主机字节序转小端字节序 */ +DWORD HToLE32(DWORD value); +/* 长整型主机字节序转大端字节序 */ +DWORD HToBE32(DWORD value); + +HRESULT ReadSmallFile(LPCTSTR lpszFileName, CFile& file, CFileMapping& fmap, DWORD dwMaxFileSize = MAX_SMALL_FILE_SIZE); +HRESULT MakeSmallFilePackage(LPCTSTR lpszFileName, CFile& file, CFileMapping& fmap, WSABUF szBuf[3], const LPWSABUF pHead = nullptr, const LPWSABUF pTail = nullptr); + +/************************************************************************ +名称:setsockopt() 帮助方法 +描述:简化常用的 setsockopt() 调用 +************************************************************************/ + +int SSO_SetSocketOption (SOCKET sock, int level, int name, LPVOID val, int len); +int SSO_GetSocketOption (SOCKET sock, int level, int name, LPVOID val, int* len); +int SSO_IoctlSocket (SOCKET sock, long cmd, PVOID arg); + +int SSO_NoBlock (SOCKET sock, BOOL bNoBlock = TRUE); +int SSO_NoDelay (SOCKET sock, BOOL bNoDelay = TRUE); +int SSO_DontLinger (SOCKET sock, BOOL bDont = TRUE); +int SSO_Linger (SOCKET sock, int l_onoff, int l_linger); +int SSO_KeepAlive (SOCKET sock, BOOL bKeepAlive = TRUE); +int SSO_KeepAliveVals (SOCKET sock, BOOL bOnOff, DWORD dwIdle, DWORD dwInterval, DWORD dwCount = 5); +int SSO_ReuseAddress (SOCKET sock, EnReuseAddressPolicy opt); +int SSO_RecvBuffSize (SOCKET sock, int size); +int SSO_SendBuffSize (SOCKET sock, int size); +int SSO_RecvTimeOut (SOCKET sock, int ms); +int SSO_SendTimeOut (SOCKET sock, int ms); +int SSO_GetError (SOCKET sock); + +/* 生成 Connection ID */ +CONNID GenerateConnectionID(); +/* 检测 UDP 连接关闭通知 */ +int IsUdpCloseNotify(const BYTE* pData, int iLength); +/* 发送 UDP 连接关闭通知 */ +int SendUdpCloseNotify(SOCKET sock); +/* 发送 UDP 连接关闭通知 */ +int SendUdpCloseNotify(SOCKET sock, const HP_SOCKADDR& remoteAddr); +/* 关闭 Socket */ +int ManualCloseSocket(SOCKET sock, int iShutdownFlag = 0xFF, BOOL bGraceful = TRUE); + +#ifdef _ICONV_SUPPORT + +#define CHARSET_GBK "GBK" +#define CHARSET_UTF_8 "UTF-8" +#define CHARSET_UTF_16LE "UTF-16LE" +#define CHARSET_UTF_32LE "UTF-32LE" +#define CHARSET_UTF_16BE "UTF-16BE" +#define CHARSET_UTF_32BE "UTF-32BE" + +// 系统 UNICODE 字符集 +#define SYSTEM_CHARSET_UNICODE ( (sizeof(WCHAR) == 4) \ + ? (IsLittleEndian() ? CHARSET_UTF_32LE : CHARSET_UTF_32BE) \ + : (IsLittleEndian() ? CHARSET_UTF_16LE : CHARSET_UTF_16BE) ) + +// Charset A -> Charset B +BOOL CharsetConvert(LPCSTR lpszFromCharset, LPCSTR lpszToCharset, LPCSTR lpszInBuf, int iInBufLen, LPSTR lpszOutBuf, int& iOutBufLen); + +// GBK -> UNICODE +BOOL GbkToUnicodeEx(const char szSrc[], int iSrcLength, WCHAR szDest[], int& iDestLength); +// UNICODE -> GBK +BOOL UnicodeToGbkEx(const WCHAR szSrc[], int iSrcLength, char szDest[], int& iDestLength); +// UTF8 -> UNICODE +BOOL Utf8ToUnicodeEx(const char szSrc[], int iSrcLength, WCHAR szDest[], int& iDestLength); +// UNICODE -> UTF8 +BOOL UnicodeToUtf8Ex(const WCHAR szSrc[], int iSrcLength, char szDest[], int& iDestLength); +// GBK -> UTF8 +BOOL GbkToUtf8Ex(const char szSrc[], int iSrcLength, char szDest[], int& iDestLength); +// UTF8 -> GBK +BOOL Utf8ToGbkEx(const char szSrc[], int iSrcLength, char szDest[], int& iDestLength); + +// GBK -> UNICODE +BOOL GbkToUnicode(const char szSrc[], WCHAR szDest[], int& iDestLength); +// UNICODE -> GBK +BOOL UnicodeToGbk(const WCHAR szSrc[], char szDest[], int& iDestLength); +// UTF8 -> UNICODE +BOOL Utf8ToUnicode(const char szSrc[], WCHAR szDest[], int& iDestLength); +// UNICODE -> UTF8 +BOOL UnicodeToUtf8(const WCHAR szSrc[], char szDest[], int& iDestLength); +// GBK -> UTF8 +BOOL GbkToUtf8(const char szSrc[], char szDest[], int& iDestLength); +// UTF8 -> GBK +BOOL Utf8ToGbk(const char szSrc[], char szDest[], int& iDestLength); + +#endif + +// 计算 Base64 编码后长度 +DWORD GuessBase64EncodeBound(DWORD dwSrcLen); +// 计算 Base64 解码后长度 +DWORD GuessBase64DecodeBound(const BYTE* lpszSrc, DWORD dwSrcLen); +// Base64 编码(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +int Base64Encode(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen); +// Base64 解码(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +int Base64Decode(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen); + +// 计算 URL 编码后长度 +DWORD GuessUrlEncodeBound(const BYTE* lpszSrc, DWORD dwSrcLen); +// 计算 URL 解码后长度 +DWORD GuessUrlDecodeBound(const BYTE* lpszSrc, DWORD dwSrcLen); +// URL 编码(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +int UrlEncode(BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen); +// URL 解码(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +int UrlDecode(BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen); + +/* 销毁压缩器对象 */ +void DestroyCompressor(IHPCompressor* pCompressor); +/* 销毁解压器对象 */ +void DestroyDecompressor(IHPDecompressor* pDecompressor); + +#ifdef _ZLIB_SUPPORT + +/* ZLib 压缩器 */ +class CHPZLibCompressor : public IHPCompressor +{ +public: + virtual BOOL Process(const BYTE* pData, int iLength, BOOL bLast, PVOID pContext = nullptr); + virtual BOOL ProcessEx(const BYTE* pData, int iLength, BOOL bLast, BOOL bFlush = FALSE, PVOID pContext = nullptr); + virtual BOOL IsValid() {return m_bValid;} + virtual BOOL Reset(); + +public: + CHPZLibCompressor(Fn_CompressDataCallback fnCallback, int iWindowBits = MAX_WBITS, int iLevel = Z_DEFAULT_COMPRESSION, int iMethod = Z_DEFLATED, int iMemLevel = MAX_MEM_LEVEL, int iStrategy = Z_DEFAULT_STRATEGY, DWORD dwBuffSize = DEFAULT_COMPRESS_BUFFER_SIZE); + virtual ~CHPZLibCompressor(); + +private: + Fn_CompressDataCallback m_fnCallback; + z_stream m_Stream; + BOOL m_bValid; + DWORD m_dwBuffSize; +}; + +/* ZLib 解压器 */ +class CHPZLibDecompressor : public IHPDecompressor +{ +public: + virtual BOOL Process(const BYTE* pData, int iLength, PVOID pContext = nullptr); + virtual BOOL IsValid() {return m_bValid;} + virtual BOOL Reset(); + +public: + CHPZLibDecompressor(Fn_DecompressDataCallback fnCallback, int iWindowBits = MAX_WBITS, DWORD dwBuffSize = DEFAULT_COMPRESS_BUFFER_SIZE); + virtual ~CHPZLibDecompressor(); + +private: + Fn_DecompressDataCallback m_fnCallback; + z_stream m_Stream; + BOOL m_bValid; + DWORD m_dwBuffSize; +}; + +/* 创建 ZLib 压缩器对象 */ +IHPCompressor* CreateZLibCompressor(Fn_CompressDataCallback fnCallback, int iWindowBits = MAX_WBITS, int iLevel = Z_DEFAULT_COMPRESSION, int iMethod = Z_DEFLATED, int iMemLevel = MAX_MEM_LEVEL, int iStrategy = Z_DEFAULT_STRATEGY, DWORD dwBuffSize = DEFAULT_COMPRESS_BUFFER_SIZE); +/* 创建 GZip 压缩器对象 */ +IHPCompressor* CreateGZipCompressor(Fn_CompressDataCallback fnCallback, int iLevel = Z_DEFAULT_COMPRESSION, int iMethod = Z_DEFLATED, int iMemLevel = MAX_MEM_LEVEL, int iStrategy = Z_DEFAULT_STRATEGY, DWORD dwBuffSize = DEFAULT_COMPRESS_BUFFER_SIZE); +/* 创建 ZLib 解压器对象 */ +IHPDecompressor* CreateZLibDecompressor(Fn_DecompressDataCallback fnCallback, int iWindowBits = MAX_WBITS, DWORD dwBuffSize = DEFAULT_COMPRESS_BUFFER_SIZE); +/* 创建 GZip 解压器对象 */ +IHPDecompressor* CreateGZipDecompressor(Fn_DecompressDataCallback fnCallback, DWORD dwBuffSize = DEFAULT_COMPRESS_BUFFER_SIZE); + +// 普通压缩(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +int Compress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen); +// 高级压缩(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +int CompressEx(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen, int iLevel = Z_DEFAULT_COMPRESSION, int iMethod = Z_DEFLATED, int iWindowBits = MAX_WBITS, int iMemLevel = MAX_MEM_LEVEL, int iStrategy = Z_DEFAULT_STRATEGY); +// 普通解压(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +int Uncompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen); +// 高级解压(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +int UncompressEx(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen, int iWindowBits = MAX_WBITS); +// 推测压缩结果长度 +DWORD GuessCompressBound(DWORD dwSrcLen, BOOL bGZip = FALSE); + +// Gzip 压缩(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +int GZipCompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen); +// Gzip 解压(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +int GZipUncompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen); +// 推测 Gzip 解压结果长度(如果返回 0 或不合理值则说明输入内容并非有效的 Gzip 格式) +DWORD GZipGuessUncompressBound(const BYTE* lpszSrc, DWORD dwSrcLen); + +#endif + +#ifdef _BROTLI_SUPPORT + +/* Brotli 压缩器 */ +class CHPBrotliCompressor : public IHPCompressor +{ +public: + virtual BOOL Process(const BYTE* pData, int iLength, BOOL bLast, PVOID pContext = nullptr); + virtual BOOL ProcessEx(const BYTE* pData, int iLength, BOOL bLast, BOOL bFlush = FALSE, PVOID pContext = nullptr); + virtual BOOL IsValid() {return m_bValid;} + virtual BOOL Reset(); + +public: + CHPBrotliCompressor(Fn_CompressDataCallback fnCallback, int iQuality = BROTLI_DEFAULT_QUALITY, int iWindow = BROTLI_DEFAULT_WINDOW, int iMode = BROTLI_DEFAULT_MODE, DWORD dwBuffSize = DEFAULT_COMPRESS_BUFFER_SIZE); + virtual ~CHPBrotliCompressor(); + +private: + Fn_CompressDataCallback m_fnCallback; + BrotliEncoderState* m_pState; + BOOL m_bValid; + + int m_iQuality; + int m_iWindow; + int m_iMode; + DWORD m_dwBuffSize; +}; + +/* Brotli 解压器 */ +class CHPBrotliDecompressor : public IHPDecompressor +{ +public: + virtual BOOL Process(const BYTE* pData, int iLength, PVOID pContext = nullptr); + virtual BOOL IsValid() {return m_bValid;} + virtual BOOL Reset(); + +public: + CHPBrotliDecompressor(Fn_DecompressDataCallback fnCallback, DWORD dwBuffSize = DEFAULT_COMPRESS_BUFFER_SIZE); + virtual ~CHPBrotliDecompressor(); + +private: + Fn_DecompressDataCallback m_fnCallback; + BrotliDecoderState* m_pState; + BOOL m_bValid; + DWORD m_dwBuffSize; +}; + +/* 创建 Brotli 压缩器对象 */ +IHPCompressor* CreateBrotliCompressor(Fn_CompressDataCallback fnCallback, int iQuality = BROTLI_DEFAULT_QUALITY, int iWindow = BROTLI_DEFAULT_WINDOW, int iMode = BROTLI_DEFAULT_MODE, DWORD dwBuffSize = DEFAULT_COMPRESS_BUFFER_SIZE); +/* 创建 Brotli 解压器对象 */ +IHPDecompressor* CreateBrotliDecompressor(Fn_DecompressDataCallback fnCallback, DWORD dwBuffSize = DEFAULT_COMPRESS_BUFFER_SIZE); + +// Brotli 压缩(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +int BrotliCompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen); +// Brotli 高级压缩(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +int BrotliCompressEx(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen, int iQuality = BROTLI_DEFAULT_QUALITY, int iWindow = BROTLI_DEFAULT_WINDOW, int iMode = BROTLI_DEFAULT_MODE); +// Brotli 解压(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +int BrotliUncompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen); +// Brotli 推测压缩结果长度 +DWORD BrotliGuessCompressBound(DWORD dwSrcLen); + +#endif diff --git a/SocketObject4C.h b/SocketObject4C.h new file mode 100644 index 0000000..500f5f0 --- /dev/null +++ b/SocketObject4C.h @@ -0,0 +1,770 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "hpsocket/HPSocket4C.h" +#include "hpsocket/SocketInterface.h" +#include "common/FuncHelper.h" + +class C_HP_Object +{ +public: + template static inline HP_Object FromFirst(T* pFirst) + { + return (HP_Object)((char*)pFirst - first); + } + + template static inline T* ToFirst(HP_Object pObject) + { + return (T*)((char*)pObject + first); + } + + template static inline HP_Object FromSecond(T* pSecond) + { + return (C_HP_Object*)((char*)pSecond - first - (offset + __DUAL_VPTR_GAP__)); + } + + template static inline T* ToSecond(HP_Object pObject) + { + return (T*)((char*)pObject + ((C_HP_Object*)pObject)->second); + } + +public: + C_HP_Object(int offset = 0) : second(first + (offset + __DUAL_VPTR_GAP__)) {} + virtual ~C_HP_Object() {} + +private: + static const size_t first = (sizeof(PVOID) + sizeof(size_t)); + size_t second; +}; + +template class C_HP_ObjectT : private C_HP_Object, public T +{ +public: + C_HP_ObjectT(L* pListener) : C_HP_Object(offset), T(pListener) {} +}; + +template class C_HP_ServerListenerT : public L +{ +public: + virtual EnHandleResult OnPrepareListen(T* pSender, SOCKET soListen) + { + return (m_fnOnPrepareListen) + ? m_fnOnPrepareListen(C_HP_Object::FromSecond(pSender), soListen) + : HR_IGNORE; + } + + virtual EnHandleResult OnAccept(T* pSender, CONNID dwConnID, UINT_PTR soClient) + { + return (m_fnOnAccept) + ? m_fnOnAccept(C_HP_Object::FromSecond(pSender), dwConnID, soClient) + : HR_IGNORE; + } + + virtual EnHandleResult OnHandShake(T* pSender, CONNID dwConnID) + { + return (m_fnOnHandShake) + ? m_fnOnHandShake(C_HP_Object::FromSecond(pSender), dwConnID) + : HR_IGNORE; + } + + virtual EnHandleResult OnSend(T* pSender, CONNID dwConnID, const BYTE* pData, int iLength) + { + return (m_fnOnSend) + ? m_fnOnSend(C_HP_Object::FromSecond(pSender), dwConnID, pData, iLength) + : HR_IGNORE; + } + + virtual EnHandleResult OnReceive(T* pSender, CONNID dwConnID, const BYTE* pData, int iLength) + { + ASSERT(m_fnOnReceive); + + return (m_fnOnReceive) + ? m_fnOnReceive(C_HP_Object::FromSecond(pSender), dwConnID, pData, iLength) + : HR_IGNORE; + } + + virtual EnHandleResult OnReceive(T* pSender, CONNID dwConnID, int iLength) + { + ASSERT(m_fnOnPullReceive); + + return (m_fnOnPullReceive) + ? m_fnOnPullReceive(C_HP_Object::FromSecond(pSender), dwConnID, iLength) + : HR_IGNORE; + } + + virtual EnHandleResult OnClose(T* pSender, CONNID dwConnID, EnSocketOperation enOperation, int iErrorCode) + { + ASSERT(m_fnOnClose); + + return (m_fnOnClose) + ? m_fnOnClose(C_HP_Object::FromSecond(pSender), dwConnID, enOperation, iErrorCode) + : HR_IGNORE; + } + + virtual EnHandleResult OnShutdown(T* pSender) + { + return (m_fnOnShutdown) + ? m_fnOnShutdown(C_HP_Object::FromSecond(pSender)) + : HR_IGNORE; + } + +public: + C_HP_ServerListenerT() + : m_fnOnPrepareListen (nullptr) + , m_fnOnAccept (nullptr) + , m_fnOnHandShake (nullptr) + , m_fnOnSend (nullptr) + , m_fnOnReceive (nullptr) + , m_fnOnPullReceive (nullptr) + , m_fnOnClose (nullptr) + , m_fnOnShutdown (nullptr) + { + } + +public: + HP_FN_Server_OnPrepareListen m_fnOnPrepareListen ; + HP_FN_Server_OnAccept m_fnOnAccept ; + HP_FN_Server_OnHandShake m_fnOnHandShake ; + HP_FN_Server_OnSend m_fnOnSend ; + HP_FN_Server_OnReceive m_fnOnReceive ; + HP_FN_Server_OnPullReceive m_fnOnPullReceive ; + HP_FN_Server_OnClose m_fnOnClose ; + HP_FN_Server_OnShutdown m_fnOnShutdown ; +}; + +template class C_HP_AgentListenerT : public L +{ +public: + virtual EnHandleResult OnPrepareConnect(T* pSender, CONNID dwConnID, SOCKET socket) + { + return (m_fnOnPrepareConnect) + ? m_fnOnPrepareConnect(C_HP_Object::FromSecond(pSender), dwConnID, socket) + : HR_IGNORE; + } + + virtual EnHandleResult OnConnect(T* pSender, CONNID dwConnID) + { + return (m_fnOnConnect) + ? m_fnOnConnect(C_HP_Object::FromSecond(pSender), dwConnID) + : HR_IGNORE; + } + + virtual EnHandleResult OnHandShake(T* pSender, CONNID dwConnID) + { + return (m_fnOnHandShake) + ? m_fnOnHandShake(C_HP_Object::FromSecond(pSender), dwConnID) + : HR_IGNORE; + } + + virtual EnHandleResult OnSend(T* pSender, CONNID dwConnID, const BYTE* pData, int iLength) + { + return (m_fnOnSend) + ? m_fnOnSend(C_HP_Object::FromSecond(pSender), dwConnID, pData, iLength) + : HR_IGNORE; + } + + virtual EnHandleResult OnReceive(T* pSender, CONNID dwConnID, const BYTE* pData, int iLength) + { + ASSERT(m_fnOnReceive); + + return (m_fnOnReceive) + ? m_fnOnReceive(C_HP_Object::FromSecond(pSender), dwConnID, pData, iLength) + : HR_IGNORE; + } + + virtual EnHandleResult OnReceive(T* pSender, CONNID dwConnID, int iLength) + { + ASSERT(m_fnOnPullReceive); + + return (m_fnOnPullReceive) + ? m_fnOnPullReceive(C_HP_Object::FromSecond(pSender), dwConnID, iLength) + : HR_IGNORE; + } + + virtual EnHandleResult OnClose(T* pSender, CONNID dwConnID, EnSocketOperation enOperation, int iErrorCode) + { + ASSERT(m_fnOnClose); + + return (m_fnOnClose) + ? m_fnOnClose(C_HP_Object::FromSecond(pSender), dwConnID, enOperation, iErrorCode) + : HR_IGNORE; + } + + virtual EnHandleResult OnShutdown(T* pSender) + { + return (m_fnOnShutdown) + ? m_fnOnShutdown(C_HP_Object::FromSecond(pSender)) + : HR_IGNORE; + } + +public: + C_HP_AgentListenerT() + : m_fnOnPrepareConnect (nullptr) + , m_fnOnConnect (nullptr) + , m_fnOnHandShake (nullptr) + , m_fnOnSend (nullptr) + , m_fnOnReceive (nullptr) + , m_fnOnPullReceive (nullptr) + , m_fnOnClose (nullptr) + , m_fnOnShutdown (nullptr) + { + } + +public: + HP_FN_Agent_OnPrepareConnect m_fnOnPrepareConnect; + HP_FN_Agent_OnConnect m_fnOnConnect ; + HP_FN_Agent_OnHandShake m_fnOnHandShake ; + HP_FN_Agent_OnSend m_fnOnSend ; + HP_FN_Agent_OnReceive m_fnOnReceive ; + HP_FN_Agent_OnPullReceive m_fnOnPullReceive ; + HP_FN_Agent_OnClose m_fnOnClose ; + HP_FN_Agent_OnShutdown m_fnOnShutdown ; +}; + +template class C_HP_ClientListenerT : public L +{ +public: + virtual EnHandleResult OnPrepareConnect(T* pSender, CONNID dwConnID, SOCKET socket) + { + return (m_fnOnPrepareConnect) + ? m_fnOnPrepareConnect(C_HP_Object::FromSecond(pSender), dwConnID, socket) + : HR_IGNORE; + } + + virtual EnHandleResult OnConnect(T* pSender, CONNID dwConnID) + { + return (m_fnOnConnect) + ? m_fnOnConnect(C_HP_Object::FromSecond(pSender), dwConnID) + : HR_IGNORE; + } + + virtual EnHandleResult OnHandShake(T* pSender, CONNID dwConnID) + { + return (m_fnOnHandShake) + ? m_fnOnHandShake(C_HP_Object::FromSecond(pSender), dwConnID) + : HR_IGNORE; + } + + virtual EnHandleResult OnSend(T* pSender, CONNID dwConnID, const BYTE* pData, int iLength) + { + return (m_fnOnSend) + ? m_fnOnSend(C_HP_Object::FromSecond(pSender), dwConnID, pData, iLength) + : HR_IGNORE; + } + + virtual EnHandleResult OnReceive(T* pSender, CONNID dwConnID, const BYTE* pData, int iLength) + { + ASSERT(m_fnOnReceive); + + return (m_fnOnReceive) + ? m_fnOnReceive(C_HP_Object::FromSecond(pSender), dwConnID, pData, iLength) + : HR_IGNORE; + } + + virtual EnHandleResult OnReceive(T* pSender, CONNID dwConnID, int iLength) + { + ASSERT(m_fnOnPullReceive); + + return (m_fnOnPullReceive) + ? m_fnOnPullReceive(C_HP_Object::FromSecond(pSender), dwConnID, iLength) + : HR_IGNORE; + } + + virtual EnHandleResult OnClose(T* pSender, CONNID dwConnID, EnSocketOperation enOperation, int iErrorCode) + { + ASSERT(m_fnOnClose); + + return (m_fnOnClose) + ? m_fnOnClose(C_HP_Object::FromSecond(pSender), dwConnID, enOperation, iErrorCode) + : HR_IGNORE; + } + +public: + C_HP_ClientListenerT() + : m_fnOnPrepareConnect (nullptr) + , m_fnOnConnect (nullptr) + , m_fnOnHandShake (nullptr) + , m_fnOnSend (nullptr) + , m_fnOnReceive (nullptr) + , m_fnOnPullReceive (nullptr) + , m_fnOnClose (nullptr) + { + } + +public: + HP_FN_Client_OnPrepareConnect m_fnOnPrepareConnect; + HP_FN_Client_OnConnect m_fnOnConnect ; + HP_FN_Client_OnHandShake m_fnOnHandShake ; + HP_FN_Client_OnSend m_fnOnSend ; + HP_FN_Client_OnReceive m_fnOnReceive ; + HP_FN_Client_OnPullReceive m_fnOnPullReceive ; + HP_FN_Client_OnClose m_fnOnClose ; +}; + +typedef C_HP_ServerListenerT C_HP_TcpServerListener; +typedef C_HP_ServerListenerT C_HP_TcpPullServerListener; +typedef C_HP_ServerListenerT C_HP_TcpPackServerListener; + +typedef C_HP_AgentListenerT C_HP_TcpAgentListener; +typedef C_HP_AgentListenerT C_HP_TcpPullAgentListener; +typedef C_HP_AgentListenerT C_HP_TcpPackAgentListener; + +typedef C_HP_ClientListenerT C_HP_TcpClientListener; +typedef C_HP_ClientListenerT C_HP_TcpPullClientListener; +typedef C_HP_ClientListenerT C_HP_TcpPackClientListener; + +#ifdef _UDP_SUPPORT + +template class C_HP_UdpNodeListenerT : public L +{ +public: + virtual EnHandleResult OnPrepareListen(T* pSender, SOCKET soListen) + { + return (m_fnOnPrepareListen) + ? m_fnOnPrepareListen(C_HP_Object::FromSecond(pSender), soListen) + : HR_IGNORE; + } + + virtual EnHandleResult OnSend(T* pSender, LPCTSTR lpszRemoteAddress, USHORT usRemotePort, const BYTE* pData, int iLength) + { + return (m_fnOnSend) + ? m_fnOnSend(C_HP_Object::FromSecond(pSender), lpszRemoteAddress, usRemotePort, pData, iLength) + : HR_IGNORE; + } + + virtual EnHandleResult OnReceive(T* pSender, LPCTSTR lpszRemoteAddress, USHORT usRemotePort, const BYTE* pData, int iLength) + { + ASSERT(m_fnOnReceive); + + return (m_fnOnReceive) + ? m_fnOnReceive(C_HP_Object::FromSecond(pSender), lpszRemoteAddress, usRemotePort, pData, iLength) + : HR_IGNORE; + } + + virtual EnHandleResult OnError(T* pSender, EnSocketOperation enOperation, int iErrorCode, LPCTSTR lpszRemoteAddress, USHORT usRemotePort, const BYTE* pData, int iLength) + { + ASSERT(m_fnOnError); + + return (m_fnOnError) + ? m_fnOnError(C_HP_Object::FromSecond(pSender), enOperation, iErrorCode, lpszRemoteAddress, usRemotePort, pData, iLength) + : HR_IGNORE; + } + + virtual EnHandleResult OnShutdown(T* pSender) + { + return (m_fnOnShutdown) + ? m_fnOnShutdown(C_HP_Object::FromSecond(pSender)) + : HR_IGNORE; + } + +public: + C_HP_UdpNodeListenerT() + : m_fnOnPrepareListen (nullptr) + , m_fnOnSend (nullptr) + , m_fnOnReceive (nullptr) + , m_fnOnError (nullptr) + , m_fnOnShutdown (nullptr) + { + } + +public: + HP_FN_UdpNode_OnPrepareListen m_fnOnPrepareListen ; + HP_FN_UdpNode_OnSend m_fnOnSend ; + HP_FN_UdpNode_OnReceive m_fnOnReceive ; + HP_FN_UdpNode_OnError m_fnOnError ; + HP_FN_UdpNode_OnShutdown m_fnOnShutdown ; +}; + +typedef C_HP_ServerListenerT C_HP_UdpServerListener; +typedef C_HP_ClientListenerT C_HP_UdpClientListener; +typedef C_HP_ClientListenerT C_HP_UdpCastListener; +typedef C_HP_UdpNodeListenerT C_HP_UdpNodeListener; + +typedef C_HP_ServerListenerT C_HP_UdpArqServerListener; +typedef C_HP_ClientListenerT C_HP_UdpArqClientListener; + +#endif + +#ifdef _HTTP_SUPPORT + +template class C_HP_HttpListenerT : public IHttpListenerT +{ +public: + virtual EnHttpParseResult OnMessageBegin(T* pSender, CONNID dwConnID) + { + return (m_fnOnMessageBegin) + ? m_fnOnMessageBegin(C_HP_Object::FromFirst(pSender), dwConnID) + : HPR_OK; + } + + virtual EnHttpParseResult OnRequestLine(T* pSender, CONNID dwConnID, LPCSTR lpszMethod, LPCSTR lpszUrl) + { + return (m_fnOnRequestLine) + ? m_fnOnRequestLine(C_HP_Object::FromFirst(pSender), dwConnID, lpszMethod, lpszUrl) + : HPR_OK; + } + + virtual EnHttpParseResult OnStatusLine(T* pSender, CONNID dwConnID, USHORT usStatusCode, LPCSTR lpszDesc) + { + return (m_fnOnStatusLine) + ? m_fnOnStatusLine(C_HP_Object::FromFirst(pSender), dwConnID, usStatusCode, lpszDesc) + : HPR_OK; + } + + virtual EnHttpParseResult OnHeader(T* pSender, CONNID dwConnID, LPCSTR lpszName, LPCSTR lpszValue) + { + return (m_fnOnHeader) + ? m_fnOnHeader(C_HP_Object::FromFirst(pSender), dwConnID, lpszName, lpszValue) + : HPR_OK; + } + + virtual EnHttpParseResult OnHeadersComplete(T* pSender, CONNID dwConnID) + { + ASSERT(m_fnOnHeadersComplete); + + return (m_fnOnHeadersComplete) + ? m_fnOnHeadersComplete(C_HP_Object::FromFirst(pSender), dwConnID) + : HPR_OK; + } + + virtual EnHttpParseResult OnBody(T* pSender, CONNID dwConnID, const BYTE* pData, int iLength) + { + ASSERT(m_fnOnBody); + + return (m_fnOnBody) + ? m_fnOnBody(C_HP_Object::FromFirst(pSender), dwConnID, pData, iLength) + : HPR_OK; + } + + virtual EnHttpParseResult OnChunkHeader(T* pSender, CONNID dwConnID, int iLength) + { + return (m_fnOnChunkHeader) + ? m_fnOnChunkHeader(C_HP_Object::FromFirst(pSender), dwConnID, iLength) + : HPR_OK; + } + + virtual EnHttpParseResult OnChunkComplete(T* pSender, CONNID dwConnID) + { + return (m_fnOnChunkComplete) + ? m_fnOnChunkComplete(C_HP_Object::FromFirst(pSender), dwConnID) + : HPR_OK; + } + + virtual EnHttpParseResult OnMessageComplete(T* pSender, CONNID dwConnID) + { + ASSERT(m_fnOnMessageComplete); + + return (m_fnOnMessageComplete) + ? m_fnOnMessageComplete(C_HP_Object::FromFirst(pSender), dwConnID) + : HPR_OK; + } + + virtual EnHttpParseResult OnUpgrade(T* pSender, CONNID dwConnID, EnHttpUpgradeType enUpgradeType) + { + return (m_fnOnUpgrade) + ? m_fnOnUpgrade(C_HP_Object::FromFirst(pSender), dwConnID, enUpgradeType) + : HPR_OK; + } + + virtual EnHttpParseResult OnParseError(T* pSender, CONNID dwConnID, int iErrorCode, LPCSTR lpszErrorDesc) + { + ASSERT(m_fnOnParseError); + + return (m_fnOnParseError) + ? m_fnOnParseError(C_HP_Object::FromFirst(pSender), dwConnID, iErrorCode, lpszErrorDesc) + : HPR_OK; + } + + virtual EnHandleResult OnWSMessageHeader(T* pSender, CONNID dwConnID, BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], ULONGLONG ullBodyLen) + { + return (m_fnOnWSMessageHeader) + ? m_fnOnWSMessageHeader(C_HP_Object::FromFirst(pSender), dwConnID, bFinal, iReserved, iOperationCode, lpszMask, ullBodyLen) + : HR_OK; + } + + virtual EnHandleResult OnWSMessageBody(T* pSender, CONNID dwConnID, const BYTE* pData, int iLength) + { + return (m_fnOnWSMessageBody) + ? m_fnOnWSMessageBody(C_HP_Object::FromFirst(pSender), dwConnID, pData, iLength) + : HR_OK; + } + + virtual EnHandleResult OnWSMessageComplete(T* pSender, CONNID dwConnID) + { + return (m_fnOnWSMessageComplete) + ? m_fnOnWSMessageComplete(C_HP_Object::FromFirst(pSender), dwConnID) + : HR_OK; + } + +public: + C_HP_HttpListenerT() + : m_fnOnMessageBegin (nullptr) + , m_fnOnRequestLine (nullptr) + , m_fnOnStatusLine (nullptr) + , m_fnOnHeader (nullptr) + , m_fnOnHeadersComplete (nullptr) + , m_fnOnBody (nullptr) + , m_fnOnChunkHeader (nullptr) + , m_fnOnChunkComplete (nullptr) + , m_fnOnMessageComplete (nullptr) + , m_fnOnUpgrade (nullptr) + , m_fnOnParseError (nullptr) + , m_fnOnWSMessageHeader (nullptr) + , m_fnOnWSMessageBody (nullptr) + , m_fnOnWSMessageComplete(nullptr) + { + } + +public: + HP_FN_Http_OnMessageBegin m_fnOnMessageBegin ; + HP_FN_Http_OnRequestLine m_fnOnRequestLine ; + HP_FN_Http_OnStatusLine m_fnOnStatusLine ; + HP_FN_Http_OnHeader m_fnOnHeader ; + HP_FN_Http_OnHeadersComplete m_fnOnHeadersComplete ; + HP_FN_Http_OnBody m_fnOnBody ; + HP_FN_Http_OnChunkHeader m_fnOnChunkHeader ; + HP_FN_Http_OnChunkComplete m_fnOnChunkComplete ; + HP_FN_Http_OnMessageComplete m_fnOnMessageComplete ; + HP_FN_Http_OnUpgrade m_fnOnUpgrade ; + HP_FN_Http_OnParseError m_fnOnParseError ; + HP_FN_Http_OnWSMessageHeader m_fnOnWSMessageHeader ; + HP_FN_Http_OnWSMessageBody m_fnOnWSMessageBody ; + HP_FN_Http_OnWSMessageComplete m_fnOnWSMessageComplete ; +}; + +typedef C_HP_HttpListenerT C_HP_HttpServerBaseListener1; +typedef C_HP_HttpListenerT C_HP_HttpAgentBaseListener1; +typedef C_HP_HttpListenerT C_HP_HttpClientBaseListener1; + +typedef C_HP_ServerListenerT C_HP_HttpServerBaseListener2; +typedef C_HP_AgentListenerT C_HP_HttpAgentBaseListener2; +typedef C_HP_ClientListenerT C_HP_HttpClientBaseListener2; + +class C_HP_HttpServerListener : public IHttpServerListener +{ +public: + virtual EnHttpParseResult OnMessageBegin(IHttpServer* pSender, CONNID dwConnID) + {return m_lsnHttp.OnMessageBegin(pSender, dwConnID);} + virtual EnHttpParseResult OnRequestLine(IHttpServer* pSender, CONNID dwConnID, LPCSTR lpszMethod, LPCSTR lpszUrl) + {return m_lsnHttp.OnRequestLine(pSender, dwConnID, lpszMethod, lpszUrl);} + virtual EnHttpParseResult OnStatusLine(IHttpServer* pSender, CONNID dwConnID, USHORT usStatusCode, LPCSTR lpszDesc) + {return m_lsnHttp.OnStatusLine(pSender, dwConnID, usStatusCode, lpszDesc);} + virtual EnHttpParseResult OnHeader(IHttpServer* pSender, CONNID dwConnID, LPCSTR lpszName, LPCSTR lpszValue) + {return m_lsnHttp.OnHeader(pSender, dwConnID, lpszName, lpszValue);} + virtual EnHttpParseResult OnHeadersComplete(IHttpServer* pSender, CONNID dwConnID) + {return m_lsnHttp.OnHeadersComplete(pSender, dwConnID);} + virtual EnHttpParseResult OnBody(IHttpServer* pSender, CONNID dwConnID, const BYTE* pData, int iLength) + {return m_lsnHttp.OnBody(pSender, dwConnID, pData, iLength);} + virtual EnHttpParseResult OnChunkHeader(IHttpServer* pSender, CONNID dwConnID, int iLength) + {return m_lsnHttp.OnChunkHeader(pSender, dwConnID, iLength);} + virtual EnHttpParseResult OnChunkComplete(IHttpServer* pSender, CONNID dwConnID) + {return m_lsnHttp.OnChunkComplete(pSender, dwConnID);} + virtual EnHttpParseResult OnMessageComplete(IHttpServer* pSender, CONNID dwConnID) + {return m_lsnHttp.OnMessageComplete(pSender, dwConnID);} + virtual EnHttpParseResult OnUpgrade(IHttpServer* pSender, CONNID dwConnID, EnHttpUpgradeType enUpgradeType) + {return m_lsnHttp.OnUpgrade(pSender, dwConnID, enUpgradeType);} + virtual EnHttpParseResult OnParseError(IHttpServer* pSender, CONNID dwConnID, int iErrorCode, LPCSTR lpszErrorDesc) + {return m_lsnHttp.OnParseError(pSender, dwConnID, iErrorCode, lpszErrorDesc);} + + virtual EnHandleResult OnWSMessageHeader(IHttpServer* pSender, CONNID dwConnID, BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], ULONGLONG ullBodyLen) + {return m_lsnHttp.OnWSMessageHeader(pSender, dwConnID, bFinal, iReserved, iOperationCode, lpszMask, ullBodyLen);} + virtual EnHandleResult OnWSMessageBody(IHttpServer* pSender, CONNID dwConnID, const BYTE* pData, int iLength) + {return m_lsnHttp.OnWSMessageBody(pSender, dwConnID, pData, iLength);} + virtual EnHandleResult OnWSMessageComplete(IHttpServer* pSender, CONNID dwConnID) + {return m_lsnHttp.OnWSMessageComplete(pSender, dwConnID);} + + virtual EnHandleResult OnPrepareListen(ITcpServer* pSender, SOCKET soListen) + {return m_lsnServer.OnPrepareListen(pSender, soListen);} + virtual EnHandleResult OnAccept(ITcpServer* pSender, CONNID dwConnID, UINT_PTR soClient) + {return m_lsnServer.OnAccept(pSender, dwConnID, soClient);} + virtual EnHandleResult OnHandShake(ITcpServer* pSender, CONNID dwConnID) + {return m_lsnServer.OnHandShake(pSender, dwConnID);} + virtual EnHandleResult OnSend(ITcpServer* pSender, CONNID dwConnID, const BYTE* pData, int iLength) + {return m_lsnServer.OnSend(pSender, dwConnID, pData, iLength);} + virtual EnHandleResult OnReceive(ITcpServer* pSender, CONNID dwConnID, const BYTE* pData, int iLength) + {return m_lsnServer.OnReceive(pSender, dwConnID, pData, iLength);} + virtual EnHandleResult OnReceive(ITcpServer* pSender, CONNID dwConnID, int iLength) + {return m_lsnServer.OnReceive(pSender, dwConnID, iLength);} + virtual EnHandleResult OnClose(ITcpServer* pSender, CONNID dwConnID, EnSocketOperation enOperation, int iErrorCode) + {return m_lsnServer.OnClose(pSender, dwConnID, enOperation, iErrorCode);} + virtual EnHandleResult OnShutdown(ITcpServer* pSender) + {return m_lsnServer.OnShutdown(pSender);} + +public: + C_HP_HttpServerBaseListener1 m_lsnHttp; + C_HP_HttpServerBaseListener2 m_lsnServer; +}; + +class C_HP_HttpAgentListener : public IHttpAgentListener +{ +public: + virtual EnHttpParseResult OnMessageBegin(IHttpAgent* pSender, CONNID dwConnID) + {return m_lsnHttp.OnMessageBegin(pSender, dwConnID);} + virtual EnHttpParseResult OnRequestLine(IHttpAgent* pSender, CONNID dwConnID, LPCSTR lpszMethod, LPCSTR lpszUrl) + {return m_lsnHttp.OnRequestLine(pSender, dwConnID, lpszMethod, lpszUrl);} + virtual EnHttpParseResult OnStatusLine(IHttpAgent* pSender, CONNID dwConnID, USHORT usStatusCode, LPCSTR lpszDesc) + {return m_lsnHttp.OnStatusLine(pSender, dwConnID, usStatusCode, lpszDesc);} + virtual EnHttpParseResult OnHeader(IHttpAgent* pSender, CONNID dwConnID, LPCSTR lpszName, LPCSTR lpszValue) + {return m_lsnHttp.OnHeader(pSender, dwConnID, lpszName, lpszValue);} + virtual EnHttpParseResult OnHeadersComplete(IHttpAgent* pSender, CONNID dwConnID) + {return m_lsnHttp.OnHeadersComplete(pSender, dwConnID);} + virtual EnHttpParseResult OnBody(IHttpAgent* pSender, CONNID dwConnID, const BYTE* pData, int iLength) + {return m_lsnHttp.OnBody(pSender, dwConnID, pData, iLength);} + virtual EnHttpParseResult OnChunkHeader(IHttpAgent* pSender, CONNID dwConnID, int iLength) + {return m_lsnHttp.OnChunkHeader(pSender, dwConnID, iLength);} + virtual EnHttpParseResult OnChunkComplete(IHttpAgent* pSender, CONNID dwConnID) + {return m_lsnHttp.OnChunkComplete(pSender, dwConnID);} + virtual EnHttpParseResult OnMessageComplete(IHttpAgent* pSender, CONNID dwConnID) + {return m_lsnHttp.OnMessageComplete(pSender, dwConnID);} + virtual EnHttpParseResult OnUpgrade(IHttpAgent* pSender, CONNID dwConnID, EnHttpUpgradeType enUpgradeType) + {return m_lsnHttp.OnUpgrade(pSender, dwConnID, enUpgradeType);} + virtual EnHttpParseResult OnParseError(IHttpAgent* pSender, CONNID dwConnID, int iErrorCode, LPCSTR lpszErrorDesc) + {return m_lsnHttp.OnParseError(pSender, dwConnID, iErrorCode, lpszErrorDesc);} + + virtual EnHandleResult OnWSMessageHeader(IHttpAgent* pSender, CONNID dwConnID, BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], ULONGLONG ullBodyLen) + {return m_lsnHttp.OnWSMessageHeader(pSender, dwConnID, bFinal, iReserved, iOperationCode, lpszMask, ullBodyLen);} + virtual EnHandleResult OnWSMessageBody(IHttpAgent* pSender, CONNID dwConnID, const BYTE* pData, int iLength) + {return m_lsnHttp.OnWSMessageBody(pSender, dwConnID, pData, iLength);} + virtual EnHandleResult OnWSMessageComplete(IHttpAgent* pSender, CONNID dwConnID) + {return m_lsnHttp.OnWSMessageComplete(pSender, dwConnID);} + + virtual EnHandleResult OnPrepareConnect(ITcpAgent* pSender, CONNID dwConnID, SOCKET socket) + {return m_lsnAgent.OnPrepareConnect(pSender, dwConnID, socket);} + virtual EnHandleResult OnConnect(ITcpAgent* pSender, CONNID dwConnID) + {return m_lsnAgent.OnConnect(pSender, dwConnID);} + virtual EnHandleResult OnHandShake(ITcpAgent* pSender, CONNID dwConnID) + {return m_lsnAgent.OnHandShake(pSender, dwConnID);} + virtual EnHandleResult OnSend(ITcpAgent* pSender, CONNID dwConnID, const BYTE* pData, int iLength) + {return m_lsnAgent.OnSend(pSender, dwConnID, pData, iLength);} + virtual EnHandleResult OnReceive(ITcpAgent* pSender, CONNID dwConnID, const BYTE* pData, int iLength) + {return m_lsnAgent.OnReceive(pSender, dwConnID, pData, iLength);} + virtual EnHandleResult OnReceive(ITcpAgent* pSender, CONNID dwConnID, int iLength) + {return m_lsnAgent.OnReceive(pSender, dwConnID, iLength);} + virtual EnHandleResult OnClose(ITcpAgent* pSender, CONNID dwConnID, EnSocketOperation enOperation, int iErrorCode) + {return m_lsnAgent.OnClose(pSender, dwConnID, enOperation, iErrorCode);} + virtual EnHandleResult OnShutdown(ITcpAgent* pSender) + {return m_lsnAgent.OnShutdown(pSender);} + +public: + C_HP_HttpAgentBaseListener1 m_lsnHttp; + C_HP_HttpAgentBaseListener2 m_lsnAgent; +}; + +class C_HP_HttpClientListener : public IHttpClientListener +{ +public: + virtual EnHttpParseResult OnMessageBegin(IHttpClient* pSender, CONNID dwConnID) + {return m_lsnHttp.OnMessageBegin(pSender, dwConnID);} + virtual EnHttpParseResult OnRequestLine(IHttpClient* pSender, CONNID dwConnID, LPCSTR lpszMethod, LPCSTR lpszUrl) + {return m_lsnHttp.OnRequestLine(pSender, dwConnID, lpszMethod, lpszUrl);} + virtual EnHttpParseResult OnStatusLine(IHttpClient* pSender, CONNID dwConnID, USHORT usStatusCode, LPCSTR lpszDesc) + {return m_lsnHttp.OnStatusLine(pSender, dwConnID, usStatusCode, lpszDesc);} + virtual EnHttpParseResult OnHeader(IHttpClient* pSender, CONNID dwConnID, LPCSTR lpszName, LPCSTR lpszValue) + {return m_lsnHttp.OnHeader(pSender, dwConnID, lpszName, lpszValue);} + virtual EnHttpParseResult OnHeadersComplete(IHttpClient* pSender, CONNID dwConnID) + {return m_lsnHttp.OnHeadersComplete(pSender, dwConnID);} + virtual EnHttpParseResult OnBody(IHttpClient* pSender, CONNID dwConnID, const BYTE* pData, int iLength) + {return m_lsnHttp.OnBody(pSender, dwConnID, pData, iLength);} + virtual EnHttpParseResult OnChunkHeader(IHttpClient* pSender, CONNID dwConnID, int iLength) + {return m_lsnHttp.OnChunkHeader(pSender, dwConnID, iLength);} + virtual EnHttpParseResult OnChunkComplete(IHttpClient* pSender, CONNID dwConnID) + {return m_lsnHttp.OnChunkComplete(pSender, dwConnID);} + virtual EnHttpParseResult OnMessageComplete(IHttpClient* pSender, CONNID dwConnID) + {return m_lsnHttp.OnMessageComplete(pSender, dwConnID);} + virtual EnHttpParseResult OnUpgrade(IHttpClient* pSender, CONNID dwConnID, EnHttpUpgradeType enUpgradeType) + {return m_lsnHttp.OnUpgrade(pSender, dwConnID, enUpgradeType);} + virtual EnHttpParseResult OnParseError(IHttpClient* pSender, CONNID dwConnID, int iErrorCode, LPCSTR lpszErrorDesc) + {return m_lsnHttp.OnParseError(pSender, dwConnID, iErrorCode, lpszErrorDesc);} + + virtual EnHandleResult OnWSMessageHeader(IHttpClient* pSender, CONNID dwConnID, BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], ULONGLONG ullBodyLen) + {return m_lsnHttp.OnWSMessageHeader(pSender, dwConnID, bFinal, iReserved, iOperationCode, lpszMask, ullBodyLen);} + virtual EnHandleResult OnWSMessageBody(IHttpClient* pSender, CONNID dwConnID, const BYTE* pData, int iLength) + {return m_lsnHttp.OnWSMessageBody(pSender, dwConnID, pData, iLength);} + virtual EnHandleResult OnWSMessageComplete(IHttpClient* pSender, CONNID dwConnID) + {return m_lsnHttp.OnWSMessageComplete(pSender, dwConnID);} + + virtual EnHandleResult OnPrepareConnect(ITcpClient* pSender, CONNID dwConnID, SOCKET socket) + {return m_lsnClient.OnPrepareConnect(pSender, dwConnID, socket);} + virtual EnHandleResult OnConnect(ITcpClient* pSender, CONNID dwConnID) + {return m_lsnClient.OnConnect(pSender, dwConnID);} + virtual EnHandleResult OnHandShake(ITcpClient* pSender, CONNID dwConnID) + {return m_lsnClient.OnHandShake(pSender, dwConnID);} + virtual EnHandleResult OnSend(ITcpClient* pSender, CONNID dwConnID, const BYTE* pData, int iLength) + {return m_lsnClient.OnSend(pSender, dwConnID, pData, iLength);} + virtual EnHandleResult OnReceive(ITcpClient* pSender, CONNID dwConnID, const BYTE* pData, int iLength) + {return m_lsnClient.OnReceive(pSender, dwConnID, pData, iLength);} + virtual EnHandleResult OnReceive(ITcpClient* pSender, CONNID dwConnID, int iLength) + {return m_lsnClient.OnReceive(pSender, dwConnID, iLength);} + virtual EnHandleResult OnClose(ITcpClient* pSender, CONNID dwConnID, EnSocketOperation enOperation, int iErrorCode) + {return m_lsnClient.OnClose(pSender, dwConnID, enOperation, iErrorCode);} + +public: + C_HP_HttpClientBaseListener1 m_lsnHttp; + C_HP_HttpClientBaseListener2 m_lsnClient; +}; + +#endif + +class C_HP_ThreadPoolListener : public IHPThreadPoolListener +{ +public: + virtual void OnStartup(IHPThreadPool* pThreadPool) + { + if(m_fnOnStartup) + m_fnOnStartup((HP_ThreadPool)pThreadPool); + } + + virtual void OnShutdown(IHPThreadPool* pThreadPool) + { + if(m_fnOnShutdown) + m_fnOnShutdown((HP_ThreadPool)pThreadPool); + } + + virtual void OnWorkerThreadStart(IHPThreadPool* pThreadPool, THR_ID dwThreadID) + { + if(m_fnOnWorkerThreadStart) + m_fnOnWorkerThreadStart((HP_ThreadPool)pThreadPool, dwThreadID); + } + virtual void OnWorkerThreadEnd(IHPThreadPool* pThreadPool, THR_ID dwThreadID) + { + if(m_fnOnWorkerThreadEnd) + m_fnOnWorkerThreadEnd((HP_ThreadPool)pThreadPool, dwThreadID); + } + +public: + C_HP_ThreadPoolListener() + : m_fnOnStartup (nullptr) + , m_fnOnShutdown (nullptr) + , m_fnOnWorkerThreadStart (nullptr) + , m_fnOnWorkerThreadEnd (nullptr) + { + } + +public: + HP_FN_ThreadPool_OnStartup m_fnOnStartup; + HP_FN_ThreadPool_OnShutdown m_fnOnShutdown; + HP_FN_ThreadPool_OnWorkerThreadStart m_fnOnWorkerThreadStart; + HP_FN_ThreadPool_OnWorkerThreadEnd m_fnOnWorkerThreadEnd; +}; diff --git a/TcpAgent.cpp b/TcpAgent.cpp new file mode 100644 index 0000000..e5bc700 --- /dev/null +++ b/TcpAgent.cpp @@ -0,0 +1,1306 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TcpAgent.h" + +#include "./common/FileHelper.h" + +BOOL CTcpAgent::Start(LPCTSTR lpszBindAddress, BOOL bAsyncConnect) +{ + if(!CheckParams() || !CheckStarting()) + return FALSE; + + PrepareStart(); + + if(ParseBindAddress(lpszBindAddress)) + if(CreateWorkerThreads()) + { + m_bAsyncConnect = bAsyncConnect; + m_enState = SS_STARTED; + + return TRUE; + } + + EXECUTE_RESTORE_ERROR(Stop()); + + return FALSE; +} + +void CTcpAgent::SetLastError(EnSocketError code, LPCSTR func, int ec) +{ + m_enLastError = code; + ::SetLastError(ec); +} + +BOOL CTcpAgent::CheckParams() +{ + if ((m_enSendPolicy >= SP_PACK && m_enSendPolicy <= SP_DIRECT) && + (m_enOnSendSyncPolicy >= OSSP_NONE && m_enOnSendSyncPolicy <= OSSP_RECEIVE) && + ((int)m_dwSyncConnectTimeout > 0) && + ((int)m_dwMaxConnectionCount > 0 && m_dwMaxConnectionCount <= MAX_CONNECTION_COUNT) && + ((int)m_dwWorkerThreadCount > 0 && m_dwWorkerThreadCount <= MAX_WORKER_THREAD_COUNT) && + ((int)m_dwSocketBufferSize >= MIN_SOCKET_BUFFER_SIZE) && + ((int)m_dwFreeSocketObjLockTime >= 1000) && + ((int)m_dwFreeSocketObjPool >= 0) && + ((int)m_dwFreeBufferObjPool >= 0) && + ((int)m_dwFreeSocketObjHold >= 0) && + ((int)m_dwFreeBufferObjHold >= 0) && + ((int)m_dwKeepAliveTime >= 1000 || m_dwKeepAliveTime == 0) && + ((int)m_dwKeepAliveInterval >= 1000 || m_dwKeepAliveInterval == 0) ) + return TRUE; + + SetLastError(SE_INVALID_PARAM, __FUNCTION__, ERROR_INVALID_PARAMETER); + return FALSE; +} + +void CTcpAgent::PrepareStart() +{ + m_bfActiveSockets.Reset(m_dwMaxConnectionCount); + m_lsFreeSocket.Reset(m_dwFreeSocketObjPool); + + m_bfObjPool.SetItemCapacity(m_dwSocketBufferSize); + m_bfObjPool.SetPoolSize(m_dwFreeBufferObjPool); + m_bfObjPool.SetPoolHold(m_dwFreeBufferObjHold); + + m_bfObjPool.Prepare(); + + m_rcBuffers = make_unique(m_dwWorkerThreadCount); + for_each(m_rcBuffers.get(), m_rcBuffers.get() + m_dwWorkerThreadCount, [this](CBufferPtr& buff) {buff.Malloc(m_dwSocketBufferSize);}); +} + +BOOL CTcpAgent::CheckStarting() +{ + CSpinLock locallock(m_csState); + + if(m_enState == SS_STOPPED) + m_enState = SS_STARTING; + else + { + SetLastError(SE_ILLEGAL_STATE, __FUNCTION__, ERROR_INVALID_STATE); + return FALSE; + } + + return TRUE; +} + +BOOL CTcpAgent::CheckStoping() +{ + if(m_enState != SS_STOPPED) + { + CSpinLock locallock(m_csState); + + if(HasStarted()) + { + m_enState = SS_STOPPING; + return TRUE; + } + } + + SetLastError(SE_ILLEGAL_STATE, __FUNCTION__, ERROR_INVALID_STATE); + + return FALSE; +} + +BOOL CTcpAgent::ParseBindAddress(LPCTSTR lpszBindAddress) +{ + if(::IsStrEmpty(lpszBindAddress)) + return TRUE; + + HP_SOCKADDR addr; + + if(::sockaddr_A_2_IN(lpszBindAddress, 0, addr)) + { + SOCKET sock = socket(addr.family, SOCK_STREAM, IPPROTO_TCP); + + if(sock != INVALID_SOCKET) + { + if(::bind(sock, addr.Addr(), addr.AddrSize()) != SOCKET_ERROR) + { + addr.Copy(m_soAddr); + return TRUE; + } + else + SetLastError(SE_SOCKET_BIND, __FUNCTION__, ::WSAGetLastError()); + + ::ManualCloseSocket(sock); + } + else + SetLastError(SE_SOCKET_CREATE, __FUNCTION__, ::WSAGetLastError()); + } + else + SetLastError(SE_SOCKET_CREATE, __FUNCTION__, ::WSAGetLastError()); + + return FALSE; +} + +BOOL CTcpAgent::CreateWorkerThreads() +{ + DWORD dwWorkerThreadCount = m_dwWorkerThreadCount +#ifdef USE_EXTERNAL_GC + + 1 +#endif + ; + + if(!m_ioDispatcher.Start(this, DEFAULT_WORKER_MAX_EVENT_COUNT, dwWorkerThreadCount)) + { + SetLastError(SE_WORKER_THREAD_CREATE, __FUNCTION__, ::WSAGetLastError()); + return FALSE; + } + +#ifdef USE_EXTERNAL_GC + m_fdGCTimer = m_ioDispatcher.AddTimer(m_dwWorkerThreadCount, GC_CHECK_INTERVAL, this); + + if(IS_INVALID_FD(m_fdGCTimer)) + { + SetLastError(SE_GC_START, __FUNCTION__, ::WSAGetLastError()); + return FALSE; + } +#endif + + return TRUE; +} + +BOOL CTcpAgent::Stop() +{ + if(!CheckStoping()) + return FALSE; + + DisconnectClientSocket(); + WaitForClientSocketClose(); + WaitForWorkerThreadEnd(); + + ReleaseClientSocket(); + + FireShutdown(); + + ReleaseFreeSocket(); + + Reset(); + + return TRUE; +} + +void CTcpAgent::DisconnectClientSocket() +{ + ::WaitFor(100); + + if(m_bfActiveSockets.Elements() == 0) + return; + + TAgentSocketObjPtrPool::IndexSet indexes; + m_bfActiveSockets.CopyIndexes(indexes); + + for(auto it = indexes.begin(), end = indexes.end(); it != end; ++it) + Disconnect(*it); +} + +void CTcpAgent::WaitForClientSocketClose() +{ + while(m_bfActiveSockets.Elements() > 0) + ::WaitFor(50); +} + +void CTcpAgent::WaitForWorkerThreadEnd() +{ + m_ioDispatcher.Stop(); +} + +void CTcpAgent::ReleaseClientSocket() +{ + VERIFY(m_bfActiveSockets.IsEmpty()); + m_bfActiveSockets.Reset(); +} + +void CTcpAgent::ReleaseFreeSocket() +{ + m_lsFreeSocket.Clear(); + +#ifdef USE_EXTERNAL_GC + if(IS_VALID_FD(m_fdGCTimer)) + { + close(m_fdGCTimer); + m_fdGCTimer = INVALID_FD; + } +#endif + + ReleaseGCSocketObj(TRUE); + VERIFY(m_lsGCSocket.IsEmpty()); +} + +void CTcpAgent::Reset() +{ + m_bfObjPool.Clear(); + m_phSocket.Reset(); + m_soAddr.Reset(); + + m_rcBuffers = nullptr; + + m_enState = SS_STOPPED; + + m_evWait.SyncNotifyAll(); +} + +BOOL CTcpAgent::Connect(LPCTSTR lpszRemoteAddress, USHORT usPort, CONNID* pdwConnID, PVOID pExtra, USHORT usLocalPort, LPCTSTR lpszLocalAddress) +{ + ASSERT(lpszRemoteAddress && usPort != 0); + + if(!HasStarted()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + if(!pdwConnID) + pdwConnID = CreateLocalObject(CONNID); + + *pdwConnID = 0; + + HP_SOCKADDR addr; + HP_SCOPE_HOST host(lpszRemoteAddress); + SOCKET soClient = INVALID_SOCKET; + + DWORD result = CreateClientSocket(host.addr, usPort, lpszLocalAddress, usLocalPort, soClient, addr); + + if(result == NO_ERROR) + { + result = PrepareConnect(*pdwConnID, soClient); + + if(result == NO_ERROR) + result = ConnectToServer(*pdwConnID, host.name, soClient, addr, pExtra); + } + + if(result != NO_ERROR) + { + if(soClient != INVALID_SOCKET) + ::ManualCloseSocket(soClient); + + ::SetLastError(result); + } + + return (result == NO_ERROR); +} + +int CTcpAgent::CreateClientSocket(LPCTSTR lpszRemoteAddress, USHORT usPort, LPCTSTR lpszLocalAddress, USHORT usLocalPort, SOCKET& soClient, HP_SOCKADDR& addr) +{ + if(!::GetSockAddrByHostName(lpszRemoteAddress, usPort, addr)) + return ERROR_ADDRNOTAVAIL; + + HP_SOCKADDR* lpBindAddr = &m_soAddr; + + if(::IsStrNotEmpty(lpszLocalAddress)) + { + lpBindAddr = CreateLocalObject(HP_SOCKADDR); + + if(!::sockaddr_A_2_IN(lpszLocalAddress, 0, *lpBindAddr)) + return ::WSAGetLastError(); + } + + BOOL bBind = lpBindAddr->IsSpecified(); + + if(bBind && lpBindAddr->family != addr.family) + return ERROR_AFNOSUPPORT; + + int result = NO_ERROR; + soClient = socket(addr.family, SOCK_STREAM, IPPROTO_TCP); + + if(soClient == INVALID_SOCKET) + result = ::WSAGetLastError(); + else + { + BOOL bOnOff = (m_dwKeepAliveTime > 0 && m_dwKeepAliveInterval > 0); + VERIFY(IS_NO_ERROR(::SSO_KeepAliveVals(soClient, bOnOff, m_dwKeepAliveTime, m_dwKeepAliveInterval))); + VERIFY(IS_NO_ERROR(::SSO_ReuseAddress(soClient, m_enReusePolicy))); + VERIFY(IS_NO_ERROR(::SSO_NoDelay(soClient, m_bNoDelay))); + + if(bBind && usLocalPort == 0) + { + if(::bind(soClient, lpBindAddr->Addr(), lpBindAddr->AddrSize()) == SOCKET_ERROR) + result = ::WSAGetLastError(); + } + else if(usLocalPort != 0) + { + HP_SOCKADDR bindAddr = bBind ? *lpBindAddr : HP_SOCKADDR::AnyAddr(addr.family); + + bindAddr.SetPort(usLocalPort); + + if(::bind(soClient, bindAddr.Addr(), bindAddr.AddrSize()) == SOCKET_ERROR) + result = ::WSAGetLastError(); + } + } + + return result; +} + +int CTcpAgent::PrepareConnect(CONNID& dwConnID, SOCKET soClient) +{ + if(!m_bfActiveSockets.AcquireLock(dwConnID)) + return ERROR_CONNECTION_COUNT_LIMIT; + + if(TRIGGER(FirePrepareConnect(dwConnID, soClient)) == HR_ERROR) + { + VERIFY(m_bfActiveSockets.ReleaseLock(dwConnID, nullptr)); + return ENSURE_ERROR_CANCELLED; + } + + return NO_ERROR; +} + +int CTcpAgent::ConnectToServer(CONNID dwConnID, LPCTSTR lpszRemoteHostName, SOCKET& soClient, const HP_SOCKADDR& addr, PVOID pExtra) +{ + TAgentSocketObj* pSocketObj = GetFreeSocketObj(dwConnID, soClient); + AddClientSocketObj(dwConnID, pSocketObj, addr, lpszRemoteHostName, pExtra); + + int result = HAS_ERROR; + + VERIFY(::fcntl_SETFL(pSocketObj->socket, O_NOATIME | O_NONBLOCK | O_CLOEXEC)); + + int rc = ::connect(pSocketObj->socket, addr.Addr(), addr.AddrSize()); + + if(IS_NO_ERROR(rc) || IS_IO_PENDING_ERROR()) + { + if(m_bAsyncConnect) + { + if(m_ioDispatcher.AddFD(pSocketObj->socket, EPOLLOUT, pSocketObj)) + result = NO_ERROR; + } + else + { + if(IS_HAS_ERROR(result)) + result = ::WaitForSocketWrite(pSocketObj->socket, m_dwSyncConnectTimeout); + + if(IS_NO_ERROR(result)) + { + pSocketObj->SetConnected(); + + if(TRIGGER(FireConnect(pSocketObj)) == HR_ERROR) + result = ENSURE_ERROR_CANCELLED; + else + { + UINT evts = (pSocketObj->IsPending() ? EPOLLOUT : 0) | (pSocketObj->IsPaused() ? 0 : EPOLLIN); + + if(!m_ioDispatcher.AddFD(pSocketObj->socket, evts | EPOLLRDHUP, pSocketObj)) + result = HAS_ERROR; + } + } + } + } + + if(result == HAS_ERROR) + result = ::WSAGetLastError(); + if(result != NO_ERROR) + { + AddFreeSocketObj(pSocketObj, SCF_NONE); + soClient = INVALID_SOCKET; + } + + return result; +} + +TAgentSocketObj* CTcpAgent::GetFreeSocketObj(CONNID dwConnID, SOCKET soClient) +{ + DWORD dwIndex; + TAgentSocketObj* pSocketObj = nullptr; + + if(m_lsFreeSocket.TryLock(&pSocketObj, dwIndex)) + { + if(::GetTimeGap32(pSocketObj->freeTime) >= m_dwFreeSocketObjLockTime) + VERIFY(m_lsFreeSocket.ReleaseLock(nullptr, dwIndex)); + else + { + VERIFY(m_lsFreeSocket.ReleaseLock(pSocketObj, dwIndex)); + pSocketObj = nullptr; + } + } + + if(!pSocketObj) pSocketObj = CreateSocketObj(); + pSocketObj->Reset(dwConnID, soClient); + + return pSocketObj; +} + +TAgentSocketObj* CTcpAgent::CreateSocketObj() +{ + return TAgentSocketObj::Construct(m_phSocket, m_bfObjPool); +} + +void CTcpAgent::DeleteSocketObj(TAgentSocketObj* pSocketObj) +{ + TAgentSocketObj::Destruct(pSocketObj); +} + +void CTcpAgent::AddFreeSocketObj(TAgentSocketObj* pSocketObj, EnSocketCloseFlag enFlag, EnSocketOperation enOperation, int iErrorCode) +{ + if(!InvalidSocketObj(pSocketObj)) + return; + + CloseClientSocketObj(pSocketObj, enFlag, enOperation, iErrorCode); + + m_bfActiveSockets.Remove(pSocketObj->connID); + TAgentSocketObj::Release(pSocketObj); + +#ifndef USE_EXTERNAL_GC + ReleaseGCSocketObj(); +#endif + + if(!m_lsFreeSocket.TryPut(pSocketObj)) + m_lsGCSocket.PushBack(pSocketObj); +} + +void CTcpAgent::ReleaseGCSocketObj(BOOL bForce) +{ + ::ReleaseGCObj(m_lsGCSocket, m_dwFreeSocketObjLockTime, bForce); +} + +BOOL CTcpAgent::InvalidSocketObj(TAgentSocketObj* pSocketObj) +{ + return TAgentSocketObj::InvalidSocketObj(pSocketObj); +} + +void CTcpAgent::AddClientSocketObj(CONNID dwConnID, TAgentSocketObj* pSocketObj, const HP_SOCKADDR& remoteAddr, LPCTSTR lpszRemoteHostName, PVOID pExtra) +{ + ASSERT(FindSocketObj(dwConnID) == nullptr); + + pSocketObj->connTime = ::TimeGetTime(); + pSocketObj->activeTime = pSocketObj->connTime; + pSocketObj->host = lpszRemoteHostName; + pSocketObj->extra = pExtra; + + pSocketObj->SetConnected(CST_CONNECTING); + remoteAddr.Copy(pSocketObj->remoteAddr); + + VERIFY(m_bfActiveSockets.ReleaseLock(dwConnID, pSocketObj)); +} + +TAgentSocketObj* CTcpAgent::FindSocketObj(CONNID dwConnID) +{ + TAgentSocketObj* pSocketObj = nullptr; + + if(m_bfActiveSockets.Get(dwConnID, &pSocketObj) != TAgentSocketObjPtrPool::GR_VALID) + pSocketObj = nullptr; + + return pSocketObj; +} + +void CTcpAgent::CloseClientSocketObj(TAgentSocketObj* pSocketObj, EnSocketCloseFlag enFlag, EnSocketOperation enOperation, int iErrorCode, int iShutdownFlag) +{ + ASSERT(TAgentSocketObj::IsExist(pSocketObj)); + + if(enFlag == SCF_CLOSE) + FireClose(pSocketObj, SO_CLOSE, SE_OK); + else if(enFlag == SCF_ERROR) + FireClose(pSocketObj, enOperation, iErrorCode); + + SOCKET socket = pSocketObj->socket; + pSocketObj->socket = INVALID_SOCKET; + + ::ManualCloseSocket(socket, iShutdownFlag); +} + +BOOL CTcpAgent::GetLocalAddress(CONNID dwConnID, TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort) +{ + ASSERT(lpszAddress != nullptr && iAddressLen > 0); + + TAgentSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TAgentSocketObj::IsValid(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + return ::GetSocketLocalAddress(pSocketObj->socket, lpszAddress, iAddressLen, usPort); +} + +BOOL CTcpAgent::GetRemoteAddress(CONNID dwConnID, TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort) +{ + ASSERT(lpszAddress != nullptr && iAddressLen > 0); + + TAgentSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TAgentSocketObj::IsExist(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + ADDRESS_FAMILY usFamily; + return ::sockaddr_IN_2_A(pSocketObj->remoteAddr, usFamily, lpszAddress, iAddressLen, usPort); +} + +BOOL CTcpAgent::GetRemoteHost(CONNID dwConnID, TCHAR lpszHost[], int& iHostLen, USHORT& usPort) +{ + ASSERT(lpszHost != nullptr && iHostLen > 0); + + BOOL isOK = FALSE; + TAgentSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TAgentSocketObj::IsExist(pSocketObj)) + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + else + { + int iLen = pSocketObj->host.GetLength() + 1; + + if(iHostLen >= iLen) + { + memcpy(lpszHost, CA2CT((LPCSTR)pSocketObj->host), iLen * sizeof(TCHAR)); + usPort = pSocketObj->remoteAddr.Port(); + + isOK = TRUE; + } + + iHostLen = iLen; + } + + return isOK; +} + +BOOL CTcpAgent::GetRemoteHost(CONNID dwConnID, LPCSTR* lpszHost, USHORT* pusPort) +{ + *lpszHost = nullptr; + TAgentSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TAgentSocketObj::IsExist(pSocketObj)) + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + else + { + *lpszHost = pSocketObj->host; + + if(pusPort) + *pusPort = pSocketObj->remoteAddr.Port(); + } + + return (*lpszHost != nullptr && (*lpszHost)[0] != 0); +} + +BOOL CTcpAgent::SetConnectionExtra(CONNID dwConnID, PVOID pExtra) +{ + TAgentSocketObj* pSocketObj = FindSocketObj(dwConnID); + return SetConnectionExtra(pSocketObj, pExtra); +} + +BOOL CTcpAgent::SetConnectionExtra(TAgentSocketObj* pSocketObj, PVOID pExtra) +{ + if(!TAgentSocketObj::IsExist(pSocketObj)) + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + else + { + pSocketObj->extra = pExtra; + return TRUE; + } + + return FALSE; +} + +BOOL CTcpAgent::GetConnectionExtra(CONNID dwConnID, PVOID* ppExtra) +{ + TAgentSocketObj* pSocketObj = FindSocketObj(dwConnID); + return GetConnectionExtra(pSocketObj, ppExtra); +} + +BOOL CTcpAgent::GetConnectionExtra(TAgentSocketObj* pSocketObj, PVOID* ppExtra) +{ + ASSERT(ppExtra != nullptr); + + if(!TAgentSocketObj::IsExist(pSocketObj)) + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + else + { + *ppExtra = pSocketObj->extra; + return TRUE; + } + + return FALSE; +} + +BOOL CTcpAgent::SetConnectionReserved(CONNID dwConnID, PVOID pReserved) +{ + TAgentSocketObj* pSocketObj = FindSocketObj(dwConnID); + return SetConnectionReserved(pSocketObj, pReserved); +} + +BOOL CTcpAgent::SetConnectionReserved(TAgentSocketObj* pSocketObj, PVOID pReserved) +{ + if(!TAgentSocketObj::IsExist(pSocketObj)) + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + else + { + pSocketObj->reserved = pReserved; + return TRUE; + } + + return FALSE; +} + +BOOL CTcpAgent::GetConnectionReserved(CONNID dwConnID, PVOID* ppReserved) +{ + TAgentSocketObj* pSocketObj = FindSocketObj(dwConnID); + return GetConnectionReserved(pSocketObj, ppReserved); +} + +BOOL CTcpAgent::GetConnectionReserved(TAgentSocketObj* pSocketObj, PVOID* ppReserved) +{ + ASSERT(ppReserved != nullptr); + + if(!TAgentSocketObj::IsExist(pSocketObj)) + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + else + { + *ppReserved = pSocketObj->reserved; + return TRUE; + } + + return FALSE; +} + +BOOL CTcpAgent::SetConnectionReserved2(CONNID dwConnID, PVOID pReserved2) +{ + TAgentSocketObj* pSocketObj = FindSocketObj(dwConnID); + return SetConnectionReserved2(pSocketObj, pReserved2); +} + +BOOL CTcpAgent::SetConnectionReserved2(TAgentSocketObj* pSocketObj, PVOID pReserved2) +{ + if(!TAgentSocketObj::IsExist(pSocketObj)) + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + else + { + pSocketObj->reserved2 = pReserved2; + return TRUE; + } + + return FALSE; +} + +BOOL CTcpAgent::GetConnectionReserved2(CONNID dwConnID, PVOID* ppReserved2) +{ + TAgentSocketObj* pSocketObj = FindSocketObj(dwConnID); + return GetConnectionReserved2(pSocketObj, ppReserved2); +} + +BOOL CTcpAgent::GetConnectionReserved2(TAgentSocketObj* pSocketObj, PVOID* ppReserved2) +{ + ASSERT(ppReserved2 != nullptr); + + if(!TAgentSocketObj::IsExist(pSocketObj)) + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + else + { + *ppReserved2 = pSocketObj->reserved2; + return TRUE; + } + + return FALSE; +} + +BOOL CTcpAgent::IsPauseReceive(CONNID dwConnID, BOOL& bPaused) +{ + TAgentSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TAgentSocketObj::IsValid(pSocketObj)) + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + else + { + bPaused = pSocketObj->paused; + return TRUE; + } + + return FALSE; +} + +BOOL CTcpAgent::IsConnected(CONNID dwConnID) +{ + TAgentSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TAgentSocketObj::IsValid(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + return pSocketObj->HasConnected(); +} + +BOOL CTcpAgent::GetPendingDataLength(CONNID dwConnID, int& iPending) +{ + TAgentSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TAgentSocketObj::IsValid(pSocketObj)) + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + else + { + iPending = pSocketObj->Pending(); + return TRUE; + } + + return FALSE; +} + +DWORD CTcpAgent::GetConnectionCount() +{ + return m_bfActiveSockets.Elements(); +} + +BOOL CTcpAgent::GetAllConnectionIDs(CONNID pIDs[], DWORD& dwCount) +{ + return m_bfActiveSockets.GetAllElementIndexes(pIDs, dwCount); +} + +BOOL CTcpAgent::GetConnectPeriod(CONNID dwConnID, DWORD& dwPeriod) +{ + BOOL isOK = TRUE; + TAgentSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(TAgentSocketObj::IsValid(pSocketObj)) + dwPeriod = ::GetTimeGap32(pSocketObj->connTime); + else + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + isOK = FALSE; + } + + return isOK; +} + +BOOL CTcpAgent::GetSilencePeriod(CONNID dwConnID, DWORD& dwPeriod) +{ + if(!m_bMarkSilence) + return FALSE; + + BOOL isOK = TRUE; + TAgentSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(TAgentSocketObj::IsValid(pSocketObj)) + dwPeriod = ::GetTimeGap32(pSocketObj->activeTime); + else + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + isOK = FALSE; + } + + return isOK; +} + +BOOL CTcpAgent::Disconnect(CONNID dwConnID, BOOL bForce) +{ + TAgentSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TAgentSocketObj::IsValid(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + return m_ioDispatcher.SendCommandByFD(pSocketObj->socket, DISP_CMD_DISCONNECT, dwConnID, bForce); +} + +BOOL CTcpAgent::DisconnectLongConnections(DWORD dwPeriod, BOOL bForce) +{ + if(dwPeriod > MAX_CONNECTION_PERIOD) + return FALSE; + if(m_bfActiveSockets.Elements() == 0) + return TRUE; + + DWORD now = ::TimeGetTime(); + + TAgentSocketObjPtrPool::IndexSet indexes; + m_bfActiveSockets.CopyIndexes(indexes); + + for(auto it = indexes.begin(), end = indexes.end(); it != end; ++it) + { + CONNID connID = *it; + TAgentSocketObj* pSocketObj = FindSocketObj(connID); + + if(TAgentSocketObj::IsValid(pSocketObj) && (int)(now - pSocketObj->connTime) >= (int)dwPeriod) + Disconnect(connID, bForce); + } + + return TRUE; +} + +BOOL CTcpAgent::DisconnectSilenceConnections(DWORD dwPeriod, BOOL bForce) +{ + if(!m_bMarkSilence) + return FALSE; + if(dwPeriod > MAX_CONNECTION_PERIOD) + return FALSE; + if(m_bfActiveSockets.Elements() == 0) + return TRUE; + + DWORD now = ::TimeGetTime(); + + TAgentSocketObjPtrPool::IndexSet indexes; + m_bfActiveSockets.CopyIndexes(indexes); + + for(auto it = indexes.begin(), end = indexes.end(); it != end; ++it) + { + CONNID connID = *it; + TAgentSocketObj* pSocketObj = FindSocketObj(connID); + + if(TAgentSocketObj::IsValid(pSocketObj) && (int)(now - pSocketObj->activeTime) >= (int)dwPeriod) + Disconnect(connID, bForce); + } + + return TRUE; +} + +BOOL CTcpAgent::PauseReceive(CONNID dwConnID, BOOL bPause) +{ + TAgentSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TAgentSocketObj::IsValid(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + if(!pSocketObj->HasConnected()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + if(pSocketObj->paused == bPause) + return TRUE; + + pSocketObj->paused = bPause; + + if(!bPause) + return m_ioDispatcher.SendCommandByFD(pSocketObj->socket, DISP_CMD_UNPAUSE, pSocketObj->connID); + + return TRUE; +} + +BOOL CTcpAgent::OnBeforeProcessIo(const TDispContext* pContext, PVOID pv, UINT events) +{ + if(pv == this) + { + ReleaseGCSocketObj(FALSE); + ::ReadTimer(m_fdGCTimer); + + return FALSE; + } + + TAgentSocketObj* pSocketObj = (TAgentSocketObj*)(pv); + + if(!TAgentSocketObj::IsValid(pSocketObj)) + return FALSE; + + if(events & _EPOLL_ALL_ERROR_EVENTS) + pSocketObj->SetConnected(FALSE); + + pSocketObj->Increment(); + + if(!TAgentSocketObj::IsValid(pSocketObj)) + { + pSocketObj->Decrement(); + return FALSE; + } + + if(pSocketObj->IsConnecting()) + { + HandleConnect(pContext, pSocketObj, events); + + pSocketObj->Decrement(); + return FALSE; + } + + return TRUE; +} + +VOID CTcpAgent::OnAfterProcessIo(const TDispContext* pContext, PVOID pv, UINT events, BOOL rs) +{ + TAgentSocketObj* pSocketObj = (TAgentSocketObj*)(pv); + + if(TAgentSocketObj::IsValid(pSocketObj)) + { + ASSERT(rs && !(events & (EPOLLERR | EPOLLHUP | EPOLLRDHUP))); + + UINT evts = (pSocketObj->IsPending() ? EPOLLOUT : 0) | (pSocketObj->IsPaused() ? 0 : EPOLLIN); + m_ioDispatcher.ModFD(pSocketObj->socket, evts | EPOLLRDHUP, pSocketObj); + } + + pSocketObj->Decrement(); +} + +VOID CTcpAgent::OnCommand(const TDispContext* pContext, TDispCommand* pCmd) +{ + switch(pCmd->type) + { + case DISP_CMD_SEND: + HandleCmdSend(pContext, (CONNID)(pCmd->wParam)); + break; + case DISP_CMD_UNPAUSE: + HandleCmdUnpause(pContext, (CONNID)(pCmd->wParam)); + break; + case DISP_CMD_DISCONNECT: + HandleCmdDisconnect(pContext, (CONNID)(pCmd->wParam), (BOOL)pCmd->lParam); + break; + } +} + +VOID CTcpAgent::HandleCmdSend(const TDispContext* pContext, CONNID dwConnID) +{ + TAgentSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(TAgentSocketObj::IsValid(pSocketObj) && pSocketObj->IsPending()) + m_ioDispatcher.ProcessIo(pContext, pSocketObj, EPOLLOUT); +} + +VOID CTcpAgent::HandleCmdUnpause(const TDispContext* pContext, CONNID dwConnID) +{ + TAgentSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TAgentSocketObj::IsValid(pSocketObj)) + return; + + if(BeforeUnpause(pSocketObj)) + m_ioDispatcher.ProcessIo(pContext, pSocketObj, EPOLLIN); + else + AddFreeSocketObj(pSocketObj, SCF_ERROR, SO_RECEIVE, ENSURE_ERROR_CANCELLED); +} + +VOID CTcpAgent::HandleCmdDisconnect(const TDispContext* pContext, CONNID dwConnID, BOOL bForce) +{ + TAgentSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(TAgentSocketObj::IsValid(pSocketObj)) + m_ioDispatcher.ProcessIo(pContext, pSocketObj, EPOLLHUP); +} + +BOOL CTcpAgent::OnReadyRead(const TDispContext* pContext, PVOID pv, UINT events) +{ + return HandleReceive(pContext, (TAgentSocketObj*)pv, RETRIVE_EVENT_FLAG_H(events)); +} + +BOOL CTcpAgent::OnReadyWrite(const TDispContext* pContext, PVOID pv, UINT events) +{ + return HandleSend(pContext, (TAgentSocketObj*)pv, RETRIVE_EVENT_FLAG_H(events)); +} + +BOOL CTcpAgent::OnHungUp(const TDispContext* pContext, PVOID pv, UINT events) +{ + return HandleClose(pContext, (TAgentSocketObj*)pv, SCF_CLOSE, events); +} + +BOOL CTcpAgent::OnError(const TDispContext* pContext, PVOID pv, UINT events) +{ + return HandleClose(pContext, (TAgentSocketObj*)pv, SCF_ERROR, events); +} + +VOID CTcpAgent::OnDispatchThreadStart(THR_ID tid) +{ + OnWorkerThreadStart(tid); +} + +VOID CTcpAgent::OnDispatchThreadEnd(THR_ID tid) +{ + OnWorkerThreadEnd(tid); +} + +BOOL CTcpAgent::HandleClose(const TDispContext* pContext, TAgentSocketObj* pSocketObj, EnSocketCloseFlag enFlag, UINT events) +{ + EnSocketOperation enOperation = SO_CLOSE; + + if(events & _EPOLL_HUNGUP_EVENTS) + enOperation = SO_CLOSE; + else if(events & EPOLLIN) + enOperation = SO_RECEIVE; + else if(events & EPOLLOUT) + enOperation = SO_SEND; + + int iErrorCode = 0; + + if(enFlag == SCF_ERROR) + iErrorCode = ::SSO_GetError(pSocketObj->socket); + + AddFreeSocketObj(pSocketObj, enFlag, enOperation, iErrorCode); + + return TRUE; +} + +BOOL CTcpAgent::HandleConnect(const TDispContext* pContext, TAgentSocketObj* pSocketObj, UINT events) +{ + int code = ::SSO_GetError(pSocketObj->socket); + + if(!IS_NO_ERROR(code) || (events & _EPOLL_ERROR_EVENTS)) + { + AddFreeSocketObj(pSocketObj, SCF_ERROR, SO_CONNECT, code); + return FALSE; + } + + if((events & (_EPOLL_HUNGUP_EVENTS | _EPOLL_READ_EVENTS)) || !(events & EPOLLOUT)) + { + AddFreeSocketObj(pSocketObj, SCF_CLOSE, SO_CONNECT, SE_OK); + return FALSE; + } + + pSocketObj->SetConnected(); + + if(TRIGGER(FireConnect(pSocketObj)) == HR_ERROR) + { + AddFreeSocketObj(pSocketObj, SCF_NONE); + return FALSE; + } + + UINT evts = (pSocketObj->IsPending() ? EPOLLOUT : 0) | (pSocketObj->IsPaused() ? 0 : EPOLLIN); + + if(!m_ioDispatcher.ModFD(pSocketObj->socket, evts | EPOLLRDHUP, pSocketObj)) + { + AddFreeSocketObj(pSocketObj, SCF_ERROR, SO_CONNECT, ::WSAGetLastError()); + return FALSE; + } + + return TRUE; +} + +BOOL CTcpAgent::HandleReceive(const TDispContext* pContext, TAgentSocketObj* pSocketObj, int flag) +{ + ASSERT(TAgentSocketObj::IsValid(pSocketObj)); + + if(m_bMarkSilence) pSocketObj->activeTime = ::TimeGetTime(); + + CBufferPtr& buffer = m_rcBuffers[pContext->GetIndex()]; + + int reads = flag ? -1 : MAX_CONTINUE_READS; + + for(int i = 0; i < reads || reads < 0; i++) + { + if(pSocketObj->paused) + break; + + int rc = (int)read(pSocketObj->socket, buffer.Ptr(), buffer.Size()); + + if(rc > 0) + { + if(TRIGGER(FireReceive(pSocketObj, buffer.Ptr(), rc)) == HR_ERROR) + { + TRACE(" OnReceive() event return 'HR_ERROR', connection will be closed !", pSocketObj->connID); + + AddFreeSocketObj(pSocketObj, SCF_ERROR, SO_RECEIVE, ENSURE_ERROR_CANCELLED); + return FALSE; + } + } + else if(rc == 0) + { + AddFreeSocketObj(pSocketObj, SCF_CLOSE, SO_RECEIVE, SE_OK); + return FALSE; + } + else + { + ASSERT(rc == SOCKET_ERROR); + + int code = ::WSAGetLastError(); + + if(code == ERROR_WOULDBLOCK) + break; + + AddFreeSocketObj(pSocketObj, SCF_ERROR, SO_RECEIVE, code); + return FALSE; + } + } + + return TRUE; +} + +BOOL CTcpAgent::HandleSend(const TDispContext* pContext, TAgentSocketObj* pSocketObj, int flag) +{ + ASSERT(TAgentSocketObj::IsValid(pSocketObj)); + + if(!pSocketObj->IsPending()) + return TRUE; + + BOOL bBlocked = FALSE; + int writes = flag ? -1 : MAX_CONTINUE_WRITES; + + TBufferObjList& sndBuff = pSocketObj->sndBuff; + TItemPtr itPtr(sndBuff); + + for(int i = 0; i < writes || writes < 0; i++) + { + { + CReentrantCriSecLock locallock(pSocketObj->csSend); + itPtr = sndBuff.PopFront(); + } + + if(!itPtr.IsValid()) + break; + + ASSERT(!itPtr->IsEmpty()); + + if(!SendItem(pSocketObj, itPtr, bBlocked)) + return FALSE; + + if(bBlocked) + { + ASSERT(!itPtr->IsEmpty()); + + CReentrantCriSecLock locallock(pSocketObj->csSend); + sndBuff.PushFront(itPtr.Detach()); + + break; + } + } + + return TRUE; +} + +BOOL CTcpAgent::SendItem(TAgentSocketObj* pSocketObj, TItem* pItem, BOOL& bBlocked) +{ + while(!pItem->IsEmpty()) + { + int rc = (int)write(pSocketObj->socket, pItem->Ptr(), pItem->Size()); + + if(rc > 0) + { + if(TRIGGER(FireSend(pSocketObj, pItem->Ptr(), rc)) == HR_ERROR) + { + TRACE(" OnSend() event should not return 'HR_ERROR' !!", pSocketObj->connID); + ASSERT(FALSE); + } + + pItem->Reduce(rc); + } + else if(rc == SOCKET_ERROR) + { + int code = ::WSAGetLastError(); + + if(code == ERROR_WOULDBLOCK) + { + bBlocked = TRUE; + break; + } + else + { + AddFreeSocketObj(pSocketObj, SCF_ERROR, SO_SEND, code); + return FALSE; + } + } + else + ASSERT(FALSE); + } + + return TRUE; +} + +BOOL CTcpAgent::Send(CONNID dwConnID, const BYTE* pBuffer, int iLength, int iOffset) +{ + ASSERT(pBuffer && iLength > 0); + + if(iOffset != 0) pBuffer += iOffset; + + WSABUF buffer; + buffer.len = iLength; + buffer.buf = (BYTE*)pBuffer; + + return SendPackets(dwConnID, &buffer, 1); +} + +BOOL CTcpAgent::DoSendPackets(CONNID dwConnID, const WSABUF pBuffers[], int iCount) +{ + ASSERT(pBuffers && iCount > 0); + + TAgentSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TAgentSocketObj::IsValid(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + return DoSendPackets(pSocketObj, pBuffers, iCount); +} + +BOOL CTcpAgent::DoSendPackets(TAgentSocketObj* pSocketObj, const WSABUF pBuffers[], int iCount) +{ + ASSERT(pSocketObj && pBuffers && iCount > 0); + + int result = NO_ERROR; + + if(!pSocketObj->HasConnected()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + if(pBuffers && iCount > 0) + { + CLocalSafeCounter localcounter(*pSocketObj); + CReentrantCriSecLock locallock(pSocketObj->csSend); + + if(TAgentSocketObj::IsValid(pSocketObj)) + result = SendInternal(pSocketObj, pBuffers, iCount); + else + result = ERROR_OBJECT_NOT_FOUND; + } + else + result = ERROR_INVALID_PARAMETER; + + if(result != NO_ERROR) + ::SetLastError(result); + + return (result == NO_ERROR); +} + +int CTcpAgent::SendInternal(TAgentSocketObj* pSocketObj, const WSABUF pBuffers[], int iCount) +{ + BOOL bPending = pSocketObj->IsPending(); + + for(int i = 0; i < iCount; i++) + { + int iBufLen = pBuffers[i].len; + + if(iBufLen > 0) + { + BYTE* pBuffer = (BYTE*)pBuffers[i].buf; + ASSERT(pBuffer); + + pSocketObj->sndBuff.Cat(pBuffer, iBufLen); + ASSERT(pSocketObj->sndBuff.Length() > 0); + } + } + + if(!bPending && pSocketObj->IsPending()) + { + if(!m_ioDispatcher.SendCommandByFD(pSocketObj->socket, DISP_CMD_SEND, pSocketObj->connID)) + return ::GetLastError(); + } + + return NO_ERROR; +} + +BOOL CTcpAgent::SendSmallFile(CONNID dwConnID, LPCTSTR lpszFileName, const LPWSABUF pHead, const LPWSABUF pTail) +{ + CFile file; + CFileMapping fmap; + WSABUF szBuf[3]; + + HRESULT hr = ::MakeSmallFilePackage(lpszFileName, file, fmap, szBuf, pHead, pTail); + + if(FAILED(hr)) + { + ::SetLastError(hr); + return FALSE; + } + + return SendPackets(dwConnID, szBuf, 3); +} diff --git a/TcpAgent.h b/TcpAgent.h new file mode 100644 index 0000000..5acbffc --- /dev/null +++ b/TcpAgent.h @@ -0,0 +1,306 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "SocketHelper.h" +#include "./common/GeneralHelper.h" +#include "./common/IODispatcher.h" + +class CTcpAgent : public ITcpAgent, private CIOHandler +{ +public: + virtual BOOL Start (LPCTSTR lpszBindAddress = nullptr, BOOL bAsyncConnect = TRUE); + virtual BOOL Stop (); + virtual BOOL Connect(LPCTSTR lpszRemoteAddress, USHORT usPort, CONNID* pdwConnID = nullptr, PVOID pExtra = nullptr, USHORT usLocalPort = 0, LPCTSTR lpszLocalAddress = nullptr); + virtual BOOL Send (CONNID dwConnID, const BYTE* pBuffer, int iLength, int iOffset = 0); + virtual BOOL SendSmallFile (CONNID dwConnID, LPCTSTR lpszFileName, const LPWSABUF pHead = nullptr, const LPWSABUF pTail = nullptr); + virtual BOOL SendPackets (CONNID dwConnID, const WSABUF pBuffers[], int iCount) {return DoSendPackets(dwConnID, pBuffers, iCount);} + virtual BOOL PauseReceive (CONNID dwConnID, BOOL bPause = TRUE); + virtual BOOL Wait (DWORD dwMilliseconds = INFINITE) {return m_evWait.WaitFor(dwMilliseconds, WAIT_FOR_STOP_PREDICATE);} + virtual BOOL HasStarted () {return m_enState == SS_STARTED || m_enState == SS_STARTING;} + virtual EnServiceState GetState () {return m_enState;} + virtual BOOL Disconnect (CONNID dwConnID, BOOL bForce = TRUE); + virtual BOOL DisconnectLongConnections (DWORD dwPeriod, BOOL bForce = TRUE); + virtual BOOL DisconnectSilenceConnections(DWORD dwPeriod, BOOL bForce = TRUE); + virtual BOOL GetLocalAddress (CONNID dwConnID, TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort); + virtual BOOL GetRemoteAddress (CONNID dwConnID, TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort); + virtual BOOL GetRemoteHost (CONNID dwConnID, TCHAR lpszHost[], int& iHostLen, USHORT& usPort); + + virtual BOOL IsConnected (CONNID dwConnID); + virtual BOOL IsPauseReceive (CONNID dwConnID, BOOL& bPaused); + virtual BOOL GetPendingDataLength (CONNID dwConnID, int& iPending); + virtual DWORD GetConnectionCount (); + virtual BOOL GetAllConnectionIDs (CONNID pIDs[], DWORD& dwCount); + virtual BOOL GetConnectPeriod (CONNID dwConnID, DWORD& dwPeriod); + virtual BOOL GetSilencePeriod (CONNID dwConnID, DWORD& dwPeriod); + virtual EnSocketError GetLastError () {return m_enLastError;} + virtual LPCTSTR GetLastErrorDesc () {return ::GetSocketErrorDesc(m_enLastError);} + +#ifdef _SSL_SUPPORT + virtual BOOL SetupSSLContext (int iVerifyMode = SSL_VM_NONE, LPCTSTR lpszPemCertFile = nullptr, LPCTSTR lpszPemKeyFile = nullptr, LPCTSTR lpszKeyPassword = nullptr, LPCTSTR lpszCAPemCertFileOrPath = nullptr) {return FALSE;} + virtual BOOL SetupSSLContextByMemory(int iVerifyMode = SSL_VM_NONE, LPCSTR lpszPemCert = nullptr, LPCSTR lpszPemKey = nullptr, LPCSTR lpszKeyPassword = nullptr, LPCSTR lpszCAPemCert = nullptr) {return FALSE;} + virtual void CleanupSSLContext () {} + + virtual BOOL StartSSLHandShake (CONNID dwConnID) {return FALSE;} + virtual void SetSSLAutoHandShake(BOOL bAutoHandShake) {} + virtual BOOL IsSSLAutoHandShake () {return FALSE;} + virtual void SetSSLCipherList (LPCTSTR lpszCipherList){} + virtual LPCTSTR GetSSLCipherList() {return nullptr;} + virtual BOOL GetSSLSessionInfo(CONNID dwConnID, EnSSLSessionInfo enInfo, LPVOID* lppInfo) {return FALSE;} + +protected: + virtual BOOL StartSSLHandShake (TAgentSocketObj* pSocketObj) {return FALSE;} +#endif + +private: + virtual BOOL OnBeforeProcessIo(const TDispContext* pContext, PVOID pv, UINT events) override; + virtual VOID OnAfterProcessIo(const TDispContext* pContext, PVOID pv, UINT events, BOOL rs) override; + virtual VOID OnCommand(const TDispContext* pContext, TDispCommand* pCmd) override; + virtual BOOL OnReadyRead(const TDispContext* pContext, PVOID pv, UINT events) override; + virtual BOOL OnReadyWrite(const TDispContext* pContext, PVOID pv, UINT events) override; + virtual BOOL OnHungUp(const TDispContext* pContext, PVOID pv, UINT events) override; + virtual BOOL OnError(const TDispContext* pContext, PVOID pv, UINT events) override; + virtual VOID OnDispatchThreadStart(THR_ID tid) override; + virtual VOID OnDispatchThreadEnd(THR_ID tid) override; + +public: + virtual BOOL IsSecure () {return FALSE;} + + virtual BOOL SetConnectionExtra(CONNID dwConnID, PVOID pExtra); + virtual BOOL GetConnectionExtra(CONNID dwConnID, PVOID* ppExtra); + + virtual void SetReuseAddressPolicy (EnReuseAddressPolicy enReusePolicy) {ENSURE_HAS_STOPPED(); m_enReusePolicy = enReusePolicy;} + virtual void SetSendPolicy (EnSendPolicy enSendPolicy) {ENSURE_HAS_STOPPED(); ASSERT(m_enSendPolicy == enSendPolicy);} + virtual void SetOnSendSyncPolicy (EnOnSendSyncPolicy enOnSendSyncPolicy) {ENSURE_HAS_STOPPED(); ASSERT(m_enOnSendSyncPolicy == enOnSendSyncPolicy);} + virtual void SetSyncConnectTimeout (DWORD dwSyncConnectTimeout) {ENSURE_HAS_STOPPED(); m_dwSyncConnectTimeout = dwSyncConnectTimeout;} + virtual void SetMaxConnectionCount (DWORD dwMaxConnectionCount) {ENSURE_HAS_STOPPED(); m_dwMaxConnectionCount = dwMaxConnectionCount;} + virtual void SetWorkerThreadCount (DWORD dwWorkerThreadCount) {ENSURE_HAS_STOPPED(); m_dwWorkerThreadCount = dwWorkerThreadCount;} + virtual void SetSocketBufferSize (DWORD dwSocketBufferSize) {ENSURE_HAS_STOPPED(); m_dwSocketBufferSize = dwSocketBufferSize;} + virtual void SetFreeSocketObjLockTime (DWORD dwFreeSocketObjLockTime) {ENSURE_HAS_STOPPED(); m_dwFreeSocketObjLockTime = dwFreeSocketObjLockTime;} + virtual void SetFreeSocketObjPool (DWORD dwFreeSocketObjPool) {ENSURE_HAS_STOPPED(); m_dwFreeSocketObjPool = dwFreeSocketObjPool;} + virtual void SetFreeBufferObjPool (DWORD dwFreeBufferObjPool) {ENSURE_HAS_STOPPED(); m_dwFreeBufferObjPool = dwFreeBufferObjPool;} + virtual void SetFreeSocketObjHold (DWORD dwFreeSocketObjHold) {ENSURE_HAS_STOPPED(); m_dwFreeSocketObjHold = dwFreeSocketObjHold;} + virtual void SetFreeBufferObjHold (DWORD dwFreeBufferObjHold) {ENSURE_HAS_STOPPED(); m_dwFreeBufferObjHold = dwFreeBufferObjHold;} + virtual void SetKeepAliveTime (DWORD dwKeepAliveTime) {ENSURE_HAS_STOPPED(); m_dwKeepAliveTime = dwKeepAliveTime;} + virtual void SetKeepAliveInterval (DWORD dwKeepAliveInterval) {ENSURE_HAS_STOPPED(); m_dwKeepAliveInterval = dwKeepAliveInterval;} + virtual void SetMarkSilence (BOOL bMarkSilence) {ENSURE_HAS_STOPPED(); m_bMarkSilence = bMarkSilence;} + virtual void SetNoDelay (BOOL bNoDelay) {ENSURE_HAS_STOPPED(); m_bNoDelay = bNoDelay;} + + virtual EnReuseAddressPolicy GetReuseAddressPolicy () {return m_enReusePolicy;} + virtual EnSendPolicy GetSendPolicy () {return m_enSendPolicy;} + virtual EnOnSendSyncPolicy GetOnSendSyncPolicy () {return m_enOnSendSyncPolicy;} + virtual DWORD GetSyncConnectTimeout () {return m_dwSyncConnectTimeout;} + virtual DWORD GetMaxConnectionCount () {return m_dwMaxConnectionCount;} + virtual DWORD GetWorkerThreadCount () {return m_dwWorkerThreadCount;} + virtual DWORD GetSocketBufferSize () {return m_dwSocketBufferSize;} + virtual DWORD GetFreeSocketObjLockTime () {return m_dwFreeSocketObjLockTime;} + virtual DWORD GetFreeSocketObjPool () {return m_dwFreeSocketObjPool;} + virtual DWORD GetFreeBufferObjPool () {return m_dwFreeBufferObjPool;} + virtual DWORD GetFreeSocketObjHold () {return m_dwFreeSocketObjHold;} + virtual DWORD GetFreeBufferObjHold () {return m_dwFreeBufferObjHold;} + virtual DWORD GetKeepAliveTime () {return m_dwKeepAliveTime;} + virtual DWORD GetKeepAliveInterval () {return m_dwKeepAliveInterval;} + virtual BOOL IsMarkSilence () {return m_bMarkSilence;} + virtual BOOL IsNoDelay () {return m_bNoDelay;} + +protected: + virtual EnHandleResult FirePrepareConnect(CONNID dwConnID, SOCKET socket) + {return DoFirePrepareConnect(dwConnID, socket);} + virtual EnHandleResult FireConnect(TAgentSocketObj* pSocketObj) + { + EnHandleResult rs = DoFireConnect(pSocketObj); + if(rs != HR_ERROR) rs = FireHandShake(pSocketObj); + return rs; + } + virtual EnHandleResult FireHandShake(TAgentSocketObj* pSocketObj) + {return DoFireHandShake(pSocketObj);} + virtual EnHandleResult FireReceive(TAgentSocketObj* pSocketObj, const BYTE* pData, int iLength) + {return DoFireReceive(pSocketObj, pData, iLength);} + virtual EnHandleResult FireReceive(TAgentSocketObj* pSocketObj, int iLength) + {return DoFireReceive(pSocketObj, iLength);} + virtual EnHandleResult FireSend(TAgentSocketObj* pSocketObj, const BYTE* pData, int iLength) + {return DoFireSend(pSocketObj, pData, iLength);} + virtual EnHandleResult FireClose(TAgentSocketObj* pSocketObj, EnSocketOperation enOperation, int iErrorCode) + {return DoFireClose(pSocketObj, enOperation, iErrorCode);} + virtual EnHandleResult FireShutdown() + {return DoFireShutdown();} + + virtual EnHandleResult DoFirePrepareConnect(CONNID dwConnID, SOCKET socket) + {return m_pListener->OnPrepareConnect(this, dwConnID, socket);} + virtual EnHandleResult DoFireConnect(TAgentSocketObj* pSocketObj) + {return m_pListener->OnConnect(this, pSocketObj->connID);} + virtual EnHandleResult DoFireHandShake(TAgentSocketObj* pSocketObj) + {return m_pListener->OnHandShake(this, pSocketObj->connID);} + virtual EnHandleResult DoFireReceive(TAgentSocketObj* pSocketObj, const BYTE* pData, int iLength) + {return m_pListener->OnReceive(this, pSocketObj->connID, pData, iLength);} + virtual EnHandleResult DoFireReceive(TAgentSocketObj* pSocketObj, int iLength) + {return m_pListener->OnReceive(this, pSocketObj->connID, iLength);} + virtual EnHandleResult DoFireSend(TAgentSocketObj* pSocketObj, const BYTE* pData, int iLength) + {return m_pListener->OnSend(this, pSocketObj->connID, pData, iLength);} + virtual EnHandleResult DoFireClose(TAgentSocketObj* pSocketObj, EnSocketOperation enOperation, int iErrorCode) + {return m_pListener->OnClose(this, pSocketObj->connID, enOperation, iErrorCode);} + virtual EnHandleResult DoFireShutdown() + {return m_pListener->OnShutdown(this);} + + void SetLastError(EnSocketError code, LPCSTR func, int ec); + virtual BOOL CheckParams(); + virtual void PrepareStart(); + virtual void Reset(); + + virtual BOOL BeforeUnpause(TAgentSocketObj* pSocketObj) {return TRUE;} + + virtual void OnWorkerThreadStart(THR_ID tid) {} + virtual void OnWorkerThreadEnd(THR_ID tid) {} + + virtual void ReleaseGCSocketObj(BOOL bForce = FALSE); + + BOOL DoSendPackets(CONNID dwConnID, const WSABUF pBuffers[], int iCount); + BOOL DoSendPackets(TAgentSocketObj* pSocketObj, const WSABUF pBuffers[], int iCount); + TAgentSocketObj* FindSocketObj(CONNID dwConnID); + BOOL GetRemoteHost(CONNID dwConnID, LPCSTR* lpszHost, USHORT* pusPort = nullptr); + +protected: + BOOL SetConnectionExtra(TAgentSocketObj* pSocketObj, PVOID pExtra); + BOOL GetConnectionExtra(TAgentSocketObj* pSocketObj, PVOID* ppExtra); + BOOL SetConnectionReserved(CONNID dwConnID, PVOID pReserved); + BOOL GetConnectionReserved(CONNID dwConnID, PVOID* ppReserved); + BOOL SetConnectionReserved(TAgentSocketObj* pSocketObj, PVOID pReserved); + BOOL GetConnectionReserved(TAgentSocketObj* pSocketObj, PVOID* ppReserved); + BOOL SetConnectionReserved2(CONNID dwConnID, PVOID pReserved2); + BOOL GetConnectionReserved2(CONNID dwConnID, PVOID* ppReserved2); + BOOL SetConnectionReserved2(TAgentSocketObj* pSocketObj, PVOID pReserved2); + BOOL GetConnectionReserved2(TAgentSocketObj* pSocketObj, PVOID* ppReserved2); + +private: + BOOL CheckStarting(); + BOOL CheckStoping(); + BOOL ParseBindAddress(LPCTSTR lpszBindAddress); + BOOL CreateWorkerThreads(); + + void DisconnectClientSocket(); + void WaitForClientSocketClose(); + void ReleaseClientSocket(); + void ReleaseFreeSocket(); + void WaitForWorkerThreadEnd(); + + TAgentSocketObj* GetFreeSocketObj(CONNID dwConnID, SOCKET soClient); + TAgentSocketObj* CreateSocketObj(); + void AddFreeSocketObj (TAgentSocketObj* pSocketObj, EnSocketCloseFlag enFlag = SCF_NONE, EnSocketOperation enOperation = SO_UNKNOWN, int iErrorCode = 0); + void DeleteSocketObj (TAgentSocketObj* pSocketObj); + BOOL InvalidSocketObj (TAgentSocketObj* pSocketObj); + void AddClientSocketObj (CONNID dwConnID, TAgentSocketObj* pSocketObj, const HP_SOCKADDR& remoteAddr, LPCTSTR lpszRemoteHostName, PVOID pExtra); + void CloseClientSocketObj(TAgentSocketObj* pSocketObj, EnSocketCloseFlag enFlag = SCF_NONE, EnSocketOperation enOperation = SO_UNKNOWN, int iErrorCode = 0, int iShutdownFlag = SHUT_WR); + +private: + int CreateClientSocket(LPCTSTR lpszRemoteAddress, USHORT usPort, LPCTSTR lpszLocalAddress, USHORT usLocalPort, SOCKET& soClient, HP_SOCKADDR& addr); + int PrepareConnect (CONNID& dwConnID, SOCKET soClient); + int ConnectToServer (CONNID dwConnID, LPCTSTR lpszRemoteHostName, SOCKET& soClient, const HP_SOCKADDR& addr, PVOID pExtra); + + VOID HandleCmdSend (const TDispContext* pContext, CONNID dwConnID); + VOID HandleCmdUnpause (const TDispContext* pContext, CONNID dwConnID); + VOID HandleCmdDisconnect(const TDispContext* pContext, CONNID dwConnID, BOOL bForce); + BOOL HandleConnect (const TDispContext* pContext, TAgentSocketObj* pSocketObj, UINT events); + BOOL HandleReceive (const TDispContext* pContext, TAgentSocketObj* pSocketObj, int flag); + BOOL HandleSend (const TDispContext* pContext, TAgentSocketObj* pSocketObj, int flag); + BOOL HandleClose (const TDispContext* pContext, TAgentSocketObj* pSocketObj, EnSocketCloseFlag enFlag, UINT events); + + int SendInternal (TAgentSocketObj* pSocketObj, const WSABUF pBuffers[], int iCount); + BOOL SendItem (TAgentSocketObj* pSocketObj, TItem* pItem, BOOL& bBlocked); + +public: + CTcpAgent(ITcpAgentListener* pListener) + : m_pListener (pListener) + , m_enLastError (SE_OK) + , m_enState (SS_STOPPED) + , m_fdGCTimer (INVALID_FD) + , m_bAsyncConnect (TRUE) + , m_enReusePolicy (RAP_ADDR_ONLY) + , m_enSendPolicy (SP_PACK) + , m_enOnSendSyncPolicy (OSSP_RECEIVE) + , m_dwSyncConnectTimeout (DEFAULT_SYNC_CONNECT_TIMEOUT) + , m_dwMaxConnectionCount (DEFAULT_CONNECTION_COUNT) + , m_dwWorkerThreadCount (DEFAULT_WORKER_THREAD_COUNT) + , m_dwSocketBufferSize (DEFAULT_TCP_SOCKET_BUFFER_SIZE) + , m_dwFreeSocketObjLockTime (DEFAULT_FREE_SOCKETOBJ_LOCK_TIME) + , m_dwFreeSocketObjPool (DEFAULT_FREE_SOCKETOBJ_POOL) + , m_dwFreeBufferObjPool (DEFAULT_FREE_BUFFEROBJ_POOL) + , m_dwFreeSocketObjHold (DEFAULT_FREE_SOCKETOBJ_HOLD) + , m_dwFreeBufferObjHold (DEFAULT_FREE_BUFFEROBJ_HOLD) + , m_dwKeepAliveTime (DEFALUT_TCP_KEEPALIVE_TIME) + , m_dwKeepAliveInterval (DEFALUT_TCP_KEEPALIVE_INTERVAL) + , m_bMarkSilence (TRUE) + , m_bNoDelay (FALSE) + , m_soAddr (AF_UNSPEC, TRUE) + { + ASSERT(m_pListener); + } + + virtual ~CTcpAgent() + { + ENSURE_STOP(); + } + +private: + EnReuseAddressPolicy m_enReusePolicy; + EnSendPolicy m_enSendPolicy; + EnOnSendSyncPolicy m_enOnSendSyncPolicy; + DWORD m_dwSyncConnectTimeout; + DWORD m_dwMaxConnectionCount; + DWORD m_dwWorkerThreadCount; + DWORD m_dwSocketBufferSize; + DWORD m_dwFreeSocketObjLockTime; + DWORD m_dwFreeSocketObjPool; + DWORD m_dwFreeBufferObjPool; + DWORD m_dwFreeSocketObjHold; + DWORD m_dwFreeBufferObjHold; + DWORD m_dwKeepAliveTime; + DWORD m_dwKeepAliveInterval; + BOOL m_bMarkSilence; + BOOL m_bNoDelay; + +private: + CSEM m_evWait; + + ITcpAgentListener* m_pListener; + BOOL m_bAsyncConnect; + EnServiceState m_enState; + EnSocketError m_enLastError; + HP_SOCKADDR m_soAddr; + + CReceiveBuffersPtr m_rcBuffers; + + CPrivateHeap m_phSocket; + CBufferObjPool m_bfObjPool; + + CSpinGuard m_csState; + + FD m_fdGCTimer; + + TAgentSocketObjPtrPool m_bfActiveSockets; + + TAgentSocketObjPtrList m_lsFreeSocket; + TAgentSocketObjPtrQueue m_lsGCSocket; + + CIODispatcher m_ioDispatcher; +}; diff --git a/TcpClient.cpp b/TcpClient.cpp new file mode 100644 index 0000000..c24edb6 --- /dev/null +++ b/TcpClient.cpp @@ -0,0 +1,723 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TcpClient.h" + +#include "./common/FileHelper.h" + +BOOL CTcpClient::Start(LPCTSTR lpszRemoteAddress, USHORT usPort, BOOL bAsyncConnect, LPCTSTR lpszBindAddress, USHORT usLocalPort) +{ + if(!CheckParams() || !CheckStarting()) + return FALSE; + + PrepareStart(); + m_ccContext.Reset(); + + BOOL isOK = FALSE; + + HP_SOCKADDR addrRemote, addrBind; + + if(CreateClientSocket(lpszRemoteAddress, addrRemote, usPort, lpszBindAddress, addrBind)) + { + if(BindClientSocket(addrBind, addrRemote, usLocalPort)) + { + if(TRIGGER(FirePrepareConnect(m_soClient)) != HR_ERROR) + { + if(ConnectToServer(addrRemote, bAsyncConnect)) + { + if(CreateWorkerThread()) + isOK = TRUE; + else + SetLastError(SE_WORKER_THREAD_CREATE, __FUNCTION__, ERROR_CREATE_FAILED); + } + else + SetLastError(SE_CONNECT_SERVER, __FUNCTION__, ::WSAGetLastError()); + } + else + SetLastError(SE_SOCKET_PREPARE, __FUNCTION__, ENSURE_ERROR_CANCELLED); + } + else + SetLastError(SE_SOCKET_BIND, __FUNCTION__, ::WSAGetLastError()); + } + else + SetLastError(SE_SOCKET_CREATE, __FUNCTION__, ::WSAGetLastError()); + + if(!isOK) + { + m_ccContext.Reset(FALSE); + EXECUTE_RESTORE_ERROR(Stop()); + } + + return isOK; +} + +BOOL CTcpClient::CheckParams() +{ + if (((int)m_dwSyncConnectTimeout > 0) && + ((int)m_dwSocketBufferSize > 0) && + ((int)m_dwFreeBufferPoolSize >= 0) && + ((int)m_dwFreeBufferPoolHold >= 0) && + ((int)m_dwKeepAliveTime >= 1000 || m_dwKeepAliveTime == 0) && + ((int)m_dwKeepAliveInterval >= 1000 || m_dwKeepAliveInterval == 0) ) + return TRUE; + + SetLastError(SE_INVALID_PARAM, __FUNCTION__, ERROR_INVALID_PARAMETER); + return FALSE; +} + +void CTcpClient::PrepareStart() +{ + m_itPool.SetItemCapacity(m_dwSocketBufferSize); + m_itPool.SetPoolSize(m_dwFreeBufferPoolSize); + m_itPool.SetPoolHold(m_dwFreeBufferPoolHold); + + m_itPool.Prepare(); +} + +BOOL CTcpClient::CheckStarting() +{ + CSpinLock locallock(m_csState); + + if(m_enState == SS_STOPPED) + m_enState = SS_STARTING; + else + { + SetLastError(SE_ILLEGAL_STATE, __FUNCTION__, ERROR_INVALID_STATE); + return FALSE; + } + + return TRUE; +} + +BOOL CTcpClient::CheckStoping() +{ + if(m_enState != SS_STOPPED) + { + CSpinLock locallock(m_csState); + + if(HasStarted()) + { + m_enState = SS_STOPPING; + return TRUE; + } + } + + SetLastError(SE_ILLEGAL_STATE, __FUNCTION__, ERROR_INVALID_STATE); + + return FALSE; +} + +BOOL CTcpClient::CreateClientSocket(LPCTSTR lpszRemoteAddress, HP_SOCKADDR& addrRemote, USHORT usPort, LPCTSTR lpszBindAddress, HP_SOCKADDR& addrBind) +{ + HP_SCOPE_HOST host(lpszRemoteAddress); + + if(!::GetSockAddrByHostName(host.addr, usPort, addrRemote)) + return FALSE; + + if(::IsStrNotEmpty(lpszBindAddress)) + { + if(!::sockaddr_A_2_IN(lpszBindAddress, 0, addrBind)) + return FALSE; + + if(addrRemote.family != addrBind.family) + { + ::WSASetLastError(ERROR_AFNOSUPPORT); + return FALSE; + } + } + + m_soClient = socket(addrRemote.family, SOCK_STREAM, IPPROTO_TCP); + + if(m_soClient == INVALID_SOCKET) + return FALSE; + + BOOL bOnOff = (m_dwKeepAliveTime > 0 && m_dwKeepAliveInterval > 0); + VERIFY(::SSO_KeepAliveVals(m_soClient, bOnOff, m_dwKeepAliveTime, m_dwKeepAliveInterval) == NO_ERROR); + VERIFY(::SSO_ReuseAddress(m_soClient, m_enReusePolicy) == NO_ERROR); + VERIFY(::SSO_NoDelay(m_soClient, m_bNoDelay) == NO_ERROR); + + SetRemoteHost(host.name, usPort); + + return TRUE; +} + +BOOL CTcpClient::BindClientSocket(const HP_SOCKADDR& addrBind, const HP_SOCKADDR& addrRemote, USHORT usLocalPort) +{ + if(addrBind.IsSpecified() && usLocalPort == 0) + { + if(::bind(m_soClient, addrBind.Addr(), addrBind.AddrSize()) == SOCKET_ERROR) + return FALSE; + } + else if(usLocalPort != 0) + { + HP_SOCKADDR realBindAddr = addrBind.IsSpecified() ? addrBind : HP_SOCKADDR::AnyAddr(addrRemote.family); + + realBindAddr.SetPort(usLocalPort); + + if(::bind(m_soClient, realBindAddr.Addr(), realBindAddr.AddrSize()) == SOCKET_ERROR) + return FALSE; + } + + m_dwConnID = ::GenerateConnectionID(); + + return TRUE; +} + +BOOL CTcpClient::ConnectToServer(const HP_SOCKADDR& addrRemote, BOOL bAsyncConnect) +{ + BOOL isOK = FALSE; + + VERIFY(::fcntl_SETFL(m_soClient, O_NOATIME | O_NONBLOCK | O_CLOEXEC)); + + int rc = ::connect(m_soClient, addrRemote.Addr(), addrRemote.AddrSize()); + + if(IS_NO_ERROR(rc) || IS_IO_PENDING_ERROR()) + { + if(bAsyncConnect) + { + m_nEvents = POLLOUT; + isOK = TRUE; + } + else + { + if(IS_HAS_ERROR(rc)) + rc = ::WaitForSocketWrite(m_soClient, m_dwSyncConnectTimeout); + + if(!IS_NO_ERROR(rc)) + ::WSASetLastError(rc); + else + { + SetConnected(); + + if(TRIGGER(FireConnect()) == HR_ERROR) + ::WSASetLastError(ENSURE_ERROR_CANCELLED); + else + { + m_nEvents = (SHORT)((m_lsSend.IsEmpty() ? 0 : POLLOUT) | (m_bPaused ? 0 : POLLIN) | POLLRDHUP); + isOK = TRUE; + } + + } + } + } + + return isOK; +} + +BOOL CTcpClient::Stop() +{ + if(!CheckStoping()) + return FALSE; + + WaitForWorkerThreadEnd(); + + SetConnected(FALSE); + + if(m_ccContext.bFireOnClose) + FireClose(m_ccContext.enOperation, m_ccContext.iErrorCode); + + if(m_soClient != INVALID_SOCKET) + { + shutdown(m_soClient, SHUT_WR); + closesocket(m_soClient); + + m_soClient = INVALID_SOCKET; + } + + Reset(); + + return TRUE; +} + +void CTcpClient::Reset() +{ + CCriSecLock locallock(m_csSend); + + m_evSend.Reset(); + m_evRecv.Reset(); + m_evStop.Reset(); + + m_lsSend.Clear(); + m_itPool.Clear(); + m_rcBuffer.Free(); + + m_strHost.Empty(); + + m_usPort = 0; + m_nEvents = 0; + m_bPaused = FALSE; + m_enState = SS_STOPPED; + + m_evWait.SyncNotifyAll(); +} + +void CTcpClient::WaitForWorkerThreadEnd() +{ + if(!m_thWorker.IsRunning()) + return; + + if(m_thWorker.IsInMyThread()) + m_thWorker.Detach(); + else + { + m_evStop.Set(); + m_thWorker.Join(); + } +} + +BOOL CTcpClient::CreateWorkerThread() +{ + return m_thWorker.Start(this, &CTcpClient::WorkerThreadProc); +} + +UINT WINAPI CTcpClient::WorkerThreadProc(LPVOID pv) +{ + ::SetCurrentWorkerThreadName(); + + TRACE("---------------> Client Worker Thread 0x%08X started <---------------", SELF_THREAD_ID); + + OnWorkerThreadStart(SELF_THREAD_ID); + + BOOL bCallStop = TRUE; + pollfd pfds[] = { {m_soClient, m_nEvents}, + {m_evSend.GetFD(), POLLIN}, + {m_evRecv.GetFD(), POLLIN}, + {m_evStop.GetFD(), POLLIN} }; + int size = ARRAY_SIZE(pfds); + + m_rcBuffer.Malloc(m_dwSocketBufferSize); + + while(HasStarted()) + { + int rs = (int)::PollForMultipleObjects(pfds, size); + ASSERT(rs > TIMEOUT); + + if(rs <= 0) + { + m_ccContext.Reset(TRUE, SO_UNKNOWN, ::WSAGetLastError()); + goto EXIT_WORKER_THREAD; + } + + for(int i = 0; i < size; i++) + { + if((1 << i) & rs) + { + SHORT revents = pfds[i].revents; + + if(i == 0) + { + if(!ProcessNetworkEvent(revents)) + goto EXIT_WORKER_THREAD; + } + else if(i == 1) + { + m_evSend.Reset(); + + if(!SendData()) + goto EXIT_WORKER_THREAD; + } + else if(i == 2) + { + m_evRecv.Reset(); + + if(!BeforeUnpause()) + goto EXIT_WORKER_THREAD; + + if(!ReadData()) + goto EXIT_WORKER_THREAD; + } + else if(i == 3) + { + m_evStop.Reset(); + + bCallStop = FALSE; + goto EXIT_WORKER_THREAD; + } + else + VERIFY(FALSE); + } + } + + m_nEvents = (SHORT)((m_lsSend.IsEmpty() ? 0 : POLLOUT) | (m_bPaused ? 0 : POLLIN) | POLLRDHUP); + pfds[0].events = m_nEvents; + } + +EXIT_WORKER_THREAD: + + OnWorkerThreadEnd(SELF_THREAD_ID); + + if(bCallStop && HasStarted()) + Stop(); + + TRACE("---------------> Client Worker Thread 0x%08X stoped <---------------", SELF_THREAD_ID); + + return 0; +} + +BOOL CTcpClient::ProcessNetworkEvent(SHORT events) +{ + BOOL bContinue = TRUE; + + if(bContinue && events & POLLERR) + bContinue = HandleClose(events); + + if(bContinue && !IsConnected()) + bContinue = HandleConnect(events); + + if(bContinue && events & POLLIN) + bContinue = HandleRead(events); + + if(bContinue && events & POLLOUT) + bContinue = HandleWrite(events); + + if(bContinue && events & _POLL_HUNGUP_EVENTS) + bContinue = HandleClose(events); + + return bContinue; +} + +BOOL CTcpClient::HandleConnect(SHORT events) +{ + ASSERT(events & POLLOUT); + + int code = ::SSO_GetError(m_soClient); + + if(!IS_NO_ERROR(code) || (events & _POLL_ERROR_EVENTS)) + { + m_ccContext.Reset(TRUE, SO_CONNECT, code); + return FALSE; + } + + if(events & _POLL_HUNGUP_EVENTS) + { + m_ccContext.Reset(TRUE, SO_CONNECT, NO_ERROR); + return FALSE; + } + + SetConnected(); + + if(TRIGGER(FireConnect()) == HR_ERROR) + { + m_ccContext.Reset(FALSE); + return FALSE; + } + + return TRUE; +} + +BOOL CTcpClient::HandleClose(SHORT events) +{ + EnSocketOperation enOperation = SO_CLOSE; + + if(events & _POLL_HUNGUP_EVENTS) + enOperation = SO_CLOSE; + else if(events & POLLIN) + enOperation = SO_RECEIVE; + else if(events & POLLOUT) + enOperation = SO_SEND; + + m_ccContext.Reset(TRUE, enOperation, ::SSO_GetError(m_soClient)); + + return FALSE; +} + +BOOL CTcpClient::HandleRead(SHORT events) +{ + return ReadData(); +} + +BOOL CTcpClient::HandleWrite(SHORT events) +{ + return SendData(); +} + +BOOL CTcpClient::ReadData() +{ + while(TRUE) + { + if(m_bPaused) + break; + + int rc = (int)read(m_soClient, (char*)(BYTE*)m_rcBuffer, m_dwSocketBufferSize); + + if(rc > 0) + { + if(TRIGGER(FireReceive(m_rcBuffer, rc)) == HR_ERROR) + { + TRACE(" OnReceive() event return 'HR_ERROR', connection will be closed !", m_dwConnID); + + m_ccContext.Reset(TRUE, SO_RECEIVE, ENSURE_ERROR_CANCELLED); + return FALSE; + } + } + else if(rc == SOCKET_ERROR) + { + int code = ::WSAGetLastError(); + + if(code == ERROR_WOULDBLOCK) + break; + else + { + m_ccContext.Reset(TRUE, SO_RECEIVE, code); + return FALSE; + } + } + else if(rc == 0) + { + m_ccContext.Reset(TRUE, SO_CLOSE, SE_OK); + return FALSE; + } + else + ASSERT(FALSE); + } + + return TRUE; +} + +BOOL CTcpClient::PauseReceive(BOOL bPause) +{ + if(!IsConnected()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + if(m_bPaused == bPause) + return TRUE; + + m_bPaused = bPause; + + if(!bPause) + return m_evRecv.Set(); + + return TRUE; +} + +BOOL CTcpClient::SendData() +{ + BOOL bBlocked = FALSE; + + while(m_lsSend.Length() > 0) + { + TItemPtr itPtr(m_itPool); + + { + CCriSecLock locallock(m_csSend); + itPtr = m_lsSend.PopFront(); + } + + if(!itPtr.IsValid()) + break; + + ASSERT(!itPtr->IsEmpty()); + + if(!DoSendData(itPtr, bBlocked)) + return FALSE; + + if(bBlocked) + { + ASSERT(!itPtr->IsEmpty()); + + CCriSecLock locallock(m_csSend); + m_lsSend.PushFront(itPtr.Detach()); + break; + } + } + + return TRUE; +} + +BOOL CTcpClient::DoSendData(TItem* pItem, BOOL& bBlocked) +{ + while(!pItem->IsEmpty()) + { + int rc = (int)write(m_soClient, (char*)pItem->Ptr(), pItem->Size()); + + if(rc > 0) + { + if(TRIGGER(FireSend(pItem->Ptr(), rc)) == HR_ERROR) + { + TRACE(" OnSend() event should not return 'HR_ERROR' !!", m_dwConnID); + ASSERT(FALSE); + } + + pItem->Reduce(rc); + } + else if(rc == SOCKET_ERROR) + { + int code = ::WSAGetLastError(); + + if(code == ERROR_WOULDBLOCK) + { + bBlocked = TRUE; + break; + } + else + { + m_ccContext.Reset(TRUE, SO_SEND, code); + return FALSE; + } + } + else + ASSERT(FALSE); + } + + return TRUE; +} + +BOOL CTcpClient::Send(const BYTE* pBuffer, int iLength, int iOffset) +{ + ASSERT(pBuffer && iLength > 0); + + if(iOffset != 0) pBuffer += iOffset; + + WSABUF buffer; + buffer.len = iLength; + buffer.buf = (BYTE*)pBuffer; + + return SendPackets(&buffer, 1); +} + +BOOL CTcpClient::DoSendPackets(const WSABUF pBuffers[], int iCount) +{ + ASSERT(pBuffers && iCount > 0); + + int result = NO_ERROR; + + if(pBuffers && iCount > 0) + { + if(IsConnected()) + { + CCriSecLock locallock(m_csSend); + + if(IsConnected()) + result = SendInternal(pBuffers, iCount); + else + result = ERROR_INVALID_STATE; + } + else + result = ERROR_INVALID_STATE; + } + else + result = ERROR_INVALID_PARAMETER; + + if(result != NO_ERROR) + ::SetLastError(result); + + return (result == NO_ERROR); +} + +int CTcpClient::SendInternal(const WSABUF pBuffers[], int iCount) +{ + ASSERT(m_lsSend.Length() >= 0); + + int iPending = m_lsSend.Length(); + + for(int i = 0; i < iCount; i++) + { + int iBufLen = pBuffers[i].len; + + if(iBufLen > 0) + { + BYTE* pBuffer = (BYTE*)pBuffers[i].buf; + ASSERT(pBuffer); + + m_lsSend.Cat(pBuffer, iBufLen); + ASSERT(m_lsSend.Length() > 0); + } + } + + if(iPending == 0 && m_lsSend.Length() > 0) m_evSend.Set(); + + return NO_ERROR; +} + +BOOL CTcpClient::SendSmallFile(LPCTSTR lpszFileName, const LPWSABUF pHead, const LPWSABUF pTail) +{ + CFile file; + CFileMapping fmap; + WSABUF szBuf[3]; + + HRESULT hr = ::MakeSmallFilePackage(lpszFileName, file, fmap, szBuf, pHead, pTail); + + if(FAILED(hr)) + { + ::SetLastError(hr); + return FALSE; + } + + return SendPackets(szBuf, 3); +} + +void CTcpClient::SetLastError(EnSocketError code, LPCSTR func, int ec) +{ + TRACE("%s --> Error: %d, EC: %d", func, code, ec); + + m_enLastError = code; + ::SetLastError(ec); +} + +BOOL CTcpClient::GetLocalAddress(TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort) +{ + ASSERT(lpszAddress != nullptr && iAddressLen > 0); + + return ::GetSocketLocalAddress(m_soClient, lpszAddress, iAddressLen, usPort); +} + +void CTcpClient::SetRemoteHost(LPCTSTR lpszHost, USHORT usPort) +{ + m_strHost = lpszHost; + m_usPort = usPort; +} + +BOOL CTcpClient::GetRemoteHost(TCHAR lpszHost[], int& iHostLen, USHORT& usPort) +{ + BOOL isOK = FALSE; + + if(m_strHost.IsEmpty()) + return isOK; + + int iLen = m_strHost.GetLength() + 1; + + if(iHostLen >= iLen) + { + memcpy(lpszHost, CA2CT(m_strHost), iLen * sizeof(TCHAR)); + usPort = m_usPort; + + isOK = TRUE; + } + + iHostLen = iLen; + + return isOK; +} + +BOOL CTcpClient::GetRemoteHost(LPCSTR* lpszHost, USHORT* pusPort) +{ + *lpszHost = m_strHost; + + if(pusPort != nullptr) + *pusPort = m_usPort; + + return !m_strHost.IsEmpty(); +} diff --git a/TcpClient.h b/TcpClient.h new file mode 100644 index 0000000..bc19b15 --- /dev/null +++ b/TcpClient.h @@ -0,0 +1,248 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "SocketHelper.h" +#include "./common/GeneralHelper.h" + +class CTcpClient : public ITcpClient +{ +public: + virtual BOOL Start (LPCTSTR lpszRemoteAddress, USHORT usPort, BOOL bAsyncConnect = TRUE, LPCTSTR lpszBindAddress = nullptr, USHORT usLocalPort = 0); + virtual BOOL Stop (); + virtual BOOL Send (const BYTE* pBuffer, int iLength, int iOffset = 0); + virtual BOOL SendSmallFile (LPCTSTR lpszFileName, const LPWSABUF pHead = nullptr, const LPWSABUF pTail = nullptr); + virtual BOOL SendPackets (const WSABUF pBuffers[], int iCount) {return DoSendPackets(pBuffers, iCount);} + virtual BOOL PauseReceive (BOOL bPause = TRUE); + virtual BOOL Wait (DWORD dwMilliseconds = INFINITE) {return m_evWait.WaitFor(dwMilliseconds, WAIT_FOR_STOP_PREDICATE);} + virtual BOOL HasStarted () {return m_enState == SS_STARTED || m_enState == SS_STARTING;} + virtual EnServiceState GetState () {return m_enState;} + virtual CONNID GetConnectionID () {return m_dwConnID;} + virtual EnSocketError GetLastError () {return m_enLastError;} + virtual LPCTSTR GetLastErrorDesc () {return ::GetSocketErrorDesc(m_enLastError);} + + virtual BOOL GetLocalAddress (TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort); + virtual BOOL GetRemoteHost (TCHAR lpszHost[], int& iHostLen, USHORT& usPort); + virtual BOOL GetPendingDataLength (int& iPending) {iPending = m_lsSend.Length(); return HasStarted();} + virtual BOOL IsPauseReceive (BOOL& bPaused) {bPaused = m_bPaused; return HasStarted();} + virtual BOOL IsConnected () {return m_bConnected;} + +#ifdef _SSL_SUPPORT + virtual BOOL SetupSSLContext (int iVerifyMode = SSL_VM_NONE, LPCTSTR lpszPemCertFile = nullptr, LPCTSTR lpszPemKeyFile = nullptr, LPCTSTR lpszKeyPassword = nullptr, LPCTSTR lpszCAPemCertFileOrPath = nullptr) {return FALSE;} + virtual BOOL SetupSSLContextByMemory(int iVerifyMode = SSL_VM_NONE, LPCSTR lpszPemCert = nullptr, LPCSTR lpszPemKey = nullptr, LPCSTR lpszKeyPassword = nullptr, LPCSTR lpszCAPemCert = nullptr) {return FALSE;} + virtual void CleanupSSLContext () {} + + virtual BOOL StartSSLHandShake () {return FALSE;} + virtual void SetSSLAutoHandShake(BOOL bAutoHandShake) {} + virtual BOOL IsSSLAutoHandShake () {return FALSE;} + virtual void SetSSLCipherList (LPCTSTR lpszCipherList){} + virtual LPCTSTR GetSSLCipherList() {return nullptr;} + virtual BOOL GetSSLSessionInfo(EnSSLSessionInfo enInfo, LPVOID* lppInfo) {return FALSE;} + +protected: + virtual BOOL StartSSLHandShakeNoCheck() {return FALSE;} +#endif + +public: + virtual BOOL IsSecure () {return FALSE;} + + virtual void SetReuseAddressPolicy (EnReuseAddressPolicy enReusePolicy){ENSURE_HAS_STOPPED(); m_enReusePolicy = enReusePolicy;} + virtual void SetSyncConnectTimeout (DWORD dwSyncConnectTimeout) {ENSURE_HAS_STOPPED(); m_dwSyncConnectTimeout = dwSyncConnectTimeout;} + virtual void SetSocketBufferSize (DWORD dwSocketBufferSize) {ENSURE_HAS_STOPPED(); m_dwSocketBufferSize = dwSocketBufferSize;} + virtual void SetKeepAliveTime (DWORD dwKeepAliveTime) {ENSURE_HAS_STOPPED(); m_dwKeepAliveTime = dwKeepAliveTime;} + virtual void SetKeepAliveInterval (DWORD dwKeepAliveInterval) {ENSURE_HAS_STOPPED(); m_dwKeepAliveInterval = dwKeepAliveInterval;} + virtual void SetFreeBufferPoolSize (DWORD dwFreeBufferPoolSize) {ENSURE_HAS_STOPPED(); m_dwFreeBufferPoolSize = dwFreeBufferPoolSize;} + virtual void SetFreeBufferPoolHold (DWORD dwFreeBufferPoolHold) {ENSURE_HAS_STOPPED(); m_dwFreeBufferPoolHold = dwFreeBufferPoolHold;} + virtual void SetNoDelay (BOOL bNoDelay) {ENSURE_HAS_STOPPED(); m_bNoDelay = bNoDelay;} + virtual void SetExtra (PVOID pExtra) {m_pExtra = pExtra;} + + virtual EnReuseAddressPolicy GetReuseAddressPolicy () {return m_enReusePolicy;} + virtual DWORD GetSyncConnectTimeout () {return m_dwSyncConnectTimeout;} + virtual DWORD GetSocketBufferSize () {return m_dwSocketBufferSize;} + virtual DWORD GetKeepAliveTime () {return m_dwKeepAliveTime;} + virtual DWORD GetKeepAliveInterval () {return m_dwKeepAliveInterval;} + virtual DWORD GetFreeBufferPoolSize () {return m_dwFreeBufferPoolSize;} + virtual DWORD GetFreeBufferPoolHold () {return m_dwFreeBufferPoolHold;} + virtual BOOL IsNoDelay () {return m_bNoDelay;} + virtual PVOID GetExtra () {return m_pExtra;} + +protected: + virtual EnHandleResult FirePrepareConnect(SOCKET socket) + {return DoFirePrepareConnect(this, socket);} + virtual EnHandleResult FireConnect() + { + EnHandleResult rs = DoFireConnect(this); + if(rs != HR_ERROR) rs = FireHandShake(); + return rs; + } + virtual EnHandleResult FireHandShake() + {return DoFireHandShake(this);} + virtual EnHandleResult FireSend(const BYTE* pData, int iLength) + {return DoFireSend(this, pData, iLength);} + virtual EnHandleResult FireReceive(const BYTE* pData, int iLength) + {return DoFireReceive(this, pData, iLength);} + virtual EnHandleResult FireReceive(int iLength) + {return DoFireReceive(this, iLength);} + virtual EnHandleResult FireClose(EnSocketOperation enOperation, int iErrorCode) + {return DoFireClose(this, enOperation, iErrorCode);} + + virtual EnHandleResult DoFirePrepareConnect(ITcpClient* pSender, SOCKET socket) + {return m_pListener->OnPrepareConnect(pSender, pSender->GetConnectionID(), socket);} + virtual EnHandleResult DoFireConnect(ITcpClient* pSender) + {return m_pListener->OnConnect(pSender, pSender->GetConnectionID());} + virtual EnHandleResult DoFireHandShake(ITcpClient* pSender) + {return m_pListener->OnHandShake(pSender, pSender->GetConnectionID());} + virtual EnHandleResult DoFireSend(ITcpClient* pSender, const BYTE* pData, int iLength) + {return m_pListener->OnSend(pSender, pSender->GetConnectionID(), pData, iLength);} + virtual EnHandleResult DoFireReceive(ITcpClient* pSender, const BYTE* pData, int iLength) + {return m_pListener->OnReceive(pSender, pSender->GetConnectionID(), pData, iLength);} + virtual EnHandleResult DoFireReceive(ITcpClient* pSender, int iLength) + {return m_pListener->OnReceive(pSender, pSender->GetConnectionID(), iLength);} + virtual EnHandleResult DoFireClose(ITcpClient* pSender, EnSocketOperation enOperation, int iErrorCode) + {return m_pListener->OnClose(pSender, pSender->GetConnectionID(), enOperation, iErrorCode);} + + void SetLastError(EnSocketError code, LPCSTR func, int ec); + virtual BOOL CheckParams(); + virtual void PrepareStart(); + virtual void Reset(); + + virtual BOOL BeforeUnpause() {return TRUE;} + + virtual void OnWorkerThreadStart(THR_ID tid) {} + virtual void OnWorkerThreadEnd(THR_ID tid) {} + + BOOL DoSendPackets(const WSABUF pBuffers[], int iCount); + + static BOOL DoSendPackets(CTcpClient* pClient, const WSABUF pBuffers[], int iCount) + {return pClient->DoSendPackets(pBuffers, iCount);} + +protected: + BOOL IsPaused () {return m_bPaused;} + void SetReserved (PVOID pReserved) {m_pReserved = pReserved;} + PVOID GetReserved () {return m_pReserved;} + BOOL GetRemoteHost (LPCSTR* lpszHost, USHORT* pusPort = nullptr); + +private: + void SetRemoteHost (LPCTSTR lpszHost, USHORT usPort); + void SetConnected (BOOL bConnected = TRUE) {m_bConnected = bConnected; if(bConnected) m_enState = SS_STARTED;} + + BOOL CheckStarting(); + BOOL CheckStoping(); + BOOL CreateClientSocket(LPCTSTR lpszRemoteAddress, HP_SOCKADDR& addrRemote, USHORT usPort, LPCTSTR lpszBindAddress, HP_SOCKADDR& addrBind); + BOOL BindClientSocket(const HP_SOCKADDR& addrBind, const HP_SOCKADDR& addrRemote, USHORT usLocalPort); + BOOL ConnectToServer(const HP_SOCKADDR& addrRemote, BOOL bAsyncConnect); + BOOL CreateWorkerThread(); + BOOL ProcessNetworkEvent(SHORT events); + BOOL ReadData(); + BOOL SendData(); + BOOL DoSendData(TItem* pItem, BOOL& bBlocked); + int SendInternal(const WSABUF pBuffers[], int iCount); + void WaitForWorkerThreadEnd(); + + BOOL HandleConnect (SHORT events); + BOOL HandleClose (SHORT events); + BOOL HandleRead (SHORT events); + BOOL HandleWrite (SHORT events); + + UINT WINAPI WorkerThreadProc(LPVOID pv); + +public: + CTcpClient(ITcpClientListener* pListener) + : m_pListener (pListener) + , m_lsSend (m_itPool) + , m_soClient (INVALID_SOCKET) + , m_nEvents (0) + , m_dwConnID (0) + , m_usPort (0) + , m_bPaused (FALSE) + , m_bConnected (FALSE) + , m_enLastError (SE_OK) + , m_enState (SS_STOPPED) + , m_bNoDelay (FALSE) + , m_pExtra (nullptr) + , m_pReserved (nullptr) + , m_enReusePolicy (RAP_ADDR_ONLY) + , m_dwSyncConnectTimeout(DEFAULT_SYNC_CONNECT_TIMEOUT) + , m_dwSocketBufferSize (DEFAULT_TCP_SOCKET_BUFFER_SIZE) + , m_dwFreeBufferPoolSize(DEFAULT_CLIENT_FREE_BUFFER_POOL_SIZE) + , m_dwFreeBufferPoolHold(DEFAULT_CLIENT_FREE_BUFFER_POOL_HOLD) + , m_dwKeepAliveTime (DEFALUT_TCP_KEEPALIVE_TIME) + , m_dwKeepAliveInterval (DEFALUT_TCP_KEEPALIVE_INTERVAL) + { + ASSERT(m_pListener); + } + + virtual ~CTcpClient() + { + ENSURE_STOP(); + } + +private: + CSEM m_evWait; + + ITcpClientListener* m_pListener; + TClientCloseContext m_ccContext; + + SOCKET m_soClient; + SHORT m_nEvents; + CONNID m_dwConnID; + + EnReuseAddressPolicy m_enReusePolicy; + DWORD m_dwSyncConnectTimeout; + DWORD m_dwSocketBufferSize; + DWORD m_dwFreeBufferPoolSize; + DWORD m_dwFreeBufferPoolHold; + DWORD m_dwKeepAliveTime; + DWORD m_dwKeepAliveInterval; + BOOL m_bNoDelay; + + EnSocketError m_enLastError; + volatile BOOL m_bConnected; + volatile EnServiceState m_enState; + + PVOID m_pExtra; + PVOID m_pReserved; + + CBufferPtr m_rcBuffer; + +protected: + CStringA m_strHost; + USHORT m_usPort; + + CItemPool m_itPool; + +private: + CSpinGuard m_csState; + + CCriSec m_csSend; + TItemListExV m_lsSend; + + CEvt m_evSend; + CEvt m_evRecv; + CEvt m_evStop; + + volatile BOOL m_bPaused; + + CThread m_thWorker; +}; diff --git a/TcpPackAgent.cpp b/TcpPackAgent.cpp new file mode 100644 index 0000000..bfe5530 --- /dev/null +++ b/TcpPackAgent.cpp @@ -0,0 +1,24 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TcpPackAgent.h" diff --git a/TcpPackAgent.h b/TcpPackAgent.h new file mode 100644 index 0000000..ad36e91 --- /dev/null +++ b/TcpPackAgent.h @@ -0,0 +1,221 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "TcpAgent.h" +#include "MiscHelper.h" + +template class CTcpPackAgentT : public IPackSocket, public T +{ + using __super = T; + using __super::SetConnectionReserved; + using __super::GetConnectionReserved; + using __super::GetMaxConnectionCount; + using __super::GetSocketBufferSize; + using __super::GetFreeBufferObjPool; + using __super::GetFreeBufferObjHold; + using __super::GetFreeSocketObjLockTime; + using __super::GetFreeSocketObjPool; + using __super::GetFreeSocketObjHold; + using __super::SetLastError; + +public: + using __super::Stop; + using __super::Wait; + using __super::GetState; + +public: + virtual BOOL SendPackets(CONNID dwConnID, const WSABUF pBuffers[], int iCount) + { + int iNewCount = iCount + 1; + unique_ptr buffers(new WSABUF[iNewCount]); + + DWORD dwHeader; + if(!::AddPackHeader(pBuffers, iCount, buffers, m_dwMaxPackSize, m_usHeaderFlag, dwHeader)) + return FALSE; + + return __super::SendPackets(dwConnID, buffers.get(), iNewCount); + } + +protected: + virtual EnHandleResult DoFireConnect(TAgentSocketObj* pSocketObj) + { + EnHandleResult result = __super::DoFireConnect(pSocketObj); + + if(result != HR_ERROR) + { + TBuffer* pBuffer = m_bfPool.PickFreeBuffer(pSocketObj->connID); + ENSURE(SetConnectionReserved(pSocketObj, TBufferPackInfo::Construct(pBuffer))); + } + + return result; + } + + virtual EnHandleResult DoFireHandShake(TAgentSocketObj* pSocketObj) + { + EnHandleResult result = __super::DoFireHandShake(pSocketObj); + + if(result == HR_ERROR) + ReleaseConnectionExtra(pSocketObj); + + return result; + } + + virtual EnHandleResult DoFireReceive(TAgentSocketObj* pSocketObj, const BYTE* pData, int iLength) + { + TBufferPackInfo* pInfo = nullptr; + GetConnectionReserved(pSocketObj, (PVOID*)&pInfo); + ASSERT(pInfo); + + TBuffer* pBuffer = (TBuffer*)pInfo->pBuffer; + ASSERT(pBuffer && pBuffer->IsValid()); + + return ParsePack(this, pInfo, pBuffer, pSocketObj, m_dwMaxPackSize, m_usHeaderFlag, pData, iLength); + } + + virtual EnHandleResult DoFireClose(TAgentSocketObj* pSocketObj, EnSocketOperation enOperation, int iErrorCode) + { + EnHandleResult result = __super::DoFireClose(pSocketObj, enOperation, iErrorCode); + + ReleaseConnectionExtra(pSocketObj); + + return result; + } + + virtual EnHandleResult DoFireShutdown() + { + EnHandleResult result = __super::DoFireShutdown(); + + m_bfPool.Clear(); + + return result; + } + + virtual BOOL BeforeUnpause(TAgentSocketObj* pSocketObj) + { + if(!TAgentSocketObj::IsValid(pSocketObj)) + return FALSE; + + if(pSocketObj->IsPaused()) + return TRUE; + + TBufferPackInfo* pInfo = nullptr; + GetConnectionReserved(pSocketObj, (PVOID*)&pInfo); + ASSERT(pInfo); + + TBuffer* pBuffer = (TBuffer*)pInfo->pBuffer; + ASSERT(pBuffer && pBuffer->IsValid()); + + return (ParsePack(this, pInfo, pBuffer, pSocketObj, m_dwMaxPackSize, m_usHeaderFlag) != HR_ERROR); + } + + virtual BOOL CheckParams() + { + if ((m_dwMaxPackSize > 0 && m_dwMaxPackSize <= TCP_PACK_MAX_SIZE_LIMIT) && + (m_usHeaderFlag >= 0 && m_usHeaderFlag <= TCP_PACK_HEADER_FLAG_LIMIT) ) + return __super::CheckParams(); + + SetLastError(SE_INVALID_PARAM, __FUNCTION__, ERROR_INVALID_PARAMETER); + return FALSE; + } + + virtual void PrepareStart() + { + __super::PrepareStart(); + + m_bfPool.SetMaxCacheSize (GetMaxConnectionCount()); + m_bfPool.SetItemCapacity (GetSocketBufferSize()); + m_bfPool.SetItemPoolSize (GetFreeBufferObjPool()); + m_bfPool.SetItemPoolHold (GetFreeBufferObjHold()); + m_bfPool.SetBufferLockTime (GetFreeSocketObjLockTime()); + m_bfPool.SetBufferPoolSize (GetFreeSocketObjPool()); + m_bfPool.SetBufferPoolHold (GetFreeSocketObjHold()); + + m_bfPool.Prepare(); + } + + virtual void ReleaseGCSocketObj(BOOL bForce = FALSE) + { + __super::ReleaseGCSocketObj(bForce); + +#ifdef USE_EXTERNAL_GC + m_bfPool.ReleaseGCBuffer(bForce); +#endif + } + +public: + virtual void SetMaxPackSize (DWORD dwMaxPackSize) {ENSURE_HAS_STOPPED(); m_dwMaxPackSize = dwMaxPackSize;} + virtual void SetPackHeaderFlag (USHORT usPackHeaderFlag) {ENSURE_HAS_STOPPED(); m_usHeaderFlag = usPackHeaderFlag;} + virtual DWORD GetMaxPackSize () {return m_dwMaxPackSize;} + virtual USHORT GetPackHeaderFlag() {return m_usHeaderFlag;} + +private: + void ReleaseConnectionExtra(TAgentSocketObj* pSocketObj) + { + TBufferPackInfo* pInfo = nullptr; + GetConnectionReserved(pSocketObj, (PVOID*)&pInfo); + + if(pInfo != nullptr) + { + m_bfPool.PutFreeBuffer(pInfo->pBuffer); + TBufferPackInfo::Destruct(pInfo); + + ENSURE(SetConnectionReserved(pSocketObj, nullptr)); + } + } + + EnHandleResult DoFireSuperReceive(TAgentSocketObj* pSocketObj, const BYTE* pData, int iLength) + {return __super::DoFireReceive(pSocketObj, pData, iLength);} + + friend EnHandleResult ParsePack<>(CTcpPackAgentT* pThis, TBufferPackInfo* pInfo, TBuffer* pBuffer, TAgentSocketObj* pSocket, DWORD dwMaxPackSize, USHORT usPackHeaderFlag); + +public: + CTcpPackAgentT(ITcpAgentListener* pListener) + : T (pListener) + , m_dwMaxPackSize (TCP_PACK_DEFAULT_MAX_SIZE) + , m_usHeaderFlag (TCP_PACK_DEFAULT_HEADER_FLAG) + { + + } + + virtual ~CTcpPackAgentT() + { + ENSURE_STOP(); + } + +private: + DWORD m_dwMaxPackSize; + USHORT m_usHeaderFlag; + + CBufferPool m_bfPool; +}; + +typedef CTcpPackAgentT CTcpPackAgent; + +#ifdef _SSL_SUPPORT + +#include "SSLAgent.h" +typedef CTcpPackAgentT CSSLPackAgent; + +#endif diff --git a/TcpPackClient.cpp b/TcpPackClient.cpp new file mode 100644 index 0000000..b78d7a3 --- /dev/null +++ b/TcpPackClient.cpp @@ -0,0 +1,24 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TcpPackClient.h" diff --git a/TcpPackClient.h b/TcpPackClient.h new file mode 100644 index 0000000..6b1d904 --- /dev/null +++ b/TcpPackClient.h @@ -0,0 +1,126 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "TcpClient.h" +#include "MiscHelper.h" + +template class CTcpPackClientT : public IPackClient, public T +{ + using __super = T; + using __super::SetLastError; + using __super::m_itPool; + +public: + using __super::Stop; + using __super::Wait; + using __super::GetState; + +public: + virtual BOOL SendPackets(const WSABUF pBuffers[], int iCount) + { + int iNewCount = iCount + 1; + unique_ptr buffers(new WSABUF[iNewCount]); + + DWORD dwHeader; + if(!::AddPackHeader(pBuffers, iCount, buffers, m_dwMaxPackSize, m_usHeaderFlag, dwHeader)) + return FALSE; + + return __super::SendPackets(buffers.get(), iNewCount); + } + +protected: + virtual EnHandleResult DoFireReceive(ITcpClient* pSender, const BYTE* pData, int iLength) + { + return ParsePack(this, &m_pkInfo, &m_lsBuffer, (CTcpPackClientT*)pSender, m_dwMaxPackSize, m_usHeaderFlag, pData, iLength); + } + + virtual BOOL BeforeUnpause() + { + return (ParsePack(this, &m_pkInfo, &m_lsBuffer, (CTcpPackClientT*)this, m_dwMaxPackSize, m_usHeaderFlag) != HR_ERROR); + } + + virtual BOOL CheckParams() + { + if ((m_dwMaxPackSize > 0 && m_dwMaxPackSize <= TCP_PACK_MAX_SIZE_LIMIT) && + (m_usHeaderFlag >= 0 && m_usHeaderFlag <= TCP_PACK_HEADER_FLAG_LIMIT) ) + return __super::CheckParams(); + + SetLastError(SE_INVALID_PARAM, __FUNCTION__, ERROR_INVALID_PARAMETER); + return FALSE; + } + + virtual void Reset() + { + m_lsBuffer.Clear(); + m_pkInfo.Reset(); + + __super::Reset(); + } + +public: + virtual void SetMaxPackSize (DWORD dwMaxPackSize) {ENSURE_HAS_STOPPED(); m_dwMaxPackSize = dwMaxPackSize;} + virtual void SetPackHeaderFlag (USHORT usPackHeaderFlag) {ENSURE_HAS_STOPPED(); m_usHeaderFlag = usPackHeaderFlag;} + virtual DWORD GetMaxPackSize () {return m_dwMaxPackSize;} + virtual USHORT GetPackHeaderFlag() {return m_usHeaderFlag;} + +private: + EnHandleResult DoFireSuperReceive(ITcpClient* pSender, const BYTE* pData, int iLength) + {return __super::DoFireReceive(pSender, pData, iLength);} + + friend EnHandleResult ParsePack<> (CTcpPackClientT* pThis, TPackInfo* pInfo, TItemListEx* pBuffer, CTcpPackClientT* pSocket, + DWORD dwMaxPackSize, USHORT usPackHeaderFlag); + +public: + CTcpPackClientT(ITcpClientListener* pListener) + : T (pListener) + , m_dwMaxPackSize (TCP_PACK_DEFAULT_MAX_SIZE) + , m_usHeaderFlag (TCP_PACK_DEFAULT_HEADER_FLAG) + , m_pkInfo (nullptr) + , m_lsBuffer (m_itPool) + { + + } + + virtual ~CTcpPackClientT() + { + ENSURE_STOP(); + } + +private: + DWORD m_dwMaxPackSize; + USHORT m_usHeaderFlag; + + TPackInfo m_pkInfo; + TItemListEx m_lsBuffer; +}; + +typedef CTcpPackClientT CTcpPackClient; + +#ifdef _SSL_SUPPORT + +#include "SSLClient.h" +typedef CTcpPackClientT CSSLPackClient; + +#endif diff --git a/TcpPackServer.cpp b/TcpPackServer.cpp new file mode 100644 index 0000000..68ddd7b --- /dev/null +++ b/TcpPackServer.cpp @@ -0,0 +1,24 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TcpPackServer.h" diff --git a/TcpPackServer.h b/TcpPackServer.h new file mode 100644 index 0000000..b7dd786 --- /dev/null +++ b/TcpPackServer.h @@ -0,0 +1,221 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "TcpServer.h" +#include "MiscHelper.h" + +template class CTcpPackServerT : public IPackSocket, public T +{ + using __super = T; + using __super::SetConnectionReserved; + using __super::GetConnectionReserved; + using __super::GetMaxConnectionCount; + using __super::GetSocketBufferSize; + using __super::GetFreeBufferObjPool; + using __super::GetFreeBufferObjHold; + using __super::GetFreeSocketObjLockTime; + using __super::GetFreeSocketObjPool; + using __super::GetFreeSocketObjHold; + using __super::SetLastError; + +public: + using __super::Stop; + using __super::Wait; + using __super::GetState; + +public: + virtual BOOL SendPackets(CONNID dwConnID, const WSABUF pBuffers[], int iCount) + { + int iNewCount = iCount + 1; + unique_ptr buffers(new WSABUF[iNewCount]); + + DWORD dwHeader; + if(!::AddPackHeader(pBuffers, iCount, buffers, m_dwMaxPackSize, m_usHeaderFlag, dwHeader)) + return FALSE; + + return __super::SendPackets(dwConnID, buffers.get(), iNewCount); + } + +protected: + virtual EnHandleResult DoFireAccept(TSocketObj* pSocketObj) + { + EnHandleResult result = __super::DoFireAccept(pSocketObj); + + if(result != HR_ERROR) + { + TBuffer* pBuffer = m_bfPool.PickFreeBuffer(pSocketObj->connID); + ENSURE(SetConnectionReserved(pSocketObj, TBufferPackInfo::Construct(pBuffer))); + } + + return result; + } + + virtual EnHandleResult DoFireHandShake(TSocketObj* pSocketObj) + { + EnHandleResult result = __super::DoFireHandShake(pSocketObj); + + if(result == HR_ERROR) + ReleaseConnectionExtra(pSocketObj); + + return result; + } + + virtual EnHandleResult DoFireReceive(TSocketObj* pSocketObj, const BYTE* pData, int iLength) + { + TBufferPackInfo* pInfo = nullptr; + GetConnectionReserved(pSocketObj, (PVOID*)&pInfo); + ASSERT(pInfo); + + TBuffer* pBuffer = (TBuffer*)pInfo->pBuffer; + ASSERT(pBuffer && pBuffer->IsValid()); + + return ParsePack(this, pInfo, pBuffer, pSocketObj, m_dwMaxPackSize, m_usHeaderFlag, pData, iLength); + } + + virtual EnHandleResult DoFireClose(TSocketObj* pSocketObj, EnSocketOperation enOperation, int iErrorCode) + { + EnHandleResult result = __super::DoFireClose(pSocketObj, enOperation, iErrorCode); + + ReleaseConnectionExtra(pSocketObj); + + return result; + } + + virtual EnHandleResult DoFireShutdown() + { + EnHandleResult result = __super::DoFireShutdown(); + + m_bfPool.Clear(); + + return result; + } + + virtual BOOL BeforeUnpause(TSocketObj* pSocketObj) + { + if(!TSocketObj::IsValid(pSocketObj)) + return FALSE; + + if(pSocketObj->IsPaused()) + return TRUE; + + TBufferPackInfo* pInfo = nullptr; + GetConnectionReserved(pSocketObj, (PVOID*)&pInfo); + ASSERT(pInfo); + + TBuffer* pBuffer = (TBuffer*)pInfo->pBuffer; + ASSERT(pBuffer && pBuffer->IsValid()); + + return (ParsePack(this, pInfo, pBuffer, pSocketObj, m_dwMaxPackSize, m_usHeaderFlag) != HR_ERROR); + } + + virtual BOOL CheckParams() + { + if ((m_dwMaxPackSize > 0 && m_dwMaxPackSize <= TCP_PACK_MAX_SIZE_LIMIT) && + (m_usHeaderFlag >= 0 && m_usHeaderFlag <= TCP_PACK_HEADER_FLAG_LIMIT) ) + return __super::CheckParams(); + + SetLastError(SE_INVALID_PARAM, __FUNCTION__, ERROR_INVALID_PARAMETER); + return FALSE; + } + + virtual void PrepareStart() + { + __super::PrepareStart(); + + m_bfPool.SetMaxCacheSize (GetMaxConnectionCount()); + m_bfPool.SetItemCapacity (GetSocketBufferSize()); + m_bfPool.SetItemPoolSize (GetFreeBufferObjPool()); + m_bfPool.SetItemPoolHold (GetFreeBufferObjHold()); + m_bfPool.SetBufferLockTime (GetFreeSocketObjLockTime()); + m_bfPool.SetBufferPoolSize (GetFreeSocketObjPool()); + m_bfPool.SetBufferPoolHold (GetFreeSocketObjHold()); + + m_bfPool.Prepare(); + } + + virtual void ReleaseGCSocketObj(BOOL bForce = FALSE) + { + __super::ReleaseGCSocketObj(bForce); + +#ifdef USE_EXTERNAL_GC + m_bfPool.ReleaseGCBuffer(bForce); +#endif + } + +public: + virtual void SetMaxPackSize (DWORD dwMaxPackSize) {ENSURE_HAS_STOPPED(); m_dwMaxPackSize = dwMaxPackSize;} + virtual void SetPackHeaderFlag (USHORT usPackHeaderFlag) {ENSURE_HAS_STOPPED(); m_usHeaderFlag = usPackHeaderFlag;} + virtual DWORD GetMaxPackSize () {return m_dwMaxPackSize;} + virtual USHORT GetPackHeaderFlag() {return m_usHeaderFlag;} + +private: + void ReleaseConnectionExtra(TSocketObj* pSocketObj) + { + TBufferPackInfo* pInfo = nullptr; + GetConnectionReserved(pSocketObj, (PVOID*)&pInfo); + + if(pInfo != nullptr) + { + m_bfPool.PutFreeBuffer(pInfo->pBuffer); + TBufferPackInfo::Destruct(pInfo); + + ENSURE(SetConnectionReserved(pSocketObj, nullptr)); + } + } + + EnHandleResult DoFireSuperReceive(TSocketObj* pSocketObj, const BYTE* pData, int iLength) + {return __super::DoFireReceive(pSocketObj, pData, iLength);} + + friend EnHandleResult ParsePack<>(CTcpPackServerT* pThis, TBufferPackInfo* pInfo, TBuffer* pBuffer, TSocketObj* pSocket, DWORD dwMaxPackSize, USHORT usPackHeaderFlag); + +public: + CTcpPackServerT(ITcpServerListener* pListener) + : T (pListener) + , m_dwMaxPackSize (TCP_PACK_DEFAULT_MAX_SIZE) + , m_usHeaderFlag (TCP_PACK_DEFAULT_HEADER_FLAG) + { + + } + + virtual ~CTcpPackServerT() + { + ENSURE_STOP(); + } + +private: + DWORD m_dwMaxPackSize; + USHORT m_usHeaderFlag; + + CBufferPool m_bfPool; +}; + +typedef CTcpPackServerT CTcpPackServer; + +#ifdef _SSL_SUPPORT + +#include "SSLServer.h" +typedef CTcpPackServerT CSSLPackServer; + +#endif diff --git a/TcpPullAgent.cpp b/TcpPullAgent.cpp new file mode 100644 index 0000000..8efdd2c --- /dev/null +++ b/TcpPullAgent.cpp @@ -0,0 +1,24 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TcpPullAgent.h" diff --git a/TcpPullAgent.h b/TcpPullAgent.h new file mode 100644 index 0000000..068a004 --- /dev/null +++ b/TcpPullAgent.h @@ -0,0 +1,173 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "TcpAgent.h" +#include "MiscHelper.h" + +template class CTcpPullAgentT : public IPullSocket, public T +{ + using __super = T; + using __super::SetConnectionReserved; + using __super::GetConnectionReserved; + using __super::GetMaxConnectionCount; + using __super::GetSocketBufferSize; + using __super::GetFreeBufferObjPool; + using __super::GetFreeBufferObjHold; + using __super::GetFreeSocketObjLockTime; + using __super::GetFreeSocketObjPool; + using __super::GetFreeSocketObjHold; + +public: + using __super::Stop; + using __super::Wait; + using __super::GetState; + +public: + virtual EnFetchResult Fetch(CONNID dwConnID, BYTE* pData, int iLength) + { + TBuffer* pBuffer = m_bfPool[dwConnID]; + return ::FetchBuffer(pBuffer, pData, iLength); + } + + virtual EnFetchResult Peek(CONNID dwConnID, BYTE* pData, int iLength) + { + TBuffer* pBuffer = m_bfPool[dwConnID]; + return ::PeekBuffer(pBuffer, pData, iLength); + } + +protected: + virtual EnHandleResult DoFireConnect(TAgentSocketObj* pSocketObj) + { + EnHandleResult result = __super::DoFireConnect(pSocketObj); + + if(result != HR_ERROR) + { + TBuffer* pBuffer = m_bfPool.PutCacheBuffer(pSocketObj->connID); + ENSURE(SetConnectionReserved(pSocketObj, pBuffer)); + } + + return result; + } + + virtual EnHandleResult DoFireHandShake(TAgentSocketObj* pSocketObj) + { + EnHandleResult result = __super::DoFireHandShake(pSocketObj); + + if(result == HR_ERROR) + ReleaseConnectionExtra(pSocketObj); + + return result; + } + + virtual EnHandleResult DoFireReceive(TAgentSocketObj* pSocketObj, const BYTE* pData, int iLength) + { + TBuffer* pBuffer = nullptr; + GetConnectionReserved(pSocketObj, (PVOID*)&pBuffer); + ASSERT(pBuffer && pBuffer->IsValid()); + + pBuffer->Cat(pData, iLength); + + return __super::DoFireReceive(pSocketObj, pBuffer->Length()); + } + + virtual EnHandleResult DoFireClose(TAgentSocketObj* pSocketObj, EnSocketOperation enOperation, int iErrorCode) + { + EnHandleResult result = __super::DoFireClose(pSocketObj, enOperation, iErrorCode); + + ReleaseConnectionExtra(pSocketObj); + + return result; + } + + virtual EnHandleResult DoFireShutdown() + { + EnHandleResult result = __super::DoFireShutdown(); + + m_bfPool.Clear(); + + return result; + } + + virtual void PrepareStart() + { + __super::PrepareStart(); + + m_bfPool.SetMaxCacheSize (GetMaxConnectionCount()); + m_bfPool.SetItemCapacity (GetSocketBufferSize()); + m_bfPool.SetItemPoolSize (GetFreeBufferObjPool()); + m_bfPool.SetItemPoolHold (GetFreeBufferObjHold()); + m_bfPool.SetBufferLockTime (GetFreeSocketObjLockTime()); + m_bfPool.SetBufferPoolSize (GetFreeSocketObjPool()); + m_bfPool.SetBufferPoolHold (GetFreeSocketObjHold()); + + m_bfPool.Prepare(); + } + + virtual void ReleaseGCSocketObj(BOOL bForce = FALSE) + { + __super::ReleaseGCSocketObj(bForce); + +#ifdef USE_EXTERNAL_GC + m_bfPool.ReleaseGCBuffer(bForce); +#endif + } + +private: + void ReleaseConnectionExtra(TAgentSocketObj* pSocketObj) + { + TBuffer* pBuffer = nullptr; + GetConnectionReserved(pSocketObj, (PVOID*)&pBuffer); + + if(pBuffer != nullptr) + { + m_bfPool.PutFreeBuffer(pBuffer); + ENSURE(SetConnectionReserved(pSocketObj, nullptr)); + } + } + +public: + CTcpPullAgentT(ITcpAgentListener* pListener) + : T(pListener) + { + + } + + virtual ~CTcpPullAgentT() + { + ENSURE_STOP(); + } + +private: + CBufferPool m_bfPool; +}; + +typedef CTcpPullAgentT CTcpPullAgent; + +#ifdef _SSL_SUPPORT + +#include "SSLAgent.h" +typedef CTcpPullAgentT CSSLPullAgent; + +#endif diff --git a/TcpPullClient.cpp b/TcpPullClient.cpp new file mode 100644 index 0000000..cd79d57 --- /dev/null +++ b/TcpPullClient.cpp @@ -0,0 +1,24 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TcpPullClient.h" diff --git a/TcpPullClient.h b/TcpPullClient.h new file mode 100644 index 0000000..e0118c8 --- /dev/null +++ b/TcpPullClient.h @@ -0,0 +1,89 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "TcpClient.h" +#include "MiscHelper.h" + +template class CTcpPullClientT : public IPullClient, public T +{ + using __super = T; + using __super::m_itPool; + +public: + using __super::Stop; + using __super::Wait; + using __super::GetState; + +public: + virtual EnFetchResult Fetch(BYTE* pData, int iLength) + { + return ::FetchBuffer(&m_lsBuffer, pData, iLength); + } + + virtual EnFetchResult Peek(BYTE* pData, int iLength) + { + return ::PeekBuffer(&m_lsBuffer, pData, iLength); + } + +protected: + virtual EnHandleResult DoFireReceive(ITcpClient* pSender, const BYTE* pData, int iLength) + { + m_lsBuffer.Cat(pData, iLength); + + return __super::DoFireReceive(pSender, m_lsBuffer.Length()); + } + + virtual void Reset() + { + m_lsBuffer.Clear(); + + __super::Reset(); + } + +public: + CTcpPullClientT(ITcpClientListener* pListener) + : T (pListener) + , m_lsBuffer(m_itPool) + { + + } + + virtual ~CTcpPullClientT() + { + ENSURE_STOP(); + } + +private: + TItemListEx m_lsBuffer; +}; + +typedef CTcpPullClientT CTcpPullClient; + +#ifdef _SSL_SUPPORT + +#include "SSLClient.h" +typedef CTcpPullClientT CSSLPullClient; + +#endif diff --git a/TcpPullServer.cpp b/TcpPullServer.cpp new file mode 100644 index 0000000..8ed7731 --- /dev/null +++ b/TcpPullServer.cpp @@ -0,0 +1,24 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TcpPullServer.h" diff --git a/TcpPullServer.h b/TcpPullServer.h new file mode 100644 index 0000000..19213b9 --- /dev/null +++ b/TcpPullServer.h @@ -0,0 +1,173 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "TcpServer.h" +#include "MiscHelper.h" + +template class CTcpPullServerT : public IPullSocket, public T +{ + using __super = T; + using __super::SetConnectionReserved; + using __super::GetConnectionReserved; + using __super::GetMaxConnectionCount; + using __super::GetSocketBufferSize; + using __super::GetFreeBufferObjPool; + using __super::GetFreeBufferObjHold; + using __super::GetFreeSocketObjLockTime; + using __super::GetFreeSocketObjPool; + using __super::GetFreeSocketObjHold; + +public: + using __super::Stop; + using __super::Wait; + using __super::GetState; + +public: + virtual EnFetchResult Fetch(CONNID dwConnID, BYTE* pData, int iLength) + { + TBuffer* pBuffer = m_bfPool[dwConnID]; + return ::FetchBuffer(pBuffer, pData, iLength); + } + + virtual EnFetchResult Peek(CONNID dwConnID, BYTE* pData, int iLength) + { + TBuffer* pBuffer = m_bfPool[dwConnID]; + return ::PeekBuffer(pBuffer, pData, iLength); + } + +protected: + virtual EnHandleResult DoFireAccept(TSocketObj* pSocketObj) + { + EnHandleResult result = __super::DoFireAccept(pSocketObj); + + if(result != HR_ERROR) + { + TBuffer* pBuffer = m_bfPool.PutCacheBuffer(pSocketObj->connID); + ENSURE(SetConnectionReserved(pSocketObj, pBuffer)); + } + + return result; + } + + virtual EnHandleResult DoFireHandShake(TSocketObj* pSocketObj) + { + EnHandleResult result = __super::DoFireHandShake(pSocketObj); + + if(result == HR_ERROR) + ReleaseConnectionExtra(pSocketObj); + + return result; + } + + virtual EnHandleResult DoFireReceive(TSocketObj* pSocketObj, const BYTE* pData, int iLength) + { + TBuffer* pBuffer = nullptr; + GetConnectionReserved(pSocketObj, (PVOID*)&pBuffer); + ASSERT(pBuffer && pBuffer->IsValid()); + + pBuffer->Cat(pData, iLength); + + return __super::DoFireReceive(pSocketObj, pBuffer->Length()); + } + + virtual EnHandleResult DoFireClose(TSocketObj* pSocketObj, EnSocketOperation enOperation, int iErrorCode) + { + EnHandleResult result = __super::DoFireClose(pSocketObj, enOperation, iErrorCode); + + ReleaseConnectionExtra(pSocketObj); + + return result; + } + + virtual EnHandleResult DoFireShutdown() + { + EnHandleResult result = __super::DoFireShutdown(); + + m_bfPool.Clear(); + + return result; + } + + virtual void PrepareStart() + { + __super::PrepareStart(); + + m_bfPool.SetMaxCacheSize (GetMaxConnectionCount()); + m_bfPool.SetItemCapacity (GetSocketBufferSize()); + m_bfPool.SetItemPoolSize (GetFreeBufferObjPool()); + m_bfPool.SetItemPoolHold (GetFreeBufferObjHold()); + m_bfPool.SetBufferLockTime (GetFreeSocketObjLockTime()); + m_bfPool.SetBufferPoolSize (GetFreeSocketObjPool()); + m_bfPool.SetBufferPoolHold (GetFreeSocketObjHold()); + + m_bfPool.Prepare(); + } + + virtual void ReleaseGCSocketObj(BOOL bForce = FALSE) + { + __super::ReleaseGCSocketObj(bForce); + +#ifdef USE_EXTERNAL_GC + m_bfPool.ReleaseGCBuffer(bForce); +#endif + } + +private: + void ReleaseConnectionExtra(TSocketObj* pSocketObj) + { + TBuffer* pBuffer = nullptr; + GetConnectionReserved(pSocketObj, (PVOID*)&pBuffer); + + if(pBuffer != nullptr) + { + m_bfPool.PutFreeBuffer(pBuffer); + ENSURE(SetConnectionReserved(pSocketObj, nullptr)); + } + } + +public: + CTcpPullServerT(ITcpServerListener* pListener) + : T(pListener) + { + + } + + virtual ~CTcpPullServerT() + { + ENSURE_STOP(); + } + +private: + CBufferPool m_bfPool; +}; + +typedef CTcpPullServerT CTcpPullServer; + +#ifdef _SSL_SUPPORT + +#include "SSLServer.h" +typedef CTcpPullServerT CSSLPullServer; + +#endif diff --git a/TcpServer.cpp b/TcpServer.cpp new file mode 100644 index 0000000..b051cc0 --- /dev/null +++ b/TcpServer.cpp @@ -0,0 +1,1187 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TcpServer.h" + +#include "./common/FileHelper.h" + +BOOL CTcpServer::Start(LPCTSTR lpszBindAddress, USHORT usPort) +{ + if(!CheckParams() || !CheckStarting()) + return FALSE; + + PrepareStart(); + + if(CreateListenSocket(lpszBindAddress, usPort)) + if(CreateWorkerThreads()) + if(StartAccept()) + { + m_enState = SS_STARTED; + return TRUE; + } + + EXECUTE_RESTORE_ERROR(Stop()); + + return FALSE; +} + +void CTcpServer::SetLastError(EnSocketError code, LPCSTR func, int ec) +{ + m_enLastError = code; + ::SetLastError(ec); +} + +BOOL CTcpServer::CheckParams() +{ + if ((m_enSendPolicy >= SP_PACK && m_enSendPolicy <= SP_DIRECT) && + (m_enOnSendSyncPolicy >= OSSP_NONE && m_enOnSendSyncPolicy <= OSSP_RECEIVE) && + ((int)m_dwMaxConnectionCount > 0 && m_dwMaxConnectionCount <= MAX_CONNECTION_COUNT) && + ((int)m_dwWorkerThreadCount > 0 && m_dwWorkerThreadCount <= MAX_WORKER_THREAD_COUNT) && + ((int)m_dwAcceptSocketCount > 0) && + ((int)m_dwSocketBufferSize >= MIN_SOCKET_BUFFER_SIZE) && + ((int)m_dwSocketListenQueue > 0) && + ((int)m_dwFreeSocketObjLockTime >= 1000) && + ((int)m_dwFreeSocketObjPool >= 0) && + ((int)m_dwFreeBufferObjPool >= 0) && + ((int)m_dwFreeSocketObjHold >= 0) && + ((int)m_dwFreeBufferObjHold >= 0) && + ((int)m_dwKeepAliveTime >= 1000 || m_dwKeepAliveTime == 0) && + ((int)m_dwKeepAliveInterval >= 1000 || m_dwKeepAliveInterval == 0) ) + return TRUE; + + SetLastError(SE_INVALID_PARAM, __FUNCTION__, ERROR_INVALID_PARAMETER); + return FALSE; +} + +void CTcpServer::PrepareStart() +{ + m_bfActiveSockets.Reset(m_dwMaxConnectionCount); + m_lsFreeSocket.Reset(m_dwFreeSocketObjPool); + + m_bfObjPool.SetItemCapacity(m_dwSocketBufferSize); + m_bfObjPool.SetPoolSize(m_dwFreeBufferObjPool); + m_bfObjPool.SetPoolHold(m_dwFreeBufferObjHold); + + m_bfObjPool.Prepare(); + + m_rcBuffers = make_unique(m_dwWorkerThreadCount); + for_each(m_rcBuffers.get(), m_rcBuffers.get() + m_dwWorkerThreadCount, [this](CBufferPtr& buff) {buff.Malloc(m_dwSocketBufferSize);}); + + m_soListens = make_unique(m_dwWorkerThreadCount); + for_each(m_soListens.get(), m_soListens.get() + m_dwWorkerThreadCount, [](SOCKET& sock) {sock = INVALID_FD;}); +} + +BOOL CTcpServer::CheckStarting() +{ + CSpinLock locallock(m_csState); + + if(m_enState == SS_STOPPED) + m_enState = SS_STARTING; + else + { + SetLastError(SE_ILLEGAL_STATE, __FUNCTION__, ERROR_INVALID_STATE); + return FALSE; + } + + return TRUE; +} + +BOOL CTcpServer::CheckStoping() +{ + if(m_enState != SS_STOPPED) + { + CSpinLock locallock(m_csState); + + if(HasStarted()) + { + m_enState = SS_STOPPING; + return TRUE; + } + } + + SetLastError(SE_ILLEGAL_STATE, __FUNCTION__, ERROR_INVALID_STATE); + + return FALSE; +} + +BOOL CTcpServer::CreateListenSocket(LPCTSTR lpszBindAddress, USHORT usPort) +{ + if(::IsStrEmpty(lpszBindAddress)) + lpszBindAddress = DEFAULT_IPV4_BIND_ADDRESS; + + HP_SOCKADDR addr; + + if(!::sockaddr_A_2_IN(lpszBindAddress, usPort, addr)) + { + SetLastError(SE_SOCKET_CREATE, __FUNCTION__, ::WSAGetLastError()); + return FALSE; + } + + for(DWORD i = 0; i < m_dwWorkerThreadCount; i++) + { + m_soListens[i] = socket(addr.family, SOCK_STREAM, IPPROTO_TCP); + SOCKET soListen = m_soListens[i]; + + if(IS_INVALID_FD(soListen)) + { + SetLastError(SE_SOCKET_CREATE, __FUNCTION__, ::WSAGetLastError()); + return FALSE; + } + + ::fcntl_SETFL(soListen, O_NOATIME | O_NONBLOCK | O_CLOEXEC); + + BOOL bOnOff = (m_dwKeepAliveTime > 0 && m_dwKeepAliveInterval > 0); + VERIFY(IS_NO_ERROR(::SSO_KeepAliveVals(soListen, bOnOff, m_dwKeepAliveTime, m_dwKeepAliveInterval))); + VERIFY(IS_NO_ERROR(::SSO_ReuseAddress(soListen, m_enReusePolicy))); + VERIFY(IS_NO_ERROR(::SSO_NoDelay(soListen, m_bNoDelay))); + + if(IS_HAS_ERROR(::bind(soListen, addr.Addr(), addr.AddrSize()))) + { + SetLastError(SE_SOCKET_BIND, __FUNCTION__, ::WSAGetLastError()); + return FALSE; + } + + if(TRIGGER(FirePrepareListen(soListen)) == HR_ERROR) + { + SetLastError(SE_SOCKET_PREPARE, __FUNCTION__, ENSURE_ERROR_CANCELLED); + return FALSE; + } + + if(IS_HAS_ERROR(::listen(soListen, m_dwSocketListenQueue))) + { + SetLastError(SE_SOCKET_LISTEN, __FUNCTION__, ::WSAGetLastError()); + return FALSE; + } + } + + return TRUE; +} + +BOOL CTcpServer::CreateWorkerThreads() +{ + DWORD dwWorkerThreadCount = m_dwWorkerThreadCount +#ifdef USE_EXTERNAL_GC + + 1 +#endif + ; + + if(!m_ioDispatcher.Start(this, m_dwAcceptSocketCount, dwWorkerThreadCount)) + { + SetLastError(SE_WORKER_THREAD_CREATE, __FUNCTION__, ::WSAGetLastError()); + return FALSE; + } + + return TRUE; +} + +BOOL CTcpServer::StartAccept() +{ + for(int i = 0; i < (int)m_dwWorkerThreadCount; i++) + { + SOCKET& soListen = m_soListens[i]; + + if(!m_ioDispatcher.AddFD(i, soListen, EPOLLIN | EPOLLET, TO_PVOID(&soListen))) + { + SetLastError(SE_SOCKE_ATTACH_TO_CP, __FUNCTION__, ::WSAGetLastError()); + return FALSE; + } + } + +#ifdef USE_EXTERNAL_GC + m_fdGCTimer = m_ioDispatcher.AddTimer(m_dwWorkerThreadCount, GC_CHECK_INTERVAL, this); + + if(IS_INVALID_FD(m_fdGCTimer)) + { + SetLastError(SE_GC_START, __FUNCTION__, ::WSAGetLastError()); + return FALSE; + } +#endif + + return TRUE; +} + +BOOL CTcpServer::Stop() +{ + if(!CheckStoping()) + return FALSE; + + CloseListenSocket(); + + DisconnectClientSocket(); + WaitForClientSocketClose(); + WaitForWorkerThreadEnd(); + + ReleaseClientSocket(); + + FireShutdown(); + + ReleaseFreeSocket(); + + Reset(); + + return TRUE; +} + +void CTcpServer::CloseListenSocket() +{ + if(m_soListens) + { + for_each(m_soListens.get(), m_soListens.get() + m_dwWorkerThreadCount, [](SOCKET& sock) + { + if(sock != INVALID_FD) + { + ::ManualCloseSocket(sock); + sock = INVALID_FD; + } + }); + + ::WaitFor(100); + } +} + +void CTcpServer::DisconnectClientSocket() +{ + if(m_bfActiveSockets.Elements() == 0) + return; + + TSocketObjPtrPool::IndexSet indexes; + m_bfActiveSockets.CopyIndexes(indexes); + + for(auto it = indexes.begin(), end = indexes.end(); it != end; ++it) + Disconnect(*it); +} + +void CTcpServer::WaitForClientSocketClose() +{ + while(m_bfActiveSockets.Elements() > 0) + ::WaitFor(50); +} + +void CTcpServer::WaitForWorkerThreadEnd() +{ + m_ioDispatcher.Stop(); +} + +void CTcpServer::ReleaseClientSocket() +{ + VERIFY(m_bfActiveSockets.IsEmpty()); + m_bfActiveSockets.Reset(); +} + +void CTcpServer::ReleaseFreeSocket() +{ + m_lsFreeSocket.Clear(); + +#ifdef USE_EXTERNAL_GC + if(IS_VALID_FD(m_fdGCTimer)) + { + close(m_fdGCTimer); + m_fdGCTimer = INVALID_FD; + } +#endif + + ReleaseGCSocketObj(TRUE); + VERIFY(m_lsGCSocket.IsEmpty()); +} + +void CTcpServer::Reset() +{ + m_phSocket.Reset(); + m_bfObjPool.Clear(); + + m_rcBuffers = nullptr; + m_soListens = nullptr; + + m_enState = SS_STOPPED; + + m_evWait.SyncNotifyAll(); +} + +TSocketObj* CTcpServer::GetFreeSocketObj(CONNID dwConnID, SOCKET soClient) +{ + DWORD dwIndex; + TSocketObj* pSocketObj = nullptr; + + if(m_lsFreeSocket.TryLock(&pSocketObj, dwIndex)) + { + if(::GetTimeGap32(pSocketObj->freeTime) >= m_dwFreeSocketObjLockTime) + VERIFY(m_lsFreeSocket.ReleaseLock(nullptr, dwIndex)); + else + { + VERIFY(m_lsFreeSocket.ReleaseLock(pSocketObj, dwIndex)); + pSocketObj = nullptr; + } + } + + if(!pSocketObj) pSocketObj = CreateSocketObj(); + pSocketObj->Reset(dwConnID, soClient); + + return pSocketObj; +} + +TSocketObj* CTcpServer::CreateSocketObj() +{ + return TSocketObj::Construct(m_phSocket, m_bfObjPool); +} + +void CTcpServer::DeleteSocketObj(TSocketObj* pSocketObj) +{ + TSocketObj::Destruct(pSocketObj); +} + +void CTcpServer::AddFreeSocketObj(TSocketObj* pSocketObj, EnSocketCloseFlag enFlag, EnSocketOperation enOperation, int iErrorCode) +{ + if(!InvalidSocketObj(pSocketObj)) + return; + + CloseClientSocketObj(pSocketObj, enFlag, enOperation, iErrorCode); + + m_bfActiveSockets.Remove(pSocketObj->connID); + TSocketObj::Release(pSocketObj); + +#ifndef USE_EXTERNAL_GC + ReleaseGCSocketObj(); +#endif + + if(!m_lsFreeSocket.TryPut(pSocketObj)) + m_lsGCSocket.PushBack(pSocketObj); +} + +void CTcpServer::ReleaseGCSocketObj(BOOL bForce) +{ + ::ReleaseGCObj(m_lsGCSocket, m_dwFreeSocketObjLockTime, bForce); +} + +BOOL CTcpServer::InvalidSocketObj(TSocketObj* pSocketObj) +{ + return TSocketObj::InvalidSocketObj(pSocketObj); +} + +void CTcpServer::AddClientSocketObj(CONNID dwConnID, TSocketObj* pSocketObj, const HP_SOCKADDR& remoteAddr) +{ + ASSERT(FindSocketObj(dwConnID) == nullptr); + + pSocketObj->connTime = ::TimeGetTime(); + pSocketObj->activeTime = pSocketObj->connTime; + + remoteAddr.Copy(pSocketObj->remoteAddr); + pSocketObj->SetConnected(); + + VERIFY(m_bfActiveSockets.ReleaseLock(dwConnID, pSocketObj)); +} + +TSocketObj* CTcpServer::FindSocketObj(CONNID dwConnID) +{ + TSocketObj* pSocketObj = nullptr; + + if(m_bfActiveSockets.Get(dwConnID, &pSocketObj) != TSocketObjPtrPool::GR_VALID) + pSocketObj = nullptr; + + return pSocketObj; +} + +void CTcpServer::CloseClientSocketObj(TSocketObj* pSocketObj, EnSocketCloseFlag enFlag, EnSocketOperation enOperation, int iErrorCode, int iShutdownFlag) +{ + ASSERT(TSocketObj::IsExist(pSocketObj)); + + if(enFlag == SCF_CLOSE) + FireClose(pSocketObj, SO_CLOSE, SE_OK); + else if(enFlag == SCF_ERROR) + FireClose(pSocketObj, enOperation, iErrorCode); + + SOCKET socket = pSocketObj->socket; + pSocketObj->socket = INVALID_SOCKET; + + ::ManualCloseSocket(socket, iShutdownFlag); +} + +BOOL CTcpServer::GetListenAddress(TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort) +{ + ASSERT(lpszAddress != nullptr && iAddressLen > 0); + + if(!HasStarted()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + return ::GetSocketLocalAddress(m_soListens[0], lpszAddress, iAddressLen, usPort); +} + +BOOL CTcpServer::GetLocalAddress(CONNID dwConnID, TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort) +{ + ASSERT(lpszAddress != nullptr && iAddressLen > 0); + + TSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TSocketObj::IsValid(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + return ::GetSocketLocalAddress(pSocketObj->socket, lpszAddress, iAddressLen, usPort); +} + +BOOL CTcpServer::GetRemoteAddress(CONNID dwConnID, TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort) +{ + ASSERT(lpszAddress != nullptr && iAddressLen > 0); + + TSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TSocketObj::IsExist(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + ADDRESS_FAMILY usFamily; + return ::sockaddr_IN_2_A(pSocketObj->remoteAddr, usFamily, lpszAddress, iAddressLen, usPort); +} + +BOOL CTcpServer::SetConnectionExtra(CONNID dwConnID, PVOID pExtra) +{ + TSocketObj* pSocketObj = FindSocketObj(dwConnID); + return SetConnectionExtra(pSocketObj, pExtra); +} + +BOOL CTcpServer::SetConnectionExtra(TSocketObj* pSocketObj, PVOID pExtra) +{ + if(!TSocketObj::IsExist(pSocketObj)) + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + else + { + pSocketObj->extra = pExtra; + return TRUE; + } + + return FALSE; +} + +BOOL CTcpServer::GetConnectionExtra(CONNID dwConnID, PVOID* ppExtra) +{ + TSocketObj* pSocketObj = FindSocketObj(dwConnID); + return GetConnectionExtra(pSocketObj, ppExtra); +} + +BOOL CTcpServer::GetConnectionExtra(TSocketObj* pSocketObj, PVOID* ppExtra) +{ + ASSERT(ppExtra != nullptr); + + if(!TSocketObj::IsExist(pSocketObj)) + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + else + { + *ppExtra = pSocketObj->extra; + return TRUE; + } + + return FALSE; +} + +BOOL CTcpServer::SetConnectionReserved(CONNID dwConnID, PVOID pReserved) +{ + TSocketObj* pSocketObj = FindSocketObj(dwConnID); + return SetConnectionReserved(pSocketObj, pReserved); +} + +BOOL CTcpServer::SetConnectionReserved(TSocketObj* pSocketObj, PVOID pReserved) +{ + if(!TSocketObj::IsExist(pSocketObj)) + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + else + { + pSocketObj->reserved = pReserved; + return TRUE; + } + + return FALSE; +} + +BOOL CTcpServer::GetConnectionReserved(CONNID dwConnID, PVOID* ppReserved) +{ + TSocketObj* pSocketObj = FindSocketObj(dwConnID); + return GetConnectionReserved(pSocketObj, ppReserved); +} + +BOOL CTcpServer::GetConnectionReserved(TSocketObj* pSocketObj, PVOID* ppReserved) +{ + ASSERT(ppReserved != nullptr); + + if(!TSocketObj::IsExist(pSocketObj)) + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + else + { + *ppReserved = pSocketObj->reserved; + return TRUE; + } + + return FALSE; +} + +BOOL CTcpServer::SetConnectionReserved2(CONNID dwConnID, PVOID pReserved2) +{ + TSocketObj* pSocketObj = FindSocketObj(dwConnID); + return SetConnectionReserved2(pSocketObj, pReserved2); +} + +BOOL CTcpServer::SetConnectionReserved2(TSocketObj* pSocketObj, PVOID pReserved2) +{ + if(!TSocketObj::IsExist(pSocketObj)) + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + else + { + pSocketObj->reserved2 = pReserved2; + return TRUE; + } + + return FALSE; +} + +BOOL CTcpServer::GetConnectionReserved2(CONNID dwConnID, PVOID* ppReserved2) +{ + TSocketObj* pSocketObj = FindSocketObj(dwConnID); + return GetConnectionReserved2(pSocketObj, ppReserved2); +} + +BOOL CTcpServer::GetConnectionReserved2(TSocketObj* pSocketObj, PVOID* ppReserved2) +{ + ASSERT(ppReserved2 != nullptr); + + if(!TSocketObj::IsExist(pSocketObj)) + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + else + { + *ppReserved2 = pSocketObj->reserved2; + return TRUE; + } + + return FALSE; +} + +BOOL CTcpServer::IsPauseReceive(CONNID dwConnID, BOOL& bPaused) +{ + TSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TSocketObj::IsValid(pSocketObj)) + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + else + { + bPaused = pSocketObj->paused; + return TRUE; + } + + return FALSE; +} + +BOOL CTcpServer::IsConnected(CONNID dwConnID) +{ + TSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TSocketObj::IsValid(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + return pSocketObj->HasConnected(); +} + +BOOL CTcpServer::GetPendingDataLength(CONNID dwConnID, int& iPending) +{ + TSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TSocketObj::IsValid(pSocketObj)) + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + else + { + iPending = pSocketObj->Pending(); + return TRUE; + } + + return FALSE; +} + +DWORD CTcpServer::GetConnectionCount() +{ + return m_bfActiveSockets.Elements(); +} + +BOOL CTcpServer::GetAllConnectionIDs(CONNID pIDs[], DWORD& dwCount) +{ + return m_bfActiveSockets.GetAllElementIndexes(pIDs, dwCount); +} + +BOOL CTcpServer::GetConnectPeriod(CONNID dwConnID, DWORD& dwPeriod) +{ + BOOL isOK = TRUE; + TSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(TSocketObj::IsValid(pSocketObj)) + dwPeriod = ::GetTimeGap32(pSocketObj->connTime); + else + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + isOK = FALSE; + } + + return isOK; +} + +BOOL CTcpServer::GetSilencePeriod(CONNID dwConnID, DWORD& dwPeriod) +{ + if(!m_bMarkSilence) + return FALSE; + + BOOL isOK = TRUE; + TSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(TSocketObj::IsValid(pSocketObj)) + dwPeriod = ::GetTimeGap32(pSocketObj->activeTime); + else + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + isOK = FALSE; + } + + return isOK; +} + +BOOL CTcpServer::Disconnect(CONNID dwConnID, BOOL bForce) +{ + TSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TSocketObj::IsValid(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + return m_ioDispatcher.SendCommandByFD(pSocketObj->socket, DISP_CMD_DISCONNECT, dwConnID, bForce); +} + +BOOL CTcpServer::DisconnectLongConnections(DWORD dwPeriod, BOOL bForce) +{ + if(dwPeriod > MAX_CONNECTION_PERIOD) + return FALSE; + if(m_bfActiveSockets.Elements() == 0) + return TRUE; + + DWORD now = ::TimeGetTime(); + + TSocketObjPtrPool::IndexSet indexes; + m_bfActiveSockets.CopyIndexes(indexes); + + for(auto it = indexes.begin(), end = indexes.end(); it != end; ++it) + { + CONNID connID = *it; + TSocketObj* pSocketObj = FindSocketObj(connID); + + if(TSocketObj::IsValid(pSocketObj) && (int)(now - pSocketObj->connTime) >= (int)dwPeriod) + Disconnect(connID, bForce); + } + + return TRUE; +} + +BOOL CTcpServer::DisconnectSilenceConnections(DWORD dwPeriod, BOOL bForce) +{ + if(!m_bMarkSilence) + return FALSE; + if(dwPeriod > MAX_CONNECTION_PERIOD) + return FALSE; + if(m_bfActiveSockets.Elements() == 0) + return TRUE; + + DWORD now = ::TimeGetTime(); + + TSocketObjPtrPool::IndexSet indexes; + m_bfActiveSockets.CopyIndexes(indexes); + + for(auto it = indexes.begin(), end = indexes.end(); it != end; ++it) + { + CONNID connID = *it; + TSocketObj* pSocketObj = FindSocketObj(connID); + + if(TSocketObj::IsValid(pSocketObj) && (int)(now - pSocketObj->activeTime) >= (int)dwPeriod) + Disconnect(connID, bForce); + } + + return TRUE; +} + +BOOL CTcpServer::PauseReceive(CONNID dwConnID, BOOL bPause) +{ + TSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TSocketObj::IsValid(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + if(pSocketObj->paused == bPause) + return TRUE; + + pSocketObj->paused = bPause; + + if(!bPause) + return m_ioDispatcher.SendCommandByFD(pSocketObj->socket, DISP_CMD_UNPAUSE, pSocketObj->connID); + + return TRUE; +} + +BOOL CTcpServer::OnBeforeProcessIo(const TDispContext* pContext, PVOID pv, UINT events) +{ + if(pv == &m_soListens[pContext->GetIndex()]) + { + HandleAccept(pContext, events); + return FALSE; + } + else if(pv == this) + { + ReleaseGCSocketObj(FALSE); + ::ReadTimer(m_fdGCTimer); + + return FALSE; + } + + TSocketObj* pSocketObj = (TSocketObj*)(pv); + + if(!TSocketObj::IsValid(pSocketObj)) + return FALSE; + + if(events & _EPOLL_ALL_ERROR_EVENTS) + pSocketObj->SetConnected(FALSE); + + pSocketObj->Increment(); + + if(!TSocketObj::IsValid(pSocketObj)) + { + pSocketObj->Decrement(); + return FALSE; + } + + return TRUE; +} + +VOID CTcpServer::OnAfterProcessIo(const TDispContext* pContext, PVOID pv, UINT events, BOOL rs) +{ + TSocketObj* pSocketObj = (TSocketObj*)(pv); + + if(TSocketObj::IsValid(pSocketObj)) + { + ASSERT(rs && !(events & (EPOLLERR | EPOLLHUP | EPOLLRDHUP))); + + UINT evts = (pSocketObj->IsPending() ? EPOLLOUT : 0) | (pSocketObj->IsPaused() ? 0 : EPOLLIN); + m_ioDispatcher.ModFD(pSocketObj->socket, evts | EPOLLRDHUP, pSocketObj); + } + + pSocketObj->Decrement(); +} + +VOID CTcpServer::OnCommand(const TDispContext* pContext, TDispCommand* pCmd) +{ + switch(pCmd->type) + { + case DISP_CMD_SEND: + HandleCmdSend(pContext, (CONNID)(pCmd->wParam)); + break; + case DISP_CMD_UNPAUSE: + HandleCmdUnpause(pContext, (CONNID)(pCmd->wParam)); + break; + case DISP_CMD_DISCONNECT: + HandleCmdDisconnect(pContext, (CONNID)(pCmd->wParam), (BOOL)pCmd->lParam); + break; + } +} + +VOID CTcpServer::HandleCmdSend(const TDispContext* pContext, CONNID dwConnID) +{ + TSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(TSocketObj::IsValid(pSocketObj) && pSocketObj->IsPending()) + m_ioDispatcher.ProcessIo(pContext, pSocketObj, EPOLLOUT); +} + +VOID CTcpServer::HandleCmdUnpause(const TDispContext* pContext, CONNID dwConnID) +{ + TSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TSocketObj::IsValid(pSocketObj)) + return; + + if(BeforeUnpause(pSocketObj)) + m_ioDispatcher.ProcessIo(pContext, pSocketObj, EPOLLIN); + else + AddFreeSocketObj(pSocketObj, SCF_ERROR, SO_RECEIVE, ENSURE_ERROR_CANCELLED); +} + +VOID CTcpServer::HandleCmdDisconnect(const TDispContext* pContext, CONNID dwConnID, BOOL bForce) +{ + TSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(TSocketObj::IsValid(pSocketObj)) + m_ioDispatcher.ProcessIo(pContext, pSocketObj, EPOLLHUP); +} + +BOOL CTcpServer::OnReadyRead(const TDispContext* pContext, PVOID pv, UINT events) +{ + return HandleReceive(pContext, (TSocketObj*)pv, RETRIVE_EVENT_FLAG_H(events)); +} + +BOOL CTcpServer::OnReadyWrite(const TDispContext* pContext, PVOID pv, UINT events) +{ + return HandleSend(pContext, (TSocketObj*)pv, RETRIVE_EVENT_FLAG_H(events)); +} + +BOOL CTcpServer::OnHungUp(const TDispContext* pContext, PVOID pv, UINT events) +{ + return HandleClose(pContext, (TSocketObj*)pv, SCF_CLOSE, events); +} + +BOOL CTcpServer::OnError(const TDispContext* pContext, PVOID pv, UINT events) +{ + return HandleClose(pContext, (TSocketObj*)pv, SCF_ERROR, events); +} + +VOID CTcpServer::OnDispatchThreadStart(THR_ID tid) +{ + OnWorkerThreadStart(tid); +} + +VOID CTcpServer::OnDispatchThreadEnd(THR_ID tid) +{ + OnWorkerThreadEnd(tid); +} + +BOOL CTcpServer::HandleClose(const TDispContext* pContext, TSocketObj* pSocketObj, EnSocketCloseFlag enFlag, UINT events) +{ + EnSocketOperation enOperation = SO_CLOSE; + + if(events & _EPOLL_HUNGUP_EVENTS) + enOperation = SO_CLOSE; + else if(events & EPOLLIN) + enOperation = SO_RECEIVE; + else if(events & EPOLLOUT) + enOperation = SO_SEND; + + int iErrorCode = 0; + + if(enFlag == SCF_ERROR) + iErrorCode = ::SSO_GetError(pSocketObj->socket); + + AddFreeSocketObj(pSocketObj, enFlag, enOperation, iErrorCode); + + return TRUE; +} + +BOOL CTcpServer::HandleAccept(const TDispContext* pContext, UINT events) +{ + if(events & _EPOLL_ALL_ERROR_EVENTS) + { + ASSERT(!HasStarted()); + return FALSE; + } + + while(TRUE) + { + HP_SOCKADDR addr; + + socklen_t addrLen = (socklen_t)addr.AddrSize(); + SOCKET soClient = ::accept(m_soListens[pContext->GetIndex()], addr.Addr(), &addrLen); + + if(soClient == INVALID_SOCKET) + { + int code = ::WSAGetLastError(); + + switch(code) + { + case ERROR_WOULDBLOCK : return TRUE; + case ERROR_CONNABORTED : continue; + case ERROR_HANDLES_CLOSED : return FALSE; + default : ERROR_EXIT2(EXIT_CODE_SOFTWARE, code); + } + } + + if(!::fcntl_SETFL(soClient, O_NOATIME | O_NONBLOCK | O_CLOEXEC)) + { + ::ManualCloseSocket(soClient, SHUT_RDWR); + continue; + } + + CONNID dwConnID = 0; + + if(!m_bfActiveSockets.AcquireLock(dwConnID)) + { + ::ManualCloseSocket(soClient, SHUT_RDWR); + continue; + } + + TSocketObj* pSocketObj = GetFreeSocketObj(dwConnID, soClient); + + AddClientSocketObj(dwConnID, pSocketObj, addr); + + if(TRIGGER(FireAccept(pSocketObj)) == HR_ERROR) + { + AddFreeSocketObj(pSocketObj, SCF_NONE); + continue; + } + + UINT evts = (pSocketObj->IsPending() ? EPOLLOUT : 0) | (pSocketObj->IsPaused() ? 0 : EPOLLIN); + + if(!m_ioDispatcher.AddFD(pSocketObj->socket, evts | EPOLLRDHUP, pSocketObj)) + { + AddFreeSocketObj(pSocketObj, SCF_ERROR, SO_ACCEPT, ::WSAGetLastError()); + continue; + } + } + + return TRUE; +} + +BOOL CTcpServer::HandleReceive(const TDispContext* pContext, TSocketObj* pSocketObj, int flag) +{ + ASSERT(TSocketObj::IsValid(pSocketObj)); + + if(m_bMarkSilence) pSocketObj->activeTime = ::TimeGetTime(); + + CBufferPtr& buffer = m_rcBuffers[pContext->GetIndex()]; + + int reads = flag ? -1 : MAX_CONTINUE_READS; + + for(int i = 0; i < reads || reads < 0; i++) + { + if(pSocketObj->paused) + break; + + int rc = (int)read(pSocketObj->socket, buffer.Ptr(), buffer.Size()); + + if(rc > 0) + { + if(TRIGGER(FireReceive(pSocketObj, buffer.Ptr(), rc)) == HR_ERROR) + { + TRACE(" OnReceive() event return 'HR_ERROR', connection will be closed !", pSocketObj->connID); + + AddFreeSocketObj(pSocketObj, SCF_ERROR, SO_RECEIVE, ENSURE_ERROR_CANCELLED); + return FALSE; + } + } + else if(rc == 0) + { + AddFreeSocketObj(pSocketObj, SCF_CLOSE, SO_RECEIVE, SE_OK); + return FALSE; + } + else + { + ASSERT(rc == SOCKET_ERROR); + + int code = ::WSAGetLastError(); + + if(code == ERROR_WOULDBLOCK) + break; + + AddFreeSocketObj(pSocketObj, SCF_ERROR, SO_RECEIVE, code); + return FALSE; + } + } + + return TRUE; +} + +BOOL CTcpServer::HandleSend(const TDispContext* pContext, TSocketObj* pSocketObj, int flag) +{ + ASSERT(TSocketObj::IsValid(pSocketObj)); + + if(!pSocketObj->IsPending()) + return TRUE; + + BOOL bBlocked = FALSE; + int writes = flag ? -1 : MAX_CONTINUE_WRITES; + + TBufferObjList& sndBuff = pSocketObj->sndBuff; + TItemPtr itPtr(sndBuff); + + for(int i = 0; i < writes || writes < 0; i++) + { + { + CReentrantCriSecLock locallock(pSocketObj->csSend); + itPtr = sndBuff.PopFront(); + } + + if(!itPtr.IsValid()) + break; + + ASSERT(!itPtr->IsEmpty()); + + if(!SendItem(pSocketObj, itPtr, bBlocked)) + return FALSE; + + if(bBlocked) + { + ASSERT(!itPtr->IsEmpty()); + + CReentrantCriSecLock locallock(pSocketObj->csSend); + sndBuff.PushFront(itPtr.Detach()); + + break; + } + } + + return TRUE; +} + +BOOL CTcpServer::SendItem(TSocketObj* pSocketObj, TItem* pItem, BOOL& bBlocked) +{ + while(!pItem->IsEmpty()) + { + int rc = (int)write(pSocketObj->socket, pItem->Ptr(), pItem->Size()); + + if(rc > 0) + { + if(TRIGGER(FireSend(pSocketObj, pItem->Ptr(), rc)) == HR_ERROR) + { + TRACE(" OnSend() event should not return 'HR_ERROR' !!", pSocketObj->connID); + ASSERT(FALSE); + } + + pItem->Reduce(rc); + } + else if(rc == SOCKET_ERROR) + { + int code = ::WSAGetLastError(); + + if(code == ERROR_WOULDBLOCK) + { + bBlocked = TRUE; + break; + } + else + { + AddFreeSocketObj(pSocketObj, SCF_ERROR, SO_SEND, code); + return FALSE; + } + } + else + ASSERT(FALSE); + } + + return TRUE; +} + +BOOL CTcpServer::Send(CONNID dwConnID, const BYTE* pBuffer, int iLength, int iOffset) +{ + ASSERT(pBuffer && iLength > 0); + + if(iOffset != 0) pBuffer += iOffset; + + WSABUF buffer; + buffer.len = iLength; + buffer.buf = (BYTE*)pBuffer; + + return SendPackets(dwConnID, &buffer, 1); +} + +BOOL CTcpServer::DoSendPackets(CONNID dwConnID, const WSABUF pBuffers[], int iCount) +{ + ASSERT(pBuffers && iCount > 0); + + TSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TSocketObj::IsValid(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + return DoSendPackets(pSocketObj, pBuffers, iCount); +} + +BOOL CTcpServer::DoSendPackets(TSocketObj* pSocketObj, const WSABUF pBuffers[], int iCount) +{ + ASSERT(pSocketObj && pBuffers && iCount > 0); + + int result = NO_ERROR; + + if(pBuffers && iCount > 0) + { + CLocalSafeCounter localcounter(*pSocketObj); + CReentrantCriSecLock locallock(pSocketObj->csSend); + + if(TSocketObj::IsValid(pSocketObj)) + result = SendInternal(pSocketObj, pBuffers, iCount); + else + result = ERROR_OBJECT_NOT_FOUND; + } + else + result = ERROR_INVALID_PARAMETER; + + if(result != NO_ERROR) + ::SetLastError(result); + + return (result == NO_ERROR); +} + +int CTcpServer::SendInternal(TSocketObj* pSocketObj, const WSABUF pBuffers[], int iCount) +{ + BOOL bPending = pSocketObj->IsPending(); + + for(int i = 0; i < iCount; i++) + { + int iBufLen = pBuffers[i].len; + + if(iBufLen > 0) + { + BYTE* pBuffer = (BYTE*)pBuffers[i].buf; + ASSERT(pBuffer); + + pSocketObj->sndBuff.Cat(pBuffer, iBufLen); + ASSERT(pSocketObj->sndBuff.Length() > 0); + } + } + + if(!bPending && pSocketObj->IsPending()) + { + if(!m_ioDispatcher.SendCommandByFD(pSocketObj->socket, DISP_CMD_SEND, pSocketObj->connID)) + return ::GetLastError(); + } + + return NO_ERROR; +} + +BOOL CTcpServer::SendSmallFile(CONNID dwConnID, LPCTSTR lpszFileName, const LPWSABUF pHead, const LPWSABUF pTail) +{ + CFile file; + CFileMapping fmap; + WSABUF szBuf[3]; + + HRESULT hr = ::MakeSmallFilePackage(lpszFileName, file, fmap, szBuf, pHead, pTail); + + if(FAILED(hr)) + { + ::SetLastError(hr); + return FALSE; + } + + return SendPackets(dwConnID, szBuf, 3); +} diff --git a/TcpServer.h b/TcpServer.h new file mode 100644 index 0000000..a13f44f --- /dev/null +++ b/TcpServer.h @@ -0,0 +1,306 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "SocketHelper.h" +#include "./common/GeneralHelper.h" +#include "./common/IODispatcher.h" + +class CTcpServer : public ITcpServer, private CIOHandler +{ +public: + virtual BOOL Start (LPCTSTR lpszBindAddress, USHORT usPort); + virtual BOOL Stop (); + virtual BOOL Send (CONNID dwConnID, const BYTE* pBuffer, int iLength, int iOffset = 0); + virtual BOOL SendSmallFile (CONNID dwConnID, LPCTSTR lpszFileName, const LPWSABUF pHead = nullptr, const LPWSABUF pTail = nullptr); + virtual BOOL SendPackets (CONNID dwConnID, const WSABUF pBuffers[], int iCount) {return DoSendPackets(dwConnID, pBuffers, iCount);} + virtual BOOL PauseReceive (CONNID dwConnID, BOOL bPause = TRUE); + virtual BOOL Wait (DWORD dwMilliseconds = INFINITE) {return m_evWait.WaitFor(dwMilliseconds, WAIT_FOR_STOP_PREDICATE);} + virtual BOOL HasStarted () {return m_enState == SS_STARTED || m_enState == SS_STARTING;} + virtual EnServiceState GetState () {return m_enState;} + virtual BOOL Disconnect (CONNID dwConnID, BOOL bForce = TRUE); + virtual BOOL DisconnectLongConnections (DWORD dwPeriod, BOOL bForce = TRUE); + virtual BOOL DisconnectSilenceConnections(DWORD dwPeriod, BOOL bForce = TRUE); + virtual BOOL GetListenAddress (TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort); + virtual BOOL GetLocalAddress (CONNID dwConnID, TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort); + virtual BOOL GetRemoteAddress (CONNID dwConnID, TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort); + + virtual BOOL IsConnected (CONNID dwConnID); + virtual BOOL IsPauseReceive (CONNID dwConnID, BOOL& bPaused); + virtual BOOL GetPendingDataLength (CONNID dwConnID, int& iPending); + virtual DWORD GetConnectionCount (); + virtual BOOL GetAllConnectionIDs (CONNID pIDs[], DWORD& dwCount); + virtual BOOL GetConnectPeriod (CONNID dwConnID, DWORD& dwPeriod); + virtual BOOL GetSilencePeriod (CONNID dwConnID, DWORD& dwPeriod); + virtual EnSocketError GetLastError () {return m_enLastError;} + virtual LPCTSTR GetLastErrorDesc () {return ::GetSocketErrorDesc(m_enLastError);} + +#ifdef _SSL_SUPPORT + virtual BOOL SetupSSLContext (int iVerifyMode = SSL_VM_NONE, LPCTSTR lpszPemCertFile = nullptr, LPCTSTR lpszPemKeyFile = nullptr, LPCTSTR lpszKeyPassword = nullptr, LPCTSTR lpszCAPemCertFileOrPath = nullptr, Fn_SNI_ServerNameCallback fnServerNameCallback = nullptr) {return FALSE;} + virtual BOOL SetupSSLContextByMemory(int iVerifyMode = SSL_VM_NONE, LPCSTR lpszPemCert = nullptr, LPCSTR lpszPemKey = nullptr, LPCSTR lpszKeyPassword = nullptr, LPCSTR lpszCAPemCert = nullptr, Fn_SNI_ServerNameCallback fnServerNameCallback = nullptr) {return FALSE;} + virtual int AddSSLContext (int iVerifyMode = SSL_VM_NONE, LPCTSTR lpszPemCertFile = nullptr, LPCTSTR lpszPemKeyFile = nullptr, LPCTSTR lpszKeyPassword = nullptr, LPCTSTR lpszCAPemCertFileOrPath = nullptr) {return FALSE;} + virtual int AddSSLContextByMemory(int iVerifyMode = SSL_VM_NONE, LPCSTR lpszPemCert = nullptr, LPCSTR lpszPemKey = nullptr, LPCSTR lpszKeyPassword = nullptr, LPCSTR lpszCAPemCert = nullptr) {return FALSE;} + virtual BOOL BindSSLServerName (LPCTSTR lpszServerName, int iContextIndex) {return FALSE;} + virtual void CleanupSSLContext () {} + + virtual BOOL StartSSLHandShake (CONNID dwConnID) {return FALSE;} + virtual void SetSSLAutoHandShake(BOOL bAutoHandShake) {} + virtual BOOL IsSSLAutoHandShake () {return FALSE;} + virtual void SetSSLCipherList (LPCTSTR lpszCipherList){} + virtual LPCTSTR GetSSLCipherList() {return nullptr;} + virtual BOOL GetSSLSessionInfo(CONNID dwConnID, EnSSLSessionInfo enInfo, LPVOID* lppInfo) {return FALSE;} + +protected: + virtual BOOL StartSSLHandShake (TSocketObj* pSocketObj){return FALSE;} +#endif + +private: + virtual BOOL OnBeforeProcessIo(const TDispContext* pContext, PVOID pv, UINT events) override; + virtual VOID OnAfterProcessIo(const TDispContext* pContext, PVOID pv, UINT events, BOOL rs) override; + virtual VOID OnCommand(const TDispContext* pContext, TDispCommand* pCmd) override; + virtual BOOL OnReadyRead(const TDispContext* pContext, PVOID pv, UINT events) override; + virtual BOOL OnReadyWrite(const TDispContext* pContext, PVOID pv, UINT events) override; + virtual BOOL OnHungUp(const TDispContext* pContext, PVOID pv, UINT events) override; + virtual BOOL OnError(const TDispContext* pContext, PVOID pv, UINT events) override; + virtual VOID OnDispatchThreadStart(THR_ID tid) override; + virtual VOID OnDispatchThreadEnd(THR_ID tid) override; + +public: + virtual BOOL IsSecure () {return FALSE;} + + virtual BOOL SetConnectionExtra(CONNID dwConnID, PVOID pExtra); + virtual BOOL GetConnectionExtra(CONNID dwConnID, PVOID* ppExtra); + + virtual void SetReuseAddressPolicy (EnReuseAddressPolicy enReusePolicy) {ENSURE_HAS_STOPPED(); ASSERT(m_enReusePolicy == enReusePolicy);} + virtual void SetSendPolicy (EnSendPolicy enSendPolicy) {ENSURE_HAS_STOPPED(); ASSERT(m_enSendPolicy == enSendPolicy);} + virtual void SetOnSendSyncPolicy (EnOnSendSyncPolicy enOnSendSyncPolicy) {ENSURE_HAS_STOPPED(); ASSERT(m_enOnSendSyncPolicy == enOnSendSyncPolicy);} + virtual void SetMaxConnectionCount (DWORD dwMaxConnectionCount) {ENSURE_HAS_STOPPED(); m_dwMaxConnectionCount = dwMaxConnectionCount;} + virtual void SetWorkerThreadCount (DWORD dwWorkerThreadCount) {ENSURE_HAS_STOPPED(); m_dwWorkerThreadCount = dwWorkerThreadCount;} + virtual void SetSocketListenQueue (DWORD dwSocketListenQueue) {ENSURE_HAS_STOPPED(); m_dwSocketListenQueue = dwSocketListenQueue;} + virtual void SetAcceptSocketCount (DWORD dwAcceptSocketCount) {ENSURE_HAS_STOPPED(); m_dwAcceptSocketCount = dwAcceptSocketCount;} + virtual void SetSocketBufferSize (DWORD dwSocketBufferSize) {ENSURE_HAS_STOPPED(); m_dwSocketBufferSize = dwSocketBufferSize;} + virtual void SetFreeSocketObjLockTime (DWORD dwFreeSocketObjLockTime) {ENSURE_HAS_STOPPED(); m_dwFreeSocketObjLockTime = dwFreeSocketObjLockTime;} + virtual void SetFreeSocketObjPool (DWORD dwFreeSocketObjPool) {ENSURE_HAS_STOPPED(); m_dwFreeSocketObjPool = dwFreeSocketObjPool;} + virtual void SetFreeBufferObjPool (DWORD dwFreeBufferObjPool) {ENSURE_HAS_STOPPED(); m_dwFreeBufferObjPool = dwFreeBufferObjPool;} + virtual void SetFreeSocketObjHold (DWORD dwFreeSocketObjHold) {ENSURE_HAS_STOPPED(); m_dwFreeSocketObjHold = dwFreeSocketObjHold;} + virtual void SetFreeBufferObjHold (DWORD dwFreeBufferObjHold) {ENSURE_HAS_STOPPED(); m_dwFreeBufferObjHold = dwFreeBufferObjHold;} + virtual void SetKeepAliveTime (DWORD dwKeepAliveTime) {ENSURE_HAS_STOPPED(); m_dwKeepAliveTime = dwKeepAliveTime;} + virtual void SetKeepAliveInterval (DWORD dwKeepAliveInterval) {ENSURE_HAS_STOPPED(); m_dwKeepAliveInterval = dwKeepAliveInterval;} + virtual void SetMarkSilence (BOOL bMarkSilence) {ENSURE_HAS_STOPPED(); m_bMarkSilence = bMarkSilence;} + virtual void SetNoDelay (BOOL bNoDelay) {ENSURE_HAS_STOPPED(); m_bNoDelay = bNoDelay;} + + virtual EnReuseAddressPolicy GetReuseAddressPolicy () {return m_enReusePolicy;} + virtual EnSendPolicy GetSendPolicy () {return m_enSendPolicy;} + virtual EnOnSendSyncPolicy GetOnSendSyncPolicy () {return m_enOnSendSyncPolicy;} + virtual DWORD GetMaxConnectionCount () {return m_dwMaxConnectionCount;} + virtual DWORD GetWorkerThreadCount () {return m_dwWorkerThreadCount;} + virtual DWORD GetSocketListenQueue () {return m_dwSocketListenQueue;} + virtual DWORD GetAcceptSocketCount () {return m_dwAcceptSocketCount;} + virtual DWORD GetSocketBufferSize () {return m_dwSocketBufferSize;} + virtual DWORD GetFreeSocketObjLockTime () {return m_dwFreeSocketObjLockTime;} + virtual DWORD GetFreeSocketObjPool () {return m_dwFreeSocketObjPool;} + virtual DWORD GetFreeBufferObjPool () {return m_dwFreeBufferObjPool;} + virtual DWORD GetFreeSocketObjHold () {return m_dwFreeSocketObjHold;} + virtual DWORD GetFreeBufferObjHold () {return m_dwFreeBufferObjHold;} + virtual DWORD GetKeepAliveTime () {return m_dwKeepAliveTime;} + virtual DWORD GetKeepAliveInterval () {return m_dwKeepAliveInterval;} + virtual BOOL IsMarkSilence () {return m_bMarkSilence;} + virtual BOOL IsNoDelay () {return m_bNoDelay;} + +protected: + virtual EnHandleResult FirePrepareListen(SOCKET soListen) + {return DoFirePrepareListen(soListen);} + virtual EnHandleResult FireAccept(TSocketObj* pSocketObj) + { + EnHandleResult rs = DoFireAccept(pSocketObj); + if(rs != HR_ERROR) rs = FireHandShake(pSocketObj); + return rs; + } + virtual EnHandleResult FireHandShake(TSocketObj* pSocketObj) + {return DoFireHandShake(pSocketObj);} + virtual EnHandleResult FireReceive(TSocketObj* pSocketObj, const BYTE* pData, int iLength) + {return DoFireReceive(pSocketObj, pData, iLength);} + virtual EnHandleResult FireReceive(TSocketObj* pSocketObj, int iLength) + {return DoFireReceive(pSocketObj, iLength);} + virtual EnHandleResult FireSend(TSocketObj* pSocketObj, const BYTE* pData, int iLength) + {return DoFireSend(pSocketObj, pData, iLength);} + virtual EnHandleResult FireClose(TSocketObj* pSocketObj, EnSocketOperation enOperation, int iErrorCode) + {return DoFireClose(pSocketObj, enOperation, iErrorCode);} + virtual EnHandleResult FireShutdown() + {return DoFireShutdown();} + + virtual EnHandleResult DoFirePrepareListen(SOCKET soListen) + {return m_pListener->OnPrepareListen(this, soListen);} + virtual EnHandleResult DoFireAccept(TSocketObj* pSocketObj) + {return m_pListener->OnAccept(this, pSocketObj->connID, pSocketObj->socket);} + virtual EnHandleResult DoFireHandShake(TSocketObj* pSocketObj) + {return m_pListener->OnHandShake(this, pSocketObj->connID);} + virtual EnHandleResult DoFireReceive(TSocketObj* pSocketObj, const BYTE* pData, int iLength) + {return m_pListener->OnReceive(this, pSocketObj->connID, pData, iLength);} + virtual EnHandleResult DoFireReceive(TSocketObj* pSocketObj, int iLength) + {return m_pListener->OnReceive(this, pSocketObj->connID, iLength);} + virtual EnHandleResult DoFireSend(TSocketObj* pSocketObj, const BYTE* pData, int iLength) + {return m_pListener->OnSend(this, pSocketObj->connID, pData, iLength);} + virtual EnHandleResult DoFireClose(TSocketObj* pSocketObj, EnSocketOperation enOperation, int iErrorCode) + {return m_pListener->OnClose(this, pSocketObj->connID, enOperation, iErrorCode);} + virtual EnHandleResult DoFireShutdown() + {return m_pListener->OnShutdown(this);} + + void SetLastError(EnSocketError code, LPCSTR func, int ec); + virtual BOOL CheckParams(); + virtual void PrepareStart(); + virtual void Reset(); + + virtual BOOL BeforeUnpause(TSocketObj* pSocketObj) {return TRUE;} + + virtual void OnWorkerThreadStart(THR_ID tid) {} + virtual void OnWorkerThreadEnd(THR_ID tid) {} + + virtual void ReleaseGCSocketObj(BOOL bForce = FALSE); + + BOOL DoSendPackets(CONNID dwConnID, const WSABUF pBuffers[], int iCount); + BOOL DoSendPackets(TSocketObj* pSocketObj, const WSABUF pBuffers[], int iCount); + TSocketObj* FindSocketObj(CONNID dwConnID); + +protected: + BOOL SetConnectionExtra(TSocketObj* pSocketObj, PVOID pExtra); + BOOL GetConnectionExtra(TSocketObj* pSocketObj, PVOID* ppExtra); + BOOL SetConnectionReserved(CONNID dwConnID, PVOID pReserved); + BOOL GetConnectionReserved(CONNID dwConnID, PVOID* ppReserved); + BOOL SetConnectionReserved(TSocketObj* pSocketObj, PVOID pReserved); + BOOL GetConnectionReserved(TSocketObj* pSocketObj, PVOID* ppReserved); + BOOL SetConnectionReserved2(CONNID dwConnID, PVOID pReserved2); + BOOL GetConnectionReserved2(CONNID dwConnID, PVOID* ppReserved2); + BOOL SetConnectionReserved2(TSocketObj* pSocketObj, PVOID pReserved2); + BOOL GetConnectionReserved2(TSocketObj* pSocketObj, PVOID* ppReserved2); + +private: + BOOL CheckStarting(); + BOOL CheckStoping(); + BOOL CreateListenSocket(LPCTSTR lpszBindAddress, USHORT usPort); + BOOL CreateWorkerThreads(); + BOOL StartAccept(); + + void CloseListenSocket(); + void DisconnectClientSocket(); + void WaitForClientSocketClose(); + void ReleaseClientSocket(); + void ReleaseFreeSocket(); + void WaitForWorkerThreadEnd(); + + TSocketObj* GetFreeSocketObj(CONNID dwConnID, SOCKET soClient); + TSocketObj* CreateSocketObj(); + void AddFreeSocketObj (TSocketObj* pSocketObj, EnSocketCloseFlag enFlag = SCF_NONE, EnSocketOperation enOperation = SO_UNKNOWN, int iErrorCode = 0); + void DeleteSocketObj (TSocketObj* pSocketObj); + BOOL InvalidSocketObj (TSocketObj* pSocketObj); + void AddClientSocketObj (CONNID dwConnID, TSocketObj* pSocketObj, const HP_SOCKADDR& remoteAddr); + void CloseClientSocketObj(TSocketObj* pSocketObj, EnSocketCloseFlag enFlag = SCF_NONE, EnSocketOperation enOperation = SO_UNKNOWN, int iErrorCode = 0, int iShutdownFlag = SHUT_WR); + +private: + VOID HandleCmdSend (const TDispContext* pContext, CONNID dwConnID); + VOID HandleCmdUnpause (const TDispContext* pContext, CONNID dwConnID); + VOID HandleCmdDisconnect(const TDispContext* pContext, CONNID dwConnID, BOOL bForce); + BOOL HandleAccept (const TDispContext* pContext, UINT events); + BOOL HandleReceive (const TDispContext* pContext, TSocketObj* pSocketObj, int flag); + BOOL HandleSend (const TDispContext* pContext, TSocketObj* pSocketObj, int flag); + BOOL HandleClose (const TDispContext* pContext, TSocketObj* pSocketObj, EnSocketCloseFlag enFlag, UINT events); + + int SendInternal (TSocketObj* pSocketObj, const WSABUF pBuffers[], int iCount); + BOOL SendItem (TSocketObj* pSocketObj, TItem* pItem, BOOL& bBlocked); + +public: + CTcpServer(ITcpServerListener* pListener) + : m_pListener (pListener) + , m_enLastError (SE_OK) + , m_enState (SS_STOPPED) + , m_fdGCTimer (INVALID_FD) + , m_enReusePolicy (RAP_ADDR_AND_PORT) + , m_enSendPolicy (SP_PACK) + , m_enOnSendSyncPolicy (OSSP_RECEIVE) + , m_dwMaxConnectionCount (DEFAULT_CONNECTION_COUNT) + , m_dwWorkerThreadCount (DEFAULT_WORKER_THREAD_COUNT) + , m_dwSocketListenQueue (DEFAULT_TCP_SERVER_SOCKET_LISTEN_QUEUE) + , m_dwAcceptSocketCount (DEFAULT_WORKER_MAX_EVENT_COUNT) + , m_dwSocketBufferSize (DEFAULT_TCP_SOCKET_BUFFER_SIZE) + , m_dwFreeSocketObjLockTime (DEFAULT_FREE_SOCKETOBJ_LOCK_TIME) + , m_dwFreeSocketObjPool (DEFAULT_FREE_SOCKETOBJ_POOL) + , m_dwFreeBufferObjPool (DEFAULT_FREE_BUFFEROBJ_POOL) + , m_dwFreeSocketObjHold (DEFAULT_FREE_SOCKETOBJ_HOLD) + , m_dwFreeBufferObjHold (DEFAULT_FREE_BUFFEROBJ_HOLD) + , m_dwKeepAliveTime (DEFALUT_TCP_KEEPALIVE_TIME) + , m_dwKeepAliveInterval (DEFALUT_TCP_KEEPALIVE_INTERVAL) + , m_bMarkSilence (TRUE) + , m_bNoDelay (FALSE) + { + ASSERT(m_pListener); + } + + virtual ~CTcpServer() + { + ENSURE_STOP(); + } + +private: + EnReuseAddressPolicy m_enReusePolicy; + EnSendPolicy m_enSendPolicy; + EnOnSendSyncPolicy m_enOnSendSyncPolicy; + DWORD m_dwMaxConnectionCount; + DWORD m_dwWorkerThreadCount; + DWORD m_dwSocketListenQueue; + DWORD m_dwAcceptSocketCount; + DWORD m_dwSocketBufferSize; + DWORD m_dwFreeSocketObjLockTime; + DWORD m_dwFreeSocketObjPool; + DWORD m_dwFreeBufferObjPool; + DWORD m_dwFreeSocketObjHold; + DWORD m_dwFreeBufferObjHold; + DWORD m_dwKeepAliveTime; + DWORD m_dwKeepAliveInterval; + BOOL m_bMarkSilence; + BOOL m_bNoDelay; + +private: + CSEM m_evWait; + + ITcpServerListener* m_pListener; + ListenSocketsPtr m_soListens; + EnServiceState m_enState; + EnSocketError m_enLastError; + + CReceiveBuffersPtr m_rcBuffers; + + CPrivateHeap m_phSocket; + CBufferObjPool m_bfObjPool; + + CSpinGuard m_csState; + + FD m_fdGCTimer; + + TSocketObjPtrPool m_bfActiveSockets; + + TSocketObjPtrList m_lsFreeSocket; + TSocketObjPtrQueue m_lsGCSocket; + + CIODispatcher m_ioDispatcher; +}; diff --git a/UdpArqClient.cpp b/UdpArqClient.cpp new file mode 100644 index 0000000..9b6aa65 --- /dev/null +++ b/UdpArqClient.cpp @@ -0,0 +1,186 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "UdpArqClient.h" + +#ifdef _UDP_SUPPORT + +BOOL CUdpArqClient::CheckParams() +{ + DWORD dwMaxDatagramSize = GetMaxDatagramSize(); + + if(m_dwMtu == 0) + m_arqAttr.dwMtu = dwMaxDatagramSize; + else + { + if(m_dwMtu > dwMaxDatagramSize) + return FALSE; + + m_arqAttr.dwMtu = m_dwMtu; + } + + return __super::CheckParams() && m_arqAttr.IsValid(); +} + +void CUdpArqClient::PrepareStart() +{ + __super::PrepareStart(); +} + +void CUdpArqClient::Reset() +{ + m_arqSession.Reset(); + + __super::Reset(); +} + +void CUdpArqClient::OnWorkerThreadStart(THR_ID dwThreadID) +{ + m_arqBuffer.Malloc(m_arqAttr.dwMaxMessageSize); + m_arqTimer = ::CreateTimer(m_arqAttr.dwFlushInterval); +} + +void CUdpArqClient::OnWorkerThreadEnd(THR_ID dwThreadID) +{ + if(IS_VALID_FD(m_arqTimer)) + { + close(m_arqTimer); + m_arqTimer = INVALID_FD; + } + + m_arqBuffer.Free(); +} + +HANDLE CUdpArqClient::GetUserEvent() +{ + return m_arqTimer; +} + +BOOL CUdpArqClient::OnUserEvent() +{ + ::ReadTimer(m_arqTimer); + + return m_arqSession.Check(); +} + +BOOL CUdpArqClient::Send(const BYTE* pBuffer, int iLength, int iOffset) +{ + ASSERT(pBuffer && iLength > 0 && iLength <= (int)m_arqAttr.dwMaxMessageSize); + + int result = NO_ERROR; + + if(pBuffer && iLength > 0 && iLength <= (int)m_arqAttr.dwMaxMessageSize) + { + if(IsConnected()) + { + if(iOffset != 0) pBuffer += iOffset; + result = m_arqSession.Send(pBuffer, iLength); + } + else + result = ERROR_INVALID_STATE; + } + else + result = ERROR_INVALID_PARAMETER; + + if(result != NO_ERROR) + ::SetLastError(result); + + return (result == NO_ERROR); +} + +BOOL CUdpArqClient::SendPackets(const WSABUF pBuffers[], int iCount) +{ + ASSERT(pBuffers && iCount > 0); + + if(!pBuffers || iCount <= 0) + return ERROR_INVALID_PARAMETER; + if(iCount == 1) + return Send((const BYTE*)pBuffers[0].buf, pBuffers[0].len); + if(!IsConnected()) + return ERROR_INVALID_STATE; + + int iLength = 0; + int iMaxLen = (int)m_arqAttr.dwMaxMessageSize; + + for(int i = 0; i < iCount; i++) + iLength += pBuffers[i].len; + + if(iLength <= 0 || iLength > iMaxLen) + return ERROR_INCORRECT_SIZE; + + CBufferPtr sndBuffer(iLength); + sndBuffer.SetSize(0); + + for(int i = 0; i < iCount; i++) + { + int iBufLen = pBuffers[i].len; + + if(iBufLen > 0) + { + BYTE* pBuffer = (BYTE*)pBuffers[i].buf; + ASSERT(pBuffer); + + sndBuffer.Cat(pBuffer, iBufLen); + } + } + + int result = m_arqSession.Send(sndBuffer.Ptr(), (int)sndBuffer.Size()); + + if(result != NO_ERROR) + ::SetLastError(result); + + return (result == NO_ERROR); +} + +int CUdpArqClient::ArqOutputProc(const char* pBuffer, int iLength, IKCPCB* kcp, LPVOID pv) +{ + CUdpArqClient* pClient = (CUdpArqClient*)pv; + + BOOL isOK = pClient->__super::Send((const BYTE*)pBuffer, iLength); + + return isOK ? NO_ERROR : ::WSAGetLastError(); +} + +EnHandleResult CUdpArqClient::FireConnect() +{ + EnHandleResult result = DoFireConnect(this); + + if(result != HR_ERROR) + m_arqSession.Renew(this, this, m_arqAttr); + + return result; +} + +EnHandleResult CUdpArqClient::FireReceive(const BYTE* pData, int iLength) +{ + return m_arqSession.Receive(pData, iLength, m_arqBuffer.Ptr(), (int)m_arqBuffer.Size()); +} + +BOOL CUdpArqClient::GetWaitingSendMessageCount(int& iCount) +{ + iCount = m_arqSession.GetWaitingSend(); + + return (iCount >= 0); +} + +#endif diff --git a/UdpArqClient.h b/UdpArqClient.h new file mode 100644 index 0000000..d9e8cb2 --- /dev/null +++ b/UdpArqClient.h @@ -0,0 +1,114 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "UdpClient.h" +#include "ArqHelper.h" + +#ifdef _UDP_SUPPORT + +class CUdpArqClient : public IArqClient, public CUdpClient +{ + using __super = CUdpClient; + + using CArqSession = CArqSessionT; + + friend typename CUdpArqClient::CArqSession; + +public: + virtual BOOL Send (const BYTE* pBuffer, int iLength, int iOffset = 0); + virtual BOOL SendPackets(const WSABUF pBuffers[], int iCount); + +protected: + virtual EnHandleResult FireConnect(); + virtual EnHandleResult FireReceive(const BYTE* pData, int iLength); + + virtual BOOL CheckParams(); + virtual void PrepareStart(); + virtual void Reset(); + + virtual void OnWorkerThreadStart(THR_ID dwThreadID); + virtual void OnWorkerThreadEnd(THR_ID dwThreadID); + + virtual FD GetUserEvent(); + virtual BOOL OnUserEvent(); + +public: + virtual void SetNoDelay (BOOL bNoDelay) {ENSURE_HAS_STOPPED(); m_arqAttr.bNoDelay = bNoDelay;} + virtual void SetTurnoffCongestCtrl (BOOL bTurnOff) {ENSURE_HAS_STOPPED(); m_arqAttr.bTurnoffNc = bTurnOff;} + virtual void SetFlushInterval (DWORD dwFlushInterval) {ENSURE_HAS_STOPPED(); m_arqAttr.dwFlushInterval = dwFlushInterval;} + virtual void SetResendByAcks (DWORD dwResendByAcks) {ENSURE_HAS_STOPPED(); m_arqAttr.dwResendByAcks = dwResendByAcks;} + virtual void SetSendWndSize (DWORD dwSendWndSize) {ENSURE_HAS_STOPPED(); m_arqAttr.dwSendWndSize = dwSendWndSize;} + virtual void SetRecvWndSize (DWORD dwRecvWndSize) {ENSURE_HAS_STOPPED(); m_arqAttr.dwRecvWndSize = dwRecvWndSize;} + virtual void SetMinRto (DWORD dwMinRto) {ENSURE_HAS_STOPPED(); m_arqAttr.dwMinRto = dwMinRto;} + virtual void SetFastLimit (DWORD dwFastLimit) {ENSURE_HAS_STOPPED(); m_arqAttr.dwFastLimit = dwFastLimit;} + virtual void SetMaxTransUnit (DWORD dwMaxTransUnit) {ENSURE_HAS_STOPPED(); m_dwMtu = dwMaxTransUnit;} + virtual void SetMaxMessageSize (DWORD dwMaxMessageSize) {ENSURE_HAS_STOPPED(); m_arqAttr.dwMaxMessageSize = dwMaxMessageSize;} + virtual void SetHandShakeTimeout (DWORD dwHandShakeTimeout) {ENSURE_HAS_STOPPED(); m_arqAttr.dwHandShakeTimeout = dwHandShakeTimeout;} + + virtual BOOL IsNoDelay () {return m_arqAttr.bNoDelay;} + virtual BOOL IsTurnoffCongestCtrl () {return m_arqAttr.bTurnoffNc;} + virtual DWORD GetFlushInterval () {return m_arqAttr.dwFlushInterval;} + virtual DWORD GetResendByAcks () {return m_arqAttr.dwResendByAcks;} + virtual DWORD GetSendWndSize () {return m_arqAttr.dwSendWndSize;} + virtual DWORD GetRecvWndSize () {return m_arqAttr.dwRecvWndSize;} + virtual DWORD GetMinRto () {return m_arqAttr.dwMinRto;} + virtual DWORD GetFastLimit () {return m_arqAttr.dwFastLimit;} + virtual DWORD GetMaxTransUnit () {return m_dwMtu;} + virtual DWORD GetMaxMessageSize () {return m_arqAttr.dwMaxMessageSize;} + virtual DWORD GetHandShakeTimeout () {return m_arqAttr.dwHandShakeTimeout;} + + virtual BOOL GetWaitingSendMessageCount (int& iCount); + +public: + const TArqAttr& GetArqAttribute () {return m_arqAttr;} + Fn_ArqOutputProc GetArqOutputProc () {return ArqOutputProc;} + +private: + static int ArqOutputProc(const char* pBuffer, int iLength, IKCPCB* kcp, LPVOID pv); + +public: + CUdpArqClient(IUdpClientListener* pListener) + : CUdpClient(pListener) + , m_dwMtu (0) + { + + } + + virtual ~CUdpArqClient() + { + ENSURE_STOP(); + } + +private: + DWORD m_dwMtu; + TArqAttr m_arqAttr; + + CBufferPtr m_arqBuffer; + FD m_arqTimer; + + CArqSession m_arqSession; +}; + +#endif diff --git a/UdpArqServer.cpp b/UdpArqServer.cpp new file mode 100644 index 0000000..6eac8d4 --- /dev/null +++ b/UdpArqServer.cpp @@ -0,0 +1,253 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "UdpArqServer.h" + +#ifdef _UDP_SUPPORT + +BOOL CUdpArqServer::CheckParams() +{ + DWORD dwMaxDatagramSize = GetMaxDatagramSize(); + + if(m_dwMtu == 0) + m_arqAttr.dwMtu = dwMaxDatagramSize; + else + { + if(m_dwMtu > dwMaxDatagramSize) + return FALSE; + + m_arqAttr.dwMtu = m_dwMtu; + } + + return __super::CheckParams() && m_arqAttr.IsValid(); +} + +void CUdpArqServer::PrepareStart() +{ + __super::PrepareStart(); + + m_ssPool.SetSessionLockTime(GetFreeSocketObjLockTime()); + m_ssPool.SetSessionPoolSize(GetFreeSocketObjPool()); + m_ssPool.SetSessionPoolHold(GetFreeSocketObjHold()); + + m_ssPool.Prepare(); +} + +void CUdpArqServer::Reset() +{ + ::ClearPtrMap(m_rcBuffers); + + m_ssPool.Clear(); + + __super::Reset(); +} + +void CUdpArqServer::OnWorkerThreadStart(THR_ID dwThreadID) +{ + { + CCriSecLock locallock(m_csRcBuffers); + m_rcBuffers[dwThreadID] = new CBufferPtr(m_arqAttr.dwMaxMessageSize); + } + + while((DWORD)m_rcBuffers.size() < GetWorkerThreadCount()) + ::WaitFor(3); +} + +void CUdpArqServer::ReleaseGCSocketObj(BOOL bForce) +{ + __super::ReleaseGCSocketObj(bForce); + +#ifdef USE_EXTERNAL_GC + m_ssPool.ReleaseGCSession(bForce); +#endif +} + +BOOL CUdpArqServer::Send(CONNID dwConnID, const BYTE* pBuffer, int iLength, int iOffset) +{ + ASSERT(pBuffer && iLength > 0 && iLength <= (int)m_arqAttr.dwMaxMessageSize); + + int result = NO_ERROR; + + if(pBuffer && iLength > 0 && iLength <= (int)m_arqAttr.dwMaxMessageSize) + { + if(iOffset != 0) pBuffer += iOffset; + TUdpSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(TUdpSocketObj::IsValid(pSocketObj)) + result = SendArq(pSocketObj, pBuffer, iLength); + else + result = ERROR_OBJECT_NOT_FOUND; + } + else + result = ERROR_INVALID_PARAMETER; + + if(result != NO_ERROR) + ::SetLastError(result); + + return (result == NO_ERROR); +} + +BOOL CUdpArqServer::SendPackets(CONNID dwConnID, const WSABUF pBuffers[], int iCount) +{ + ASSERT(pBuffers && iCount > 0); + + if(!pBuffers || iCount <= 0) + return ERROR_INVALID_PARAMETER; + if(iCount == 1) + return Send(dwConnID, (const BYTE*)pBuffers[0].buf, pBuffers[0].len); + + TUdpSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TUdpSocketObj::IsValid(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + int iLength = 0; + int iMaxLen = (int)m_arqAttr.dwMaxMessageSize; + + for(int i = 0; i < iCount; i++) + iLength += pBuffers[i].len; + + if(iLength <= 0 || iLength > iMaxLen) + return ERROR_INCORRECT_SIZE; + + CBufferPtr sndBuffer(iLength); + sndBuffer.SetSize(0); + + for(int i = 0; i < iCount; i++) + { + int iBufLen = pBuffers[i].len; + + if(iBufLen > 0) + { + BYTE* pBuffer = (BYTE*)pBuffers[i].buf; + ASSERT(pBuffer); + + sndBuffer.Cat(pBuffer, iBufLen); + } + } + + int result = SendArq(pSocketObj, sndBuffer.Ptr(), (int)sndBuffer.Size()); + + if(result != NO_ERROR) + ::SetLastError(result); + + return (result == NO_ERROR); +} + +int CUdpArqServer::SendArq(TUdpSocketObj* pSocketObj, const BYTE* pBuffer, int iLength) +{ + CArqSessionEx* pSession = nullptr; + GetConnectionReserved(pSocketObj, (PVOID*)&pSession); + + if(pSession == nullptr) + return ERROR_OBJECT_NOT_FOUND; + + CLocalSafeCounter localcounter(*pSession); + + return pSession->Send(pBuffer, iLength); +} + +int CUdpArqServer::ArqOutputProc(const char* pBuffer, int iLength, IKCPCB* kcp, LPVOID pv) +{ + TUdpSocketObj* pSocketObj = (TUdpSocketObj*)pv; + + if(!TUdpSocketObj::IsValid(pSocketObj)) + return ERROR_OBJECT_NOT_FOUND; + + CUdpArqServer* pServer = (CUdpArqServer*)IUdpArqServer::FromS((IUdpServer*)pSocketObj->pHolder); + + TItemPtr itPtr(pServer->m_bfObjPool, pServer->m_bfObjPool.PickFreeItem()); + itPtr->Cat((const BYTE*)pBuffer, iLength); + + return pServer->SendInternal(pSocketObj, itPtr); +} + +EnHandleResult CUdpArqServer::FireAccept(TUdpSocketObj* pSocketObj) +{ + EnHandleResult result = DoFireAccept(pSocketObj); + + if(result != HR_ERROR) + { + CArqSessionEx* pSession = m_ssPool.PickFreeSession(this, pSocketObj, m_arqAttr); + ENSURE(SetConnectionReserved(pSocketObj, pSession)); + } + + return result; +} + +EnHandleResult CUdpArqServer::FireReceive(TUdpSocketObj* pSocketObj, const BYTE* pData, int iLength) +{ + CArqSessionEx* pSession = nullptr; + GetConnectionReserved(pSocketObj, (PVOID*)&pSession); + + CLocalSafeCounter localcounter(*pSession); + + CBufferPtr& rcBuffer = *m_rcBuffers[SELF_THREAD_ID]; + return pSession->Receive(pData, iLength, rcBuffer.Ptr(), (int)rcBuffer.Size()); +} + +EnHandleResult CUdpArqServer::FireClose(TUdpSocketObj* pSocketObj, EnSocketOperation enOperation, int iErrorCode) +{ + EnHandleResult result = DoFireClose(pSocketObj, enOperation, iErrorCode); + + CArqSessionEx* pSession = nullptr; + GetConnectionReserved(pSocketObj, (PVOID*)&pSession); + + if(pSession != nullptr) + m_ssPool.PutFreeSession(pSession); + + return result; +} + +BOOL CUdpArqServer::GetWaitingSendMessageCount(CONNID dwConnID, int& iCount) +{ + TUdpSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TUdpSocketObj::IsValid(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + CArqSessionEx* pSession = nullptr; + GetConnectionReserved(pSocketObj, (PVOID*)&pSession); + + if(pSession == nullptr) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + { + CLocalSafeCounter localcounter(*pSession); + + iCount = pSession->GetWaitingSend(); + } + + return (iCount >= 0); +} + +#endif diff --git a/UdpArqServer.h b/UdpArqServer.h new file mode 100644 index 0000000..2572606 --- /dev/null +++ b/UdpArqServer.h @@ -0,0 +1,120 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "UdpServer.h" +#include "ArqHelper.h" + +#ifdef _UDP_SUPPORT + +#include "common/STLHelper.h" + +class CUdpArqServer : public IArqSocket, public CUdpServer +{ + using __super = CUdpServer; + + using CArqSession = CArqSessionT; + using CArqSessionEx = CArqSessionExT; + using CArqSessionPool = CArqSessionPoolT; + using CRecvBufferMap = unordered_map; + + friend typename CUdpArqServer::CArqSession; + +public: + virtual BOOL Send (CONNID dwConnID, const BYTE* pBuffer, int iLength, int iOffset = 0); + virtual BOOL SendPackets(CONNID dwConnID, const WSABUF pBuffers[], int iCount); + +protected: + virtual EnHandleResult FireAccept(TUdpSocketObj* pSocketObj); + virtual EnHandleResult FireReceive(TUdpSocketObj* pSocketObj, const BYTE* pData, int iLength); + virtual EnHandleResult FireClose(TUdpSocketObj* pSocketObj, EnSocketOperation enOperation, int iErrorCode); + + virtual BOOL CheckParams(); + virtual void PrepareStart(); + virtual void Reset(); + virtual void OnWorkerThreadStart(THR_ID dwThreadID); + + virtual void ReleaseGCSocketObj(BOOL bForce = FALSE); + +public: + virtual void SetNoDelay (BOOL bNoDelay) {ENSURE_HAS_STOPPED(); m_arqAttr.bNoDelay = bNoDelay;} + virtual void SetTurnoffCongestCtrl (BOOL bTurnOff) {ENSURE_HAS_STOPPED(); m_arqAttr.bTurnoffNc = bTurnOff;} + virtual void SetFlushInterval (DWORD dwFlushInterval) {ENSURE_HAS_STOPPED(); m_arqAttr.dwFlushInterval = dwFlushInterval;} + virtual void SetResendByAcks (DWORD dwResendByAcks) {ENSURE_HAS_STOPPED(); m_arqAttr.dwResendByAcks = dwResendByAcks;} + virtual void SetSendWndSize (DWORD dwSendWndSize) {ENSURE_HAS_STOPPED(); m_arqAttr.dwSendWndSize = dwSendWndSize;} + virtual void SetRecvWndSize (DWORD dwRecvWndSize) {ENSURE_HAS_STOPPED(); m_arqAttr.dwRecvWndSize = dwRecvWndSize;} + virtual void SetMinRto (DWORD dwMinRto) {ENSURE_HAS_STOPPED(); m_arqAttr.dwMinRto = dwMinRto;} + virtual void SetFastLimit (DWORD dwFastLimit) {ENSURE_HAS_STOPPED(); m_arqAttr.dwFastLimit = dwFastLimit;} + virtual void SetMaxTransUnit (DWORD dwMaxTransUnit) {ENSURE_HAS_STOPPED(); m_dwMtu = dwMaxTransUnit;} + virtual void SetMaxMessageSize (DWORD dwMaxMessageSize) {ENSURE_HAS_STOPPED(); m_arqAttr.dwMaxMessageSize = dwMaxMessageSize;} + virtual void SetHandShakeTimeout (DWORD dwHandShakeTimeout) {ENSURE_HAS_STOPPED(); m_arqAttr.dwHandShakeTimeout = dwHandShakeTimeout;} + + virtual BOOL IsNoDelay () {return m_arqAttr.bNoDelay;} + virtual BOOL IsTurnoffCongestCtrl () {return m_arqAttr.bTurnoffNc;} + virtual DWORD GetFlushInterval () {return m_arqAttr.dwFlushInterval;} + virtual DWORD GetResendByAcks () {return m_arqAttr.dwResendByAcks;} + virtual DWORD GetSendWndSize () {return m_arqAttr.dwSendWndSize;} + virtual DWORD GetRecvWndSize () {return m_arqAttr.dwRecvWndSize;} + virtual DWORD GetMinRto () {return m_arqAttr.dwMinRto;} + virtual DWORD GetFastLimit () {return m_arqAttr.dwFastLimit;} + virtual DWORD GetMaxTransUnit () {return m_dwMtu;} + virtual DWORD GetMaxMessageSize () {return m_arqAttr.dwMaxMessageSize;} + virtual DWORD GetHandShakeTimeout () {return m_arqAttr.dwHandShakeTimeout;} + + virtual BOOL GetWaitingSendMessageCount (CONNID dwConnID, int& iCount); + +public: + const TArqAttr& GetArqAttribute () {return m_arqAttr;} + Fn_ArqOutputProc GetArqOutputProc () {return ArqOutputProc;} + +private: + int SendArq(TUdpSocketObj* pSocketObj, const BYTE* pBuffer, int iLength); + + static int ArqOutputProc(const char* pBuffer, int iLength, IKCPCB* kcp, LPVOID pv); + +public: + CUdpArqServer(IUdpServerListener* pListener) + : CUdpServer(pListener) + , m_ssPool (this) + , m_dwMtu (0) + { + + } + + virtual ~CUdpArqServer() + { + ENSURE_STOP(); + } + +private: + DWORD m_dwMtu; + TArqAttr m_arqAttr; + + CCriSec m_csRcBuffers; + CRecvBufferMap m_rcBuffers; + + CArqSessionPool m_ssPool; +}; + +#endif diff --git a/UdpCast.cpp b/UdpCast.cpp new file mode 100644 index 0000000..43d22f1 --- /dev/null +++ b/UdpCast.cpp @@ -0,0 +1,701 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "UdpCast.h" + +#ifdef _UDP_SUPPORT + +BOOL CUdpCast::Start(LPCTSTR lpszRemoteAddress, USHORT usPort, BOOL bAsyncConnect, LPCTSTR lpszBindAddress, USHORT usLocalPort) +{ + ASSERT(usLocalPort == 0); + + if(!CheckParams() || !CheckStarting()) + return FALSE; + + PrepareStart(); + m_ccContext.Reset(); + + BOOL isOK = FALSE; + HP_SOCKADDR bindAddr(AF_UNSPEC, TRUE); + + if(CreateClientSocket(lpszRemoteAddress, usPort, lpszBindAddress, bindAddr)) + { + if(BindClientSocket(bindAddr)) + { + if(TRIGGER(FirePrepareConnect(m_soClient)) != HR_ERROR) + { + if(ConnectToGroup(bindAddr)) + { + if(CreateWorkerThread()) + { + isOK = TRUE; + } + else + SetLastError(SE_WORKER_THREAD_CREATE, __FUNCTION__, ERROR_CREATE_FAILED); + } + else + SetLastError(SE_CONNECT_SERVER, __FUNCTION__, ::WSAGetLastError()); + } + else + SetLastError(SE_SOCKET_PREPARE, __FUNCTION__, ENSURE_ERROR_CANCELLED); + } + else + SetLastError(SE_SOCKET_BIND, __FUNCTION__, ::WSAGetLastError()); + } + else + SetLastError(SE_SOCKET_CREATE, __FUNCTION__, ::WSAGetLastError()); + + if(!isOK) + { + m_ccContext.Reset(FALSE); + EXECUTE_RESTORE_ERROR(Stop()); + } + + return isOK; +} + +BOOL CUdpCast::CheckParams() +{ + if (((int)m_dwMaxDatagramSize > 0 && m_dwMaxDatagramSize <= MAXIMUM_UDP_MAX_DATAGRAM_SIZE) && + ((int)m_dwFreeBufferPoolSize >= 0) && + ((int)m_dwFreeBufferPoolHold >= 0) && + (m_enCastMode >= CM_MULTICAST && m_enCastMode <= CM_BROADCAST) && + (m_iMCTtl >= 0 && m_iMCTtl <= 255) && + (m_bMCLoop == TRUE || m_bMCLoop == FALSE) ) + return TRUE; + + SetLastError(SE_INVALID_PARAM, __FUNCTION__, ERROR_INVALID_PARAMETER); + return FALSE; +} + +void CUdpCast::PrepareStart() +{ + m_itPool.SetItemCapacity(m_dwMaxDatagramSize); + m_itPool.SetPoolSize(m_dwFreeBufferPoolSize); + m_itPool.SetPoolHold(m_dwFreeBufferPoolHold); + + m_itPool.Prepare(); +} + +BOOL CUdpCast::CheckStarting() +{ + CSpinLock locallock(m_csState); + + if(m_enState == SS_STOPPED) + m_enState = SS_STARTING; + else + { + SetLastError(SE_ILLEGAL_STATE, __FUNCTION__, ERROR_INVALID_STATE); + return FALSE; + } + + return TRUE; +} + +BOOL CUdpCast::CheckStoping() +{ + if(m_enState != SS_STOPPED) + { + CSpinLock locallock(m_csState); + + if(HasStarted()) + { + m_enState = SS_STOPPING; + return TRUE; + } + } + + SetLastError(SE_ILLEGAL_STATE, __FUNCTION__, ERROR_INVALID_STATE); + + return FALSE; +} + +BOOL CUdpCast::CreateClientSocket(LPCTSTR lpszRemoteAddress, USHORT usPort, LPCTSTR lpszBindAddress, HP_SOCKADDR& bindAddr) +{ + HP_SCOPE_HOST host(lpszRemoteAddress); + LPCTSTR lpszRealAddress = host.addr; + + if(m_enCastMode == CM_BROADCAST && ::IsStrEmpty(lpszRealAddress)) + lpszRealAddress = DEFAULT_IPV4_BROAD_CAST_ADDRESS; + + if(!::GetSockAddrByHostName(lpszRealAddress, usPort, m_castAddr)) + return FALSE; + + if(::IsStrEmpty(lpszBindAddress)) + { + bindAddr.family = m_castAddr.family; + bindAddr.SetPort(usPort); + } + else + { + if(!::sockaddr_A_2_IN(lpszBindAddress, usPort, bindAddr)) + return FALSE; + } + + if(m_enCastMode == CM_BROADCAST && bindAddr.IsIPv6()) + { + ::WSASetLastError(ERROR_PFNOSUPPORT); + return FALSE; + } + + if(m_castAddr.family != bindAddr.family) + { + ::WSASetLastError(ERROR_AFNOSUPPORT); + return FALSE; + } + + m_soClient = socket(m_castAddr.family, SOCK_DGRAM, IPPROTO_UDP); + + if(m_soClient == INVALID_SOCKET) + return FALSE; + + VERIFY(::fcntl_SETFL(m_soClient, O_NOATIME | O_NONBLOCK | O_CLOEXEC)); + VERIFY(::SSO_ReuseAddress(m_soClient, m_enReusePolicy) == NO_ERROR); + + SetRemoteHost(host.name, usPort); + + return TRUE; +} + +BOOL CUdpCast::BindClientSocket(HP_SOCKADDR& bindAddr) +{ + HP_SOCKADDR anyAddr = HP_SOCKADDR::AnyAddr(m_castAddr.family); + anyAddr.SetPort(m_castAddr.Port()); + + if(::bind(m_soClient, anyAddr.Addr(), anyAddr.AddrSize()) == SOCKET_ERROR) + return FALSE; + + m_dwConnID = ::GenerateConnectionID(); + + return TRUE; +} + +BOOL CUdpCast::ConnectToGroup(const HP_SOCKADDR& bindAddr) +{ + if(m_enCastMode == CM_MULTICAST) + { + if(!::SetMultiCastSocketOptions(m_soClient, bindAddr, m_castAddr, m_iMCTtl, m_bMCLoop)) + return FALSE; + } + else + { + ASSERT(m_castAddr.IsIPv4()); + + UINT iSet = 1; + VERIFY(::SSO_SetSocketOption(m_soClient, SOL_SOCKET, SO_BROADCAST, &iSet, sizeof(UINT)) != SOCKET_ERROR); + } + + SetConnected(); + + if(TRIGGER(FireConnect()) == HR_ERROR) + { + ::WSASetLastError(ENSURE_ERROR_CANCELLED); + return FALSE; + } + + m_nEvents = (SHORT)((m_lsSend.IsEmpty() ? 0 : POLLOUT) | (m_bPaused ? 0 : POLLIN) | POLLRDHUP); + + return TRUE; +} + +BOOL CUdpCast::Stop() +{ + if(!CheckStoping()) + return FALSE; + + WaitForWorkerThreadEnd(); + + SetConnected(FALSE); + + if(m_ccContext.bFireOnClose) + FireClose(m_ccContext.enOperation, m_ccContext.iErrorCode); + + if(m_soClient != INVALID_SOCKET) + { + shutdown(m_soClient, SHUT_WR); + closesocket(m_soClient); + + m_soClient = INVALID_SOCKET; + } + + Reset(); + + return TRUE; +} + +void CUdpCast::Reset() +{ + CCriSecLock locallock(m_csSend); + + m_evSend.Reset(); + m_evRecv.Reset(); + m_evStop.Reset(); + + m_lsSend.Clear(); + m_itPool.Clear(); + m_rcBuffer.Free(); + + m_castAddr.Reset(); + m_remoteAddr.Reset(); + + m_strHost.Empty(); + + m_usPort = 0; + m_nEvents = 0; + m_bPaused = FALSE; + m_enState = SS_STOPPED; + + m_evWait.SyncNotifyAll(); +} + +void CUdpCast::WaitForWorkerThreadEnd() +{ + if(!m_thWorker.IsRunning()) + return; + + if(m_thWorker.IsInMyThread()) + m_thWorker.Detach(); + else + { + m_evStop.Set(); + m_thWorker.Join(); + } +} + +BOOL CUdpCast::CreateWorkerThread() +{ + return m_thWorker.Start(this, &CUdpCast::WorkerThreadProc); +} + +UINT WINAPI CUdpCast::WorkerThreadProc(LPVOID pv) +{ + ::SetCurrentWorkerThreadName(); + + TRACE("---------------> Cast Worker Thread 0x%08X started <---------------", SELF_THREAD_ID); + + OnWorkerThreadStart(SELF_THREAD_ID); + + BOOL bCallStop = TRUE; + pollfd pfds[] = { {m_soClient, m_nEvents}, + {m_evSend.GetFD(), POLLIN}, + {m_evRecv.GetFD(), POLLIN}, + {m_evStop.GetFD(), POLLIN} }; + int size = ARRAY_SIZE(pfds); + + m_rcBuffer.Malloc(m_dwMaxDatagramSize); + + while(HasStarted()) + { + int rs = (int)::PollForMultipleObjects(pfds, size); + ASSERT(rs > TIMEOUT); + + if(rs <= 0) + { + m_ccContext.Reset(TRUE, SO_UNKNOWN, ::WSAGetLastError()); + goto EXIT_WORKER_THREAD; + } + + for(int i = 0; i < size; i++) + { + if((1 << i) & rs) + { + SHORT revents = pfds[i].revents; + + if(i == 0) + { + if(!ProcessNetworkEvent(revents)) + goto EXIT_WORKER_THREAD; + } + else if(i == 1) + { + m_evSend.Reset(); + + if(!SendData()) + goto EXIT_WORKER_THREAD; + } + else if(i == 2) + { + m_evRecv.Reset(); + + if(!ReadData()) + goto EXIT_WORKER_THREAD; + } + else if(i == 3) + { + m_evStop.Reset(); + + bCallStop = FALSE; + goto EXIT_WORKER_THREAD; + } + else + VERIFY(FALSE); + } + } + + m_nEvents = (SHORT)((m_lsSend.IsEmpty() ? 0 : POLLOUT) | (m_bPaused ? 0 : POLLIN) | POLLRDHUP); + pfds[0].events = m_nEvents; + } + +EXIT_WORKER_THREAD: + + OnWorkerThreadEnd(SELF_THREAD_ID); + + if(bCallStop && HasStarted()) + Stop(); + + TRACE("---------------> Cast Worker Thread 0x%08X stoped <---------------", SELF_THREAD_ID); + + return 0; +} + +BOOL CUdpCast::ProcessNetworkEvent(SHORT events) +{ + ASSERT(IsConnected()); + + BOOL bContinue = TRUE; + + if(bContinue && events & POLLERR) + bContinue = HandleClose(events); + + if(bContinue && events & POLLIN) + bContinue = HandleRead(events); + + if(bContinue && events & POLLOUT) + bContinue = HandleWrite(events); + + if(bContinue && events & _POLL_HUNGUP_EVENTS) + bContinue = HandleClose(events); + + return bContinue; +} + +BOOL CUdpCast::HandleClose(SHORT events) +{ + EnSocketOperation enOperation = SO_CLOSE; + + if(events & _POLL_HUNGUP_EVENTS) + enOperation = SO_CLOSE; + else if(events & POLLIN) + enOperation = SO_RECEIVE; + else if(events & POLLOUT) + enOperation = SO_SEND; + + m_ccContext.Reset(TRUE, enOperation, ::SSO_GetError(m_soClient)); + + return FALSE; +} + +BOOL CUdpCast::HandleRead(SHORT events) +{ + return ReadData(); +} + +BOOL CUdpCast::HandleWrite(SHORT events) +{ + return SendData(); +} + +BOOL CUdpCast::ReadData() +{ + while(TRUE) + { + if(m_bPaused) + break; + + socklen_t addrLen = (socklen_t)m_remoteAddr.AddrSize(); + int rc = (int)recvfrom(m_soClient, (char*)(BYTE*)m_rcBuffer, m_dwMaxDatagramSize, MSG_TRUNC, m_remoteAddr.Addr(), &addrLen); + + if(rc >= 0) + { + if(rc > (int)m_dwMaxDatagramSize) + { + m_ccContext.Reset(TRUE, SO_RECEIVE, ERROR_BAD_LENGTH); + return FALSE; + } + + if(TRIGGER(FireReceive(m_rcBuffer, rc)) == HR_ERROR) + { + TRACE(" OnReceive() event return 'HR_ERROR', connection will be closed !", m_dwConnID); + + m_ccContext.Reset(TRUE, SO_RECEIVE, ENSURE_ERROR_CANCELLED); + return FALSE; + } + } + else if(rc == SOCKET_ERROR) + { + int code = ::WSAGetLastError(); + + if(code == ERROR_WOULDBLOCK) + break; + else + { + m_ccContext.Reset(TRUE, SO_RECEIVE, code); + return FALSE; + } + } + else + ASSERT(FALSE); + } + + return TRUE; +} + +BOOL CUdpCast::PauseReceive(BOOL bPause) +{ + if(!IsConnected()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + if(m_bPaused == bPause) + return TRUE; + + m_bPaused = bPause; + + if(!bPause) + return m_evRecv.Set(); + + return TRUE; +} + +BOOL CUdpCast::SendData() +{ + BOOL bBlocked = FALSE; + + while(m_lsSend.Length() > 0) + { + TItemPtr itPtr(m_itPool); + + { + CCriSecLock locallock(m_csSend); + itPtr = m_lsSend.PopFront(); + } + + if(!itPtr.IsValid()) + break; + + if(!DoSendData(itPtr, bBlocked)) + return FALSE; + + if(bBlocked) + { + CCriSecLock locallock(m_csSend); + m_lsSend.PushFront(itPtr.Detach()); + break; + } + } + + return TRUE; +} + +BOOL CUdpCast::DoSendData(TItem* pItem, BOOL& bBlocked) +{ + int rc = (int)sendto(m_soClient, (char*)pItem->Ptr(), pItem->Size(), 0, m_castAddr.Addr(), m_castAddr.AddrSize()); + + if(rc >= 0) + { + ASSERT(rc == pItem->Size()); + + if(rc == 0) + { + CCriSecLock locallock(m_csSend); + m_lsSend.ReduceLength(1); + } + + if(TRIGGER(FireSend(pItem->Ptr(), rc)) == HR_ERROR) + { + TRACE(" OnSend() event should not return 'HR_ERROR' !!", m_dwConnID); + ASSERT(FALSE); + } + } + else if(rc == SOCKET_ERROR) + { + int code = ::WSAGetLastError(); + + if(code == ERROR_WOULDBLOCK) + bBlocked = TRUE; + else + { + m_ccContext.Reset(TRUE, SO_SEND, code); + return FALSE; + } + } + else + ASSERT(FALSE); + + return TRUE; +} + +BOOL CUdpCast::Send(const BYTE* pBuffer, int iLength, int iOffset) +{ + ASSERT(pBuffer && iLength >= 0 && iLength <= (int)m_dwMaxDatagramSize); + + int result = NO_ERROR; + + if(pBuffer && iLength >= 0 && iLength <= (int)m_dwMaxDatagramSize) + { + if(IsConnected()) + { + if(iOffset != 0) pBuffer += iOffset; + + TItemPtr itPtr(m_itPool, m_itPool.PickFreeItem()); + itPtr->Cat(pBuffer, iLength); + + result = SendInternal(itPtr); + } + else + result = ERROR_INVALID_STATE; + } + else + result = ERROR_INVALID_PARAMETER; + + if(result != NO_ERROR) + ::SetLastError(result); + + return (result == NO_ERROR); +} + +BOOL CUdpCast::SendPackets(const WSABUF pBuffers[], int iCount) +{ + ASSERT(pBuffers && iCount > 0); + + if(!pBuffers || iCount <= 0) + return ERROR_INVALID_PARAMETER; + if(!IsConnected()) + return ERROR_INVALID_STATE; + + int result = NO_ERROR; + int iLength = 0; + int iMaxLen = (int)m_dwMaxDatagramSize; + + TItemPtr itPtr(m_itPool, m_itPool.PickFreeItem()); + + for(int i = 0; i < iCount; i++) + { + int iBufLen = pBuffers[i].len; + + if(iBufLen > 0) + { + BYTE* pBuffer = (BYTE*)pBuffers[i].buf; + ASSERT(pBuffer); + + iLength += iBufLen; + + if(iLength <= iMaxLen) + itPtr->Cat(pBuffer, iBufLen); + else + break; + } + } + + if(iLength >= 0 && iLength <= iMaxLen) + result = SendInternal(itPtr); + else + result = ERROR_INCORRECT_SIZE; + + if(result != NO_ERROR) + ::SetLastError(result); + + return (result == NO_ERROR); +} + +int CUdpCast::SendInternal(TItemPtr& itPtr) +{ + int iPending; + int iBufferSize; + + { + CCriSecLock locallock(m_csSend); + + if(!IsConnected()) + return ERROR_INVALID_STATE; + + iPending = m_lsSend.Length(); + iBufferSize = itPtr->Size(); + + m_lsSend.PushBack(itPtr.Detach()); + if(iBufferSize == 0) m_lsSend.IncreaseLength(1); + + ASSERT(m_lsSend.Length() > 0); + } + + if(iPending == 0 && m_lsSend.Length() > 0) m_evSend.Set(); + + return NO_ERROR; +} + +void CUdpCast::SetLastError(EnSocketError code, LPCSTR func, int ec) +{ + TRACE("%s --> Error: %d, EC: %d", func, code, ec); + + m_enLastError = code; + ::SetLastError(ec); +} + +BOOL CUdpCast::GetLocalAddress(TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort) +{ + ASSERT(lpszAddress != nullptr && iAddressLen > 0); + + return ::GetSocketLocalAddress(m_soClient, lpszAddress, iAddressLen, usPort); +} + +void CUdpCast::SetRemoteHost(LPCTSTR lpszHost, USHORT usPort) +{ + m_strHost = lpszHost; + m_usPort = usPort; +} + +BOOL CUdpCast::GetRemoteHost(TCHAR lpszHost[], int& iHostLen, USHORT& usPort) +{ + BOOL isOK = FALSE; + + if(m_strHost.IsEmpty()) + return isOK; + + int iLen = m_strHost.GetLength() + 1; + + if(iHostLen >= iLen) + { + memcpy(lpszHost, CA2CT(m_strHost), iLen * sizeof(TCHAR)); + usPort = m_usPort; + + isOK = TRUE; + } + + iHostLen = iLen; + + return isOK; +} + +BOOL CUdpCast::GetRemoteHost(LPCSTR* lpszHost, USHORT* pusPort) +{ + *lpszHost = m_strHost; + + if(pusPort != nullptr) + *pusPort = m_usPort; + + return !m_strHost.IsEmpty(); +} + +#endif diff --git a/UdpCast.h b/UdpCast.h new file mode 100644 index 0000000..6ee3b91 --- /dev/null +++ b/UdpCast.h @@ -0,0 +1,219 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "SocketHelper.h" +#include "./common/GeneralHelper.h" + +#ifdef _UDP_SUPPORT + +class CUdpCast : public IUdpCast +{ +public: + virtual BOOL Start (LPCTSTR lpszRemoteAddress, USHORT usPort, BOOL bAsyncConnect = TRUE, LPCTSTR lpszBindAddress = nullptr, USHORT usLocalPort = 0); + virtual BOOL Stop (); + virtual BOOL Send (const BYTE* pBuffer, int iLength, int iOffset = 0); + virtual BOOL SendPackets (const WSABUF pBuffers[], int iCount); + virtual BOOL PauseReceive (BOOL bPause = TRUE); + virtual BOOL Wait (DWORD dwMilliseconds = INFINITE) {return m_evWait.WaitFor(dwMilliseconds, WAIT_FOR_STOP_PREDICATE);} + virtual BOOL HasStarted () {return m_enState == SS_STARTED || m_enState == SS_STARTING;} + virtual EnServiceState GetState () {return m_enState;} + virtual CONNID GetConnectionID () {return m_dwConnID;} + virtual EnSocketError GetLastError () {return m_enLastError;} + virtual LPCTSTR GetLastErrorDesc () {return ::GetSocketErrorDesc(m_enLastError);} + + virtual BOOL GetLocalAddress (TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort); + virtual BOOL GetRemoteHost (TCHAR lpszHost[], int& iHostLen, USHORT& usPort); + virtual BOOL GetPendingDataLength (int& iPending) {iPending = m_lsSend.Length(); return HasStarted();} + virtual BOOL IsPauseReceive (BOOL& bPaused) {bPaused = m_bPaused; return HasStarted();} + virtual BOOL IsConnected () {return m_bConnected;} + +public: + virtual BOOL IsSecure () {return FALSE;} + + virtual void SetReuseAddressPolicy (EnReuseAddressPolicy enReusePolicy){ENSURE_HAS_STOPPED(); m_enReusePolicy = enReusePolicy;} + virtual void SetMaxDatagramSize (DWORD dwMaxDatagramSize) {ENSURE_HAS_STOPPED(); m_dwMaxDatagramSize = dwMaxDatagramSize;} + virtual void SetFreeBufferPoolSize (DWORD dwFreeBufferPoolSize) {ENSURE_HAS_STOPPED(); m_dwFreeBufferPoolSize = dwFreeBufferPoolSize;} + virtual void SetFreeBufferPoolHold (DWORD dwFreeBufferPoolHold) {ENSURE_HAS_STOPPED(); m_dwFreeBufferPoolHold = dwFreeBufferPoolHold;} + virtual void SetCastMode (EnCastMode enCastMode) {ENSURE_HAS_STOPPED(); m_enCastMode = enCastMode;} + virtual void SetMultiCastTtl (int iMCTtl) {ENSURE_HAS_STOPPED(); m_iMCTtl = iMCTtl;} + virtual void SetMultiCastLoop (BOOL bMCLoop) {ENSURE_HAS_STOPPED(); m_bMCLoop = bMCLoop;} + virtual void SetExtra (PVOID pExtra) {m_pExtra = pExtra;} + + virtual EnReuseAddressPolicy GetReuseAddressPolicy () {return m_enReusePolicy;} + virtual DWORD GetMaxDatagramSize () {return m_dwMaxDatagramSize;} + virtual DWORD GetFreeBufferPoolSize () {return m_dwFreeBufferPoolSize;} + virtual DWORD GetFreeBufferPoolHold () {return m_dwFreeBufferPoolHold;} + virtual EnCastMode GetCastMode () {return m_enCastMode;} + virtual int GetMultiCastTtl () {return m_iMCTtl;} + virtual BOOL IsMultiCastLoop () {return m_bMCLoop;} + virtual PVOID GetExtra () {return m_pExtra;} + + virtual BOOL GetRemoteAddress(TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort) + { + ADDRESS_FAMILY usFamily; + return ::sockaddr_IN_2_A(m_remoteAddr, usFamily, lpszAddress, iAddressLen, usPort); + } + +protected: + virtual EnHandleResult FirePrepareConnect(SOCKET socket) + {return m_pListener->OnPrepareConnect(this, m_dwConnID, socket);} + virtual EnHandleResult FireConnect() + { + EnHandleResult rs = m_pListener->OnConnect(this, m_dwConnID); + if(rs != HR_ERROR) rs = FireHandShake(); + return rs; + } + virtual EnHandleResult FireHandShake() + {return m_pListener->OnHandShake(this, m_dwConnID);} + virtual EnHandleResult FireSend(const BYTE* pData, int iLength) + {return m_pListener->OnSend(this, m_dwConnID, pData, iLength);} + virtual EnHandleResult FireReceive(const BYTE* pData, int iLength) + {return m_pListener->OnReceive(this, m_dwConnID, pData, iLength);} + virtual EnHandleResult FireReceive(int iLength) + {return m_pListener->OnReceive(this, m_dwConnID, iLength);} + virtual EnHandleResult FireClose(EnSocketOperation enOperation, int iErrorCode) + {return m_pListener->OnClose(this, m_dwConnID, enOperation, iErrorCode);} + + void SetLastError(EnSocketError code, LPCSTR func, int ec); + virtual BOOL CheckParams(); + virtual void PrepareStart(); + virtual void Reset(); + + virtual void OnWorkerThreadStart(THR_ID tid) {} + virtual void OnWorkerThreadEnd(THR_ID tid) {} + +protected: + void SetReserved (PVOID pReserved) {m_pReserved = pReserved;} + PVOID GetReserved () {return m_pReserved;} + BOOL GetRemoteHost (LPCSTR* lpszHost, USHORT* pusPort = nullptr); + +private: + void SetRemoteHost (LPCTSTR lpszHost, USHORT usPort); + void SetConnected (BOOL bConnected = TRUE) {m_bConnected = bConnected; if(bConnected) m_enState = SS_STARTED;} + + BOOL CheckStarting(); + BOOL CheckStoping(); + BOOL CreateClientSocket(LPCTSTR lpszRemoteAddress, USHORT usPort, LPCTSTR lpszBindAddress, HP_SOCKADDR& bindAddr); + BOOL BindClientSocket(HP_SOCKADDR& bindAddr); + BOOL ConnectToGroup(const HP_SOCKADDR& bindAddr); + BOOL CreateWorkerThread(); + BOOL ProcessNetworkEvent(SHORT events); + BOOL ReadData(); + BOOL SendData(); + BOOL DoSendData(TItem* pItem, BOOL& bBlocked); + int SendInternal(TItemPtr& itPtr); + void WaitForWorkerThreadEnd(); + + BOOL HandleClose(SHORT events); + BOOL HandleRead(SHORT events); + BOOL HandleWrite(SHORT events); + + UINT WINAPI WorkerThreadProc(LPVOID pv); + +public: + CUdpCast(IUdpCastListener* pListener) + : m_pListener (pListener) + , m_lsSend (m_itPool) + , m_soClient (INVALID_SOCKET) + , m_nEvents (0) + , m_dwConnID (0) + , m_usPort (0) + , m_bPaused (FALSE) + , m_bConnected (FALSE) + , m_enLastError (SE_OK) + , m_enState (SS_STOPPED) + , m_pExtra (nullptr) + , m_pReserved (nullptr) + , m_enReusePolicy (RAP_ADDR_ONLY) + , m_dwMaxDatagramSize (DEFAULT_UDP_MAX_DATAGRAM_SIZE) + , m_dwFreeBufferPoolSize(DEFAULT_CLIENT_FREE_BUFFER_POOL_SIZE) + , m_dwFreeBufferPoolHold(DEFAULT_CLIENT_FREE_BUFFER_POOL_HOLD) + , m_iMCTtl (1) + , m_bMCLoop (FALSE) + , m_enCastMode (CM_MULTICAST) + , m_castAddr (AF_UNSPEC, TRUE) + , m_remoteAddr (AF_UNSPEC, TRUE) + { + ASSERT(m_pListener); + } + + virtual ~CUdpCast() + { + ENSURE_STOP(); + } + +private: + CSEM m_evWait; + + IUdpCastListener* m_pListener; + TClientCloseContext m_ccContext; + + SOCKET m_soClient; + SHORT m_nEvents; + CONNID m_dwConnID; + + EnReuseAddressPolicy m_enReusePolicy; + DWORD m_dwMaxDatagramSize; + DWORD m_dwFreeBufferPoolSize; + DWORD m_dwFreeBufferPoolHold; + + int m_iMCTtl; + BOOL m_bMCLoop; + EnCastMode m_enCastMode; + + EnSocketError m_enLastError; + volatile BOOL m_bConnected; + volatile EnServiceState m_enState; + + PVOID m_pExtra; + PVOID m_pReserved; + + HP_SOCKADDR m_castAddr; + HP_SOCKADDR m_remoteAddr; + + CBufferPtr m_rcBuffer; + +protected: + CStringA m_strHost; + USHORT m_usPort; + + CItemPool m_itPool; + +private: + CSpinGuard m_csState; + + CCriSec m_csSend; + TItemListExV m_lsSend; + + CEvt m_evSend; + CEvt m_evRecv; + CEvt m_evStop; + + volatile BOOL m_bPaused; + + CThread m_thWorker; +}; + +#endif diff --git a/UdpClient.cpp b/UdpClient.cpp new file mode 100644 index 0000000..82eab12 --- /dev/null +++ b/UdpClient.cpp @@ -0,0 +1,830 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "UdpClient.h" + +#ifdef _UDP_SUPPORT + +BOOL CUdpClient::Start(LPCTSTR lpszRemoteAddress, USHORT usPort, BOOL bAsyncConnect, LPCTSTR lpszBindAddress, USHORT usLocalPort) +{ + if(!CheckParams() || !CheckStarting()) + return FALSE; + + PrepareStart(); + m_ccContext.Reset(); + + BOOL isOK = FALSE; + HP_SOCKADDR addrRemote, addrBind; + + if(CreateClientSocket(lpszRemoteAddress, addrRemote, usPort, lpszBindAddress, addrBind)) + { + if(BindClientSocket(addrBind, addrRemote, usLocalPort)) + { + if(TRIGGER(FirePrepareConnect(m_soClient)) != HR_ERROR) + { + if(ConnectToServer(addrRemote, bAsyncConnect)) + { + if(CreateWorkerThread()) + isOK = TRUE; + else + SetLastError(SE_WORKER_THREAD_CREATE, __FUNCTION__, ERROR_CREATE_FAILED); + } + else + SetLastError(SE_CONNECT_SERVER, __FUNCTION__, ::WSAGetLastError()); + } + else + SetLastError(SE_SOCKET_PREPARE, __FUNCTION__, ENSURE_ERROR_CANCELLED); + } + else + SetLastError(SE_SOCKET_BIND, __FUNCTION__, ::WSAGetLastError()); + } + else + SetLastError(SE_SOCKET_CREATE, __FUNCTION__, ::WSAGetLastError()); + + if(!isOK) + { + m_ccContext.Reset(FALSE); + EXECUTE_RESTORE_ERROR(Stop()); + } + + return isOK; +} + +BOOL CUdpClient::CheckParams() +{ + if (((int)m_dwMaxDatagramSize > 0 && m_dwMaxDatagramSize <= MAXIMUM_UDP_MAX_DATAGRAM_SIZE) && + ((int)m_dwFreeBufferPoolSize >= 0) && + ((int)m_dwFreeBufferPoolHold >= 0) && + ((int)m_dwDetectAttempts >= 0) && + ((int)m_dwDetectInterval >= 1000 || m_dwDetectInterval == 0) ) + return TRUE; + + SetLastError(SE_INVALID_PARAM, __FUNCTION__, ERROR_INVALID_PARAMETER); + return FALSE; +} + +void CUdpClient::PrepareStart() +{ + m_itPool.SetItemCapacity(m_dwMaxDatagramSize); + m_itPool.SetPoolSize(m_dwFreeBufferPoolSize); + m_itPool.SetPoolHold(m_dwFreeBufferPoolHold); + + m_itPool.Prepare(); +} + +BOOL CUdpClient::CheckStarting() +{ + CSpinLock locallock(m_csState); + + if(m_enState == SS_STOPPED) + m_enState = SS_STARTING; + else + { + SetLastError(SE_ILLEGAL_STATE, __FUNCTION__, ERROR_INVALID_STATE); + return FALSE; + } + + return TRUE; +} + +BOOL CUdpClient::CheckStoping() +{ + if(m_enState != SS_STOPPED) + { + CSpinLock locallock(m_csState); + + if(HasStarted()) + { + m_enState = SS_STOPPING; + return TRUE; + } + } + + SetLastError(SE_ILLEGAL_STATE, __FUNCTION__, ERROR_INVALID_STATE); + + return FALSE; +} + +BOOL CUdpClient::CreateClientSocket(LPCTSTR lpszRemoteAddress, HP_SOCKADDR& addrRemote, USHORT usPort, LPCTSTR lpszBindAddress, HP_SOCKADDR& addrBind) +{ + HP_SCOPE_HOST host(lpszRemoteAddress); + + if(!::GetSockAddrByHostName(host.addr, usPort, addrRemote)) + return FALSE; + + if(::IsStrNotEmpty(lpszBindAddress)) + { + if(!::sockaddr_A_2_IN(lpszBindAddress, 0, addrBind)) + return FALSE; + + if(addrRemote.family != addrBind.family) + { + ::WSASetLastError(ERROR_AFNOSUPPORT); + return FALSE; + } + } + + m_soClient = socket(addrRemote.family, SOCK_DGRAM, IPPROTO_UDP); + + if(m_soClient == INVALID_SOCKET) + return FALSE; + + VERIFY(::SSO_ReuseAddress(m_soClient, m_enReusePolicy) == NO_ERROR); + + SetRemoteHost(host.name, usPort); + + return TRUE; +} + +BOOL CUdpClient::BindClientSocket(const HP_SOCKADDR& addrBind, const HP_SOCKADDR& addrRemote, USHORT usLocalPort) +{ + if(addrBind.IsSpecified() && usLocalPort == 0) + { + if(::bind(m_soClient, addrBind.Addr(), addrBind.AddrSize()) == SOCKET_ERROR) + return FALSE; + } + else if(usLocalPort != 0) + { + HP_SOCKADDR realBindAddr = addrBind.IsSpecified() ? addrBind : HP_SOCKADDR::AnyAddr(addrRemote.family); + + realBindAddr.SetPort(usLocalPort); + + if(::bind(m_soClient, realBindAddr.Addr(), realBindAddr.AddrSize()) == SOCKET_ERROR) + return FALSE; + } + + m_dwConnID = ::GenerateConnectionID(); + + return TRUE; +} + +BOOL CUdpClient::ConnectToServer(const HP_SOCKADDR& addrRemote, BOOL bAsyncConnect) +{ + BOOL isOK = FALSE; + + if(bAsyncConnect) + { + VERIFY(::fcntl_SETFL(m_soClient, O_NOATIME | O_NONBLOCK | O_CLOEXEC)); + + int rc = ::connect(m_soClient, addrRemote.Addr(), addrRemote.AddrSize()); + + if(IS_NO_ERROR(rc) || IS_IO_PENDING_ERROR()) + { + m_nEvents = POLLOUT; + isOK = TRUE; + } + } + else + { + if(::connect(m_soClient, addrRemote.Addr(), addrRemote.AddrSize()) != SOCKET_ERROR) + { + VERIFY(::fcntl_SETFL(m_soClient, O_NOATIME | O_NONBLOCK | O_CLOEXEC)); + + SetConnected(); + + if(TRIGGER(FireConnect()) == HR_ERROR) + ::WSASetLastError(ENSURE_ERROR_CANCELLED); + else + { + VERIFY(DetectConnection()); + + m_nEvents = (SHORT)((m_lsSend.IsEmpty() ? 0 : POLLOUT) | (m_bPaused ? 0 : POLLIN) | POLLRDHUP); + isOK = TRUE; + } + } + } + + return isOK; +} + +BOOL CUdpClient::Stop() +{ + if(!CheckStoping()) + return FALSE; + + WaitForWorkerThreadEnd(); + + CheckConnected(); + + if(m_ccContext.bFireOnClose) + FireClose(m_ccContext.enOperation, m_ccContext.iErrorCode); + + if(m_soClient != INVALID_SOCKET) + { + shutdown(m_soClient, SHUT_WR); + closesocket(m_soClient); + + m_soClient = INVALID_SOCKET; + } + + Reset(); + + return TRUE; +} + +void CUdpClient::Reset() +{ + CCriSecLock locallock(m_csSend); + + m_evSend.Reset(); + m_evRecv.Reset(); + m_evStop.Reset(); + + m_lsSend.Clear(); + m_itPool.Clear(); + m_rcBuffer.Free(); + + m_strHost.Empty(); + + m_usPort = 0; + m_nEvents = 0; + m_dwDetectFails = 0; + m_bPaused = FALSE; + m_enState = SS_STOPPED; + + m_evWait.SyncNotifyAll(); +} + +void CUdpClient::WaitForWorkerThreadEnd() +{ + if(!m_thWorker.IsRunning()) + return; + + if(m_thWorker.IsInMyThread()) + m_thWorker.Detach(); + else + { + m_evStop.Set(); + m_thWorker.Join(); + } +} + +void CUdpClient::CheckConnected() +{ + if(!IsConnected()) + return; + + if(m_ccContext.bNotify) + ::SendUdpCloseNotify(m_soClient); + + SetConnected(FALSE); +} + +BOOL CUdpClient::CreateWorkerThread() +{ + return m_thWorker.Start(this, &CUdpClient::WorkerThreadProc); +} + +UINT WINAPI CUdpClient::WorkerThreadProc(LPVOID pv) +{ + ::SetCurrentWorkerThreadName(); + + TRACE("---------------> Client Worker Thread 0x%08X started <---------------", SELF_THREAD_ID); + + OnWorkerThreadStart(SELF_THREAD_ID); + + BOOL bCallStop = TRUE; + DWORD dwSize = 4; + DWORD dwIndex = 0; + BOOL bDetect = IsNeedDetect(); + FD fdUserEvt = GetUserEvent(); + + if(bDetect) ++dwSize; + if(IS_VALID_FD(fdUserEvt)) ++dwSize; + + pollfd* pfds = CreateLocalObjects(pollfd, dwSize); + + pfds[dwIndex++] = {m_soClient, m_nEvents}; + pfds[dwIndex++] = {m_evSend.GetFD(), POLLIN}; + pfds[dwIndex++] = {m_evRecv.GetFD(), POLLIN}; + pfds[dwIndex++] = {m_evStop.GetFD(), POLLIN}; + + unique_ptr evDetectPtr; + + if(bDetect) + { + evDetectPtr.reset(new CTimerEvent()); + evDetectPtr->Set(m_dwDetectInterval); + pfds[dwIndex++] = {evDetectPtr->GetFD(), POLLIN}; + } + + if(IS_VALID_FD(fdUserEvt)) + pfds[dwIndex++] = {fdUserEvt, POLLIN}; + + m_rcBuffer.Malloc(m_dwMaxDatagramSize); + + while(HasStarted()) + { + int rs = (int)::PollForMultipleObjects(pfds, dwSize); + ASSERT(rs > TIMEOUT); + + if(rs <= 0) + { + m_ccContext.Reset(TRUE, SO_UNKNOWN, ::WSAGetLastError()); + goto EXIT_WORKER_THREAD; + } + + for(DWORD i = 0; i < dwSize; i++) + { + if((1 << i) & rs) + { + SHORT revents = pfds[i].revents; + + if(i == 0) + { + if(!ProcessNetworkEvent(revents)) + goto EXIT_WORKER_THREAD; + } + else if(i == 1) + { + m_evSend.Reset(); + + if(!SendData()) + goto EXIT_WORKER_THREAD; + } + else if(i == 2) + { + m_evRecv.Reset(); + + if(!ReadData()) + goto EXIT_WORKER_THREAD; + } + else if(i == 3) + { + m_evStop.Reset(); + + bCallStop = FALSE; + goto EXIT_WORKER_THREAD; + } + else if(i == 4) + { + if(bDetect) + { + evDetectPtr->Reset(); + + if(!CheckConnection()) + goto EXIT_WORKER_THREAD; + } + else + { + if(!OnUserEvent()) + { + m_ccContext.Reset(TRUE, SO_CLOSE, ENSURE_ERROR_CANCELLED); + goto EXIT_WORKER_THREAD; + } + } + } + else if(i == 5) + { + if(!OnUserEvent()) + { + m_ccContext.Reset(TRUE, SO_CLOSE, ENSURE_ERROR_CANCELLED); + goto EXIT_WORKER_THREAD; + } + } + else + VERIFY(FALSE); + } + } + + m_nEvents = (SHORT)((m_lsSend.IsEmpty() ? 0 : POLLOUT) | (m_bPaused ? 0 : POLLIN) | POLLRDHUP); + pfds[0].events = m_nEvents; + } + +EXIT_WORKER_THREAD: + + OnWorkerThreadEnd(SELF_THREAD_ID); + + if(bCallStop && HasStarted()) + Stop(); + + TRACE("---------------> Client Worker Thread 0x%08X stoped <---------------", SELF_THREAD_ID); + + return 0; +} + +BOOL CUdpClient::CheckConnection() +{ + if(m_dwDetectFails++ >= m_dwDetectAttempts) + { + m_ccContext.Reset(TRUE, SO_CLOSE, NO_ERROR, FALSE); + return FALSE; + } + + DetectConnection(); + + return TRUE; +} + +BOOL CUdpClient::DetectConnection() +{ + int result = NO_ERROR; + + if((int)send(m_soClient, nullptr, 0, 0) == SOCKET_ERROR) + { + result = ::WSAGetLastError(); + if(result == ERROR_WOULDBLOCK) + result = NO_ERROR; + } + + BOOL isOK = (result == NO_ERROR); + + if(isOK) + { + TRACE(" send 0 bytes (detect package succ)", m_dwConnID); + } + else + { + TRACE(" send 0 bytes (detect package fail [%d])", m_dwConnID, result); + } + + return isOK; +} + +BOOL CUdpClient::ProcessNetworkEvent(SHORT events) +{ + BOOL bContinue = TRUE; + + if(bContinue && events & POLLERR) + bContinue = HandleClose(events); + + if(bContinue && !IsConnected()) + bContinue = HandleConnect(events); + + if(bContinue && events & POLLIN) + bContinue = HandleRead(events); + + if(bContinue && events & POLLOUT) + bContinue = HandleWrite(events); + + if(bContinue && events & _POLL_HUNGUP_EVENTS) + bContinue = HandleClose(events); + + return bContinue; +} + +BOOL CUdpClient::HandleConnect(SHORT events) +{ + ASSERT(events & POLLOUT); + + int code = ::SSO_GetError(m_soClient); + + if(!IS_NO_ERROR(code) || (events & _POLL_ERROR_EVENTS)) + { + m_ccContext.Reset(TRUE, SO_CONNECT, code); + return FALSE; + } + + if(events & _POLL_HUNGUP_EVENTS) + { + m_ccContext.Reset(TRUE, SO_CONNECT, NO_ERROR); + return FALSE; + } + + SetConnected(); + + if(TRIGGER(FireConnect()) != HR_ERROR) + VERIFY(DetectConnection()); + else + { + m_ccContext.Reset(FALSE, SO_CLOSE, ENSURE_ERROR_CANCELLED, FALSE); + return FALSE; + } + + return TRUE; +} + +BOOL CUdpClient::HandleClose(SHORT events) +{ + EnSocketOperation enOperation = SO_CLOSE; + + if(events & _POLL_HUNGUP_EVENTS) + enOperation = SO_CLOSE; + else if(events & POLLIN) + enOperation = SO_RECEIVE; + else if(events & POLLOUT) + enOperation = SO_SEND; + + m_ccContext.Reset(TRUE, enOperation, ::SSO_GetError(m_soClient)); + + return FALSE; +} + +BOOL CUdpClient::HandleRead(SHORT events) +{ + return ReadData(); +} + +BOOL CUdpClient::HandleWrite(SHORT events) +{ + return SendData(); +} + +BOOL CUdpClient::ReadData() +{ + while(TRUE) + { + if(m_bPaused) + break; + + int rc = (int)recv(m_soClient, (char*)(BYTE*)m_rcBuffer, m_dwMaxDatagramSize, MSG_TRUNC); + + if(rc > 0) + { + m_dwDetectFails = 0; + + if(::IsUdpCloseNotify(m_rcBuffer, rc)) + { + m_ccContext.Reset(TRUE, SO_CLOSE, NO_ERROR, FALSE); + return FALSE; + } + + if(rc > (int)m_dwMaxDatagramSize) + { + m_ccContext.Reset(TRUE, SO_RECEIVE, ERROR_BAD_LENGTH); + return FALSE; + } + + if(TRIGGER(FireReceive(m_rcBuffer, rc)) == HR_ERROR) + { + TRACE(" OnReceive() event return 'HR_ERROR', connection will be closed !", m_dwConnID); + + m_ccContext.Reset(TRUE, SO_RECEIVE, ENSURE_ERROR_CANCELLED); + return FALSE; + } + } + else if(rc == SOCKET_ERROR) + { + int code = ::WSAGetLastError(); + + if(code == ERROR_WOULDBLOCK) + break; + else + { + m_ccContext.Reset(TRUE, SO_RECEIVE, code); + return FALSE; + } + } + else if(rc == 0) + { + m_dwDetectFails = 0; + TRACE(" recv 0 bytes (detect ack package)", m_dwConnID); + } + else + ASSERT(FALSE); + } + + return TRUE; +} + +BOOL CUdpClient::PauseReceive(BOOL bPause) +{ + if(!IsConnected()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + if(m_bPaused == bPause) + return TRUE; + + m_bPaused = bPause; + + if(!bPause) + return m_evRecv.Set(); + + return TRUE; +} + +BOOL CUdpClient::SendData() +{ + BOOL bBlocked = FALSE; + + while(m_lsSend.Length() > 0) + { + TItemPtr itPtr(m_itPool); + + { + CCriSecLock locallock(m_csSend); + itPtr = m_lsSend.PopFront(); + } + + if(!itPtr.IsValid()) + break; + + ASSERT(!itPtr->IsEmpty()); + + if(!DoSendData(itPtr, bBlocked)) + return FALSE; + + if(bBlocked) + { + CCriSecLock locallock(m_csSend); + m_lsSend.PushFront(itPtr.Detach()); + break; + } + } + + return TRUE; +} + +BOOL CUdpClient::DoSendData(TItem* pItem, BOOL& bBlocked) +{ + int rc = (int)send(m_soClient, (char*)pItem->Ptr(), pItem->Size(), 0); + + if(rc > 0) + { + ASSERT(rc == pItem->Size()); + + if(TRIGGER(FireSend(pItem->Ptr(), rc)) == HR_ERROR) + { + TRACE(" OnSend() event should not return 'HR_ERROR' !!", m_dwConnID); + ASSERT(FALSE); + } + } + else if(rc == SOCKET_ERROR) + { + int code = ::WSAGetLastError(); + + if(code == ERROR_WOULDBLOCK) + bBlocked = TRUE; + else + { + m_ccContext.Reset(TRUE, SO_SEND, code); + return FALSE; + } + } + else + ASSERT(FALSE); + + return TRUE; +} + +BOOL CUdpClient::DoSend(const BYTE* pBuffer, int iLength, int iOffset) +{ + ASSERT(pBuffer && iLength > 0 && iLength <= (int)m_dwMaxDatagramSize); + + int result = NO_ERROR; + + if(pBuffer && iLength > 0 && iLength <= (int)m_dwMaxDatagramSize) + { + if(IsConnected()) + { + if(iOffset != 0) pBuffer += iOffset; + + TItemPtr itPtr(m_itPool, m_itPool.PickFreeItem()); + itPtr->Cat(pBuffer, iLength); + + result = SendInternal(itPtr); + } + else + result = ERROR_INVALID_STATE; + } + else + result = ERROR_INVALID_PARAMETER; + + if(result != NO_ERROR) + ::SetLastError(result); + + return (result == NO_ERROR); +} + +BOOL CUdpClient::SendPackets(const WSABUF pBuffers[], int iCount) +{ + ASSERT(pBuffers && iCount > 0); + + if(!pBuffers || iCount <= 0) + return ERROR_INVALID_PARAMETER; + if(!IsConnected()) + return ERROR_INVALID_STATE; + + int result = NO_ERROR; + int iLength = 0; + int iMaxLen = (int)m_dwMaxDatagramSize; + + TItemPtr itPtr(m_itPool, m_itPool.PickFreeItem()); + + for(int i = 0; i < iCount; i++) + { + int iBufLen = pBuffers[i].len; + + if(iBufLen > 0) + { + BYTE* pBuffer = (BYTE*)pBuffers[i].buf; + ASSERT(pBuffer); + + iLength += iBufLen; + + if(iLength <= iMaxLen) + itPtr->Cat(pBuffer, iBufLen); + else + break; + } + } + + if(iLength > 0 && iLength <= iMaxLen) + result = SendInternal(itPtr); + else + result = ERROR_INCORRECT_SIZE; + + if(result != NO_ERROR) + ::SetLastError(result); + + return (result == NO_ERROR); +} + +int CUdpClient::SendInternal(TItemPtr& itPtr) +{ + int iPending; + + { + CCriSecLock locallock(m_csSend); + + if(!IsConnected()) + return ERROR_INVALID_STATE; + + iPending = m_lsSend.Length(); + + m_lsSend.PushBack(itPtr.Detach()); + ASSERT(m_lsSend.Length() > 0); + } + + if(iPending == 0 && m_lsSend.Length() > 0) m_evSend.Set(); + + return NO_ERROR; +} + +void CUdpClient::SetLastError(EnSocketError code, LPCSTR func, int ec) +{ + TRACE("%s --> Error: %d, EC: %d", func, code, ec); + + m_enLastError = code; + ::SetLastError(ec); +} + +BOOL CUdpClient::GetLocalAddress(TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort) +{ + ASSERT(lpszAddress != nullptr && iAddressLen > 0); + + return ::GetSocketLocalAddress(m_soClient, lpszAddress, iAddressLen, usPort); +} + +void CUdpClient::SetRemoteHost(LPCTSTR lpszHost, USHORT usPort) +{ + m_strHost = lpszHost; + m_usPort = usPort; +} + +BOOL CUdpClient::GetRemoteHost(TCHAR lpszHost[], int& iHostLen, USHORT& usPort) +{ + BOOL isOK = FALSE; + + if(m_strHost.IsEmpty()) + return isOK; + + int iLen = m_strHost.GetLength() + 1; + + if(iHostLen >= iLen) + { + memcpy(lpszHost, CA2CT(m_strHost), iLen * sizeof(TCHAR)); + usPort = m_usPort; + + isOK = TRUE; + } + + iHostLen = iLen; + + return isOK; +} + +BOOL CUdpClient::GetRemoteHost(LPCSTR* lpszHost, USHORT* pusPort) +{ + *lpszHost = m_strHost; + + if(pusPort != nullptr) + *pusPort = m_usPort; + + return !m_strHost.IsEmpty(); +} + +#endif diff --git a/UdpClient.h b/UdpClient.h new file mode 100644 index 0000000..6c5b40b --- /dev/null +++ b/UdpClient.h @@ -0,0 +1,234 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "SocketHelper.h" +#include "./common/GeneralHelper.h" + +#ifdef _UDP_SUPPORT + +class CUdpClient : public IUdpClient +{ +public: + virtual BOOL Start (LPCTSTR lpszRemoteAddress, USHORT usPort, BOOL bAsyncConnect = TRUE, LPCTSTR lpszBindAddress = nullptr, USHORT usLocalPort = 0); + virtual BOOL Stop (); + virtual BOOL Send (const BYTE* pBuffer, int iLength, int iOffset = 0) {return DoSend(pBuffer, iLength, iOffset);} + virtual BOOL SendPackets (const WSABUF pBuffers[], int iCount); + virtual BOOL PauseReceive (BOOL bPause = TRUE); + virtual BOOL Wait (DWORD dwMilliseconds = INFINITE) {return m_evWait.WaitFor(dwMilliseconds, WAIT_FOR_STOP_PREDICATE);} + virtual BOOL HasStarted () {return m_enState == SS_STARTED || m_enState == SS_STARTING;} + virtual EnServiceState GetState () {return m_enState;} + virtual CONNID GetConnectionID () {return m_dwConnID;} + virtual EnSocketError GetLastError () {return m_enLastError;} + virtual LPCTSTR GetLastErrorDesc () {return ::GetSocketErrorDesc(m_enLastError);} + + virtual BOOL GetLocalAddress (TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort); + virtual BOOL GetRemoteHost (TCHAR lpszHost[], int& iHostLen, USHORT& usPort); + virtual BOOL GetPendingDataLength (int& iPending) {iPending = m_lsSend.Length(); return HasStarted();} + virtual BOOL IsPauseReceive (BOOL& bPaused) {bPaused = m_bPaused; return HasStarted();} + virtual BOOL IsConnected () {return m_bConnected;} + +public: + virtual BOOL IsSecure () {return FALSE;} + + virtual void SetReuseAddressPolicy (EnReuseAddressPolicy enReusePolicy){ENSURE_HAS_STOPPED(); m_enReusePolicy = enReusePolicy;} + virtual void SetMaxDatagramSize (DWORD dwMaxDatagramSize) {ENSURE_HAS_STOPPED(); m_dwMaxDatagramSize = dwMaxDatagramSize;} + virtual void SetDetectAttempts (DWORD dwDetectAttempts) {ENSURE_HAS_STOPPED(); m_dwDetectAttempts = dwDetectAttempts;} + virtual void SetDetectInterval (DWORD dwDetectInterval) {ENSURE_HAS_STOPPED(); m_dwDetectInterval = dwDetectInterval;} + virtual void SetFreeBufferPoolSize (DWORD dwFreeBufferPoolSize) {ENSURE_HAS_STOPPED(); m_dwFreeBufferPoolSize = dwFreeBufferPoolSize;} + virtual void SetFreeBufferPoolHold (DWORD dwFreeBufferPoolHold) {ENSURE_HAS_STOPPED(); m_dwFreeBufferPoolHold = dwFreeBufferPoolHold;} + virtual void SetExtra (PVOID pExtra) {m_pExtra = pExtra;} + + virtual EnReuseAddressPolicy GetReuseAddressPolicy () {return m_enReusePolicy;} + virtual DWORD GetMaxDatagramSize () {return m_dwMaxDatagramSize;} + virtual DWORD GetDetectAttempts () {return m_dwDetectAttempts;} + virtual DWORD GetDetectInterval () {return m_dwDetectInterval;} + virtual DWORD GetFreeBufferPoolSize () {return m_dwFreeBufferPoolSize;} + virtual DWORD GetFreeBufferPoolHold () {return m_dwFreeBufferPoolHold;} + virtual PVOID GetExtra () {return m_pExtra;} + +protected: + virtual EnHandleResult FirePrepareConnect(SOCKET socket) + {return DoFirePrepareConnect(this, socket);} + virtual EnHandleResult FireConnect() + { + EnHandleResult rs = DoFireConnect(this); + if(rs != HR_ERROR) rs = FireHandShake(); + return rs; + } + virtual EnHandleResult FireHandShake() + {return DoFireHandShake(this);} + virtual EnHandleResult FireSend(const BYTE* pData, int iLength) + {return DoFireSend(this, pData, iLength);} + virtual EnHandleResult FireReceive(const BYTE* pData, int iLength) + {return DoFireReceive(this, pData, iLength);} + virtual EnHandleResult FireReceive(int iLength) + {return DoFireReceive(this, iLength);} + virtual EnHandleResult FireClose(EnSocketOperation enOperation, int iErrorCode) + {return DoFireClose(this, enOperation, iErrorCode);} + + virtual EnHandleResult DoFirePrepareConnect(IUdpClient* pSender, SOCKET socket) + {return m_pListener->OnPrepareConnect(pSender, pSender->GetConnectionID(), socket);} + virtual EnHandleResult DoFireConnect(IUdpClient* pSender) + {return m_pListener->OnConnect(pSender, pSender->GetConnectionID());} + virtual EnHandleResult DoFireHandShake(IUdpClient* pSender) + {return m_pListener->OnHandShake(pSender, pSender->GetConnectionID());} + virtual EnHandleResult DoFireSend(IUdpClient* pSender, const BYTE* pData, int iLength) + {return m_pListener->OnSend(pSender, pSender->GetConnectionID(), pData, iLength);} + virtual EnHandleResult DoFireReceive(IUdpClient* pSender, const BYTE* pData, int iLength) + {return m_pListener->OnReceive(pSender, pSender->GetConnectionID(), pData, iLength);} + virtual EnHandleResult DoFireReceive(IUdpClient* pSender, int iLength) + {return m_pListener->OnReceive(pSender, pSender->GetConnectionID(), iLength);} + virtual EnHandleResult DoFireClose(IUdpClient* pSender, EnSocketOperation enOperation, int iErrorCode) + {return m_pListener->OnClose(pSender, pSender->GetConnectionID(), enOperation, iErrorCode);} + + void SetLastError(EnSocketError code, LPCSTR func, int ec); + virtual BOOL CheckParams(); + virtual void PrepareStart(); + virtual void Reset(); + + virtual void OnWorkerThreadStart(THR_ID tid) {} + virtual void OnWorkerThreadEnd(THR_ID tid) {} + + virtual FD GetUserEvent() {return INVALID_FD;} + virtual BOOL OnUserEvent() {return TRUE;} + + static BOOL DoSend(CUdpClient* pClient, const BYTE* pBuffer, int iLength, int iOffset = 0) + {return pClient->DoSend(pBuffer, iLength, iOffset);} + +protected: + void SetReserved (PVOID pReserved) {m_pReserved = pReserved;} + PVOID GetReserved () {return m_pReserved;} + BOOL GetRemoteHost (LPCSTR* lpszHost, USHORT* pusPort = nullptr); + +private: + BOOL DoSend (const BYTE* pBuffer, int iLength, int iOffset = 0); + + void SetRemoteHost (LPCTSTR lpszHost, USHORT usPort); + void SetConnected (BOOL bConnected = TRUE) {m_bConnected = bConnected; if(bConnected) m_enState = SS_STARTED;} + + BOOL CheckStarting(); + BOOL CheckStoping(); + BOOL CreateClientSocket(LPCTSTR lpszRemoteAddress, HP_SOCKADDR& addrRemote, USHORT usPort, LPCTSTR lpszBindAddress, HP_SOCKADDR& addrBind); + BOOL BindClientSocket(const HP_SOCKADDR& addrBind, const HP_SOCKADDR& addrRemote, USHORT usLocalPort); + BOOL ConnectToServer(const HP_SOCKADDR& addrRemote, BOOL bAsyncConnect); + BOOL CreateWorkerThread(); + BOOL ProcessNetworkEvent(SHORT events); + BOOL ReadData(); + BOOL SendData(); + BOOL DoSendData(TItem* pItem, BOOL& bBlocked); + int SendInternal(TItemPtr& itPtr); + void WaitForWorkerThreadEnd(); + void CheckConnected(); + + BOOL HandleConnect (SHORT events); + BOOL HandleClose (SHORT events); + BOOL HandleRead (SHORT events); + BOOL HandleWrite (SHORT events); + + BOOL CheckConnection (); + BOOL DetectConnection (); + BOOL IsNeedDetect () {return m_dwDetectAttempts > 0 && m_dwDetectInterval > 0;} + + UINT WINAPI WorkerThreadProc(LPVOID pv); + +public: + CUdpClient(IUdpClientListener* pListener) + : m_pListener (pListener) + , m_lsSend (m_itPool) + , m_soClient (INVALID_SOCKET) + , m_nEvents (0) + , m_dwConnID (0) + , m_usPort (0) + , m_bPaused (FALSE) + , m_bConnected (FALSE) + , m_enLastError (SE_OK) + , m_enState (SS_STOPPED) + , m_dwDetectFails (0) + , m_pExtra (nullptr) + , m_pReserved (nullptr) + , m_enReusePolicy (RAP_ADDR_ONLY) + , m_dwMaxDatagramSize (DEFAULT_UDP_MAX_DATAGRAM_SIZE) + , m_dwFreeBufferPoolSize(DEFAULT_CLIENT_FREE_BUFFER_POOL_SIZE) + , m_dwFreeBufferPoolHold(DEFAULT_CLIENT_FREE_BUFFER_POOL_HOLD) + , m_dwDetectAttempts (DEFAULT_UDP_DETECT_ATTEMPTS) + , m_dwDetectInterval (DEFAULT_UDP_DETECT_INTERVAL) + { + ASSERT(m_pListener); + } + + virtual ~CUdpClient() + { + ENSURE_STOP(); + } + +private: + CSEM m_evWait; + + IUdpClientListener* m_pListener; + TClientCloseContext m_ccContext; + + SOCKET m_soClient; + SHORT m_nEvents; + CONNID m_dwConnID; + + EnReuseAddressPolicy m_enReusePolicy; + DWORD m_dwMaxDatagramSize; + DWORD m_dwFreeBufferPoolSize; + DWORD m_dwFreeBufferPoolHold; + DWORD m_dwDetectAttempts; + DWORD m_dwDetectInterval; + + EnSocketError m_enLastError; + volatile BOOL m_bConnected; + volatile EnServiceState m_enState; + + PVOID m_pExtra; + PVOID m_pReserved; + + CBufferPtr m_rcBuffer; + +protected: + CStringA m_strHost; + USHORT m_usPort; + + CItemPool m_itPool; + +private: + CSpinGuard m_csState; + + CCriSec m_csSend; + TItemListExV m_lsSend; + + CEvt m_evSend; + CEvt m_evRecv; + CEvt m_evStop; + + DWORD m_dwDetectFails; + volatile BOOL m_bPaused; + + CThread m_thWorker; +}; + +#endif diff --git a/UdpNode.cpp b/UdpNode.cpp new file mode 100644 index 0000000..d7b3732 --- /dev/null +++ b/UdpNode.cpp @@ -0,0 +1,757 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if defined(__GNUC__) && __GNUC__ >= 11 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" +#pragma GCC diagnostic ignored "-Wstringop-overflow" +#endif + +#include "UdpNode.h" + +#ifdef _UDP_SUPPORT + +BOOL CUdpNode::Start(LPCTSTR lpszBindAddress, USHORT usPort, EnCastMode enCastMode, LPCTSTR lpszCastAddress) +{ + m_enCastMode = enCastMode; + + if(!CheckParams() || !CheckStarting()) + return FALSE; + + PrepareStart(); + + HP_SOCKADDR bindAddr(AF_UNSPEC, TRUE); + + if(ParseBindAddr(lpszBindAddress, usPort, lpszCastAddress, bindAddr)) + if(CreateListenSocket(bindAddr)) + if(CreateWorkerThreads()) + if(StartAccept()) + { + m_enState = SS_STARTED; + return TRUE; + } + + EXECUTE_RESTORE_ERROR(Stop()); + + return FALSE; +} + +BOOL CUdpNode::CheckParams() +{ + if (((int)m_dwFreeBufferPoolSize >= 0) && + ((int)m_dwFreeBufferPoolHold >= 0) && + ((int)m_dwPostReceiveCount > 0) && + ((int)m_dwWorkerThreadCount > 0 && m_dwWorkerThreadCount <= MAX_WORKER_THREAD_COUNT) && + (m_enCastMode >= CM_UNICAST && m_enCastMode <= CM_BROADCAST) && + (m_iMCTtl >= 0 && m_iMCTtl <= 255) && + (m_bMCLoop == TRUE || m_bMCLoop == FALSE) && + ((int)m_dwMaxDatagramSize > 0 && m_dwMaxDatagramSize <= MAXIMUM_UDP_MAX_DATAGRAM_SIZE) ) + return TRUE; + + SetLastError(SE_INVALID_PARAM, __FUNCTION__, ERROR_INVALID_PARAMETER); + return FALSE; +} + +BOOL CUdpNode::CheckStarting() +{ + CReentrantWriteLock locallock(m_lcState); + + if(m_enState == SS_STOPPED) + m_enState = SS_STARTING; + else + { + SetLastError(SE_ILLEGAL_STATE, __FUNCTION__, ERROR_INVALID_STATE); + return FALSE; + } + + return TRUE; +} + +void CUdpNode::PrepareStart() +{ + m_bfObjPool.SetItemCapacity(m_dwMaxDatagramSize); + m_bfObjPool.SetPoolSize(m_dwFreeBufferPoolSize); + m_bfObjPool.SetPoolHold(m_dwFreeBufferPoolHold); + + m_bfObjPool.Prepare(); + + TNodeBufferObjList* pBufferObjList = (TNodeBufferObjList*)malloc(m_dwWorkerThreadCount * sizeof(TNodeBufferObjList)); + + for(int i = 0; i < (int)m_dwWorkerThreadCount; i++) + new (pBufferObjList + i) TNodeBufferObjList(m_bfObjPool); + + m_sndBuffs.reset(pBufferObjList); + + m_csSends = make_unique(m_dwWorkerThreadCount); + + m_rcBuffers = make_unique(m_dwWorkerThreadCount); + for_each(m_rcBuffers.get(), m_rcBuffers.get() + m_dwWorkerThreadCount, [this](CBufferPtr& buff) {buff.Malloc(m_dwMaxDatagramSize);}); + + m_soListens = make_unique(m_dwWorkerThreadCount); + for_each(m_soListens.get(), m_soListens.get() + m_dwWorkerThreadCount, [](SOCKET& sock) {sock = INVALID_FD;}); +} + +BOOL CUdpNode::ParseBindAddr(LPCTSTR lpszBindAddress, USHORT usPort, LPCTSTR lpszCastAddress, HP_SOCKADDR& bindAddr) +{ + if(::IsStrEmpty(lpszCastAddress)) + { + if(m_enCastMode == CM_BROADCAST) + lpszCastAddress = DEFAULT_IPV4_BROAD_CAST_ADDRESS; + else if(m_enCastMode == CM_MULTICAST) + { + SetLastError(SE_SOCKET_CREATE, __FUNCTION__, ERROR_ADDRNOTAVAIL); + return FALSE; + } + } + + if(m_enCastMode != CM_UNICAST && !::sockaddr_A_2_IN(lpszCastAddress, usPort, m_castAddr)) + { + SetLastError(SE_SOCKET_CREATE, __FUNCTION__, ::WSAGetLastError()); + return FALSE; + } + + if(::IsStrEmpty(lpszBindAddress)) + { + bindAddr.family = (m_enCastMode != CM_UNICAST) ? m_castAddr.family : AF_INET; + bindAddr.SetPort(usPort); + } + else + { + if(!::sockaddr_A_2_IN(lpszBindAddress, usPort, bindAddr)) + { + SetLastError(SE_SOCKET_CREATE, __FUNCTION__, ::WSAGetLastError()); + return FALSE; + } + } + + if(m_enCastMode == CM_BROADCAST && bindAddr.IsIPv6()) + { + SetLastError(SE_SOCKET_CREATE, __FUNCTION__, ERROR_PFNOSUPPORT); + return FALSE; + } + + if(m_enCastMode != CM_UNICAST && m_castAddr.family != bindAddr.family) + { + SetLastError(SE_SOCKET_CREATE, __FUNCTION__, ERROR_AFNOSUPPORT); + return FALSE; + } + + return TRUE; +} + +BOOL CUdpNode::CreateListenSocket(const HP_SOCKADDR& bindAddr) +{ + for(DWORD i = 0; i < m_dwWorkerThreadCount; i++) + { + m_soListens[i] = socket(bindAddr.family, SOCK_DGRAM, IPPROTO_UDP); + SOCKET soListen = m_soListens[i]; + + if(IS_INVALID_FD(soListen)) + { + SetLastError(SE_SOCKET_CREATE, __FUNCTION__, ::WSAGetLastError()); + return FALSE; + } + + ::fcntl_SETFL(soListen, O_NOATIME | O_NONBLOCK | O_CLOEXEC); + VERIFY(IS_NO_ERROR(::SSO_ReuseAddress(soListen, m_enReusePolicy))); + + if(IS_HAS_ERROR(::bind(soListen, bindAddr.Addr(), bindAddr.AddrSize()))) + { + SetLastError(SE_SOCKET_BIND, __FUNCTION__, ::WSAGetLastError()); + return FALSE; + } + + if(i == 0) + { + socklen_t dwAddrLen = (socklen_t)bindAddr.AddrSize(); + ENSURE(IS_NO_ERROR(::getsockname(soListen, m_localAddr.Addr(), &dwAddrLen))); + } + + if(m_enCastMode == CM_MULTICAST) + { + if(!::SetMultiCastSocketOptions(soListen, bindAddr, m_castAddr, m_iMCTtl, m_bMCLoop)) + { + SetLastError(SE_CONNECT_SERVER, __FUNCTION__, ::WSAGetLastError()); + return FALSE; + } + } + else if(m_enCastMode == CM_BROADCAST) + { + ASSERT(m_castAddr.IsIPv4()); + + BOOL bSet = TRUE; + if(IS_HAS_ERROR(::SSO_SetSocketOption(soListen, SOL_SOCKET, SO_BROADCAST, &bSet, sizeof(BOOL)))) + { + SetLastError(SE_CONNECT_SERVER, __FUNCTION__, ::WSAGetLastError()); + return FALSE; + } + } + + if(TRIGGER(FirePrepareListen(soListen)) == HR_ERROR) + { + SetLastError(SE_SOCKET_PREPARE, __FUNCTION__, ENSURE_ERROR_CANCELLED); + return FALSE; + } + } + + return TRUE; +} + +BOOL CUdpNode::CreateWorkerThreads() +{ + return m_ioDispatcher.Start(this, m_dwPostReceiveCount, m_dwWorkerThreadCount); +} + +BOOL CUdpNode::StartAccept() +{ + for(int i = 0; i < (int)m_dwWorkerThreadCount; i++) + { + SOCKET& soListen = m_soListens[i]; + + if(!m_ioDispatcher.AddFD(i, soListen, EPOLLIN | EPOLLOUT | EPOLLET, TO_PVOID(&soListen))) + return FALSE; + } + + return TRUE; +} + +BOOL CUdpNode::Stop() +{ + if(!CheckStoping()) + return FALSE; + + CloseListenSocket(); + + WaitForWorkerThreadEnd(); + + FireShutdown(); + + ReleaseFreeBuffer(); + + Reset(); + + return TRUE; +} + +BOOL CUdpNode::CheckStoping() +{ + if(m_enState != SS_STOPPED) + { + CReentrantWriteLock locallock(m_lcState); + + if(HasStarted()) + { + m_enState = SS_STOPPING; + return TRUE; + } + } + + SetLastError(SE_ILLEGAL_STATE, __FUNCTION__, ERROR_INVALID_STATE); + + return FALSE; +} + +void CUdpNode::CloseListenSocket() +{ + if(m_soListens) + { + for_each(m_soListens.get(), m_soListens.get() + m_dwWorkerThreadCount, [](SOCKET& sock) + { + if(sock != INVALID_FD) + { + ::ManualCloseSocket(sock); + sock = INVALID_FD; + } + }); + + ::WaitFor(100); + } +} + +void CUdpNode::WaitForWorkerThreadEnd() +{ + m_ioDispatcher.Stop(); +} + +void CUdpNode::ReleaseFreeBuffer() +{ + for_each(m_sndBuffs.get(), m_sndBuffs.get() + m_dwWorkerThreadCount, [](TNodeBufferObjList& sndBuff) + { + sndBuff.Clear(); + sndBuff.~TNodeBufferObjList(); + }); + + free(m_sndBuffs.release()); + + m_csSends = nullptr; + + m_bfObjPool.Clear(); +} + +void CUdpNode::Reset() +{ + m_castAddr.Reset(); + m_localAddr.Reset(); + + m_soListens = nullptr; + m_rcBuffers = nullptr; + + m_iSending = 0; + m_enState = SS_STOPPED; + + m_evWait.SyncNotifyAll(); +} + +int CUdpNode::GenerateBufferIndex(const HP_SOCKADDR& addrRemote) +{ + return (int)(addrRemote.Hash() % m_dwWorkerThreadCount); +} + +BOOL CUdpNode::Send(LPCTSTR lpszRemoteAddress, USHORT usRemotePort, const BYTE* pBuffer, int iLength, int iOffset) +{ + HP_SOCKADDR addrRemote; + + if(!::GetSockAddrByHostName(lpszRemoteAddress, usRemotePort, addrRemote)) + return FALSE; + + return DoSend(addrRemote, pBuffer, iLength, iOffset); +} + +BOOL CUdpNode::SendPackets(LPCTSTR lpszRemoteAddress, USHORT usRemotePort, const WSABUF pBuffers[], int iCount) +{ + HP_SOCKADDR addrRemote; + + if(!::GetSockAddrByHostName(lpszRemoteAddress, usRemotePort, addrRemote)) + return FALSE; + + return DoSendPackets(addrRemote, pBuffers, iCount); +} + +BOOL CUdpNode::SendCast(const BYTE* pBuffer, int iLength, int iOffset) +{ + if(m_enCastMode == CM_UNICAST) + { + ::SetLastError(ERROR_INVALID_OPERATION); + return FALSE; + } + + return DoSend(m_castAddr, pBuffer, iLength, iOffset); +} + +BOOL CUdpNode::SendCastPackets(const WSABUF pBuffers[], int iCount) +{ + if(m_enCastMode == CM_UNICAST) + { + ::SetLastError(ERROR_INVALID_OPERATION); + return FALSE; + } + + return DoSendPackets(m_castAddr, pBuffers, iCount); +} + +BOOL CUdpNode::DoSend(const HP_SOCKADDR& addrRemote, const BYTE* pBuffer, int iLength, int iOffset) +{ + ASSERT(pBuffer && iLength >= 0 && iLength <= (int)m_dwMaxDatagramSize); + + int result = NO_ERROR; + + if(IsValid()) + { + if(addrRemote.family == m_localAddr.family) + { + if(pBuffer && iLength >= 0 && iLength <= (int)m_dwMaxDatagramSize) + { + if(iOffset != 0) pBuffer += iOffset; + + TNodeBufferObjPtr bufPtr(m_bfObjPool, m_bfObjPool.PickFreeItem()); + bufPtr->Cat(pBuffer, iLength); + + result = SendInternal(addrRemote, bufPtr); + } + else + result = ERROR_INVALID_PARAMETER; + } + else + result = ERROR_AFNOSUPPORT; + } + else + result = ERROR_INVALID_STATE; + + if(result != NO_ERROR) + ::SetLastError(result); + + return (result == NO_ERROR); +} + +BOOL CUdpNode::DoSendPackets(const HP_SOCKADDR& addrRemote, const WSABUF pBuffers[], int iCount) +{ + ASSERT(pBuffers && iCount > 0); + + if(!pBuffers || iCount <= 0) + return ERROR_INVALID_PARAMETER; + + if(!IsValid()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + if(addrRemote.family != m_localAddr.family) + { + ::SetLastError(ERROR_AFNOSUPPORT); + return FALSE; + } + + int result = NO_ERROR; + int iLength = 0; + int iMaxLen = (int)m_dwMaxDatagramSize; + + TNodeBufferObjPtr bufPtr(m_bfObjPool, m_bfObjPool.PickFreeItem()); + + for(int i = 0; i < iCount; i++) + { + int iBufLen = pBuffers[i].len; + + if(iBufLen > 0) + { + BYTE* pBuffer = (BYTE*)pBuffers[i].buf; + ASSERT(pBuffer); + + iLength += iBufLen; + + if(iLength <= iMaxLen) + bufPtr->Cat(pBuffer, iBufLen); + else + break; + } + } + + if(iLength > 0 && iLength <= iMaxLen) + result = SendInternal(addrRemote, bufPtr); + else + result = ERROR_INCORRECT_SIZE; + + if(result != NO_ERROR) + ::SetLastError(result); + + return (result == NO_ERROR); +} + +int CUdpNode::SendInternal(const HP_SOCKADDR& addrRemote, TNodeBufferObjPtr& bufPtr) +{ + BOOL bPending; + int iBufferSize = bufPtr->Size(); + int idx = GenerateBufferIndex(addrRemote); + + addrRemote.Copy(bufPtr->remoteAddr); + + { + CReentrantReadLock locallock(m_lcState); + + if(!IsValid()) + return ERROR_INVALID_STATE; + + TNodeBufferObjList& sndBuff = m_sndBuffs[idx]; + + CCriSecLock locallock2(m_csSends[idx]); + + bPending = IsPending(idx); + sndBuff.PushBack(bufPtr.Detach()); + + if(iBufferSize == 0) sndBuff.IncreaseLength(1); + + ASSERT(sndBuff.Length() > 0); + } + + if(!bPending && IsPending(idx)) + VERIFY(m_ioDispatcher.SendCommandByIndex(idx, DISP_CMD_SEND)); + + return NO_ERROR; +} + +BOOL CUdpNode::OnBeforeProcessIo(const TDispContext* pContext, PVOID pv, UINT events) +{ + ASSERT(pv == &m_soListens[pContext->GetIndex()]); + + return TRUE; +} + +VOID CUdpNode::OnAfterProcessIo(const TDispContext* pContext, PVOID pv, UINT events, BOOL rs) +{ + +} + +VOID CUdpNode::OnCommand(const TDispContext* pContext, TDispCommand* pCmd) +{ + int idx = pContext->GetIndex(); + int flag = (int)(pCmd->wParam); + + switch(pCmd->type) + { + case DISP_CMD_SEND: + HandleCmdSend(idx, flag); + break; + } +} + +BOOL CUdpNode::OnReadyRead(const TDispContext* pContext, PVOID pv, UINT events) +{ + return HandleReceive(pContext, RETRIVE_EVENT_FLAG_H(events)); +} + +BOOL CUdpNode::OnReadyWrite(const TDispContext* pContext, PVOID pv, UINT events) +{ + return HandleSend(pContext, RETRIVE_EVENT_FLAG_H(events), RETRIVE_EVENT_FLAG_R(events)); +} + +BOOL CUdpNode::OnHungUp(const TDispContext* pContext, PVOID pv, UINT events) +{ + return HandleClose(pContext->GetIndex(), nullptr, SO_CLOSE, 0); +} + +BOOL CUdpNode::OnError(const TDispContext* pContext, PVOID pv, UINT events) +{ + return HandleClose(pContext->GetIndex(), nullptr, SO_CLOSE, -1); +} + +VOID CUdpNode::OnDispatchThreadStart(THR_ID tid) +{ + OnWorkerThreadStart(tid); +} + +VOID CUdpNode::OnDispatchThreadEnd(THR_ID tid) +{ + OnWorkerThreadEnd(tid); +} + +BOOL CUdpNode::HandleClose(int idx, TNodeBufferObj* pBufferObj, EnSocketOperation enOperation, int iErrorCode) +{ + if(!HasStarted()) + return FALSE; + + if(iErrorCode == -1) + iErrorCode = ::SSO_GetError(m_soListens[idx]); + + if(pBufferObj != nullptr) + TRIGGER(FireError(&pBufferObj->remoteAddr, pBufferObj->Ptr(), pBufferObj->Size(), enOperation, iErrorCode)); + else + TRIGGER(FireError(nullptr, nullptr, 0, enOperation, iErrorCode)); + + return TRUE; +} + +BOOL CUdpNode::HandleReceive(const TDispContext* pContext, int flag) +{ + int idx = pContext->GetIndex(); + CBufferPtr& buffer = m_rcBuffers[idx]; + int iBufferLen = (int)buffer.Size(); + + while(TRUE) + { + HP_SOCKADDR addr; + socklen_t dwAddrLen = (socklen_t)addr.AddrSize(); + + int rc = (int)recvfrom(m_soListens[idx], buffer.Ptr(), iBufferLen, MSG_TRUNC, addr.Addr(), &dwAddrLen); + + if(rc >= 0) + { + if(rc > iBufferLen) + { + TRIGGER(FireError(&addr, buffer.Ptr(), iBufferLen, SO_RECEIVE, ERROR_BAD_LENGTH)); + continue; + } + + TRIGGER(FireReceive(&addr, buffer.Ptr(), rc)); + } + else if(rc == SOCKET_ERROR) + { + int code = ::WSAGetLastError(); + + if(code == ERROR_WOULDBLOCK) + break; + else if(!HandleClose(idx, nullptr, SO_RECEIVE, code)) + return FALSE; + } + else + { + ASSERT(FALSE); + } + } + + return TRUE; +} + +BOOL CUdpNode::HandleSend(const TDispContext* pContext, int flag, int rd) +{ + HandleCmdSend(pContext->GetIndex(), flag); + + return TRUE; +} + +VOID CUdpNode::HandleCmdSend(int idx, int flag) +{ + BOOL bBlocked = FALSE; + TNodeBufferObjList& sndBuff = m_sndBuffs[idx]; + + TNodeBufferObjPtr bufPtr(m_bfObjPool); + + while(IsPending(idx)) + { + { + CCriSecLock locallock(m_csSends[idx]); + bufPtr = sndBuff.PopFront(); + } + + if(!bufPtr.IsValid()) + break; + + if(!SendItem(idx, sndBuff, bufPtr, bBlocked)) + return; + + if(bBlocked) + { + { + CCriSecLock locallock(m_csSends[idx]); + sndBuff.PushFront(bufPtr.Detach()); + } + + break; + } + } + + if(!bBlocked && IsPending(idx)) + VERIFY(m_ioDispatcher.SendCommandByIndex(idx, DISP_CMD_SEND)); +} + +BOOL CUdpNode::SendItem(int idx, TNodeBufferObjList& sndBuff, TNodeBufferObj* pBufferObj, BOOL& bBlocked) +{ + int rc = (int)sendto(m_soListens[idx], pBufferObj->Ptr(), pBufferObj->Size(), 0, pBufferObj->remoteAddr.Addr(), pBufferObj->remoteAddr.AddrSize()); + + if(rc >= 0) + { + ASSERT(rc == pBufferObj->Size()); + + if(rc == 0) + { + CCriSecLock locallock(m_csSends[idx]); + sndBuff.ReduceLength(1); + } + + TRIGGER(FireSend(pBufferObj)); + } + else if(rc == SOCKET_ERROR) + { + int code = ::WSAGetLastError(); + + if(code == ERROR_WOULDBLOCK) + bBlocked = TRUE; + else if(!HandleClose(idx, pBufferObj, SO_SEND, code)) + return FALSE; + } + else + { + ASSERT(FALSE); + } + + return TRUE; +} + +BOOL CUdpNode::GetLocalAddress(TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort) +{ + ADDRESS_FAMILY usFamily; + return ::sockaddr_IN_2_A(m_localAddr, usFamily, lpszAddress, iAddressLen, usPort); +} + +BOOL CUdpNode::GetCastAddress(TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort) +{ + ADDRESS_FAMILY usFamily; + return ::sockaddr_IN_2_A(m_castAddr, usFamily, lpszAddress, iAddressLen, usPort); +} + +void CUdpNode::SetLastError(EnSocketError code, LPCSTR func, int ec) +{ + m_enLastError = code; + ::SetLastError(ec); +} + +BOOL CUdpNode::GetPendingDataLength(int& iPending) +{ + iPending = 0; + + { + CReentrantReadLock locallock(m_lcState); + + if(!IsValid()) + return FALSE; + + for_each(m_sndBuffs.get(), m_sndBuffs.get() + m_dwWorkerThreadCount, [&iPending](TNodeBufferObjList& sndBuff) { iPending += sndBuff.Length(); }); + } + + return TRUE; +} + +EnHandleResult CUdpNode::FireSend(TNodeBufferObj* pBufferObj) +{ + TCHAR szAddress[60]; + int iAddressLen = ARRAY_SIZE(szAddress); + ADDRESS_FAMILY usFamily; + USHORT usPort; + + ::sockaddr_IN_2_A(pBufferObj->remoteAddr, usFamily, szAddress, iAddressLen, usPort); + + return m_pListener->OnSend(this, szAddress, usPort, pBufferObj->Ptr(), pBufferObj->Size()); +} + +EnHandleResult CUdpNode::FireReceive(const HP_SOCKADDR* pRemoteAddr, const BYTE* pData, int iLength) +{ + TCHAR szAddress[60]; + int iAddressLen = ARRAY_SIZE(szAddress); + ADDRESS_FAMILY usFamily; + USHORT usPort; + + ::sockaddr_IN_2_A(*pRemoteAddr, usFamily, szAddress, iAddressLen, usPort); + return m_pListener->OnReceive(this, szAddress, usPort, pData, iLength); +} + +EnHandleResult CUdpNode::FireError(const HP_SOCKADDR* pRemoteAddr, const BYTE* pData, int iLength, EnSocketOperation enOperation, int iErrorCode) +{ + TCHAR szAddress[60]; + int iAddressLen = ARRAY_SIZE(szAddress); + ADDRESS_FAMILY usFamily; + USHORT usPort; + + if(pRemoteAddr == nullptr) + { + ::sockaddr_IN_2_A(m_localAddr, usFamily, szAddress, iAddressLen, usPort); + return m_pListener->OnError(this, enOperation, iErrorCode, szAddress, usPort, nullptr, 0); + } + + ::sockaddr_IN_2_A(*pRemoteAddr, usFamily, szAddress, iAddressLen, usPort); + return m_pListener->OnError(this, enOperation, iErrorCode, szAddress, usPort, pData, iLength); +} + +#endif + +#if defined(__GNUC__) && __GNUC__ >= 11 +#pragma GCC diagnostic pop +#endif diff --git a/UdpNode.h b/UdpNode.h new file mode 100644 index 0000000..67b2ca9 --- /dev/null +++ b/UdpNode.h @@ -0,0 +1,198 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "SocketHelper.h" +#include "./common/GeneralHelper.h" +#include "./common/IODispatcher.h" + +#ifdef _UDP_SUPPORT + +class CUdpNode : public IUdpNode, private CIOHandler +{ +public: + virtual BOOL Start (LPCTSTR lpszBindAddress = nullptr, USHORT usPort = 0, EnCastMode enCastMode = CM_UNICAST, LPCTSTR lpszCastAddress = nullptr); + virtual BOOL Stop (); + virtual BOOL Send (LPCTSTR lpszRemoteAddress, USHORT usRemotePort, const BYTE* pBuffer, int iLength, int iOffset = 0); + virtual BOOL SendPackets (LPCTSTR lpszRemoteAddress, USHORT usRemotePort, const WSABUF pBuffers[], int iCount); + virtual BOOL SendCast (const BYTE* pBuffer, int iLength, int iOffset = 0); + virtual BOOL SendCastPackets(const WSABUF pBuffers[], int iCount); + virtual BOOL Wait (DWORD dwMilliseconds = INFINITE) {return m_evWait.WaitFor(dwMilliseconds, WAIT_FOR_STOP_PREDICATE);} + + virtual BOOL HasStarted () {return m_enState == SS_STARTED || m_enState == SS_STARTING;} + virtual EnServiceState GetState () {return m_enState;} + virtual BOOL GetLocalAddress (TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort); + virtual BOOL GetCastAddress (TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort); + + virtual EnSocketError GetLastError () {return m_enLastError;} + virtual LPCTSTR GetLastErrorDesc () {return ::GetSocketErrorDesc(m_enLastError);} + virtual BOOL GetPendingDataLength (int& iPending); + +private: + virtual BOOL OnBeforeProcessIo(const TDispContext* pContext, PVOID pv, UINT events) override; + virtual VOID OnAfterProcessIo(const TDispContext* pContext, PVOID pv, UINT events, BOOL rs) override; + virtual VOID OnCommand(const TDispContext* pContext, TDispCommand* pCmd) override; + virtual BOOL OnReadyRead(const TDispContext* pContext, PVOID pv, UINT events) override; + virtual BOOL OnReadyWrite(const TDispContext* pContext, PVOID pv, UINT events) override; + virtual BOOL OnHungUp(const TDispContext* pContext, PVOID pv, UINT events) override; + virtual BOOL OnError(const TDispContext* pContext, PVOID pv, UINT events) override; + virtual VOID OnDispatchThreadStart(THR_ID tid) override; + virtual VOID OnDispatchThreadEnd(THR_ID tid) override; + +public: + virtual void SetReuseAddressPolicy (EnReuseAddressPolicy enReusePolicy){ENSURE_HAS_STOPPED(); ASSERT(m_enReusePolicy == enReusePolicy);} + virtual void SetWorkerThreadCount (DWORD dwWorkerThreadCount) {ENSURE_HAS_STOPPED(); m_dwWorkerThreadCount = dwWorkerThreadCount;} + virtual void SetFreeBufferPoolSize (DWORD dwFreeBufferPoolSize) {ENSURE_HAS_STOPPED(); m_dwFreeBufferPoolSize = dwFreeBufferPoolSize;} + virtual void SetFreeBufferPoolHold (DWORD dwFreeBufferPoolHold) {ENSURE_HAS_STOPPED(); m_dwFreeBufferPoolHold = dwFreeBufferPoolHold;} + virtual void SetPostReceiveCount (DWORD dwPostReceiveCount) {ENSURE_HAS_STOPPED(); m_dwPostReceiveCount = dwPostReceiveCount;} + virtual void SetMaxDatagramSize (DWORD dwMaxDatagramSize) {ENSURE_HAS_STOPPED(); m_dwMaxDatagramSize = dwMaxDatagramSize;} + virtual void SetMultiCastTtl (int iMCTtl) {ENSURE_HAS_STOPPED(); m_iMCTtl = iMCTtl;} + virtual void SetMultiCastLoop (BOOL bMCLoop) {ENSURE_HAS_STOPPED(); m_bMCLoop = bMCLoop;} + virtual void SetExtra (PVOID pExtra) {m_pExtra = pExtra;} + + virtual EnReuseAddressPolicy GetReuseAddressPolicy () {return m_enReusePolicy;} + virtual DWORD GetWorkerThreadCount () {return m_dwWorkerThreadCount;} + virtual DWORD GetFreeBufferPoolSize () {return m_dwFreeBufferPoolSize;} + virtual DWORD GetFreeBufferPoolHold () {return m_dwFreeBufferPoolHold;} + virtual DWORD GetPostReceiveCount () {return m_dwPostReceiveCount;} + virtual DWORD GetMaxDatagramSize () {return m_dwMaxDatagramSize;} + virtual EnCastMode GetCastMode () {return m_enCastMode;} + virtual int GetMultiCastTtl () {return m_iMCTtl;} + virtual BOOL IsMultiCastLoop () {return m_bMCLoop;} + virtual PVOID GetExtra () {return m_pExtra;} + +protected: + EnHandleResult FirePrepareListen(SOCKET soListen) + {return m_pListener->OnPrepareListen(this, soListen);} + EnHandleResult FireShutdown() + {return m_pListener->OnShutdown(this);} + + EnHandleResult FireSend(TNodeBufferObj* pBufferObj); + EnHandleResult FireReceive(const HP_SOCKADDR* pRemoteAddr, const BYTE* pData, int iLength); + EnHandleResult FireError(const HP_SOCKADDR* pRemoteAddr, const BYTE* pData, int iLength, EnSocketOperation enOperation, int iErrorCode); + + void SetLastError(EnSocketError code, LPCSTR func, int ec); + virtual BOOL CheckParams(); + virtual void PrepareStart(); + virtual void Reset(); + + virtual void OnWorkerThreadStart(THR_ID dwThreadID) {} + virtual void OnWorkerThreadEnd(THR_ID dwThreadID) {} + + BOOL DoSend(const HP_SOCKADDR& addrRemote, const BYTE* pBuffer, int iLength, int iOffset = 0); + BOOL DoSendPackets(const HP_SOCKADDR& addrRemote, const WSABUF pBuffers[], int iCount); + int SendInternal(const HP_SOCKADDR& addrRemote, TNodeBufferObjPtr& bufPtr); + +private: + BOOL CheckStarting(); + BOOL CheckStoping(); + BOOL ParseBindAddr(LPCTSTR lpszBindAddress, USHORT usPort, LPCTSTR lpszCastAddress, HP_SOCKADDR& bindAddr); + BOOL CreateListenSocket(const HP_SOCKADDR& bindAddr); + BOOL CreateWorkerThreads(); + BOOL StartAccept(); + + void CloseListenSocket(); + void WaitForWorkerThreadEnd(); + void ReleaseFreeBuffer(); + + int GenerateBufferIndex(const HP_SOCKADDR& addrRemote); + +private: + BOOL HandleReceive(const TDispContext* pContext, int flag = 0); + BOOL HandleSend(const TDispContext* pContext, int flag = 0, int rd = 0); + BOOL HandleClose(int idx, TNodeBufferObj* pBufferObj, EnSocketOperation enOperation, int iErrorCode); + + VOID HandleCmdSend(int idx, int flag); + + BOOL SendItem(int idx, TNodeBufferObjList& sndBuff, TNodeBufferObj* pBufferObj, BOOL& bBlocked); + +private: + BOOL IsValid () {return m_enState == SS_STARTED;} + BOOL IsPending (int idx) {return m_sndBuffs[idx].Length() > 0;} + +public: + CUdpNode(IUdpNodeListener* pListener) + : m_pListener (pListener) + , m_iSending (0) + , m_enLastError (SE_OK) + , m_enState (SS_STOPPED) + , m_enReusePolicy (RAP_ADDR_AND_PORT) + , m_dwWorkerThreadCount (DEFAULT_WORKER_THREAD_COUNT) + , m_dwFreeBufferPoolSize (DEFAULT_FREE_BUFFEROBJ_POOL) + , m_dwFreeBufferPoolHold (DEFAULT_FREE_BUFFEROBJ_HOLD) + , m_dwPostReceiveCount (DEFAULT_UDP_POST_RECEIVE_COUNT) + , m_dwMaxDatagramSize (DEFAULT_UDP_MAX_DATAGRAM_SIZE) + , m_pExtra (nullptr) + , m_iMCTtl (1) + , m_bMCLoop (FALSE) + , m_enCastMode (CM_UNICAST) + , m_castAddr (AF_UNSPEC, TRUE) + , m_localAddr (AF_UNSPEC, TRUE) + { + ASSERT(m_pListener); + } + + virtual ~CUdpNode() + { + ENSURE_STOP(); + } + +private: + EnReuseAddressPolicy m_enReusePolicy; + DWORD m_dwWorkerThreadCount; + DWORD m_dwFreeBufferPoolSize; + DWORD m_dwFreeBufferPoolHold; + DWORD m_dwPostReceiveCount; + DWORD m_dwMaxDatagramSize; + PVOID m_pExtra; + + int m_iMCTtl; + BOOL m_bMCLoop; + EnCastMode m_enCastMode; + +private: + CSEM m_evWait; + + HP_SOCKADDR m_castAddr; + HP_SOCKADDR m_localAddr; + + CNodeBufferObjPool m_bfObjPool; + CNodeCriSecs m_csSends; + TNodeBufferObjLists m_sndBuffs; + + IUdpNodeListener* m_pListener; + ListenSocketsPtr m_soListens; + EnServiceState m_enState; + EnSocketError m_enLastError; + + CReceiveBuffersPtr m_rcBuffers; + + CRWLock m_lcState; + + volatile long m_iSending; + + CIODispatcher m_ioDispatcher; +}; + +#endif diff --git a/UdpServer.cpp b/UdpServer.cpp new file mode 100644 index 0000000..af7510e --- /dev/null +++ b/UdpServer.cpp @@ -0,0 +1,1231 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "UdpServer.h" + +#ifdef _UDP_SUPPORT + +EnHandleResult CUdpServer::TriggerFireAccept(TUdpSocketObj* pSocketObj) +{ + EnHandleResult rs = TRIGGER(FireAccept(pSocketObj)); + + return rs; +} + +BOOL CUdpServer::Start(LPCTSTR lpszBindAddress, USHORT usPort) +{ + if(!CheckParams() || !CheckStarting()) + return FALSE; + + PrepareStart(); + + if(CreateListenSocket(lpszBindAddress, usPort)) + if(CreateWorkerThreads()) + if(StartAccept()) + { + m_enState = SS_STARTED; + return TRUE; + } + + EXECUTE_RESTORE_ERROR(Stop()); + + return FALSE; +} + +void CUdpServer::SetLastError(EnSocketError code, LPCSTR func, int ec) +{ + m_enLastError = code; + ::SetLastError(ec); +} + +BOOL CUdpServer::CheckParams() +{ + if ((m_enSendPolicy >= SP_PACK && m_enSendPolicy <= SP_DIRECT) && + (m_enOnSendSyncPolicy >= OSSP_NONE && m_enOnSendSyncPolicy <= OSSP_RECEIVE) && + ((int)m_dwMaxConnectionCount > 0 && m_dwMaxConnectionCount <= MAX_CONNECTION_COUNT) && + ((int)m_dwWorkerThreadCount > 0 && m_dwWorkerThreadCount <= MAX_WORKER_THREAD_COUNT) && + ((int)m_dwFreeSocketObjLockTime >= 1000) && + ((int)m_dwFreeSocketObjPool >= 0) && + ((int)m_dwFreeBufferObjPool >= 0) && + ((int)m_dwFreeSocketObjHold >= 0) && + ((int)m_dwFreeBufferObjHold >= 0) && + ((int)m_dwMaxDatagramSize > 0 && m_dwMaxDatagramSize <= MAXIMUM_UDP_MAX_DATAGRAM_SIZE) && + ((int)m_dwPostReceiveCount > 0) && + ((int)m_dwDetectAttempts >= 0) && + ((int)m_dwDetectInterval >= 1000 || m_dwDetectInterval == 0) ) + return TRUE; + + SetLastError(SE_INVALID_PARAM, __FUNCTION__, ERROR_INVALID_PARAMETER); + return FALSE; +} + +void CUdpServer::PrepareStart() +{ + m_bfActiveSockets.Reset(m_dwMaxConnectionCount); + m_lsFreeSocket.Reset(m_dwFreeSocketObjPool); + + m_bfObjPool.SetItemCapacity(m_dwMaxDatagramSize); + m_bfObjPool.SetPoolSize(m_dwFreeBufferObjPool); + m_bfObjPool.SetPoolHold(m_dwFreeBufferObjHold); + + m_bfObjPool.Prepare(); + + m_quSends = make_unique(m_dwWorkerThreadCount); + + m_rcBuffers = make_unique(m_dwWorkerThreadCount); + for_each(m_rcBuffers.get(), m_rcBuffers.get() + m_dwWorkerThreadCount, [this](CBufferPtr& buff) {buff.Malloc(m_dwMaxDatagramSize);}); + + m_soListens = make_unique(m_dwWorkerThreadCount); + for_each(m_soListens.get(), m_soListens.get() + m_dwWorkerThreadCount, [](SOCKET& sock) {sock = INVALID_FD;}); +} + +BOOL CUdpServer::CheckStarting() +{ + CSpinLock locallock(m_csState); + + if(m_enState == SS_STOPPED) + m_enState = SS_STARTING; + else + { + SetLastError(SE_ILLEGAL_STATE, __FUNCTION__, ERROR_INVALID_STATE); + return FALSE; + } + + return TRUE; +} + +BOOL CUdpServer::CheckStoping() +{ + if(m_enState != SS_STOPPED) + { + CSpinLock locallock(m_csState); + + if(HasStarted()) + { + m_enState = SS_STOPPING; + return TRUE; + } + } + + SetLastError(SE_ILLEGAL_STATE, __FUNCTION__, ERROR_INVALID_STATE); + + return FALSE; +} + +BOOL CUdpServer::CreateListenSocket(LPCTSTR lpszBindAddress, USHORT usPort) +{ + if(::IsStrEmpty(lpszBindAddress)) + lpszBindAddress = DEFAULT_IPV4_BIND_ADDRESS; + + HP_SOCKADDR addr; + + if(!::sockaddr_A_2_IN(lpszBindAddress, usPort, addr)) + { + SetLastError(SE_SOCKET_CREATE, __FUNCTION__, ::WSAGetLastError()); + return FALSE; + } + + for(DWORD i = 0; i < m_dwWorkerThreadCount; i++) + { + m_soListens[i] = socket(addr.family, SOCK_DGRAM, IPPROTO_UDP); + SOCKET soListen = m_soListens[i]; + + if(IS_INVALID_FD(soListen)) + { + SetLastError(SE_SOCKET_CREATE, __FUNCTION__, ::WSAGetLastError()); + return FALSE; + } + + ::fcntl_SETFL(soListen, O_NOATIME | O_NONBLOCK | O_CLOEXEC); + VERIFY(IS_NO_ERROR(::SSO_ReuseAddress(soListen, m_enReusePolicy))); + + if(IS_HAS_ERROR(::bind(soListen, addr.Addr(), addr.AddrSize()))) + { + SetLastError(SE_SOCKET_BIND, __FUNCTION__, ::WSAGetLastError()); + return FALSE; + } + + if(TRIGGER(FirePrepareListen(soListen)) == HR_ERROR) + { + SetLastError(SE_SOCKET_PREPARE, __FUNCTION__, ENSURE_ERROR_CANCELLED); + return FALSE; + } + } + + return TRUE; +} + +BOOL CUdpServer::CreateWorkerThreads() +{ + DWORD dwWorkerThreadCount = m_dwWorkerThreadCount +#ifdef USE_EXTERNAL_GC + + 1 +#endif + ; + + if(!m_ioDispatcher.Start(this, m_dwPostReceiveCount, dwWorkerThreadCount)) + { + SetLastError(SE_WORKER_THREAD_CREATE, __FUNCTION__, ::WSAGetLastError()); + return FALSE; + } + +#ifdef USE_EXTERNAL_GC + m_fdGCTimer = m_ioDispatcher.AddTimer(m_dwWorkerThreadCount, GC_CHECK_INTERVAL, this); + + if(IS_INVALID_FD(m_fdGCTimer)) + { + SetLastError(SE_GC_START, __FUNCTION__, ::WSAGetLastError()); + return FALSE; + } +#endif + + return TRUE; +} + +BOOL CUdpServer::StartAccept() +{ + for(int i = 0; i < (int)m_dwWorkerThreadCount; i++) + { + SOCKET& soListen = m_soListens[i]; + + if(!m_ioDispatcher.AddFD(i, soListen, EPOLLIN | EPOLLOUT | EPOLLET, TO_PVOID(&soListen))) + { + SetLastError(SE_SOCKE_ATTACH_TO_CP, __FUNCTION__, ::WSAGetLastError()); + return FALSE; + } + } + +#ifdef USE_EXTERNAL_GC + m_fdGCTimer = m_ioDispatcher.AddTimer(m_dwWorkerThreadCount, GC_CHECK_INTERVAL, this); + + if(IS_INVALID_FD(m_fdGCTimer)) + { + SetLastError(SE_GC_START, __FUNCTION__, ::WSAGetLastError()); + return FALSE; + } +#endif + + return TRUE; +} + +BOOL CUdpServer::Stop() +{ + if(!CheckStoping()) + return FALSE; + + SendCloseNotify(); + + CloseListenSocket(); + + DisconnectClientSocket(); + WaitForClientSocketClose(); + + WaitForWorkerThreadEnd(); + + ReleaseClientSocket(); + + FireShutdown(); + + ReleaseFreeSocket(); + ClearSendQueues(); + + Reset(); + + return TRUE; +} + +void CUdpServer::SendCloseNotify() +{ + if(!m_soListens) + return; + if(m_bfActiveSockets.Elements() == 0) + return; + + TUdpSocketObjPtrPool::IndexSet indexes; + m_bfActiveSockets.CopyIndexes(indexes); + + for(auto it = indexes.begin(), end = indexes.end(); it != end; ++it) + { + CONNID connID = *it; + TUdpSocketObj* pSocketObj = FindSocketObj(connID); + + if(TUdpSocketObj::IsValid(pSocketObj)) + ::SendUdpCloseNotify(m_soListens[pSocketObj->index], pSocketObj->remoteAddr); + } + + ::WaitFor(30); +} + +void CUdpServer::CloseListenSocket() +{ + if(m_soListens) + { + for_each(m_soListens.get(), m_soListens.get() + m_dwWorkerThreadCount, [](SOCKET& sock) + { + if(sock != INVALID_FD) + { + ::ManualCloseSocket(sock); + sock = INVALID_FD; + } + }); + + ::WaitFor(70); + } +} + +void CUdpServer::DisconnectClientSocket() +{ + if(m_bfActiveSockets.Elements() == 0) + return; + + TUdpSocketObjPtrPool::IndexSet indexes; + m_bfActiveSockets.CopyIndexes(indexes); + + for(auto it = indexes.begin(), end = indexes.end(); it != end; ++it) + Disconnect(*it); +} + +void CUdpServer::WaitForClientSocketClose() +{ + while(m_bfActiveSockets.Elements() > 0) + ::WaitFor(50); +} + +void CUdpServer::WaitForWorkerThreadEnd() +{ + m_ioDispatcher.Stop(); +} + +void CUdpServer::ReleaseClientSocket() +{ + VERIFY(m_bfActiveSockets.IsEmpty()); + m_bfActiveSockets.Reset(); + + CWriteLock locallock(m_csClientSocket); + m_mpClientAddr.clear(); +} + +void CUdpServer::ReleaseFreeSocket() +{ + m_lsFreeSocket.Clear(); + +#ifdef USE_EXTERNAL_GC + if(IS_VALID_FD(m_fdGCTimer)) + { + close(m_fdGCTimer); + m_fdGCTimer = INVALID_FD; + } +#endif + + ReleaseGCSocketObj(TRUE); + VERIFY(m_lsGCSocket.IsEmpty()); +} + +void CUdpServer::ClearSendQueues() +{ + if(m_quSends) for_each(m_quSends.get(), m_quSends.get() + m_dwWorkerThreadCount, [](CSendQueue& queue) {queue.UnsafeClear();}); +} + +void CUdpServer::Reset() +{ + m_phSocket.Reset(); + m_bfObjPool.Clear(); + + m_soListens = nullptr; + m_rcBuffers = nullptr; + m_quSends = nullptr; + + m_enState = SS_STOPPED; + + m_evWait.SyncNotifyAll(); +} + +TUdpSocketObj* CUdpServer::GetFreeSocketObj(CONNID dwConnID) +{ + DWORD dwIndex; + TUdpSocketObj* pSocketObj = nullptr; + + if(m_lsFreeSocket.TryLock(&pSocketObj, dwIndex)) + { + if(::GetTimeGap32(pSocketObj->freeTime) >= m_dwFreeSocketObjLockTime) + VERIFY(m_lsFreeSocket.ReleaseLock(nullptr, dwIndex)); + else + { + VERIFY(m_lsFreeSocket.ReleaseLock(pSocketObj, dwIndex)); + pSocketObj = nullptr; + } + } + + if(!pSocketObj) pSocketObj = CreateSocketObj(); + pSocketObj->Reset(dwConnID); + + return pSocketObj; +} + +TUdpSocketObj* CUdpServer::CreateSocketObj() +{ + return TUdpSocketObj::Construct(m_phSocket, m_bfObjPool); +} + +void CUdpServer::DeleteSocketObj(TUdpSocketObj* pSocketObj) +{ + TUdpSocketObj::Destruct(pSocketObj); +} + +void CUdpServer::AddFreeSocketObj(TUdpSocketObj* pSocketObj, EnSocketCloseFlag enFlag, EnSocketOperation enOperation, int iErrorCode, BOOL bNotify) +{ + if(!InvalidSocketObj(pSocketObj)) + return; + + CloseClientSocketObj(pSocketObj, enFlag, enOperation, iErrorCode, bNotify); + + { + m_bfActiveSockets.Remove(pSocketObj->connID); + + CWriteLock locallock(m_csClientSocket); + m_mpClientAddr.erase(&pSocketObj->remoteAddr); + } + + m_ioDispatcher.DelTimer(pSocketObj->index, pSocketObj->fdTimer); + TUdpSocketObj::Release(pSocketObj); + +#ifndef USE_EXTERNAL_GC + ReleaseGCSocketObj(); +#endif + + if(!m_lsFreeSocket.TryPut(pSocketObj)) + m_lsGCSocket.PushBack(pSocketObj); +} + +void CUdpServer::ReleaseGCSocketObj(BOOL bForce) +{ + ::ReleaseGCObj(m_lsGCSocket, m_dwFreeSocketObjLockTime, bForce); +} + +BOOL CUdpServer::InvalidSocketObj(TUdpSocketObj* pSocketObj) +{ + return TUdpSocketObj::InvalidSocketObj(pSocketObj); +} + +void CUdpServer::AddClientSocketObj(int idx, CONNID dwConnID, TUdpSocketObj* pSocketObj, const HP_SOCKADDR& remoteAddr) +{ + ASSERT(FindSocketObj(dwConnID) == nullptr); + + pSocketObj->index = idx; + pSocketObj->pHolder = this; + pSocketObj->connTime = ::TimeGetTime(); + pSocketObj->activeTime = pSocketObj->connTime; + + if(IsNeedDetectConnection()) + pSocketObj->fdTimer = m_ioDispatcher.AddTimer(pSocketObj->index, m_dwDetectInterval, pSocketObj); + + remoteAddr.Copy(pSocketObj->remoteAddr); + pSocketObj->SetConnected(); + + VERIFY(m_bfActiveSockets.ReleaseLock(dwConnID, pSocketObj)); + + CWriteLock locallock(m_csClientSocket); + m_mpClientAddr[&pSocketObj->remoteAddr] = dwConnID; +} + +TUdpSocketObj* CUdpServer::FindSocketObj(CONNID dwConnID) +{ + TUdpSocketObj* pSocketObj = nullptr; + + if(m_bfActiveSockets.Get(dwConnID, &pSocketObj) != TUdpSocketObjPtrPool::GR_VALID) + pSocketObj = nullptr; + + return pSocketObj; +} + +CONNID CUdpServer::FindConnectionID(const HP_SOCKADDR* pAddr) +{ + CONNID dwConnID = 0; + + CReadLock locallock(m_csClientSocket); + + TSockAddrMapCI it = m_mpClientAddr.find(pAddr); + if(it != m_mpClientAddr.end()) + dwConnID = it->second; + + return dwConnID; +} + +void CUdpServer::CloseClientSocketObj(TUdpSocketObj* pSocketObj, EnSocketCloseFlag enFlag, EnSocketOperation enOperation, int iErrorCode, BOOL bNotify) +{ + ASSERT(TUdpSocketObj::IsExist(pSocketObj)); + + SOCKET soListen = m_soListens[pSocketObj->index]; + + if(bNotify && soListen != INVALID_SOCKET) + ::SendUdpCloseNotify(soListen, pSocketObj->remoteAddr); + + if(enFlag == SCF_CLOSE) + FireClose(pSocketObj, SO_CLOSE, SE_OK); + else if(enFlag == SCF_ERROR) + FireClose(pSocketObj, enOperation, iErrorCode); +} + +BOOL CUdpServer::GetListenAddress(TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort) +{ + ASSERT(lpszAddress != nullptr && iAddressLen > 0); + + if(!HasStarted()) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + return ::GetSocketLocalAddress(m_soListens[0], lpszAddress, iAddressLen, usPort); +} + +BOOL CUdpServer::GetLocalAddress(CONNID dwConnID, TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort) +{ + ASSERT(lpszAddress != nullptr && iAddressLen > 0); + + TUdpSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TUdpSocketObj::IsValid(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + return ::GetSocketLocalAddress(m_soListens[pSocketObj->index], lpszAddress, iAddressLen, usPort); +} + +BOOL CUdpServer::GetRemoteAddress(CONNID dwConnID, TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort) +{ + ASSERT(lpszAddress != nullptr && iAddressLen > 0); + + TUdpSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TUdpSocketObj::IsExist(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + ADDRESS_FAMILY usFamily; + return ::sockaddr_IN_2_A(pSocketObj->remoteAddr, usFamily, lpszAddress, iAddressLen, usPort); +} + +BOOL CUdpServer::SetConnectionExtra(CONNID dwConnID, PVOID pExtra) +{ + TUdpSocketObj* pSocketObj = FindSocketObj(dwConnID); + return SetConnectionExtra(pSocketObj, pExtra); +} + +BOOL CUdpServer::SetConnectionExtra(TUdpSocketObj* pSocketObj, PVOID pExtra) +{ + if(!TUdpSocketObj::IsExist(pSocketObj)) + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + else + { + pSocketObj->extra = pExtra; + return TRUE; + } + + return FALSE; +} + +BOOL CUdpServer::GetConnectionExtra(CONNID dwConnID, PVOID* ppExtra) +{ + TUdpSocketObj* pSocketObj = FindSocketObj(dwConnID); + return GetConnectionExtra(pSocketObj, ppExtra); +} + +BOOL CUdpServer::GetConnectionExtra(TUdpSocketObj* pSocketObj, PVOID* ppExtra) +{ + ASSERT(ppExtra != nullptr); + + if(!TUdpSocketObj::IsExist(pSocketObj)) + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + else + { + *ppExtra = pSocketObj->extra; + return TRUE; + } + + return FALSE; +} + +BOOL CUdpServer::SetConnectionReserved(CONNID dwConnID, PVOID pReserved) +{ + TUdpSocketObj* pSocketObj = FindSocketObj(dwConnID); + return SetConnectionReserved(pSocketObj, pReserved); +} + +BOOL CUdpServer::SetConnectionReserved(TUdpSocketObj* pSocketObj, PVOID pReserved) +{ + if(!TUdpSocketObj::IsExist(pSocketObj)) + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + else + { + pSocketObj->reserved = pReserved; + return TRUE; + } + + return FALSE; +} + +BOOL CUdpServer::GetConnectionReserved(CONNID dwConnID, PVOID* ppReserved) +{ + TUdpSocketObj* pSocketObj = FindSocketObj(dwConnID); + return GetConnectionReserved(pSocketObj, ppReserved); +} + +BOOL CUdpServer::GetConnectionReserved(TUdpSocketObj* pSocketObj, PVOID* ppReserved) +{ + ASSERT(ppReserved != nullptr); + + if(!TUdpSocketObj::IsExist(pSocketObj)) + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + else + { + *ppReserved = pSocketObj->reserved; + return TRUE; + } + + return FALSE; +} + +BOOL CUdpServer::SetConnectionReserved2(CONNID dwConnID, PVOID pReserved2) +{ + TUdpSocketObj* pSocketObj = FindSocketObj(dwConnID); + return SetConnectionReserved2(pSocketObj, pReserved2); +} + +BOOL CUdpServer::SetConnectionReserved2(TUdpSocketObj* pSocketObj, PVOID pReserved2) +{ + if(!TUdpSocketObj::IsExist(pSocketObj)) + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + else + { + pSocketObj->reserved2 = pReserved2; + return TRUE; + } + + return FALSE; +} + +BOOL CUdpServer::GetConnectionReserved2(CONNID dwConnID, PVOID* ppReserved2) +{ + TUdpSocketObj* pSocketObj = FindSocketObj(dwConnID); + return GetConnectionReserved2(pSocketObj, ppReserved2); +} + +BOOL CUdpServer::GetConnectionReserved2(TUdpSocketObj* pSocketObj, PVOID* ppReserved2) +{ + ASSERT(ppReserved2 != nullptr); + + if(!TUdpSocketObj::IsExist(pSocketObj)) + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + else + { + *ppReserved2 = pSocketObj->reserved2; + return TRUE; + } + + return FALSE; +} + +BOOL CUdpServer::IsPauseReceive(CONNID dwConnID, BOOL& bPaused) +{ + ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + + bPaused = FALSE; + + return FALSE; +} + +BOOL CUdpServer::IsConnected(CONNID dwConnID) +{ + TUdpSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TUdpSocketObj::IsValid(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + return pSocketObj->HasConnected(); +} + +BOOL CUdpServer::GetPendingDataLength(CONNID dwConnID, int& iPending) +{ + TUdpSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TUdpSocketObj::IsValid(pSocketObj)) + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + else + { + iPending = pSocketObj->Pending(); + return TRUE; + } + + return FALSE; +} + +DWORD CUdpServer::GetConnectionCount() +{ + return m_bfActiveSockets.Elements(); +} + +BOOL CUdpServer::GetAllConnectionIDs(CONNID pIDs[], DWORD& dwCount) +{ + return m_bfActiveSockets.GetAllElementIndexes(pIDs, dwCount); +} + +BOOL CUdpServer::GetConnectPeriod(CONNID dwConnID, DWORD& dwPeriod) +{ + BOOL isOK = TRUE; + TUdpSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(TUdpSocketObj::IsValid(pSocketObj)) + dwPeriod = ::GetTimeGap32(pSocketObj->connTime); + else + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + isOK = FALSE; + } + + return isOK; +} + +BOOL CUdpServer::GetSilencePeriod(CONNID dwConnID, DWORD& dwPeriod) +{ + if(!m_bMarkSilence) + return FALSE; + + BOOL isOK = TRUE; + TUdpSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(TUdpSocketObj::IsValid(pSocketObj)) + dwPeriod = ::GetTimeGap32(pSocketObj->activeTime); + else + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + isOK = FALSE; + } + + return isOK; +} + +BOOL CUdpServer::Disconnect(CONNID dwConnID, BOOL bForce) +{ + TUdpSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TUdpSocketObj::IsValid(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + return m_ioDispatcher.SendCommandByIndex(pSocketObj->index, DISP_CMD_DISCONNECT, dwConnID, bForce); +} + +BOOL CUdpServer::DisconnectLongConnections(DWORD dwPeriod, BOOL bForce) +{ + if(dwPeriod > MAX_CONNECTION_PERIOD) + return FALSE; + if(m_bfActiveSockets.Elements() == 0) + return TRUE; + + DWORD now = ::TimeGetTime(); + + TUdpSocketObjPtrPool::IndexSet indexes; + m_bfActiveSockets.CopyIndexes(indexes); + + for(auto it = indexes.begin(), end = indexes.end(); it != end; ++it) + { + CONNID connID = *it; + TUdpSocketObj* pSocketObj = FindSocketObj(connID); + + if(TUdpSocketObj::IsValid(pSocketObj) && (int)(now - pSocketObj->connTime) >= (int)dwPeriod) + Disconnect(connID, bForce); + } + + return TRUE; +} + +BOOL CUdpServer::DisconnectSilenceConnections(DWORD dwPeriod, BOOL bForce) +{ + if(!m_bMarkSilence) + return FALSE; + if(dwPeriod > MAX_CONNECTION_PERIOD) + return FALSE; + if(m_bfActiveSockets.Elements() == 0) + return TRUE; + + DWORD now = ::TimeGetTime(); + + TUdpSocketObjPtrPool::IndexSet indexes; + m_bfActiveSockets.CopyIndexes(indexes); + + for(auto it = indexes.begin(), end = indexes.end(); it != end; ++it) + { + CONNID connID = *it; + TUdpSocketObj* pSocketObj = FindSocketObj(connID); + + if(TUdpSocketObj::IsValid(pSocketObj) && (int)(now - pSocketObj->activeTime) >= (int)dwPeriod) + Disconnect(connID, bForce); + } + + return TRUE; +} + +BOOL CUdpServer::PauseReceive(CONNID dwConnID, BOOL bPause) +{ + ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; +} + +BOOL CUdpServer::OnBeforeProcessIo(const TDispContext* pContext, PVOID pv, UINT events) +{ + if(pv == &m_soListens[pContext->GetIndex()]) + return TRUE; + else if(pv == this) + { + ReleaseGCSocketObj(FALSE); + ::ReadTimer(m_fdGCTimer); + + return FALSE; + } + + if(!(events & _EPOLL_ALL_ERROR_EVENTS)) + DetectConnection(pv); + + return FALSE; +} + +VOID CUdpServer::OnAfterProcessIo(const TDispContext* pContext, PVOID pv, UINT events, BOOL rs) +{ + +} + +VOID CUdpServer::OnCommand(const TDispContext* pContext, TDispCommand* pCmd) +{ + CONNID dwConnID = (CONNID)(pCmd->wParam); + + switch(pCmd->type) + { + case DISP_CMD_SEND: + HandleCmdSend(dwConnID, (int)(pCmd->lParam)); + break; + case DISP_CMD_DISCONNECT: + HandleCmdDisconnect(dwConnID, (BOOL)pCmd->lParam); + break; + case DISP_CMD_TIMEOUT: + HandleCmdTimeout(dwConnID); + break; + } +} + +VOID CUdpServer::HandleCmdDisconnect(CONNID dwConnID, BOOL bForce) +{ + AddFreeSocketObj(FindSocketObj(dwConnID), SCF_CLOSE); +} + +VOID CUdpServer::HandleCmdTimeout(CONNID dwConnID) +{ + AddFreeSocketObj(FindSocketObj(dwConnID), SCF_CLOSE, SO_UNKNOWN, 0, FALSE); +} + +BOOL CUdpServer::OnReadyRead(const TDispContext* pContext, PVOID pv, UINT events) +{ + return HandleReceive(pContext, RETRIVE_EVENT_FLAG_H(events)); +} + +BOOL CUdpServer::OnReadyWrite(const TDispContext* pContext, PVOID pv, UINT events) +{ + return HandleSend(pContext, RETRIVE_EVENT_FLAG_H(events)); +} + +BOOL CUdpServer::OnHungUp(const TDispContext* pContext, PVOID pv, UINT events) +{ + return HandleClose(nullptr, SO_CLOSE, 0); +} + +BOOL CUdpServer::OnError(const TDispContext* pContext, PVOID pv, UINT events) +{ + return HandleClose(nullptr, SO_CLOSE, -1); +} + +VOID CUdpServer::OnDispatchThreadStart(THR_ID tid) +{ + OnWorkerThreadStart(tid); +} + +VOID CUdpServer::OnDispatchThreadEnd(THR_ID tid) +{ + OnWorkerThreadEnd(tid); +} + +BOOL CUdpServer::HandleClose(TUdpSocketObj* pSocketObj, EnSocketOperation enOperation, int iErrorCode) +{ + if(!HasStarted()) + return FALSE; + else if(pSocketObj == nullptr) + { + TRACE("HandleClose() -> OP: %d, EC: %d", enOperation, iErrorCode); + return TRUE; + } + + if(iErrorCode == -1) + iErrorCode = ::SSO_GetError(m_soListens[pSocketObj->index]); + + EnSocketCloseFlag enFlag = IS_NO_ERROR(iErrorCode) ? SCF_CLOSE : SCF_ERROR; + + AddFreeSocketObj(pSocketObj, enFlag, enOperation, iErrorCode); + + return FALSE; +} + +BOOL CUdpServer::HandleReceive(const TDispContext* pContext, int flag) +{ + int idx = pContext->GetIndex(); + CBufferPtr& buffer = m_rcBuffers[idx]; + int iBufferLen = (int)buffer.Size(); + + while(TRUE) + { + HP_SOCKADDR addr; + socklen_t dwAddrLen = (socklen_t)addr.AddrSize(); + + int rc = (int)recvfrom(m_soListens[idx], buffer.Ptr(), iBufferLen, MSG_TRUNC, addr.Addr(), &dwAddrLen); + + if(rc >= 0) + { + CONNID dwConnID = FindConnectionID(&addr); + + if(dwConnID == 0) + { + if(rc > iBufferLen) + continue; + + if((dwConnID = HandleAccept(pContext, addr)) == 0) + continue; + } + + TUdpSocketObj* pSocketObj = FindSocketObj(dwConnID); + ASSERT(pSocketObj->index == idx); + + if(!TUdpSocketObj::IsValid(pSocketObj)) + continue; + + if(rc == 0) + { + HandleZeroBytes(pSocketObj); + continue; + } + + if(rc > iBufferLen) + { + AddFreeSocketObj(pSocketObj, SCF_ERROR, SO_RECEIVE, ERROR_BAD_LENGTH); + continue; + } + + if(::IsUdpCloseNotify(buffer.Ptr(), rc)) + { + AddFreeSocketObj(pSocketObj, SCF_CLOSE, SO_CLOSE, SE_OK, FALSE); + continue; + } + + if(TRIGGER(FireReceive(pSocketObj, buffer.Ptr(), rc)) == HR_ERROR) + { + TRACE(" OnReceive() event return 'HR_ERROR', connection will be closed !", dwConnID); + + AddFreeSocketObj(pSocketObj, SCF_ERROR, SO_RECEIVE, ENSURE_ERROR_CANCELLED);; + continue; + } + } + else if(rc == SOCKET_ERROR) + { + int code = ::WSAGetLastError(); + + if(code == ERROR_WOULDBLOCK) + break; + else if(!HandleClose(nullptr, SO_RECEIVE, code)) + return FALSE; + } + else + { + ASSERT(FALSE); + } + } + + return TRUE; +} + +CONNID CUdpServer::HandleAccept(const TDispContext* pContext, HP_SOCKADDR& addr) +{ + int idx = pContext->GetIndex(); + CONNID dwConnID = 0; + TUdpSocketObj* pSocketObj = nullptr; + + if(!m_bfActiveSockets.AcquireLock(dwConnID)) + { + ::SendUdpCloseNotify(m_soListens[idx], addr); + return 0; + } + + pSocketObj = GetFreeSocketObj(dwConnID); + AddClientSocketObj(idx, dwConnID, pSocketObj, addr); + + if(TriggerFireAccept(pSocketObj) == HR_ERROR) + { + AddFreeSocketObj(pSocketObj); + dwConnID = 0; + } + + return dwConnID; +} + +void CUdpServer::HandleZeroBytes(TUdpSocketObj* pSocketObj) +{ + TRACE(" recv 0 bytes (detect package)", pSocketObj->connID); + + pSocketObj->detectFails = 0; + +#if defined(DEBUG_TRACE) + int rc = (int) +#endif + sendto(m_soListens[pSocketObj->index], nullptr, 0, 0, pSocketObj->remoteAddr.Addr(), pSocketObj->remoteAddr.AddrSize()); + + TRACE(" send 0 bytes (detect ack package - %s)", pSocketObj->connID, IS_HAS_ERROR(rc) ? "fail" : "succ"); +} + +BOOL CUdpServer::HandleSend(const TDispContext* pContext, int flag) +{ + CSendQueue& quSend = m_quSends[pContext->GetIndex()]; + CONNID dwConnID = 0; + + while(quSend.PopFront(&dwConnID)) + HandleCmdSend(dwConnID, flag); + + return TRUE; +} + +VOID CUdpServer::HandleCmdSend(CONNID dwConnID, int flag) +{ + TUdpSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TUdpSocketObj::IsValid(pSocketObj) || !pSocketObj->IsPending()) + return; + + BOOL bBlocked = FALSE; + int writes = flag ? -1 : MAX_CONTINUE_WRITES; + + TBufferObjList& sndBuff = pSocketObj->sndBuff; + TItemPtr itPtr(sndBuff); + + for(int i = 0; i < writes || writes < 0; i++) + { + { + CReentrantCriSecLock locallock(pSocketObj->csSend); + itPtr = sndBuff.PopFront(); + } + + if(!itPtr.IsValid()) + break; + + ASSERT(!itPtr->IsEmpty()); + + if(!SendItem(pSocketObj, itPtr, bBlocked)) + return; + + if(bBlocked) + { + { + CReentrantCriSecLock locallock(pSocketObj->csSend); + sndBuff.PushFront(itPtr.Detach()); + } + + m_quSends[pSocketObj->index].PushBack(dwConnID); + + break; + } + } + + if(!bBlocked && pSocketObj->IsPending()) + VERIFY(m_ioDispatcher.SendCommandByIndex(pSocketObj->index, DISP_CMD_SEND, dwConnID)); +} + +BOOL CUdpServer::SendItem(TUdpSocketObj* pSocketObj, TItem* pItem, BOOL& bBlocked) +{ + int rc = (int)sendto(m_soListens[pSocketObj->index], pItem->Ptr(), pItem->Size(), 0, pSocketObj->remoteAddr.Addr(), pSocketObj->remoteAddr.AddrSize()); + + if(rc > 0) + { + ASSERT(rc == pItem->Size()); + + if(TRIGGER(FireSend(pSocketObj, pItem->Ptr(), rc)) == HR_ERROR) + { + TRACE(" OnSend() event should not return 'HR_ERROR' !!", pSocketObj->connID); + ASSERT(FALSE); + } + } + else if(rc == SOCKET_ERROR) + { + int code = ::WSAGetLastError(); + + if(code == ERROR_WOULDBLOCK) + bBlocked = TRUE; + else if(!HandleClose(pSocketObj, SO_SEND, code)) + return FALSE; + } + else + ASSERT(FALSE); + + return TRUE; +} + +BOOL CUdpServer::Send(CONNID dwConnID, const BYTE* pBuffer, int iLength, int iOffset) +{ + ASSERT(pBuffer && iLength > 0 && iLength <= (int)m_dwMaxDatagramSize); + + TUdpSocketObj* pSocketObj = FindSocketObj(dwConnID); + + return DoSend(pSocketObj, pBuffer, iLength, iOffset); +} + +BOOL CUdpServer::DoSend(TUdpSocketObj* pSocketObj, const BYTE* pBuffer, int iLength, int iOffset) +{ + int result = NO_ERROR; + + if(TUdpSocketObj::IsValid(pSocketObj)) + { + if(pBuffer && iLength > 0 && iLength <= (int)m_dwMaxDatagramSize) + { + if(iOffset != 0) pBuffer += iOffset; + + TItemPtr itPtr(m_bfObjPool, m_bfObjPool.PickFreeItem()); + itPtr->Cat(pBuffer, iLength); + + result = SendInternal(pSocketObj, itPtr); + } + else + result = ERROR_INVALID_PARAMETER; + } + else + result = ERROR_OBJECT_NOT_FOUND; + + if(result != NO_ERROR) + ::SetLastError(result); + + return (result == NO_ERROR); +} + +BOOL CUdpServer::SendPackets(CONNID dwConnID, const WSABUF pBuffers[], int iCount) +{ + ASSERT(pBuffers && iCount > 0); + + if(!pBuffers || iCount <= 0) + return ERROR_INVALID_PARAMETER; + + TUdpSocketObj* pSocketObj = FindSocketObj(dwConnID); + + if(!TUdpSocketObj::IsValid(pSocketObj)) + { + ::SetLastError(ERROR_OBJECT_NOT_FOUND); + return FALSE; + } + + int result = NO_ERROR; + int iLength = 0; + int iMaxLen = (int)m_dwMaxDatagramSize; + + TItemPtr itPtr(m_bfObjPool, m_bfObjPool.PickFreeItem()); + + for(int i = 0; i < iCount; i++) + { + int iBufLen = pBuffers[i].len; + + if(iBufLen > 0) + { + BYTE* pBuffer = (BYTE*)pBuffers[i].buf; + ASSERT(pBuffer); + + iLength += iBufLen; + + if(iLength <= iMaxLen) + itPtr->Cat(pBuffer, iBufLen); + else + break; + } + } + + if(iLength > 0 && iLength <= iMaxLen) + result = SendInternal(pSocketObj, itPtr); + else + result = ERROR_INCORRECT_SIZE; + + if(result != NO_ERROR) + ::SetLastError(result); + + return (result == NO_ERROR); + +} + +int CUdpServer::SendInternal(TUdpSocketObj* pSocketObj, TItemPtr& itPtr) +{ + BOOL bPending; + + { + CLocalSafeCounter localcounter(*pSocketObj); + CReentrantCriSecLock locallock(pSocketObj->csSend); + + if(!TUdpSocketObj::IsValid(pSocketObj)) + return ERROR_OBJECT_NOT_FOUND; + + bPending = pSocketObj->IsPending(); + + pSocketObj->sndBuff.PushBack(itPtr.Detach()); + ASSERT(pSocketObj->sndBuff.Length() > 0); + } + + if(!bPending && pSocketObj->IsPending()) + VERIFY(m_ioDispatcher.SendCommandByIndex(pSocketObj->index, DISP_CMD_SEND, pSocketObj->connID)); + + return NO_ERROR; +} + +void CUdpServer::DetectConnection(PVOID pv) +{ + TUdpSocketObj* pSocketObj = (TUdpSocketObj*)pv; + + if(TUdpSocketObj::IsValid(pSocketObj)) + { + CUdpServer* pServer = (CUdpServer*)pSocketObj->pHolder; + + if(pSocketObj->detectFails >= pServer->m_dwDetectAttempts) + VERIFY(m_ioDispatcher.SendCommandByIndex(pSocketObj->index, DISP_CMD_TIMEOUT, pSocketObj->connID)); + else + ::InterlockedIncrement(&pSocketObj->detectFails); + + ::ReadTimer(pSocketObj->fdTimer); + } +} + +#endif diff --git a/UdpServer.h b/UdpServer.h new file mode 100644 index 0000000..b42caa9 --- /dev/null +++ b/UdpServer.h @@ -0,0 +1,301 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "SocketHelper.h" +#include "./common/GeneralHelper.h" +#include "./common/IODispatcher.h" + +#ifdef _UDP_SUPPORT + +class CUdpServer : public IUdpServer, private CIOHandler +{ + using CWorkerThread = CThread; + using CSendQueue = CCASSimpleQueue; + using CSendQueuesPtr = unique_ptr; + +public: + virtual BOOL Start (LPCTSTR lpszBindAddress, USHORT usPort); + virtual BOOL Stop (); + virtual BOOL Send (CONNID dwConnID, const BYTE* pBuffer, int iLength, int iOffset = 0); + virtual BOOL SendPackets (CONNID dwConnID, const WSABUF pBuffers[], int iCount); + virtual BOOL PauseReceive (CONNID dwConnID, BOOL bPause = TRUE); + virtual BOOL Wait (DWORD dwMilliseconds = INFINITE) {return m_evWait.WaitFor(dwMilliseconds, WAIT_FOR_STOP_PREDICATE);} + virtual BOOL HasStarted () {return m_enState == SS_STARTED || m_enState == SS_STARTING;} + virtual EnServiceState GetState () {return m_enState;} + virtual BOOL Disconnect (CONNID dwConnID, BOOL bForce = TRUE); + virtual BOOL DisconnectLongConnections (DWORD dwPeriod, BOOL bForce = TRUE); + virtual BOOL DisconnectSilenceConnections(DWORD dwPeriod, BOOL bForce = TRUE); + virtual BOOL GetListenAddress (TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort); + virtual BOOL GetLocalAddress (CONNID dwConnID, TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort); + virtual BOOL GetRemoteAddress (CONNID dwConnID, TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort); + + virtual BOOL IsConnected (CONNID dwConnID); + virtual BOOL IsPauseReceive (CONNID dwConnID, BOOL& bPaused); + virtual BOOL GetPendingDataLength (CONNID dwConnID, int& iPending); + virtual DWORD GetConnectionCount (); + virtual BOOL GetAllConnectionIDs (CONNID pIDs[], DWORD& dwCount); + virtual BOOL GetConnectPeriod (CONNID dwConnID, DWORD& dwPeriod); + virtual BOOL GetSilencePeriod (CONNID dwConnID, DWORD& dwPeriod); + virtual EnSocketError GetLastError () {return m_enLastError;} + virtual LPCTSTR GetLastErrorDesc () {return ::GetSocketErrorDesc(m_enLastError);} + +private: + virtual BOOL OnBeforeProcessIo(const TDispContext* pContext, PVOID pv, UINT events) override; + virtual VOID OnAfterProcessIo(const TDispContext* pContext, PVOID pv, UINT events, BOOL rs) override; + virtual VOID OnCommand(const TDispContext* pContext, TDispCommand* pCmd) override; + virtual BOOL OnReadyRead(const TDispContext* pContext, PVOID pv, UINT events) override; + virtual BOOL OnReadyWrite(const TDispContext* pContext, PVOID pv, UINT events) override; + virtual BOOL OnHungUp(const TDispContext* pContext, PVOID pv, UINT events) override; + virtual BOOL OnError(const TDispContext* pContext, PVOID pv, UINT events) override; + virtual VOID OnDispatchThreadStart(THR_ID tid) override; + virtual VOID OnDispatchThreadEnd(THR_ID tid) override; + +public: + virtual BOOL IsSecure () {return FALSE;} + + virtual BOOL SetConnectionExtra(CONNID dwConnID, PVOID pExtra); + virtual BOOL GetConnectionExtra(CONNID dwConnID, PVOID* ppExtra); + + virtual void SetReuseAddressPolicy (EnReuseAddressPolicy enReusePolicy) {ENSURE_HAS_STOPPED(); ASSERT(m_enReusePolicy == enReusePolicy);} + virtual void SetSendPolicy (EnSendPolicy enSendPolicy) {ENSURE_HAS_STOPPED(); ASSERT(m_enSendPolicy == enSendPolicy);} + virtual void SetOnSendSyncPolicy (EnOnSendSyncPolicy enOnSendSyncPolicy) {ENSURE_HAS_STOPPED(); ASSERT(m_enOnSendSyncPolicy == enOnSendSyncPolicy);} + virtual void SetMaxConnectionCount (DWORD dwMaxConnectionCount) {ENSURE_HAS_STOPPED(); m_dwMaxConnectionCount = dwMaxConnectionCount;} + virtual void SetWorkerThreadCount (DWORD dwWorkerThreadCount) {ENSURE_HAS_STOPPED(); m_dwWorkerThreadCount = dwWorkerThreadCount;} + virtual void SetFreeSocketObjLockTime (DWORD dwFreeSocketObjLockTime) {ENSURE_HAS_STOPPED(); m_dwFreeSocketObjLockTime = dwFreeSocketObjLockTime;} + virtual void SetFreeSocketObjPool (DWORD dwFreeSocketObjPool) {ENSURE_HAS_STOPPED(); m_dwFreeSocketObjPool = dwFreeSocketObjPool;} + virtual void SetFreeBufferObjPool (DWORD dwFreeBufferObjPool) {ENSURE_HAS_STOPPED(); m_dwFreeBufferObjPool = dwFreeBufferObjPool;} + virtual void SetFreeSocketObjHold (DWORD dwFreeSocketObjHold) {ENSURE_HAS_STOPPED(); m_dwFreeSocketObjHold = dwFreeSocketObjHold;} + virtual void SetFreeBufferObjHold (DWORD dwFreeBufferObjHold) {ENSURE_HAS_STOPPED(); m_dwFreeBufferObjHold = dwFreeBufferObjHold;} + virtual void SetMaxDatagramSize (DWORD dwMaxDatagramSize) {ENSURE_HAS_STOPPED(); m_dwMaxDatagramSize = dwMaxDatagramSize;} + virtual void SetPostReceiveCount (DWORD dwPostReceiveCount) {ENSURE_HAS_STOPPED(); m_dwPostReceiveCount = dwPostReceiveCount;} + virtual void SetDetectAttempts (DWORD dwDetectAttempts) {ENSURE_HAS_STOPPED(); m_dwDetectAttempts = dwDetectAttempts;} + virtual void SetDetectInterval (DWORD dwDetectInterval) {ENSURE_HAS_STOPPED(); m_dwDetectInterval = dwDetectInterval;} + virtual void SetMarkSilence (BOOL bMarkSilence) {ENSURE_HAS_STOPPED(); m_bMarkSilence = bMarkSilence;} + + virtual EnReuseAddressPolicy GetReuseAddressPolicy () {return m_enReusePolicy;} + virtual EnSendPolicy GetSendPolicy () {return m_enSendPolicy;} + virtual EnOnSendSyncPolicy GetOnSendSyncPolicy () {return m_enOnSendSyncPolicy;} + virtual DWORD GetMaxConnectionCount () {return m_dwMaxConnectionCount;} + virtual DWORD GetWorkerThreadCount () {return m_dwWorkerThreadCount;} + virtual DWORD GetFreeSocketObjLockTime () {return m_dwFreeSocketObjLockTime;} + virtual DWORD GetFreeSocketObjPool () {return m_dwFreeSocketObjPool;} + virtual DWORD GetFreeBufferObjPool () {return m_dwFreeBufferObjPool;} + virtual DWORD GetFreeSocketObjHold () {return m_dwFreeSocketObjHold;} + virtual DWORD GetFreeBufferObjHold () {return m_dwFreeBufferObjHold;} + virtual DWORD GetMaxDatagramSize () {return m_dwMaxDatagramSize;} + virtual DWORD GetPostReceiveCount () {return m_dwPostReceiveCount;} + virtual DWORD GetDetectAttempts () {return m_dwDetectAttempts;} + virtual DWORD GetDetectInterval () {return m_dwDetectInterval;} + virtual BOOL IsMarkSilence () {return m_bMarkSilence;} + +protected: + virtual EnHandleResult FirePrepareListen(SOCKET soListen) + {return DoFirePrepareListen(soListen);} + virtual EnHandleResult FireAccept(TUdpSocketObj* pSocketObj) + { + EnHandleResult rs = DoFireAccept(pSocketObj); + if(rs != HR_ERROR) rs = FireHandShake(pSocketObj); + return rs; + } + virtual EnHandleResult FireHandShake(TUdpSocketObj* pSocketObj) + {return DoFireHandShake(pSocketObj);} + virtual EnHandleResult FireReceive(TUdpSocketObj* pSocketObj, const BYTE* pData, int iLength) + {return DoFireReceive(pSocketObj, pData, iLength);} + virtual EnHandleResult FireReceive(TUdpSocketObj* pSocketObj, int iLength) + {return DoFireReceive(pSocketObj, iLength);} + virtual EnHandleResult FireSend(TUdpSocketObj* pSocketObj, const BYTE* pData, int iLength) + {return DoFireSend(pSocketObj, pData, iLength);} + virtual EnHandleResult FireClose(TUdpSocketObj* pSocketObj, EnSocketOperation enOperation, int iErrorCode) + {return DoFireClose(pSocketObj, enOperation, iErrorCode);} + virtual EnHandleResult FireShutdown() + {return DoFireShutdown();} + + virtual EnHandleResult DoFirePrepareListen(SOCKET soListen) + {return m_pListener->OnPrepareListen(this, soListen);} + virtual EnHandleResult DoFireAccept(TUdpSocketObj* pSocketObj) + {return m_pListener->OnAccept(this, pSocketObj->connID, (UINT_PTR)(&pSocketObj->remoteAddr));} + virtual EnHandleResult DoFireHandShake(TUdpSocketObj* pSocketObj) + {return m_pListener->OnHandShake(this, pSocketObj->connID);} + virtual EnHandleResult DoFireReceive(TUdpSocketObj* pSocketObj, const BYTE* pData, int iLength) + {return m_pListener->OnReceive(this, pSocketObj->connID, pData, iLength);} + virtual EnHandleResult DoFireReceive(TUdpSocketObj* pSocketObj, int iLength) + {return m_pListener->OnReceive(this, pSocketObj->connID, iLength);} + virtual EnHandleResult DoFireSend(TUdpSocketObj* pSocketObj, const BYTE* pData, int iLength) + {return m_pListener->OnSend(this, pSocketObj->connID, pData, iLength);} + virtual EnHandleResult DoFireClose(TUdpSocketObj* pSocketObj, EnSocketOperation enOperation, int iErrorCode) + {return m_pListener->OnClose(this, pSocketObj->connID, enOperation, iErrorCode);} + virtual EnHandleResult DoFireShutdown() + {return m_pListener->OnShutdown(this);} + + void SetLastError(EnSocketError code, LPCSTR func, int ec); + virtual BOOL CheckParams(); + virtual void PrepareStart(); + virtual void Reset(); + + virtual void OnWorkerThreadStart(THR_ID tid) {} + virtual void OnWorkerThreadEnd(THR_ID tid) {} + + virtual void ReleaseGCSocketObj(BOOL bForce = FALSE); + + TUdpSocketObj* FindSocketObj(CONNID dwConnID); + int SendInternal(TUdpSocketObj* pSocketObj, TItemPtr& itPtr); + + BOOL DoSend(TUdpSocketObj* pSocketObj, const BYTE* pBuffer, int iLength, int iOffset = 0); + +protected: + BOOL SetConnectionExtra(TUdpSocketObj* pSocketObj, PVOID pExtra); + BOOL GetConnectionExtra(TUdpSocketObj* pSocketObj, PVOID* ppExtra); + BOOL SetConnectionReserved(CONNID dwConnID, PVOID pReserved); + BOOL GetConnectionReserved(CONNID dwConnID, PVOID* ppReserved); + BOOL SetConnectionReserved(TUdpSocketObj* pSocketObj, PVOID pReserved); + BOOL GetConnectionReserved(TUdpSocketObj* pSocketObj, PVOID* ppReserved); + BOOL SetConnectionReserved2(CONNID dwConnID, PVOID pReserved2); + BOOL GetConnectionReserved2(CONNID dwConnID, PVOID* ppReserved2); + BOOL SetConnectionReserved2(TUdpSocketObj* pSocketObj, PVOID pReserved2); + BOOL GetConnectionReserved2(TUdpSocketObj* pSocketObj, PVOID* ppReserved2); + +private: + BOOL CheckStarting(); + BOOL CheckStoping(); + BOOL CreateListenSocket(LPCTSTR lpszBindAddress, USHORT usPort); + BOOL CreateWorkerThreads(); + BOOL StartAccept(); + + void SendCloseNotify(); + void CloseListenSocket(); + void DisconnectClientSocket(); + void WaitForClientSocketClose(); + void ReleaseClientSocket(); + void ReleaseFreeSocket(); + void ClearSendQueues(); + void WaitForWorkerThreadEnd(); + + TUdpSocketObj* GetFreeSocketObj(CONNID dwConnID); + TUdpSocketObj* CreateSocketObj(); + CONNID FindConnectionID(const HP_SOCKADDR* pAddr); + void AddFreeSocketObj(TUdpSocketObj* pSocketObj, EnSocketCloseFlag enFlag = SCF_NONE, EnSocketOperation enOperation = SO_UNKNOWN, int iErrorCode = 0, BOOL bNotify = TRUE); + void DeleteSocketObj(TUdpSocketObj* pSocketObj); + BOOL InvalidSocketObj(TUdpSocketObj* pSocketObj); + void AddClientSocketObj(int idx, CONNID dwConnID, TUdpSocketObj* pSocketObj, const HP_SOCKADDR& remoteAddr); + void CloseClientSocketObj(TUdpSocketObj* pSocketObj, EnSocketCloseFlag enFlag = SCF_NONE, EnSocketOperation enOperation = SO_UNKNOWN, int iErrorCode = 0, BOOL bNotify = TRUE); + + EnHandleResult TriggerFireAccept(TUdpSocketObj* pSocketObj); + +private: + VOID HandleCmdSend (CONNID dwConnID, int flag); + VOID HandleCmdDisconnect(CONNID dwConnID, BOOL bForce); + VOID HandleCmdTimeout (CONNID dwConnID); + + CONNID HandleAccept (const TDispContext* pContext, HP_SOCKADDR& addr); + BOOL HandleReceive (const TDispContext* pContext, int flag = 0); + BOOL HandleSend (const TDispContext* pContext, int flag = 0); + BOOL HandleClose (TUdpSocketObj* pSocketObj, EnSocketOperation enOperation, int iErrorCode); + void HandleZeroBytes (TUdpSocketObj* pSocketObj); + + BOOL SendItem (TUdpSocketObj* pSocketObj, TItem* pItem, BOOL& bBlocked); + + void DetectConnection (PVOID pv); + BOOL IsNeedDetectConnection () const {return m_dwDetectAttempts > 0 && m_dwDetectInterval > 0;} + +public: + CUdpServer(IUdpServerListener* pListener) + : m_pListener (pListener) + , m_enLastError (SE_OK) + , m_enState (SS_STOPPED) + , m_fdGCTimer (INVALID_FD) + , m_enSendPolicy (SP_PACK) + , m_enOnSendSyncPolicy (OSSP_RECEIVE) + , m_enReusePolicy (RAP_ADDR_AND_PORT) + , m_dwMaxConnectionCount (DEFAULT_CONNECTION_COUNT) + , m_dwWorkerThreadCount (DEFAULT_WORKER_THREAD_COUNT) + , m_dwFreeSocketObjLockTime (DEFAULT_FREE_SOCKETOBJ_LOCK_TIME) + , m_dwFreeSocketObjPool (DEFAULT_FREE_SOCKETOBJ_POOL) + , m_dwFreeBufferObjPool (DEFAULT_FREE_BUFFEROBJ_POOL) + , m_dwFreeSocketObjHold (DEFAULT_FREE_SOCKETOBJ_HOLD) + , m_dwFreeBufferObjHold (DEFAULT_FREE_BUFFEROBJ_HOLD) + , m_dwMaxDatagramSize (DEFAULT_UDP_MAX_DATAGRAM_SIZE) + , m_dwPostReceiveCount (DEFAULT_UDP_POST_RECEIVE_COUNT) + , m_dwDetectAttempts (DEFAULT_UDP_DETECT_ATTEMPTS) + , m_dwDetectInterval (DEFAULT_UDP_DETECT_INTERVAL) + , m_bMarkSilence (TRUE) + { + ASSERT(m_pListener); + } + + virtual ~CUdpServer() + { + ENSURE_STOP(); + } + +private: + EnReuseAddressPolicy m_enReusePolicy; + EnSendPolicy m_enSendPolicy; + EnOnSendSyncPolicy m_enOnSendSyncPolicy; + DWORD m_dwMaxConnectionCount; + DWORD m_dwWorkerThreadCount; + DWORD m_dwFreeSocketObjLockTime; + DWORD m_dwFreeSocketObjPool; + DWORD m_dwFreeBufferObjPool; + DWORD m_dwFreeSocketObjHold; + DWORD m_dwFreeBufferObjHold; + DWORD m_dwMaxDatagramSize; + DWORD m_dwPostReceiveCount; + DWORD m_dwDetectAttempts; + DWORD m_dwDetectInterval; + BOOL m_bMarkSilence; + +protected: + CBufferObjPool m_bfObjPool; + +private: + CSEM m_evWait; + + IUdpServerListener* m_pListener; + ListenSocketsPtr m_soListens; + EnServiceState m_enState; + EnSocketError m_enLastError; + + CReceiveBuffersPtr m_rcBuffers; + + CPrivateHeap m_phSocket; + + CSpinGuard m_csState; + + FD m_fdGCTimer; + + TUdpSocketObjPtrPool m_bfActiveSockets; + + CSimpleRWLock m_csClientSocket; + TSockAddrMap m_mpClientAddr; + + TUdpSocketObjPtrList m_lsFreeSocket; + TUdpSocketObjPtrQueue m_lsGCSocket; + + CSendQueuesPtr m_quSends; + + CIODispatcher m_ioDispatcher; +}; + +#endif diff --git a/brotli/decode.h b/brotli/decode.h new file mode 100644 index 0000000..af1aa23 --- /dev/null +++ b/brotli/decode.h @@ -0,0 +1,409 @@ +/* Copyright 2013 Google Inc. All Rights Reserved. + + Distributed under MIT license. + See file LICENSE for detail or copy at https://opensource.org/licenses/MIT +*/ + +/** + * @file + * API for Brotli decompression. + */ + +#ifndef BROTLI_DEC_DECODE_H_ +#define BROTLI_DEC_DECODE_H_ + +#include +#include +#include + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif + +/** + * Opaque structure that holds decoder state. + * + * Allocated and initialized with ::BrotliDecoderCreateInstance. + * Cleaned up and deallocated with ::BrotliDecoderDestroyInstance. + */ +typedef struct BrotliDecoderStateStruct BrotliDecoderState; + +/** + * Result type for ::BrotliDecoderDecompress and + * ::BrotliDecoderDecompressStream functions. + */ +typedef enum { + /** Decoding error, e.g. corrupted input or memory allocation problem. */ + BROTLI_DECODER_RESULT_ERROR = 0, + /** Decoding successfully completed. */ + BROTLI_DECODER_RESULT_SUCCESS = 1, + /** Partially done; should be called again with more input. */ + BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT = 2, + /** Partially done; should be called again with more output. */ + BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT = 3 +} BrotliDecoderResult; + +/** + * Template that evaluates items of ::BrotliDecoderErrorCode. + * + * Example: @code {.cpp} + * // Log Brotli error code. + * switch (brotliDecoderErrorCode) { + * #define CASE_(PREFIX, NAME, CODE) \ + * case BROTLI_DECODER ## PREFIX ## NAME: \ + * LOG(INFO) << "error code:" << #NAME; \ + * break; + * #define NEWLINE_ + * BROTLI_DECODER_ERROR_CODES_LIST(CASE_, NEWLINE_) + * #undef CASE_ + * #undef NEWLINE_ + * default: LOG(FATAL) << "unknown brotli error code"; + * } + * @endcode + */ +#define BROTLI_DECODER_ERROR_CODES_LIST(BROTLI_ERROR_CODE, SEPARATOR) \ + BROTLI_ERROR_CODE(_, NO_ERROR, 0) SEPARATOR \ + /* Same as BrotliDecoderResult values */ \ + BROTLI_ERROR_CODE(_, SUCCESS, 1) SEPARATOR \ + BROTLI_ERROR_CODE(_, NEEDS_MORE_INPUT, 2) SEPARATOR \ + BROTLI_ERROR_CODE(_, NEEDS_MORE_OUTPUT, 3) SEPARATOR \ + \ + /* Errors caused by invalid input */ \ + BROTLI_ERROR_CODE(_ERROR_FORMAT_, EXUBERANT_NIBBLE, -1) SEPARATOR \ + BROTLI_ERROR_CODE(_ERROR_FORMAT_, RESERVED, -2) SEPARATOR \ + BROTLI_ERROR_CODE(_ERROR_FORMAT_, EXUBERANT_META_NIBBLE, -3) SEPARATOR \ + BROTLI_ERROR_CODE(_ERROR_FORMAT_, SIMPLE_HUFFMAN_ALPHABET, -4) SEPARATOR \ + BROTLI_ERROR_CODE(_ERROR_FORMAT_, SIMPLE_HUFFMAN_SAME, -5) SEPARATOR \ + BROTLI_ERROR_CODE(_ERROR_FORMAT_, CL_SPACE, -6) SEPARATOR \ + BROTLI_ERROR_CODE(_ERROR_FORMAT_, HUFFMAN_SPACE, -7) SEPARATOR \ + BROTLI_ERROR_CODE(_ERROR_FORMAT_, CONTEXT_MAP_REPEAT, -8) SEPARATOR \ + BROTLI_ERROR_CODE(_ERROR_FORMAT_, BLOCK_LENGTH_1, -9) SEPARATOR \ + BROTLI_ERROR_CODE(_ERROR_FORMAT_, BLOCK_LENGTH_2, -10) SEPARATOR \ + BROTLI_ERROR_CODE(_ERROR_FORMAT_, TRANSFORM, -11) SEPARATOR \ + BROTLI_ERROR_CODE(_ERROR_FORMAT_, DICTIONARY, -12) SEPARATOR \ + BROTLI_ERROR_CODE(_ERROR_FORMAT_, WINDOW_BITS, -13) SEPARATOR \ + BROTLI_ERROR_CODE(_ERROR_FORMAT_, PADDING_1, -14) SEPARATOR \ + BROTLI_ERROR_CODE(_ERROR_FORMAT_, PADDING_2, -15) SEPARATOR \ + BROTLI_ERROR_CODE(_ERROR_FORMAT_, DISTANCE, -16) SEPARATOR \ + \ + /* -17 code is reserved */ \ + \ + BROTLI_ERROR_CODE(_ERROR_, COMPOUND_DICTIONARY, -18) SEPARATOR \ + BROTLI_ERROR_CODE(_ERROR_, DICTIONARY_NOT_SET, -19) SEPARATOR \ + BROTLI_ERROR_CODE(_ERROR_, INVALID_ARGUMENTS, -20) SEPARATOR \ + \ + /* Memory allocation problems */ \ + BROTLI_ERROR_CODE(_ERROR_ALLOC_, CONTEXT_MODES, -21) SEPARATOR \ + /* Literal, insert and distance trees together */ \ + BROTLI_ERROR_CODE(_ERROR_ALLOC_, TREE_GROUPS, -22) SEPARATOR \ + /* -23..-24 codes are reserved for distinct tree groups */ \ + BROTLI_ERROR_CODE(_ERROR_ALLOC_, CONTEXT_MAP, -25) SEPARATOR \ + BROTLI_ERROR_CODE(_ERROR_ALLOC_, RING_BUFFER_1, -26) SEPARATOR \ + BROTLI_ERROR_CODE(_ERROR_ALLOC_, RING_BUFFER_2, -27) SEPARATOR \ + /* -28..-29 codes are reserved for dynamic ring-buffer allocation */ \ + BROTLI_ERROR_CODE(_ERROR_ALLOC_, BLOCK_TYPE_TREES, -30) SEPARATOR \ + \ + /* "Impossible" states */ \ + BROTLI_ERROR_CODE(_ERROR_, UNREACHABLE, -31) + +/** + * Error code for detailed logging / production debugging. + * + * See ::BrotliDecoderGetErrorCode and ::BROTLI_LAST_ERROR_CODE. + */ +typedef enum { +#define BROTLI_COMMA_ , +#define BROTLI_ERROR_CODE_ENUM_ITEM_(PREFIX, NAME, CODE) \ + BROTLI_DECODER ## PREFIX ## NAME = CODE + BROTLI_DECODER_ERROR_CODES_LIST(BROTLI_ERROR_CODE_ENUM_ITEM_, BROTLI_COMMA_) +} BrotliDecoderErrorCode; +#undef BROTLI_ERROR_CODE_ENUM_ITEM_ +#undef BROTLI_COMMA_ + +/** + * The value of the last error code, negative integer. + * + * All other error code values are in the range from ::BROTLI_LAST_ERROR_CODE + * to @c -1. There are also 4 other possible non-error codes @c 0 .. @c 3 in + * ::BrotliDecoderErrorCode enumeration. + */ +#define BROTLI_LAST_ERROR_CODE BROTLI_DECODER_ERROR_UNREACHABLE + +/** Options to be used with ::BrotliDecoderSetParameter. */ +typedef enum BrotliDecoderParameter { + /** + * Disable "canny" ring buffer allocation strategy. + * + * Ring buffer is allocated according to window size, despite the real size of + * the content. + */ + BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION = 0, + /** + * Flag that determines if "Large Window Brotli" is used. + */ + BROTLI_DECODER_PARAM_LARGE_WINDOW = 1 +} BrotliDecoderParameter; + +/** + * Sets the specified parameter to the given decoder instance. + * + * @param state decoder instance + * @param param parameter to set + * @param value new parameter value + * @returns ::BROTLI_FALSE if parameter is unrecognized, or value is invalid + * @returns ::BROTLI_TRUE if value is accepted + */ +BROTLI_DEC_API BROTLI_BOOL BrotliDecoderSetParameter( + BrotliDecoderState* state, BrotliDecoderParameter param, uint32_t value); + +/** + * Adds LZ77 prefix dictionary, adds or replaces built-in static dictionary and + * transforms. + * + * Attached dictionary ownership is not transferred. + * Data provided to this method should be kept accessible until + * decoding is finished and decoder instance is destroyed. + * + * @note Dictionaries can NOT be attached after actual decoding is started. + * + * @param state decoder instance + * @param type dictionary data format + * @param data_size length of memory region pointed by @p data + * @param data dictionary data in format corresponding to @p type + * @returns ::BROTLI_FALSE if dictionary is corrupted, + * or dictionary count limit is reached + * @returns ::BROTLI_TRUE if dictionary is accepted / attached + */ +BROTLI_DEC_API BROTLI_BOOL BrotliDecoderAttachDictionary( + BrotliDecoderState* state, BrotliSharedDictionaryType type, + size_t data_size, const uint8_t data[BROTLI_ARRAY_PARAM(data_size)]); + +/** + * Creates an instance of ::BrotliDecoderState and initializes it. + * + * The instance can be used once for decoding and should then be destroyed with + * ::BrotliDecoderDestroyInstance, it cannot be reused for a new decoding + * session. + * + * @p alloc_func and @p free_func @b MUST be both zero or both non-zero. In the + * case they are both zero, default memory allocators are used. @p opaque is + * passed to @p alloc_func and @p free_func when they are called. @p free_func + * has to return without doing anything when asked to free a NULL pointer. + * + * @param alloc_func custom memory allocation function + * @param free_func custom memory free function + * @param opaque custom memory manager handle + * @returns @c 0 if instance can not be allocated or initialized + * @returns pointer to initialized ::BrotliDecoderState otherwise + */ +BROTLI_DEC_API BrotliDecoderState* BrotliDecoderCreateInstance( + brotli_alloc_func alloc_func, brotli_free_func free_func, void* opaque); + +/** + * Deinitializes and frees ::BrotliDecoderState instance. + * + * @param state decoder instance to be cleaned up and deallocated + */ +BROTLI_DEC_API void BrotliDecoderDestroyInstance(BrotliDecoderState* state); + +/** + * Performs one-shot memory-to-memory decompression. + * + * Decompresses the data in @p encoded_buffer into @p decoded_buffer, and sets + * @p *decoded_size to the decompressed length. + * + * @param encoded_size size of @p encoded_buffer + * @param encoded_buffer compressed data buffer with at least @p encoded_size + * addressable bytes + * @param[in, out] decoded_size @b in: size of @p decoded_buffer; \n + * @b out: length of decompressed data written to + * @p decoded_buffer + * @param decoded_buffer decompressed data destination buffer + * @returns ::BROTLI_DECODER_RESULT_ERROR if input is corrupted, memory + * allocation failed, or @p decoded_buffer is not large enough; + * @returns ::BROTLI_DECODER_RESULT_SUCCESS otherwise + */ +BROTLI_DEC_API BrotliDecoderResult BrotliDecoderDecompress( + size_t encoded_size, + const uint8_t encoded_buffer[BROTLI_ARRAY_PARAM(encoded_size)], + size_t* decoded_size, + uint8_t decoded_buffer[BROTLI_ARRAY_PARAM(*decoded_size)]); + +/** + * Decompresses the input stream to the output stream. + * + * The values @p *available_in and @p *available_out must specify the number of + * bytes addressable at @p *next_in and @p *next_out respectively. + * When @p *available_out is @c 0, @p next_out is allowed to be @c NULL. + * + * After each call, @p *available_in will be decremented by the amount of input + * bytes consumed, and the @p *next_in pointer will be incremented by that + * amount. Similarly, @p *available_out will be decremented by the amount of + * output bytes written, and the @p *next_out pointer will be incremented by + * that amount. + * + * @p total_out, if it is not a null-pointer, will be set to the number + * of bytes decompressed since the last @p state initialization. + * + * @note Input is never overconsumed, so @p next_in and @p available_in could be + * passed to the next consumer after decoding is complete. + * + * @param state decoder instance + * @param[in, out] available_in @b in: amount of available input; \n + * @b out: amount of unused input + * @param[in, out] next_in pointer to the next compressed byte + * @param[in, out] available_out @b in: length of output buffer; \n + * @b out: remaining size of output buffer + * @param[in, out] next_out output buffer cursor; + * can be @c NULL if @p available_out is @c 0 + * @param[out] total_out number of bytes decompressed so far; can be @c NULL + * @returns ::BROTLI_DECODER_RESULT_ERROR if input is corrupted, memory + * allocation failed, arguments were invalid, etc.; + * use ::BrotliDecoderGetErrorCode to get detailed error code + * @returns ::BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT decoding is blocked until + * more input data is provided + * @returns ::BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT decoding is blocked until + * more output space is provided + * @returns ::BROTLI_DECODER_RESULT_SUCCESS decoding is finished, no more + * input might be consumed and no more output will be produced + */ +BROTLI_DEC_API BrotliDecoderResult BrotliDecoderDecompressStream( + BrotliDecoderState* state, size_t* available_in, const uint8_t** next_in, + size_t* available_out, uint8_t** next_out, size_t* total_out); + +/** + * Checks if decoder has more output. + * + * @param state decoder instance + * @returns ::BROTLI_TRUE, if decoder has some unconsumed output + * @returns ::BROTLI_FALSE otherwise + */ +BROTLI_DEC_API BROTLI_BOOL BrotliDecoderHasMoreOutput( + const BrotliDecoderState* state); + +/** + * Acquires pointer to internal output buffer. + * + * This method is used to make language bindings easier and more efficient: + * -# push data to ::BrotliDecoderDecompressStream, + * until ::BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT is reported + * -# use ::BrotliDecoderTakeOutput to peek bytes and copy to language-specific + * entity + * + * Also this could be useful if there is an output stream that is able to + * consume all the provided data (e.g. when data is saved to file system). + * + * @attention After every call to ::BrotliDecoderTakeOutput @p *size bytes of + * output are considered consumed for all consecutive calls to the + * instance methods; returned pointer becomes invalidated as well. + * + * @note Decoder output is not guaranteed to be contiguous. This means that + * after the size-unrestricted call to ::BrotliDecoderTakeOutput, + * immediate next call to ::BrotliDecoderTakeOutput may return more data. + * + * @param state decoder instance + * @param[in, out] size @b in: number of bytes caller is ready to take, @c 0 if + * any amount could be handled; \n + * @b out: amount of data pointed by returned pointer and + * considered consumed; \n + * out value is never greater than in value, unless it is @c 0 + * @returns pointer to output data + */ +BROTLI_DEC_API const uint8_t* BrotliDecoderTakeOutput( + BrotliDecoderState* state, size_t* size); + +/** + * Checks if instance has already consumed input. + * + * Instance that returns ::BROTLI_FALSE is considered "fresh" and could be + * reused. + * + * @param state decoder instance + * @returns ::BROTLI_TRUE if decoder has already used some input bytes + * @returns ::BROTLI_FALSE otherwise + */ +BROTLI_DEC_API BROTLI_BOOL BrotliDecoderIsUsed(const BrotliDecoderState* state); + +/** + * Checks if decoder instance reached the final state. + * + * @param state decoder instance + * @returns ::BROTLI_TRUE if decoder is in a state where it reached the end of + * the input and produced all of the output + * @returns ::BROTLI_FALSE otherwise + */ +BROTLI_DEC_API BROTLI_BOOL BrotliDecoderIsFinished( + const BrotliDecoderState* state); + +/** + * Acquires a detailed error code. + * + * Should be used only after ::BrotliDecoderDecompressStream returns + * ::BROTLI_DECODER_RESULT_ERROR. + * + * See also ::BrotliDecoderErrorString + * + * @param state decoder instance + * @returns last saved error code + */ +BROTLI_DEC_API BrotliDecoderErrorCode BrotliDecoderGetErrorCode( + const BrotliDecoderState* state); + +/** + * Converts error code to a c-string. + */ +BROTLI_DEC_API const char* BrotliDecoderErrorString(BrotliDecoderErrorCode c); + +/** + * Gets a decoder library version. + * + * Look at BROTLI_MAKE_HEX_VERSION for more information. + */ +BROTLI_DEC_API uint32_t BrotliDecoderVersion(void); + +/** + * Callback to fire on metadata block start. + * + * After this callback is fired, if @p size is not @c 0, it is followed by + * ::brotli_decoder_metadata_chunk_func as more metadata block contents become + * accessible. + * + * @param opaque callback handle + * @param size size of metadata block + */ +typedef void (*brotli_decoder_metadata_start_func)(void* opaque, size_t size); + +/** + * Callback to fire on metadata block chunk becomes available. + * + * This function can be invoked multiple times per metadata block; block should + * be considered finished when sum of @p size matches the announced metadata + * block size. Chunks contents pointed by @p data are transient and shouln not + * be accessed after leaving the callback. + * + * @param opaque callback handle + * @param data pointer to metadata contents + * @param size size of metadata block chunk, at least @c 1 + */ +typedef void (*brotli_decoder_metadata_chunk_func)(void* opaque, + const uint8_t* data, + size_t size); + +/** + * Sets callback for receiving metadata blocks. + * + * @param state decoder instance + * @param start_func callback on metadata block start + * @param chunk_func callback on metadata block chunk + * @param opaque callback handle + */ +BROTLI_DEC_API void BrotliDecoderSetMetadataCallbacks( + BrotliDecoderState* state, + brotli_decoder_metadata_start_func start_func, + brotli_decoder_metadata_chunk_func chunk_func, void* opaque); + +#if defined(__cplusplus) || defined(c_plusplus) +} /* extern "C" */ +#endif + +#endif /* BROTLI_DEC_DECODE_H_ */ diff --git a/brotli/encode.h b/brotli/encode.h new file mode 100644 index 0000000..46860cd --- /dev/null +++ b/brotli/encode.h @@ -0,0 +1,501 @@ +/* Copyright 2013 Google Inc. All Rights Reserved. + + Distributed under MIT license. + See file LICENSE for detail or copy at https://opensource.org/licenses/MIT +*/ + +/** + * @file + * API for Brotli compression. + */ + +#ifndef BROTLI_ENC_ENCODE_H_ +#define BROTLI_ENC_ENCODE_H_ + +#include +#include +#include + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif + +/** Minimal value for ::BROTLI_PARAM_LGWIN parameter. */ +#define BROTLI_MIN_WINDOW_BITS 10 +/** + * Maximal value for ::BROTLI_PARAM_LGWIN parameter. + * + * @note equal to @c BROTLI_MAX_DISTANCE_BITS constant. + */ +#define BROTLI_MAX_WINDOW_BITS 24 +/** + * Maximal value for ::BROTLI_PARAM_LGWIN parameter + * in "Large Window Brotli" (32-bit). + */ +#define BROTLI_LARGE_MAX_WINDOW_BITS 30 +/** Minimal value for ::BROTLI_PARAM_LGBLOCK parameter. */ +#define BROTLI_MIN_INPUT_BLOCK_BITS 16 +/** Maximal value for ::BROTLI_PARAM_LGBLOCK parameter. */ +#define BROTLI_MAX_INPUT_BLOCK_BITS 24 +/** Minimal value for ::BROTLI_PARAM_QUALITY parameter. */ +#define BROTLI_MIN_QUALITY 0 +/** Maximal value for ::BROTLI_PARAM_QUALITY parameter. */ +#define BROTLI_MAX_QUALITY 11 + +/** Options for ::BROTLI_PARAM_MODE parameter. */ +typedef enum BrotliEncoderMode { + /** + * Default compression mode. + * + * In this mode compressor does not know anything in advance about the + * properties of the input. + */ + BROTLI_MODE_GENERIC = 0, + /** Compression mode for UTF-8 formatted text input. */ + BROTLI_MODE_TEXT = 1, + /** Compression mode used in WOFF 2.0. */ + BROTLI_MODE_FONT = 2 +} BrotliEncoderMode; + +/** Default value for ::BROTLI_PARAM_QUALITY parameter. */ +#define BROTLI_DEFAULT_QUALITY 11 +/** Default value for ::BROTLI_PARAM_LGWIN parameter. */ +#define BROTLI_DEFAULT_WINDOW 22 +/** Default value for ::BROTLI_PARAM_MODE parameter. */ +#define BROTLI_DEFAULT_MODE BROTLI_MODE_GENERIC + +/** Operations that can be performed by streaming encoder. */ +typedef enum BrotliEncoderOperation { + /** + * Process input. + * + * Encoder may postpone producing output, until it has processed enough input. + */ + BROTLI_OPERATION_PROCESS = 0, + /** + * Produce output for all processed input. + * + * Actual flush is performed when input stream is depleted and there is enough + * space in output stream. This means that client should repeat + * ::BROTLI_OPERATION_FLUSH operation until @p available_in becomes @c 0, and + * ::BrotliEncoderHasMoreOutput returns ::BROTLI_FALSE. If output is acquired + * via ::BrotliEncoderTakeOutput, then operation should be repeated after + * output buffer is drained. + * + * @warning Until flush is complete, client @b SHOULD @b NOT swap, + * reduce or extend input stream. + * + * When flush is complete, output data will be sufficient for decoder to + * reproduce all the given input. + */ + BROTLI_OPERATION_FLUSH = 1, + /** + * Finalize the stream. + * + * Actual finalization is performed when input stream is depleted and there is + * enough space in output stream. This means that client should repeat + * ::BROTLI_OPERATION_FINISH operation until @p available_in becomes @c 0, and + * ::BrotliEncoderHasMoreOutput returns ::BROTLI_FALSE. If output is acquired + * via ::BrotliEncoderTakeOutput, then operation should be repeated after + * output buffer is drained. + * + * @warning Until finalization is complete, client @b SHOULD @b NOT swap, + * reduce or extend input stream. + * + * Helper function ::BrotliEncoderIsFinished checks if stream is finalized and + * output fully dumped. + * + * Adding more input data to finalized stream is impossible. + */ + BROTLI_OPERATION_FINISH = 2, + /** + * Emit metadata block to stream. + * + * Metadata is opaque to Brotli: neither encoder, nor decoder processes this + * data or relies on it. It may be used to pass some extra information from + * encoder client to decoder client without interfering with main data stream. + * + * @note Encoder may emit empty metadata blocks internally, to pad encoded + * stream to byte boundary. + * + * @warning Until emitting metadata is complete client @b SHOULD @b NOT swap, + * reduce or extend input stream. + * + * @warning The whole content of input buffer is considered to be the content + * of metadata block. Do @b NOT @e append metadata to input stream, + * before it is depleted with other operations. + * + * Stream is soft-flushed before metadata block is emitted. Metadata block + * @b MUST be no longer than than 16MiB. + */ + BROTLI_OPERATION_EMIT_METADATA = 3 +} BrotliEncoderOperation; + +/** Options to be used with ::BrotliEncoderSetParameter. */ +typedef enum BrotliEncoderParameter { + /** + * Tune encoder for specific input. + * + * ::BrotliEncoderMode enumerates all available values. + */ + BROTLI_PARAM_MODE = 0, + /** + * The main compression speed-density lever. + * + * The higher the quality, the slower the compression. Range is + * from ::BROTLI_MIN_QUALITY to ::BROTLI_MAX_QUALITY. + */ + BROTLI_PARAM_QUALITY = 1, + /** + * Recommended sliding LZ77 window size. + * + * Encoder may reduce this value, e.g. if input is much smaller than + * window size. + * + * Window size is `(1 << value) - 16`. + * + * Range is from ::BROTLI_MIN_WINDOW_BITS to ::BROTLI_MAX_WINDOW_BITS. + */ + BROTLI_PARAM_LGWIN = 2, + /** + * Recommended input block size. + * + * Encoder may reduce this value, e.g. if input is much smaller than input + * block size. + * + * Range is from ::BROTLI_MIN_INPUT_BLOCK_BITS to + * ::BROTLI_MAX_INPUT_BLOCK_BITS. + * + * @note Bigger input block size allows better compression, but consumes more + * memory. \n The rough formula of memory used for temporary input + * storage is `3 << lgBlock`. + */ + BROTLI_PARAM_LGBLOCK = 3, + /** + * Flag that affects usage of "literal context modeling" format feature. + * + * This flag is a "decoding-speed vs compression ratio" trade-off. + */ + BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING = 4, + /** + * Estimated total input size for all ::BrotliEncoderCompressStream calls. + * + * The default value is 0, which means that the total input size is unknown. + */ + BROTLI_PARAM_SIZE_HINT = 5, + /** + * Flag that determines if "Large Window Brotli" is used. + */ + BROTLI_PARAM_LARGE_WINDOW = 6, + /** + * Recommended number of postfix bits (NPOSTFIX). + * + * Encoder may change this value. + * + * Range is from 0 to ::BROTLI_MAX_NPOSTFIX. + */ + BROTLI_PARAM_NPOSTFIX = 7, + /** + * Recommended number of direct distance codes (NDIRECT). + * + * Encoder may change this value. + * + * Range is from 0 to (15 << NPOSTFIX) in steps of (1 << NPOSTFIX). + */ + BROTLI_PARAM_NDIRECT = 8, + /** + * Number of bytes of input stream already processed by a different instance. + * + * @note It is important to configure all the encoder instances with same + * parameters (except this one) in order to allow all the encoded parts + * obey the same restrictions implied by header. + * + * If offset is not 0, then stream header is omitted. + * In any case output start is byte aligned, so for proper streams stitching + * "predecessor" stream must be flushed. + * + * Range is not artificially limited, but all the values greater or equal to + * maximal window size have the same effect. Values greater than 2**30 are not + * allowed. + */ + BROTLI_PARAM_STREAM_OFFSET = 9 +} BrotliEncoderParameter; + +/** + * Opaque structure that holds encoder state. + * + * Allocated and initialized with ::BrotliEncoderCreateInstance. + * Cleaned up and deallocated with ::BrotliEncoderDestroyInstance. + */ +typedef struct BrotliEncoderStateStruct BrotliEncoderState; + +/** + * Sets the specified parameter to the given encoder instance. + * + * @param state encoder instance + * @param param parameter to set + * @param value new parameter value + * @returns ::BROTLI_FALSE if parameter is unrecognized, or value is invalid + * @returns ::BROTLI_FALSE if value of parameter can not be changed at current + * encoder state (e.g. when encoding is started, window size might be + * already encoded and therefore it is impossible to change it) + * @returns ::BROTLI_TRUE if value is accepted + * @warning invalid values might be accepted in case they would not break + * encoding process. + */ +BROTLI_ENC_API BROTLI_BOOL BrotliEncoderSetParameter( + BrotliEncoderState* state, BrotliEncoderParameter param, uint32_t value); + +/** + * Creates an instance of ::BrotliEncoderState and initializes it. + * + * @p alloc_func and @p free_func @b MUST be both zero or both non-zero. In the + * case they are both zero, default memory allocators are used. @p opaque is + * passed to @p alloc_func and @p free_func when they are called. @p free_func + * has to return without doing anything when asked to free a NULL pointer. + * + * @param alloc_func custom memory allocation function + * @param free_func custom memory free function + * @param opaque custom memory manager handle + * @returns @c 0 if instance can not be allocated or initialized + * @returns pointer to initialized ::BrotliEncoderState otherwise + */ +BROTLI_ENC_API BrotliEncoderState* BrotliEncoderCreateInstance( + brotli_alloc_func alloc_func, brotli_free_func free_func, void* opaque); + +/** + * Deinitializes and frees ::BrotliEncoderState instance. + * + * @param state decoder instance to be cleaned up and deallocated + */ +BROTLI_ENC_API void BrotliEncoderDestroyInstance(BrotliEncoderState* state); + +/* Opaque type for pointer to different possible internal structures containing + dictionary prepared for the encoder */ +typedef struct BrotliEncoderPreparedDictionaryStruct + BrotliEncoderPreparedDictionary; + +/** + * Prepares a shared dictionary from the given file format for the encoder. + * + * @p alloc_func and @p free_func @b MUST be both zero or both non-zero. In the + * case they are both zero, default memory allocators are used. @p opaque is + * passed to @p alloc_func and @p free_func when they are called. @p free_func + * has to return without doing anything when asked to free a NULL pointer. + * + * @param type type of dictionary stored in data + * @param data_size size of @p data buffer + * @param data pointer to the dictionary data + * @param quality the maximum Brotli quality to prepare the dictionary for, + * use BROTLI_MAX_QUALITY by default + * @param alloc_func custom memory allocation function + * @param free_func custom memory free function + * @param opaque custom memory manager handle + */ +BROTLI_ENC_API BrotliEncoderPreparedDictionary* +BrotliEncoderPrepareDictionary(BrotliSharedDictionaryType type, + size_t data_size, const uint8_t data[BROTLI_ARRAY_PARAM(data_size)], + int quality, + brotli_alloc_func alloc_func, brotli_free_func free_func, void* opaque); + +BROTLI_ENC_API void BrotliEncoderDestroyPreparedDictionary( + BrotliEncoderPreparedDictionary* dictionary); + +/** + * Attaches a prepared dictionary of any type to the encoder. Can be used + * multiple times to attach multiple dictionaries. The dictionary type was + * determined by BrotliEncoderPrepareDictionary. Multiple raw prefix + * dictionaries and/or max 1 serialized dictionary with custom words can be + * attached. + * + * @returns ::BROTLI_FALSE in case of error + * @returns ::BROTLI_TRUE otherwise + */ +BROTLI_ENC_API BROTLI_BOOL BrotliEncoderAttachPreparedDictionary( + BrotliEncoderState* state, + const BrotliEncoderPreparedDictionary* dictionary); + +/** + * Calculates the output size bound for the given @p input_size. + * + * @warning Result is only valid if quality is at least @c 2 and, in + * case ::BrotliEncoderCompressStream was used, no flushes + * (::BROTLI_OPERATION_FLUSH) were performed. + * + * @param input_size size of projected input + * @returns @c 0 if result does not fit @c size_t + */ +BROTLI_ENC_API size_t BrotliEncoderMaxCompressedSize(size_t input_size); + +/** + * Performs one-shot memory-to-memory compression. + * + * Compresses the data in @p input_buffer into @p encoded_buffer, and sets + * @p *encoded_size to the compressed length. + * + * @note If ::BrotliEncoderMaxCompressedSize(@p input_size) returns non-zero + * value, then output is guaranteed to be no longer than that. + * + * @note If @p lgwin is greater than ::BROTLI_MAX_WINDOW_BITS then resulting + * stream might be incompatible with RFC 7932; to decode such streams, + * decoder should be configured with + * ::BROTLI_DECODER_PARAM_LARGE_WINDOW = @c 1 + * + * @param quality quality parameter value, e.g. ::BROTLI_DEFAULT_QUALITY + * @param lgwin lgwin parameter value, e.g. ::BROTLI_DEFAULT_WINDOW + * @param mode mode parameter value, e.g. ::BROTLI_DEFAULT_MODE + * @param input_size size of @p input_buffer + * @param input_buffer input data buffer with at least @p input_size + * addressable bytes + * @param[in, out] encoded_size @b in: size of @p encoded_buffer; \n + * @b out: length of compressed data written to + * @p encoded_buffer, or @c 0 if compression fails + * @param encoded_buffer compressed data destination buffer + * @returns ::BROTLI_FALSE in case of compression error + * @returns ::BROTLI_FALSE if output buffer is too small + * @returns ::BROTLI_TRUE otherwise + */ +BROTLI_ENC_API int BrotliEncoderCompress( + int quality, int lgwin, BrotliEncoderMode mode, size_t input_size, + const uint8_t input_buffer[BROTLI_ARRAY_PARAM(input_size)], + size_t* encoded_size, + uint8_t encoded_buffer[BROTLI_ARRAY_PARAM(*encoded_size)]); + +/** + * Compresses input stream to output stream. + * + * The values @p *available_in and @p *available_out must specify the number of + * bytes addressable at @p *next_in and @p *next_out respectively. + * When @p *available_out is @c 0, @p next_out is allowed to be @c NULL. + * + * After each call, @p *available_in will be decremented by the amount of input + * bytes consumed, and the @p *next_in pointer will be incremented by that + * amount. Similarly, @p *available_out will be decremented by the amount of + * output bytes written, and the @p *next_out pointer will be incremented by + * that amount. + * + * @p total_out, if it is not a null-pointer, will be set to the number + * of bytes compressed since the last @p state initialization. + * + * + * + * Internally workflow consists of 3 tasks: + * -# (optionally) copy input data to internal buffer + * -# actually compress data and (optionally) store it to internal buffer + * -# (optionally) copy compressed bytes from internal buffer to output stream + * + * Whenever all 3 tasks can't move forward anymore, or error occurs, this + * method returns the control flow to caller. + * + * @p op is used to perform flush, finish the stream, or inject metadata block. + * See ::BrotliEncoderOperation for more information. + * + * Flushing the stream means forcing encoding of all input passed to encoder and + * completing the current output block, so it could be fully decoded by stream + * decoder. To perform flush set @p op to ::BROTLI_OPERATION_FLUSH. + * Under some circumstances (e.g. lack of output stream capacity) this operation + * would require several calls to ::BrotliEncoderCompressStream. The method must + * be called again until both input stream is depleted and encoder has no more + * output (see ::BrotliEncoderHasMoreOutput) after the method is called. + * + * Finishing the stream means encoding of all input passed to encoder and + * adding specific "final" marks, so stream decoder could determine that stream + * is complete. To perform finish set @p op to ::BROTLI_OPERATION_FINISH. + * Under some circumstances (e.g. lack of output stream capacity) this operation + * would require several calls to ::BrotliEncoderCompressStream. The method must + * be called again until both input stream is depleted and encoder has no more + * output (see ::BrotliEncoderHasMoreOutput) after the method is called. + * + * @warning When flushing and finishing, @p op should not change until operation + * is complete; input stream should not be swapped, reduced or + * extended as well. + * + * @param state encoder instance + * @param op requested operation + * @param[in, out] available_in @b in: amount of available input; \n + * @b out: amount of unused input + * @param[in, out] next_in pointer to the next input byte + * @param[in, out] available_out @b in: length of output buffer; \n + * @b out: remaining size of output buffer + * @param[in, out] next_out compressed output buffer cursor; + * can be @c NULL if @p available_out is @c 0 + * @param[out] total_out number of bytes produced so far; can be @c NULL + * @returns ::BROTLI_FALSE if there was an error + * @returns ::BROTLI_TRUE otherwise + */ +BROTLI_ENC_API BROTLI_BOOL BrotliEncoderCompressStream( + BrotliEncoderState* state, BrotliEncoderOperation op, size_t* available_in, + const uint8_t** next_in, size_t* available_out, uint8_t** next_out, + size_t* total_out); + +/** + * Checks if encoder instance reached the final state. + * + * @param state encoder instance + * @returns ::BROTLI_TRUE if encoder is in a state where it reached the end of + * the input and produced all of the output + * @returns ::BROTLI_FALSE otherwise + */ +BROTLI_ENC_API BROTLI_BOOL BrotliEncoderIsFinished(BrotliEncoderState* state); + +/** + * Checks if encoder has more output. + * + * @param state encoder instance + * @returns ::BROTLI_TRUE, if encoder has some unconsumed output + * @returns ::BROTLI_FALSE otherwise + */ +BROTLI_ENC_API BROTLI_BOOL BrotliEncoderHasMoreOutput( + BrotliEncoderState* state); + +/** + * Acquires pointer to internal output buffer. + * + * This method is used to make language bindings easier and more efficient: + * -# push data to ::BrotliEncoderCompressStream, + * until ::BrotliEncoderHasMoreOutput returns BROTLI_TRUE + * -# use ::BrotliEncoderTakeOutput to peek bytes and copy to language-specific + * entity + * + * Also this could be useful if there is an output stream that is able to + * consume all the provided data (e.g. when data is saved to file system). + * + * @attention After every call to ::BrotliEncoderTakeOutput @p *size bytes of + * output are considered consumed for all consecutive calls to the + * instance methods; returned pointer becomes invalidated as well. + * + * @note Encoder output is not guaranteed to be contiguous. This means that + * after the size-unrestricted call to ::BrotliEncoderTakeOutput, + * immediate next call to ::BrotliEncoderTakeOutput may return more data. + * + * @param state encoder instance + * @param[in, out] size @b in: number of bytes caller is ready to take, @c 0 if + * any amount could be handled; \n + * @b out: amount of data pointed by returned pointer and + * considered consumed; \n + * out value is never greater than in value, unless it is @c 0 + * @returns pointer to output data + */ +BROTLI_ENC_API const uint8_t* BrotliEncoderTakeOutput( + BrotliEncoderState* state, size_t* size); + +/* Returns the estimated peak memory usage (in bytes) of the BrotliCompress() + function, not counting the memory needed for the input and output. */ +BROTLI_ENC_EXTRA_API size_t BrotliEncoderEstimatePeakMemoryUsage( + int quality, int lgwin, size_t input_size); +/* Returns 0 if dictionary is not valid; otherwise returns allocation size. */ +BROTLI_ENC_EXTRA_API size_t BrotliEncoderGetPreparedDictionarySize( + const BrotliEncoderPreparedDictionary* dictionary); + +/** + * Gets an encoder library version. + * + * Look at BROTLI_MAKE_HEX_VERSION for more information. + */ +BROTLI_ENC_API uint32_t BrotliEncoderVersion(void); + +#if defined(__cplusplus) || defined(c_plusplus) +} /* extern "C" */ +#endif + +#endif /* BROTLI_ENC_ENCODE_H_ */ diff --git a/brotli/port.h b/brotli/port.h new file mode 100644 index 0000000..0d50019 --- /dev/null +++ b/brotli/port.h @@ -0,0 +1,305 @@ +/* Copyright 2016 Google Inc. All Rights Reserved. + + Distributed under MIT license. + See file LICENSE for detail or copy at https://opensource.org/licenses/MIT +*/ + +/* Macros for compiler / platform specific API declarations. */ + +#ifndef BROTLI_COMMON_PORT_H_ +#define BROTLI_COMMON_PORT_H_ + +/* The following macros were borrowed from https://github.com/nemequ/hedley + * with permission of original author - Evan Nemerson */ + +/* >>> >>> >>> hedley macros */ + +#define BROTLI_MAKE_VERSION(major, minor, revision) \ + (((major) * 1000000) + ((minor) * 1000) + (revision)) + +#if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__) +#define BROTLI_GNUC_VERSION \ + BROTLI_MAKE_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#elif defined(__GNUC__) +#define BROTLI_GNUC_VERSION BROTLI_MAKE_VERSION(__GNUC__, __GNUC_MINOR__, 0) +#endif + +#if defined(BROTLI_GNUC_VERSION) +#define BROTLI_GNUC_VERSION_CHECK(major, minor, patch) \ + (BROTLI_GNUC_VERSION >= BROTLI_MAKE_VERSION(major, minor, patch)) +#else +#define BROTLI_GNUC_VERSION_CHECK(major, minor, patch) (0) +#endif + +#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) +#define BROTLI_MSVC_VERSION \ + BROTLI_MAKE_VERSION((_MSC_FULL_VER / 10000000), \ + (_MSC_FULL_VER % 10000000) / 100000, \ + (_MSC_FULL_VER % 100000) / 100) +#elif defined(_MSC_FULL_VER) +#define BROTLI_MSVC_VERSION \ + BROTLI_MAKE_VERSION((_MSC_FULL_VER / 1000000), \ + (_MSC_FULL_VER % 1000000) / 10000, \ + (_MSC_FULL_VER % 10000) / 10) +#elif defined(_MSC_VER) +#define BROTLI_MSVC_VERSION \ + BROTLI_MAKE_VERSION(_MSC_VER / 100, _MSC_VER % 100, 0) +#endif + +#if !defined(_MSC_VER) +#define BROTLI_MSVC_VERSION_CHECK(major, minor, patch) (0) +#elif defined(_MSC_VER) && (_MSC_VER >= 1400) +#define BROTLI_MSVC_VERSION_CHECK(major, minor, patch) \ + (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch))) +#elif defined(_MSC_VER) && (_MSC_VER >= 1200) +#define BROTLI_MSVC_VERSION_CHECK(major, minor, patch) \ + (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch))) +#else +#define BROTLI_MSVC_VERSION_CHECK(major, minor, patch) \ + (_MSC_VER >= ((major * 100) + (minor))) +#endif + +#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) +#define BROTLI_INTEL_VERSION \ + BROTLI_MAKE_VERSION(__INTEL_COMPILER / 100, \ + __INTEL_COMPILER % 100, \ + __INTEL_COMPILER_UPDATE) +#elif defined(__INTEL_COMPILER) +#define BROTLI_INTEL_VERSION \ + BROTLI_MAKE_VERSION(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) +#endif + +#if defined(BROTLI_INTEL_VERSION) +#define BROTLI_INTEL_VERSION_CHECK(major, minor, patch) \ + (BROTLI_INTEL_VERSION >= BROTLI_MAKE_VERSION(major, minor, patch)) +#else +#define BROTLI_INTEL_VERSION_CHECK(major, minor, patch) (0) +#endif + +#if defined(__PGI) && \ + defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__) +#define BROTLI_PGI_VERSION \ + BROTLI_MAKE_VERSION(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__) +#endif + +#if defined(BROTLI_PGI_VERSION) +#define BROTLI_PGI_VERSION_CHECK(major, minor, patch) \ + (BROTLI_PGI_VERSION >= BROTLI_MAKE_VERSION(major, minor, patch)) +#else +#define BROTLI_PGI_VERSION_CHECK(major, minor, patch) (0) +#endif + +#if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000) +#define BROTLI_SUNPRO_VERSION \ + BROTLI_MAKE_VERSION( \ + (((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), \ + (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), \ + (__SUNPRO_C & 0xf) * 10) +#elif defined(__SUNPRO_C) +#define BROTLI_SUNPRO_VERSION \ + BROTLI_MAKE_VERSION((__SUNPRO_C >> 8) & 0xf, \ + (__SUNPRO_C >> 4) & 0xf, \ + (__SUNPRO_C) & 0xf) +#elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000) +#define BROTLI_SUNPRO_VERSION \ + BROTLI_MAKE_VERSION( \ + (((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), \ + (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), \ + (__SUNPRO_CC & 0xf) * 10) +#elif defined(__SUNPRO_CC) +#define BROTLI_SUNPRO_VERSION \ + BROTLI_MAKE_VERSION((__SUNPRO_CC >> 8) & 0xf, \ + (__SUNPRO_CC >> 4) & 0xf, \ + (__SUNPRO_CC) & 0xf) +#endif + +#if defined(BROTLI_SUNPRO_VERSION) +#define BROTLI_SUNPRO_VERSION_CHECK(major, minor, patch) \ + (BROTLI_SUNPRO_VERSION >= BROTLI_MAKE_VERSION(major, minor, patch)) +#else +#define BROTLI_SUNPRO_VERSION_CHECK(major, minor, patch) (0) +#endif + +#if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION) +#define BROTLI_ARM_VERSION \ + BROTLI_MAKE_VERSION((__ARMCOMPILER_VERSION / 1000000), \ + (__ARMCOMPILER_VERSION % 1000000) / 10000, \ + (__ARMCOMPILER_VERSION % 10000) / 100) +#elif defined(__CC_ARM) && defined(__ARMCC_VERSION) +#define BROTLI_ARM_VERSION \ + BROTLI_MAKE_VERSION((__ARMCC_VERSION / 1000000), \ + (__ARMCC_VERSION % 1000000) / 10000, \ + (__ARMCC_VERSION % 10000) / 100) +#endif + +#if defined(BROTLI_ARM_VERSION) +#define BROTLI_ARM_VERSION_CHECK(major, minor, patch) \ + (BROTLI_ARM_VERSION >= BROTLI_MAKE_VERSION(major, minor, patch)) +#else +#define BROTLI_ARM_VERSION_CHECK(major, minor, patch) (0) +#endif + +#if defined(__ibmxl__) +#define BROTLI_IBM_VERSION \ + BROTLI_MAKE_VERSION(__ibmxl_version__, \ + __ibmxl_release__, \ + __ibmxl_modification__) +#elif defined(__xlC__) && defined(__xlC_ver__) +#define BROTLI_IBM_VERSION \ + BROTLI_MAKE_VERSION(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff) +#elif defined(__xlC__) +#define BROTLI_IBM_VERSION BROTLI_MAKE_VERSION(__xlC__ >> 8, __xlC__ & 0xff, 0) +#endif + +#if defined(BROTLI_IBM_VERSION) +#define BROTLI_IBM_VERSION_CHECK(major, minor, patch) \ + (BROTLI_IBM_VERSION >= BROTLI_MAKE_VERSION(major, minor, patch)) +#else +#define BROTLI_IBM_VERSION_CHECK(major, minor, patch) (0) +#endif + +#if defined(__TI_COMPILER_VERSION__) +#define BROTLI_TI_VERSION \ + BROTLI_MAKE_VERSION((__TI_COMPILER_VERSION__ / 1000000), \ + (__TI_COMPILER_VERSION__ % 1000000) / 1000, \ + (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(BROTLI_TI_VERSION) +#define BROTLI_TI_VERSION_CHECK(major, minor, patch) \ + (BROTLI_TI_VERSION >= BROTLI_MAKE_VERSION(major, minor, patch)) +#else +#define BROTLI_TI_VERSION_CHECK(major, minor, patch) (0) +#endif + +#if defined(__IAR_SYSTEMS_ICC__) +#if __VER__ > 1000 +#define BROTLI_IAR_VERSION \ + BROTLI_MAKE_VERSION((__VER__ / 1000000), \ + (__VER__ / 1000) % 1000, \ + (__VER__ % 1000)) +#else +#define BROTLI_IAR_VERSION BROTLI_MAKE_VERSION(VER / 100, __VER__ % 100, 0) +#endif +#endif + +#if defined(BROTLI_IAR_VERSION) +#define BROTLI_IAR_VERSION_CHECK(major, minor, patch) \ + (BROTLI_IAR_VERSION >= BROTLI_MAKE_VERSION(major, minor, patch)) +#else +#define BROTLI_IAR_VERSION_CHECK(major, minor, patch) (0) +#endif + +#if defined(__TINYC__) +#define BROTLI_TINYC_VERSION \ + BROTLI_MAKE_VERSION(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100) +#endif + +#if defined(BROTLI_TINYC_VERSION) +#define BROTLI_TINYC_VERSION_CHECK(major, minor, patch) \ + (BROTLI_TINYC_VERSION >= BROTLI_MAKE_VERSION(major, minor, patch)) +#else +#define BROTLI_TINYC_VERSION_CHECK(major, minor, patch) (0) +#endif + +#if defined(__has_attribute) +#define BROTLI_GNUC_HAS_ATTRIBUTE(attribute, major, minor, patch) \ + __has_attribute(attribute) +#else +#define BROTLI_GNUC_HAS_ATTRIBUTE(attribute, major, minor, patch) \ + BROTLI_GNUC_VERSION_CHECK(major, minor, patch) +#endif + +#if defined(__has_builtin) +#define BROTLI_GNUC_HAS_BUILTIN(builtin, major, minor, patch) \ + __has_builtin(builtin) +#else +#define BROTLI_GNUC_HAS_BUILTIN(builtin, major, minor, patch) \ + BROTLI_GNUC_VERSION_CHECK(major, minor, patch) +#endif + +#if defined(__has_feature) +#define BROTLI_HAS_FEATURE(feature) __has_feature(feature) +#else +#define BROTLI_HAS_FEATURE(feature) (0) +#endif + +#if defined(_WIN32) || defined(__CYGWIN__) +#define BROTLI_PUBLIC +#elif BROTLI_GNUC_VERSION_CHECK(3, 3, 0) || \ + BROTLI_TI_VERSION_CHECK(8, 0, 0) || \ + BROTLI_INTEL_VERSION_CHECK(16, 0, 0) || \ + BROTLI_ARM_VERSION_CHECK(4, 1, 0) || \ + BROTLI_IBM_VERSION_CHECK(13, 1, 0) || \ + BROTLI_SUNPRO_VERSION_CHECK(5, 11, 0) || \ + (BROTLI_TI_VERSION_CHECK(7, 3, 0) && \ + defined(__TI_GNU_ATTRIBUTE_SUPPORT__) && defined(__TI_EABI__)) +#define BROTLI_PUBLIC __attribute__ ((visibility ("default"))) +#else +#define BROTLI_PUBLIC +#endif + +/* BROTLI_INTERNAL could be defined to override visibility, e.g. for tests. */ +#if !defined(BROTLI_INTERNAL) +#if defined(_WIN32) || defined(__CYGWIN__) +#define BROTLI_INTERNAL +#elif BROTLI_GNUC_VERSION_CHECK(3, 3, 0) || \ + BROTLI_TI_VERSION_CHECK(8, 0, 0) || \ + BROTLI_INTEL_VERSION_CHECK(16, 0, 0) || \ + BROTLI_ARM_VERSION_CHECK(4, 1, 0) || \ + BROTLI_IBM_VERSION_CHECK(13, 1, 0) || \ + BROTLI_SUNPRO_VERSION_CHECK(5, 11, 0) || \ + (BROTLI_TI_VERSION_CHECK(7, 3, 0) && \ + defined(__TI_GNU_ATTRIBUTE_SUPPORT__) && defined(__TI_EABI__)) +#define BROTLI_INTERNAL __attribute__ ((visibility ("hidden"))) +#else +#define BROTLI_INTERNAL +#endif +#endif + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ + !defined(__STDC_NO_VLA__) && !defined(__cplusplus) && \ + !defined(__PGI) && !defined(__PGIC__) && !defined(__TINYC__) && \ + !defined(__clang__) +#define BROTLI_ARRAY_PARAM(name) (name) +#else +#define BROTLI_ARRAY_PARAM(name) +#endif + +/* <<< <<< <<< end of hedley macros. */ + +#if defined(BROTLI_SHARED_COMPILATION) +#if defined(_WIN32) +#if defined(BROTLICOMMON_SHARED_COMPILATION) +#define BROTLI_COMMON_API __declspec(dllexport) +#else +#define BROTLI_COMMON_API __declspec(dllimport) +#endif /* BROTLICOMMON_SHARED_COMPILATION */ +#if defined(BROTLIDEC_SHARED_COMPILATION) +#define BROTLI_DEC_API __declspec(dllexport) +#else +#define BROTLI_DEC_API __declspec(dllimport) +#endif /* BROTLIDEC_SHARED_COMPILATION */ +#if defined(BROTLIENC_SHARED_COMPILATION) +#define BROTLI_ENC_API __declspec(dllexport) +#else +#define BROTLI_ENC_API __declspec(dllimport) +#endif /* BROTLIENC_SHARED_COMPILATION */ +#else /* _WIN32 */ +#define BROTLI_COMMON_API BROTLI_PUBLIC +#define BROTLI_DEC_API BROTLI_PUBLIC +#define BROTLI_ENC_API BROTLI_PUBLIC +#endif /* _WIN32 */ +#else /* BROTLI_SHARED_COMPILATION */ +#define BROTLI_COMMON_API +#define BROTLI_DEC_API +#define BROTLI_ENC_API +#endif + +#if defined(BROTLI_BUILD_ENC_EXTRA_API) +#define BROTLI_ENC_EXTRA_API BROTLI_ENC_API +#else +#define BROTLI_ENC_EXTRA_API BROTLI_INTERNAL +#endif + +#endif /* BROTLI_COMMON_PORT_H_ */ diff --git a/brotli/shared_dictionary.h b/brotli/shared_dictionary.h new file mode 100644 index 0000000..2970c2d --- /dev/null +++ b/brotli/shared_dictionary.h @@ -0,0 +1,100 @@ +/* Copyright 2017 Google Inc. All Rights Reserved. + + Distributed under MIT license. + See file LICENSE for detail or copy at https://opensource.org/licenses/MIT +*/ + +/* (Opaque) Shared Dictionary definition and utilities. */ + +#ifndef BROTLI_COMMON_SHARED_DICTIONARY_H_ +#define BROTLI_COMMON_SHARED_DICTIONARY_H_ + +#include +#include + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif + +#define SHARED_BROTLI_MIN_DICTIONARY_WORD_LENGTH 4 +#define SHARED_BROTLI_MAX_DICTIONARY_WORD_LENGTH 31 +#define SHARED_BROTLI_NUM_DICTIONARY_CONTEXTS 64 +#define SHARED_BROTLI_MAX_COMPOUND_DICTS 15 + +/** + * Opaque structure that holds shared dictionary data. + * + * Allocated and initialized with ::BrotliSharedDictionaryCreateInstance. + * Cleaned up and deallocated with ::BrotliSharedDictionaryDestroyInstance. + */ +typedef struct BrotliSharedDictionaryStruct BrotliSharedDictionary; + +/** + * Input data type for ::BrotliSharedDictionaryAttach. + */ +typedef enum BrotliSharedDictionaryType { + /** Raw LZ77 prefix dictionary. */ + BROTLI_SHARED_DICTIONARY_RAW = 0, + /** Serialized shared dictionary. + * + * DO NOT USE: methods accepting this value will fail. + */ + BROTLI_SHARED_DICTIONARY_SERIALIZED = 1 +} BrotliSharedDictionaryType; + +/** + * Creates an instance of ::BrotliSharedDictionary. + * + * Fresh instance has default word dictionary and transforms + * and no LZ77 prefix dictionary. + * + * @p alloc_func and @p free_func @b MUST be both zero or both non-zero. In the + * case they are both zero, default memory allocators are used. @p opaque is + * passed to @p alloc_func and @p free_func when they are called. @p free_func + * has to return without doing anything when asked to free a NULL pointer. + * + * @param alloc_func custom memory allocation function + * @param free_func custom memory free function + * @param opaque custom memory manager handle + * @returns @c 0 if instance can not be allocated or initialized + * @returns pointer to initialized ::BrotliSharedDictionary otherwise + */ +BROTLI_COMMON_API BrotliSharedDictionary* BrotliSharedDictionaryCreateInstance( + brotli_alloc_func alloc_func, brotli_free_func free_func, void* opaque); + +/** + * Deinitializes and frees ::BrotliSharedDictionary instance. + * + * @param dict shared dictionary instance to be cleaned up and deallocated + */ +BROTLI_COMMON_API void BrotliSharedDictionaryDestroyInstance( + BrotliSharedDictionary* dict); + +/** + * Attaches dictionary to a given instance of ::BrotliSharedDictionary. + * + * Dictionary to be attached is represented in a serialized format as a region + * of memory. + * + * Provided data it partially referenced by a resulting (compound) dictionary, + * and should be kept untouched, while at least one compound dictionary uses it. + * This way memory overhead is kept minimal by the cost of additional resource + * management. + * + * @param dict dictionary to extend + * @param type type of dictionary to attach + * @param data_size size of @p data + * @param data serialized dictionary of type @p type, with at least @p data_size + * addressable bytes + * @returns ::BROTLI_TRUE if provided dictionary is successfully attached + * @returns ::BROTLI_FALSE otherwise + */ +BROTLI_COMMON_API BROTLI_BOOL BrotliSharedDictionaryAttach( + BrotliSharedDictionary* dict, BrotliSharedDictionaryType type, + size_t data_size, const uint8_t data[BROTLI_ARRAY_PARAM(data_size)]); + +#if defined(__cplusplus) || defined(c_plusplus) +} /* extern "C" */ +#endif + +#endif /* BROTLI_COMMON_SHARED_DICTIONARY_H_ */ diff --git a/brotli/types.h b/brotli/types.h new file mode 100644 index 0000000..eff1a3c --- /dev/null +++ b/brotli/types.h @@ -0,0 +1,83 @@ +/* Copyright 2013 Google Inc. All Rights Reserved. + + Distributed under MIT license. + See file LICENSE for detail or copy at https://opensource.org/licenses/MIT +*/ + +/** + * @file + * Common types used in decoder and encoder API. + */ + +#ifndef BROTLI_COMMON_TYPES_H_ +#define BROTLI_COMMON_TYPES_H_ + +#include /* for size_t */ + +#if defined(_MSC_VER) && (_MSC_VER < 1600) +typedef __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef unsigned __int64 uint64_t; +typedef __int64 int64_t; +#else +#include +#endif /* defined(_MSC_VER) && (_MSC_VER < 1600) */ + +/** + * A portable @c bool replacement. + * + * ::BROTLI_BOOL is a "documentation" type: actually it is @c int, but in API it + * denotes a type, whose only values are ::BROTLI_TRUE and ::BROTLI_FALSE. + * + * ::BROTLI_BOOL values passed to Brotli should either be ::BROTLI_TRUE or + * ::BROTLI_FALSE, or be a result of ::TO_BROTLI_BOOL macros. + * + * ::BROTLI_BOOL values returned by Brotli should not be tested for equality + * with @c true, @c false, ::BROTLI_TRUE, ::BROTLI_FALSE, but rather should be + * evaluated, for example: @code{.cpp} + * if (SomeBrotliFunction(encoder, BROTLI_TRUE) && + * !OtherBrotliFunction(decoder, BROTLI_FALSE)) { + * bool x = !!YetAnotherBrotliFunction(encoder, TO_BROLTI_BOOL(2 * 2 == 4)); + * DoSomething(x); + * } + * @endcode + */ +#define BROTLI_BOOL int +/** Portable @c true replacement. */ +#define BROTLI_TRUE 1 +/** Portable @c false replacement. */ +#define BROTLI_FALSE 0 +/** @c bool to ::BROTLI_BOOL conversion macros. */ +#define TO_BROTLI_BOOL(X) (!!(X) ? BROTLI_TRUE : BROTLI_FALSE) + +#define BROTLI_MAKE_UINT64_T(high, low) ((((uint64_t)(high)) << 32) | low) + +#define BROTLI_UINT32_MAX (~((uint32_t)0)) +#define BROTLI_SIZE_MAX (~((size_t)0)) + +/** + * Allocating function pointer type. + * + * @param opaque custom memory manager handle provided by client + * @param size requested memory region size; can not be @c 0 + * @returns @c 0 in the case of failure + * @returns a valid pointer to a memory region of at least @p size bytes + * long otherwise + */ +typedef void* (*brotli_alloc_func)(void* opaque, size_t size); + +/** + * Deallocating function pointer type. + * + * This function @b SHOULD do nothing if @p address is @c 0. + * + * @param opaque custom memory manager handle provided by client + * @param address memory region pointer returned by ::brotli_alloc_func, or @c 0 + */ +typedef void (*brotli_free_func)(void* opaque, void* address); + +#endif /* BROTLI_COMMON_TYPES_H_ */ diff --git a/common/BufferPool.cpp b/common/BufferPool.cpp new file mode 100644 index 0000000..447b698 --- /dev/null +++ b/common/BufferPool.cpp @@ -0,0 +1,298 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "BufferPool.h" +#include "FuncHelper.h" + +const DWORD TItem::DEFAULT_ITEM_CAPACITY = DEFAULT_BUFFER_CACHE_CAPACITY; +const DWORD CBufferPool::DEFAULT_MAX_CACHE_SIZE = 0; +const DWORD CBufferPool::DEFAULT_ITEM_CAPACITY = CItemPool::DEFAULT_ITEM_CAPACITY; +const DWORD CBufferPool::DEFAULT_ITEM_POOL_SIZE = CItemPool::DEFAULT_POOL_SIZE; +const DWORD CBufferPool::DEFAULT_ITEM_POOL_HOLD = CItemPool::DEFAULT_POOL_HOLD; +const DWORD CBufferPool::DEFAULT_BUFFER_LOCK_TIME = DEFAULT_OBJECT_CACHE_LOCK_TIME; +const DWORD CBufferPool::DEFAULT_BUFFER_POOL_SIZE = DEFAULT_OBJECT_CACHE_POOL_SIZE; +const DWORD CBufferPool::DEFAULT_BUFFER_POOL_HOLD = DEFAULT_OBJECT_CACHE_POOL_HOLD; + +int TItem::Cat(const BYTE* pData, int length) +{ + ASSERT(pData != nullptr && length >= 0); + + int cat = MIN(Remain(), length); + + if(cat > 0) + { + memcpy(end, pData, cat); + end += cat; + } + + return cat; +} + +int TItem::Cat(const TItem& other) +{ + ASSERT(this != &other); + return Cat(other.Ptr(), other.Size()); +} + +int TItem::Fetch(BYTE* pData, int length) +{ + ASSERT(pData != nullptr && length > 0); + + int fetch = MIN(Size(), length); + memcpy(pData, begin, fetch); + begin += fetch; + + return fetch; +} + +int TItem::Peek(BYTE* pData, int length) +{ + ASSERT(pData != nullptr && length > 0); + + int peek = MIN(Size(), length); + memcpy(pData, begin, peek); + + return peek; +} + +int TItem::Increase(int length) +{ + ASSERT(length >= 0); + + int increase = MIN(Remain(), length); + end += increase; + + return increase; +} + +int TItem::Reduce(int length) +{ + ASSERT(length >= 0); + + int reduce = MIN(Size(), length); + begin += reduce; + + return reduce; +} + +void TItem::Reset(int first, int last) +{ + ASSERT(first >= -1 && first <= capacity); + ASSERT(last >= -1 && last <= capacity); + + if(first >= 0) begin = head + MIN(first, capacity); + if(last >= 0) end = head + MIN(last, capacity); +} + +TBuffer* TBuffer::Construct(CBufferPool& pool, ULONG_PTR dwID) +{ + ASSERT(dwID != 0); + + CPrivateHeap& heap = pool.GetPrivateHeap(); + TBuffer* pBuffer = (TBuffer*)heap.Alloc(sizeof(TBuffer)); + + return ::ConstructObject(pBuffer, heap, pool.GetItemPool(), dwID); +} + +void TBuffer::Destruct(TBuffer* pBuffer) +{ + ASSERT(pBuffer != nullptr); + + CPrivateHeap& heap = pBuffer->heap; + ::DestructObject(pBuffer); + heap.Free(pBuffer); +} + +void TBuffer::Reset() +{ + id = 0; + length = 0; + freeTime = ::TimeGetTime(); +} + +int TBuffer::Cat(const BYTE* pData, int len) +{ + items.Cat(pData, len); + return IncreaseLength(len); +} + +int TBuffer::Cat(const TItem* pItem) +{ + items.Cat(pItem); + return IncreaseLength(pItem->Size()); +} + +int TBuffer::Cat(const TItemList& other) +{ + ASSERT(&items != &other); + + for(TItem* pItem = other.Front(); pItem != nullptr; pItem = pItem->next) + Cat(pItem); + + return length; +} + +int TBuffer::Fetch(BYTE* pData, int len) +{ + int fetch = items.Fetch(pData, len); + DecreaseLength(fetch); + + return fetch; +} + +int TBuffer::Peek(BYTE* pData, int len) +{ + return items.Peek(pData, len); +} + +int TBuffer::Reduce(int len) +{ + int reduce = items.Reduce(len); + DecreaseLength(reduce); + + return reduce; +} + +void CBufferPool::PutFreeBuffer(ULONG_PTR dwID) +{ + ASSERT(dwID != 0); + + TBuffer* pBuffer = FindCacheBuffer(dwID); + + if(pBuffer != nullptr) + PutFreeBuffer(pBuffer); +} + +void CBufferPool::PutFreeBuffer(TBuffer* pBuffer) +{ + ASSERT(pBuffer != nullptr); + + if(!pBuffer->IsValid()) + return; + + m_bfCache.RemoveEx(pBuffer->ID()); + + BOOL bOK = FALSE; + + { + CCriSecLock locallock(pBuffer->cs); + + if(pBuffer->IsValid()) + { + pBuffer->Reset(); + bOK = TRUE; + } + } + + if(bOK) + { + m_itPool.PutFreeItem(pBuffer->items); + +#ifndef USE_EXTERNAL_GC + ReleaseGCBuffer(); +#endif + if(!m_lsFreeBuffer.TryPut(pBuffer)) + m_lsGCBuffer.PushBack(pBuffer); + } +} + +void CBufferPool::ReleaseGCBuffer(BOOL bForce) +{ + ::ReleaseGCObj(m_lsGCBuffer, m_dwBufferLockTime, bForce); +} + +TBuffer* CBufferPool::PutCacheBuffer(ULONG_PTR dwID) +{ + ASSERT(dwID != 0); + + TBuffer* pBuffer = PickFreeBuffer(dwID); + m_bfCache.SetEx(dwID, pBuffer); + + return pBuffer; +} + +TBuffer* CBufferPool::PickFreeBuffer(ULONG_PTR dwID) +{ + ASSERT( dwID != 0); + + DWORD dwIndex; + TBuffer* pBuffer = nullptr; + + if(m_lsFreeBuffer.TryLock(&pBuffer, dwIndex)) + { + if(::GetTimeGap32(pBuffer->freeTime) >= m_dwBufferLockTime) + VERIFY(m_lsFreeBuffer.ReleaseLock(nullptr, dwIndex)); + else + { + VERIFY(m_lsFreeBuffer.ReleaseLock(pBuffer, dwIndex)); + pBuffer = nullptr; + } + } + + if(pBuffer) pBuffer->id = dwID; + else pBuffer = TBuffer::Construct(*this, dwID); + + ASSERT(pBuffer); + return pBuffer; +} + +TBuffer* CBufferPool::FindCacheBuffer(ULONG_PTR dwID) +{ + ASSERT(dwID != 0); + + TBuffer* pBuffer = nullptr; + + if(m_bfCache.GetEx(dwID, &pBuffer) != TBufferCache::GR_VALID) + pBuffer = nullptr; + + return pBuffer; +} + +void CBufferPool::Prepare() +{ + m_itPool.Prepare(); + + m_bfCache.Reset(m_dwMaxCacheSize); + m_lsFreeBuffer.Reset(m_dwBufferPoolSize); +} + +void CBufferPool::Clear() +{ + TBufferCache::IndexSet& indexes = m_bfCache.Indexes(); + + for(auto it = indexes.begin(), end = indexes.end(); it != end; ++it) + { + TBuffer* pBuffer = FindCacheBuffer(*it); + if(pBuffer) TBuffer::Destruct(pBuffer); + } + + m_bfCache.Reset(); + + m_lsFreeBuffer.Clear(); + + ReleaseGCBuffer(TRUE); + VERIFY(m_lsGCBuffer.IsEmpty()); + + m_itPool.Clear(); + m_heap.Reset(); +} diff --git a/common/BufferPool.h b/common/BufferPool.h new file mode 100644 index 0000000..6957f2d --- /dev/null +++ b/common/BufferPool.h @@ -0,0 +1,869 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#pragma once + +#include "hpsocket/GlobalDef.h" +#include "Singleton.h" +#include "STLHelper.h" +#include "RingBuffer.h" +#include "PrivateHeap.h" +#include "CriSec.h" + +template T* ConstructItemT(T*, CPrivateHeap& heap, int capacity, BYTE* pData, int length) +{ + ASSERT(capacity > 0); + + int item_size = sizeof(T); + T* pItem = (T*)heap.Alloc(item_size + capacity); + BYTE* pHead = (BYTE*)pItem + item_size; + + return ::ConstructObject(pItem, heap, pHead, capacity, pData, length); +} + +template void DestructItemT(T* pItem) +{ + ASSERT(pItem != nullptr); + + CPrivateHeap& heap = pItem->GetPrivateHeap(); + + ::DestructObject(pItem); + heap.Free(pItem); +} + +struct TItem +{ + template friend struct TSimpleList; + template friend class CNodePoolT; + template friend struct TItemListT; + + friend struct TBuffer; + +public: + int Cat (const BYTE* pData, int length); + int Cat (const TItem& other); + int Fetch (BYTE* pData, int length); + int Peek (BYTE* pData, int length); + int Increase(int length); + int Reduce (int length); + void Reset (int first = 0, int last = 0); + + BYTE* Ptr () {return begin;} + const BYTE* Ptr () const {return begin;} + int Size () const {return (int)(end - begin);} + int Remain () const {return capacity - (int)(end - head);} + int Capacity() const {return capacity;} + bool IsEmpty () const {return Size() == 0;} + bool IsFull () const {return Remain() == 0;} + CPrivateHeap& GetPrivateHeap() {return heap;} + + operator BYTE* () {return Ptr();} + operator const BYTE* () const {return Ptr();} + +public: + static TItem* Construct(CPrivateHeap& heap, + int capacity = DEFAULT_ITEM_CAPACITY, + BYTE* pData = nullptr, + int length = 0) + { + return ::ConstructItemT((TItem*)(nullptr), heap, capacity, pData, length); + } + + static void Destruct(TItem* pItem) + { + ::DestructItemT(pItem); + } + + TItem(CPrivateHeap& hp, BYTE* pHead, int cap = DEFAULT_ITEM_CAPACITY, BYTE* pData = nullptr, int length = 0) + : heap(hp), head(pHead), begin(pHead), end(pHead), capacity(cap), next(nullptr), last(nullptr) + { + if(pData != nullptr && length != 0) + Cat(pData, length); + } + + ~TItem() {} + + DECLARE_NO_COPY_CLASS(TItem) + +public: + static const DWORD DEFAULT_ITEM_CAPACITY; + +private: + CPrivateHeap& heap; + +private: + TItem* next; + TItem* last; + + int capacity; + BYTE* head; + BYTE* begin; + BYTE* end; +}; + +template struct TSimpleList +{ +public: + T* PushFront(T* pItem) + { + if(pFront != nullptr) + { + pFront->last = pItem; + pItem->next = pFront; + } + else + { + pItem->last = nullptr; + pItem->next = nullptr; + pBack = pItem; + } + + pFront = pItem; + ++size; + + return pItem; + } + + T* PushBack(T* pItem) + { + if(pBack != nullptr) + { + pBack->next = pItem; + pItem->last = pBack; + } + else + { + pItem->last = nullptr; + pItem->next = nullptr; + pFront = pItem; + } + + pBack = pItem; + ++size; + + return pItem; + } + + T* PopFront() + { + T* pItem = pFront; + + if(pFront != pBack) + { + pFront = (T*)pFront->next; + pFront->last = nullptr; + } + else if(pFront != nullptr) + { + pFront = nullptr; + pBack = nullptr; + } + + if(pItem != nullptr) + { + pItem->next = nullptr; + pItem->last = nullptr; + + --size; + } + + return pItem; + } + + T* PopBack() + { + T* pItem = pBack; + + if(pFront != pBack) + { + pBack = (T*)pBack->last; + pBack->next = nullptr; + } + else if(pBack != nullptr) + { + pFront = nullptr; + pBack = nullptr; + } + + if(pItem != nullptr) + { + pItem->next = nullptr; + pItem->last = nullptr; + + --size; + } + + return pItem; + } + + TSimpleList& Shift(TSimpleList& other) + { + if(&other != this && other.size > 0) + { + if(size > 0) + { + pBack->next = other.pFront; + other.pFront->last = pBack; + } + else + { + pFront = other.pFront; + } + + pBack = other.pBack; + size += other.size; + + other.Reset(); + } + + return *this; + } + + void Clear() + { + if(size > 0) + { + T* pItem; + while((pItem = PopFront()) != nullptr) + T::Destruct(pItem); + } + } + + T* Front () const {return pFront;} + T* Back () const {return pBack;} + int Size () const {return size;} + bool IsEmpty () const {return size == 0;} + +public: + TSimpleList() {Reset();} + ~TSimpleList() {Clear();} + + DECLARE_NO_COPY_CLASS(TSimpleList) + +private: + void Reset() + { + pFront = nullptr; + pBack = nullptr; + size = 0; + } + +private: + int size; + T* pFront; + T* pBack; +}; + +template class CNodePoolT +{ +public: + void PutFreeItem(T* pItem) + { + ASSERT(pItem != nullptr); + + if(!m_lsFreeItem.TryPut(pItem)) + T::Destruct(pItem); + } + + void PutFreeItem(TSimpleList& lsItem) + { + if(lsItem.IsEmpty()) + return; + + T* pItem; + while((pItem = lsItem.PopFront()) != nullptr) + PutFreeItem(pItem); + } + + T* PickFreeItem() + { + T* pItem = nullptr; + + if(!m_lsFreeItem.TryGet(&pItem)) + pItem = T::Construct(m_heap, m_dwItemCapacity); + + ASSERT(pItem); + pItem->Reset(); + + return pItem; + } + + void Prepare() + { + m_lsFreeItem.Reset(m_dwPoolSize); + } + + void Clear() + { + m_lsFreeItem.Clear(); + + m_heap.Reset(); + } + +public: + void SetItemCapacity(DWORD dwItemCapacity) {m_dwItemCapacity = dwItemCapacity;} + void SetPoolSize (DWORD dwPoolSize) {m_dwPoolSize = dwPoolSize;} + void SetPoolHold (DWORD dwPoolHold) {m_dwPoolHold = dwPoolHold;} + DWORD GetItemCapacity () {return m_dwItemCapacity;} + DWORD GetPoolSize () {return m_dwPoolSize;} + DWORD GetPoolHold () {return m_dwPoolHold;} + + CPrivateHeap& GetPrivateHeap() {return m_heap;} + +public: + CNodePoolT( DWORD dwPoolSize = DEFAULT_POOL_SIZE, + DWORD dwPoolHold = DEFAULT_POOL_HOLD, + DWORD dwItemCapacity = DEFAULT_ITEM_CAPACITY) + : m_dwPoolSize(dwPoolSize) + , m_dwPoolHold(dwPoolHold) + , m_dwItemCapacity(dwItemCapacity) + { + } + + ~CNodePoolT() {Clear();} + + DECLARE_NO_COPY_CLASS(CNodePoolT) + +public: + static const DWORD DEFAULT_ITEM_CAPACITY; + static const DWORD DEFAULT_POOL_SIZE; + static const DWORD DEFAULT_POOL_HOLD; + +private: + CPrivateHeap m_heap; + + DWORD m_dwItemCapacity; + DWORD m_dwPoolSize; + DWORD m_dwPoolHold; + + CRingPool m_lsFreeItem; +}; + +template const DWORD CNodePoolT::DEFAULT_ITEM_CAPACITY = TItem::DEFAULT_ITEM_CAPACITY; +template const DWORD CNodePoolT::DEFAULT_POOL_SIZE = DEFAULT_BUFFER_CACHE_POOL_SIZE; +template const DWORD CNodePoolT::DEFAULT_POOL_HOLD = DEFAULT_BUFFER_CACHE_POOL_HOLD; + +using CItemPool = CNodePoolT; + +template struct TItemListT : public TSimpleList +{ + using __super = TSimpleList; + +public: + int PushTail(const BYTE* pData, int length) + { + ASSERT(length <= (int)itPool.GetItemCapacity()); + + if(length > (int)itPool.GetItemCapacity()) + return 0; + + T* pItem = __super::PushBack(itPool.PickFreeItem()); + return pItem->Cat(pData, length); + } + + int Cat(const BYTE* pData, int length) + { + int remain = length; + + while(remain > 0) + { + T* pItem = __super::Back(); + + if(pItem == nullptr || pItem->IsFull()) + pItem = __super::PushBack(itPool.PickFreeItem()); + + int cat = pItem->Cat(pData, remain); + + pData += cat; + remain -= cat; + } + + return length; + } + + int Cat(const T* pItem) + { + return Cat(pItem->Ptr(), pItem->Size()); + } + + int Cat(const TItemListT& other) + { + ASSERT(this != &other); + + int length = 0; + + for(T* pItem = other.Front(); pItem != nullptr; pItem = pItem->next) + length += Cat(pItem); + + return length; + } + + int Fetch(BYTE* pData, int length) + { + int remain = length; + + while(remain > 0 && __super::Size() > 0) + { + T* pItem = __super::Front(); + int fetch = pItem->Fetch(pData, remain); + + pData += fetch; + remain -= fetch; + + if(pItem->IsEmpty()) + itPool.PutFreeItem(__super::PopFront()); + } + + return length - remain; + } + + int Peek(BYTE* pData, int length) + { + int remain = length; + T* pItem = __super::Front(); + + while(remain > 0 && pItem != nullptr) + { + int peek = pItem->Peek(pData, remain); + + pData += peek; + remain -= peek; + pItem = pItem->next; + } + + return length - remain; + } + + int Increase(int length) + { + int remain = length; + + while(remain > 0) + { + T* pItem = __super::Back(); + + if(pItem == nullptr || pItem->IsFull()) + { + pItem = itPool.PickFreeItem(); + __super::PushBack(pItem); + } + + remain -= pItem->Increase(remain); + } + + return length - remain; + } + + int Reduce(int length) + { + int remain = length; + + while(remain > 0 && __super::Size() > 0) + { + T* pItem = __super::Front(); + remain -= pItem->Reduce(remain); + + if(pItem->IsEmpty()) + itPool.PutFreeItem(__super::PopFront()); + } + + return length - remain; + } + + void Release() + { + itPool.PutFreeItem(*this); + } + + CNodePoolT& GetItemPool() {return itPool;} + +public: + TItemListT(CNodePoolT& pool) : itPool(pool) + { + } + +private: + CNodePoolT& itPool; +}; + +using TItemList = TItemListT; + +template::type>::value>> +struct TItemListExT : public TItemListT +{ + using __super = TItemListT; + +public: + T* PushFront(T* pItem) + { + length += pItem->Size(); + return __super::PushFront(pItem); + } + + T* PushBack(T* pItem) + { + length += pItem->Size(); + return __super::PushBack(pItem); + } + + T* PopFront() + { + T* pItem = __super::PopFront(); + + if(pItem != nullptr) + length -= pItem->Size(); + + return pItem; + } + + T* PopBack() + { + T* pItem = __super::PopBack(); + + if(pItem != nullptr) + length -= pItem->Size(); + + return pItem; + } + + TItemListExT& Shift(TItemListExT& other) + { + if(&other != this && other.length > 0) + { + length += other.length; + other.length = 0; + + __super::Shift(other); + } + + return *this; + } + + void Clear() + { + __super::Clear(); + length = 0; + } + + void Release() + { + __super::Release(); + length = 0; + } + +public: + int PushTail(const BYTE* pData, int length) + { + int cat = __super::PushTail(pData, length); + this->length += cat; + + return cat; + } + + int Cat(const BYTE* pData, int length) + { + int cat = __super::Cat(pData, length); + this->length += cat; + + return cat; + } + + int Cat(const T* pItem) + { + int cat = __super::Cat(pItem->Ptr(), pItem->Size()); + this->length += cat; + + return cat; + } + + int Cat(const TItemListT& other) + { + int cat = __super::Cat(other); + this->length += cat; + + return cat; + } + + int Fetch(BYTE* pData, int length) + { + int fetch = __super::Fetch(pData, length); + this->length -= fetch; + + return fetch; + } + + int Increase(int length) + { + int increase = __super::Increase(length); + this->length += increase; + + return increase; + } + + int Reduce(int length) + { + int reduce = __super::Reduce(length); + this->length -= reduce; + + return reduce; + } + + typename decay::type Length() const {return length;} + + int IncreaseLength (int length) {return (this->length += length);} + int ReduceLength (int length) {return (this->length -= length);} + +public: + TItemListExT(CNodePoolT& pool) : TItemListT(pool), length(0) + { + } + + ~TItemListExT() + { + ASSERT(length >= 0); + } + + DECLARE_NO_COPY_CLASS(TItemListExT) + +private: + length_t length; +}; + +using TItemListEx = TItemListExT; +using TItemListExV = TItemListExT; + +template struct TItemPtrT +{ +public: + T* Reset(T* pItem = nullptr) + { + if(m_pItem != nullptr) + itPool.PutFreeItem(m_pItem); + + m_pItem = pItem; + + return m_pItem; + } + + T* Attach(T* pItem) + { + return Reset(pItem); + } + + T* Detach() + { + T* pItem = m_pItem; + m_pItem = nullptr; + + return pItem; + } + + T* New() + { + return Attach(itPool.PickFreeItem()); + } + + bool IsValid () {return m_pItem != nullptr;} + T* operator -> () {return m_pItem;} + T* operator = (T* pItem) {return Reset(pItem);} + operator T* () {return m_pItem;} + T*& PtrRef () {return m_pItem;} + T* Ptr () {return m_pItem;} + const T* Ptr () const {return m_pItem;} + operator const T* () const {return m_pItem;} + +public: + TItemPtrT(CNodePoolT& pool, T* pItem = nullptr) + : itPool(pool), m_pItem(pItem) + { + + } + + TItemPtrT(TItemListT& ls, T* pItem = nullptr) + : itPool(ls.GetItemPool()), m_pItem(pItem) + { + + } + + ~TItemPtrT() + { + Reset(); + } + + DECLARE_NO_COPY_CLASS(TItemPtrT) + +private: + CNodePoolT& itPool; + T* m_pItem; +}; + +using TItemPtr = TItemPtrT; + +class CBufferPool; + +struct TBuffer +{ + template friend struct TSimpleList; + friend class CBufferPool; + +public: + static TBuffer* Construct(CBufferPool& pool, ULONG_PTR dwID); + static void Destruct(TBuffer* pBuffer); + +public: + int Cat (const BYTE* pData, int len); + int Cat (const TItem* pItem); + int Cat (const TItemList& other); + int Fetch (BYTE* pData, int length); + int Peek (BYTE* pData, int length); + int Reduce (int len); + +public: + CCriSec& CriSec () {return cs;} + TItemList& ItemList() {return items;} + + ULONG_PTR ID () const {return id;} + int Length () const {return length;} + bool IsValid () const {return id != 0;} + + DWORD GetFreeTime () const {return freeTime;} + int GetCount () const {return 0;} + +private: + int IncreaseLength (int len) {return (length += len);} + int DecreaseLength (int len) {return (length -= len);} + + void Reset (); + +private: + friend TBuffer* ConstructObject<>(TBuffer*, CPrivateHeap&, CItemPool&, ULONG_PTR&); + friend void DestructObject<>(TBuffer*); + + TBuffer(CPrivateHeap& hp, CItemPool& itPool, ULONG_PTR dwID = 0) + : heap(hp), items(itPool), id(dwID), length(0) + { + } + + ~TBuffer() {} + + DECLARE_NO_COPY_CLASS(TBuffer) + +private: + CPrivateHeap& heap; + +private: + ULONG_PTR id; + int length; + DWORD freeTime; + +private: + TBuffer* next; + TBuffer* last; + + CCriSec cs; + TItemList items; +}; + +class CBufferPool +{ + using TBufferList = CRingPool; + using TBufferQueue = CCASQueue; + using TBufferCache = CRingCache; + +public: + void PutFreeBuffer (ULONG_PTR dwID); + TBuffer* PutCacheBuffer (ULONG_PTR dwID); + TBuffer* FindCacheBuffer (ULONG_PTR dwID); + TBuffer* PickFreeBuffer (ULONG_PTR dwID); + void PutFreeBuffer (TBuffer* pBuffer); + + void Prepare (); + void Clear (); + + void ReleaseGCBuffer (BOOL bForce = FALSE); + +public: + void SetItemCapacity (DWORD dwItemCapacity) {m_itPool.SetItemCapacity(dwItemCapacity);} + void SetItemPoolSize (DWORD dwItemPoolSize) {m_itPool.SetPoolSize(dwItemPoolSize);} + void SetItemPoolHold (DWORD dwItemPoolHold) {m_itPool.SetPoolHold(dwItemPoolHold);} + + void SetMaxCacheSize (DWORD dwMaxCacheSize) {m_dwMaxCacheSize = dwMaxCacheSize;} + void SetBufferLockTime (DWORD dwBufferLockTime) {m_dwBufferLockTime = dwBufferLockTime;} + void SetBufferPoolSize (DWORD dwBufferPoolSize) {m_dwBufferPoolSize = dwBufferPoolSize;} + void SetBufferPoolHold (DWORD dwBufferPoolHold) {m_dwBufferPoolHold = dwBufferPoolHold;} + + DWORD GetItemCapacity () {return m_itPool.GetItemCapacity();} + DWORD GetItemPoolSize () {return m_itPool.GetPoolSize();} + DWORD GetItemPoolHold () {return m_itPool.GetPoolHold();} + + DWORD GetMaxCacheSize () {return m_dwMaxCacheSize;} + DWORD GetBufferLockTime () {return m_dwBufferLockTime;} + DWORD GetBufferPoolSize () {return m_dwBufferPoolSize;} + DWORD GetBufferPoolHold () {return m_dwBufferPoolHold;} + + TBuffer* operator [] (ULONG_PTR dwID) {return FindCacheBuffer(dwID);} + +public: + CBufferPool(DWORD dwPoolSize = DEFAULT_BUFFER_POOL_SIZE, + DWORD dwPoolHold = DEFAULT_BUFFER_POOL_HOLD, + DWORD dwLockTime = DEFAULT_BUFFER_LOCK_TIME, + DWORD dwMaxCacheSize = DEFAULT_MAX_CACHE_SIZE) + : m_dwBufferPoolSize(dwPoolSize) + , m_dwBufferPoolHold(dwPoolHold) + , m_dwBufferLockTime(dwLockTime) + , m_dwMaxCacheSize(dwMaxCacheSize) + { + + } + + ~CBufferPool() {Clear();} + + DECLARE_NO_COPY_CLASS(CBufferPool) + +public: + CPrivateHeap& GetPrivateHeap() {return m_heap;} + CItemPool& GetItemPool() {return m_itPool;} + +public: + static const DWORD DEFAULT_MAX_CACHE_SIZE; + static const DWORD DEFAULT_ITEM_CAPACITY; + static const DWORD DEFAULT_ITEM_POOL_SIZE; + static const DWORD DEFAULT_ITEM_POOL_HOLD; + static const DWORD DEFAULT_BUFFER_LOCK_TIME; + static const DWORD DEFAULT_BUFFER_POOL_SIZE; + static const DWORD DEFAULT_BUFFER_POOL_HOLD; + +private: + DWORD m_dwMaxCacheSize; + DWORD m_dwBufferLockTime; + DWORD m_dwBufferPoolSize; + DWORD m_dwBufferPoolHold; + + CPrivateHeap m_heap; + CItemPool m_itPool; + + TBufferCache m_bfCache; + + TBufferList m_lsFreeBuffer; + TBufferQueue m_lsGCBuffer; +}; diff --git a/common/BufferPtr.h b/common/BufferPtr.h new file mode 100644 index 0000000..8e0eb0c --- /dev/null +++ b/common/BufferPtr.h @@ -0,0 +1,198 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#pragma once + +#include "hpsocket/GlobalDef.h" + +#include +#include + +template +class CBufferPtrT +{ +public: + explicit CBufferPtrT(size_t size = 0, bool zero = false) {Reset(); Malloc(size, zero);} + explicit CBufferPtrT(const T* pch, size_t size) {Reset(); Copy(pch, size);} + CBufferPtrT(const CBufferPtrT& other) {Reset(); Copy(other);} + template CBufferPtrT(const CBufferPtrT& other) {Reset(); Copy(other);} + + ~CBufferPtrT() {Free();} + + T* Malloc(size_t size = 1, bool zero = false) + { + Free(); + return Alloc(size, zero, false); + } + + T* Realloc(size_t size, bool zero = false) + { + return Alloc(size, zero, true); + } + + void Free() + { + if(m_pch) + { + free(m_pch); + Reset(); + } + } + + template CBufferPtrT& Copy(const CBufferPtrT& other) + { + if((void*)&other != (void*)this) + Copy(other.Ptr(), other.Size()); + + return *this; + } + + CBufferPtrT& Copy(const T* pch, size_t size) + { + Malloc(size); + + if(m_pch) + memcpy(m_pch, pch, size * sizeof(T)); + + return *this; + } + + template CBufferPtrT& Cat(const CBufferPtrT& other) + { + if((void*)&other != (void*)this) + Cat(other.Ptr(), other.Size()); + + return *this; + } + + CBufferPtrT& Cat(const T* pch, size_t size = 1) + { + size_t pre_size = m_size; + Realloc(m_size + size); + + if(m_pch) + memcpy(m_pch + pre_size, pch, size * sizeof(T)); + + return *this; + } + + template bool Equal(const CBufferPtrT& other) const + { + if((void*)&other == (void*)this) + return true; + else if(m_size != other.Size()) + return false; + else if(m_size == 0) + return true; + else + return (memcmp(m_pch, other.Ptr(), m_size * sizeof(T)) == 0); + } + + bool Equal(T* pch) const + { + if(m_pch == pch) + return true; + else if(!m_pch || !pch) + return false; + else + return (memcmp(m_pch, pch, m_size * sizeof(T)) == 0); + } + + size_t SetSize(size_t size) + { + if(size < 0 || size > m_capacity) + size = m_capacity; + + return (m_size = size); + } + + T* Ptr() {return m_pch;} + const T* Ptr() const {return m_pch;} + T& Get(int i) {return *(m_pch + i);} + const T& Get(int i) const {return *(m_pch + i);} + size_t Size() const {return m_size;} + size_t Capacity() const {return m_capacity;} + bool IsValid() const {return m_pch != 0;} + + operator T* () {return Ptr();} + operator const T* () const {return Ptr();} + T& operator [] (int i) {return Get(i);} + const T& operator [] (int i) const {return Get(i);} + bool operator == (T* pv) const {return Equal(pv);} + template bool operator == (const CBufferPtrT& other) {return Equal(other);} + CBufferPtrT& operator = (const CBufferPtrT& other) {return Copy(other);} + template CBufferPtrT& operator = (const CBufferPtrT& other) {return Copy(other);} + +private: + void Reset() {m_pch = 0; m_size = 0; m_capacity = 0;} + size_t GetAllocSize(size_t size) {return MAX(size, MIN(size * 2, m_size + MAX_CACHE_SIZE));} + + T* Alloc(size_t size, bool zero = false, bool is_realloc = false) + { + if(size != m_size) + { + size_t rsize = GetAllocSize(size); + if(size > m_capacity || rsize < m_size) + { + T* pch = is_realloc ? + (T*)realloc(m_pch, rsize * sizeof(T)) : + (T*)malloc(rsize * sizeof(T)) ; + + if(pch || rsize == 0) + { + m_pch = pch; + m_size = size; + m_capacity = rsize; + } + else + { + Free(); + throw std::bad_alloc(); + } + } + else + m_size = size; + } + + if(zero && m_pch) + memset(m_pch, 0, m_size * sizeof(T)); + + return m_pch; + } + +private: + T* m_pch; + size_t m_size; + size_t m_capacity; +}; + +typedef CBufferPtrT CCharBufferPtr; +typedef CBufferPtrT CWCharBufferPtr; +typedef CBufferPtrT CByteBufferPtr; +typedef CByteBufferPtr CBufferPtr; + +#ifdef _UNICODE + typedef CWCharBufferPtr CTCharBufferPtr; +#else + typedef CCharBufferPtr CTCharBufferPtr; +#endif diff --git a/common/CriSec.h b/common/CriSec.h new file mode 100644 index 0000000..dbcecf9 --- /dev/null +++ b/common/CriSec.h @@ -0,0 +1,291 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#pragma once + +#include "hpsocket/GlobalDef.h" +#include "Singleton.h" +#include "FuncHelper.h" + +#include +#include + +using namespace std; + +class CSpinGuard +{ +public: + CSpinGuard() : m_atFlag(FALSE) + { + + } + + ~CSpinGuard() + { + ASSERT(!m_atFlag); + } + + void Lock(BOOL bWeek = TRUE, memory_order m = memory_order_acquire) + { + for(UINT i = 0; !TryLock(bWeek, m); ++i) + YieldThread(i); + } + + BOOL TryLock(BOOL bWeek = FALSE, memory_order m = memory_order_acquire) + { + BOOL bExpect = FALSE; + + return bWeek + ? m_atFlag.compare_exchange_weak(bExpect, TRUE, m) + : m_atFlag.compare_exchange_strong(bExpect, TRUE, m); + } + + void Unlock(memory_order m = memory_order_release) + { + ASSERT(m_atFlag); + m_atFlag.store(FALSE, m); + } + + DECLARE_NO_COPY_CLASS(CSpinGuard) + +private: + atomic m_atFlag; +}; + +class CReentrantSpinGuard +{ +public: + CReentrantSpinGuard() + : m_atThreadID (0) + , m_iCount (0) + { + + } + + ~CReentrantSpinGuard() + { + ASSERT(m_atThreadID == 0); + ASSERT(m_iCount == 0); + } + + void Lock(BOOL bWeek = TRUE, memory_order m = memory_order_acquire) + { + for(UINT i = 0; !_TryLock(i == 0, bWeek, m); ++i) + YieldThread(i); + } + + BOOL TryLock(BOOL bWeek = FALSE, memory_order m = memory_order_acquire) + { + return _TryLock(TRUE, bWeek, m); + } + + void Unlock(memory_order m = memory_order_release) + { + ASSERT(::IsSelfThread(m_atThreadID)); + + if((--m_iCount) == 0) + m_atThreadID.store(0, m); + } + +private: + BOOL _TryLock(BOOL bFirst, BOOL bWeek = FALSE, memory_order m = memory_order_acquire) + { + THR_ID dwCurrentThreadID = SELF_THREAD_ID; + + if(bFirst && ::IsSameThread(m_atThreadID, dwCurrentThreadID)) + { + ++m_iCount; + return TRUE; + } + + THR_ID ulExpect = 0; + + BOOL isOK = bWeek + ? m_atThreadID.compare_exchange_weak(ulExpect, dwCurrentThreadID, m) + : m_atThreadID.compare_exchange_strong(ulExpect, dwCurrentThreadID, m); + + + if(isOK) + { + ASSERT(m_iCount == 0); + + m_iCount = 1; + + return TRUE; + } + + return FALSE; + } + + DECLARE_NO_COPY_CLASS(CReentrantSpinGuard) + +private: + atomic_tid m_atThreadID; + int m_iCount; +}; + +class CFakeGuard +{ +public: + void Lock() {} + void Unlock() {} + BOOL TryLock() {return TRUE;} +}; + +template class CLocalLock +{ +public: + CLocalLock(CLockObj& obj) : m_lock(obj) {m_lock.Lock();} + ~CLocalLock() {m_lock.Unlock();} +private: + CLockObj& m_lock; +}; + +template class CLocalTryLock +{ +public: + CLocalTryLock(CLockObj& obj) : m_lock(obj) {m_bValid = m_lock.TryLock();} + ~CLocalTryLock() {if(m_bValid) m_lock.Unlock();} + + BOOL IsValid() {return m_bValid;} + +private: + CLockObj& m_lock; + BOOL m_bValid; +}; + +template class CMTXTryLock +{ +public: + CMTXTryLock(CMTXObj& obj) : m_lock(obj) {m_bValid = m_lock.try_lock();} + ~CMTXTryLock() {if(m_bValid) m_lock.unlock();} + + BOOL IsValid() {return m_bValid;} + +private: + CMTXObj& m_lock; + BOOL m_bValid; +}; + +using CSpinLock = CLocalLock; +using CReentrantSpinLock = CLocalLock; +using CFakeLock = CLocalLock; + +using CCriSec = mutex; +using CCriSecLock = lock_guard; +using CCriSecLock2 = unique_lock; +using CCriSecTryLock = CMTXTryLock; + +using CMTX = CCriSec; +using CMutexLock = CCriSecLock; +using CMutexLock2 = CCriSecLock2; +using CMutexTryLock = CCriSecTryLock; + +using CReentrantCriSec = recursive_mutex; +using CReentrantCriSecLock = lock_guard; +using CReentrantCriSecLock2 = unique_lock; +using CReentrantCriSecTryLock = CMTXTryLock; + +using CReentrantMTX = CReentrantCriSec; +using CReentrantMutexLock = CReentrantCriSecLock; +using CReentrantMutexLock2 = CReentrantCriSecLock2; +using CReentrantMutexTryLock = CReentrantCriSecTryLock; + +template::value>> class CSafeCounterT +{ +public: + T Increment() {return ::InterlockedIncrement(&m_iCount);} + T Decrement() {return ::InterlockedDecrement(&m_iCount);} + T AddFetch(T iCount) {return ::InterlockedAdd(&m_iCount, iCount);} + T SubFetch(T iCount) {return ::InterlockedSub(&m_iCount, iCount);} + T FetchAdd(T iCount) {return ::InterlockedExchangeAdd(&m_iCount, iCount);} + T FetchSub(T iCount) {return ::InterlockedExchangeSub(&m_iCount, iCount);} + + T SetCount(T iCount) {return (m_iCount = iCount);} + T ResetCount() {return SetCount(0);} + T GetCount() {return m_iCount;} + + T operator ++ () {return Increment();} + T operator -- () {return Decrement();} + T operator ++ (int) {return FetchAdd(1);} + T operator -- (int) {return FetchSub(1);} + T operator += (T iCount) {return AddFetch(iCount);} + T operator -= (T iCount) {return SubFetch(iCount);} + T operator = (T iCount) {return SetCount(iCount);} + operator T () {return GetCount();} + +public: + CSafeCounterT(T iCount = 0) : m_iCount(iCount) {} + +protected: + volatile T m_iCount; +}; + +template::value>> class CUnsafeCounterT +{ +public: + T Increment() {return ++m_iCount;} + T Decrement() {return --m_iCount;} + T AddFetch(T iCount) {return m_iCount += iCount;} + T SubFetch(T iCount) {return m_iCount -= iCount;} + T FetchAdd(T iCount) {T rs = m_iCount; m_iCount += iCount; return rs;} + T FetchSub(T iCount) {T rs = m_iCount; m_iCount -= iCount; return rs;} + + T SetCount(T iCount) {return (m_iCount = iCount);} + T ResetCount() {return SetCount(0);} + T GetCount() {return m_iCount;} + + T operator ++ () {return Increment();} + T operator -- () {return Decrement();} + T operator ++ (int) {return FetchAdd(1);} + T operator -- (int) {return FetchSub(1);} + T operator += (T iCount) {return AddFetch(iCount);} + T operator -= (T iCount) {return SubFetch(iCount);} + T operator = (T iCount) {return SetCount(iCount);} + operator T () {return GetCount();} + +public: + CUnsafeCounterT(T iCount = 0) : m_iCount(iCount) {} + +protected: + T m_iCount; +}; + +template class CLocalCounter +{ +public: + CLocalCounter(CCounter& obj) : m_counter(obj) {m_counter.Increment();} + ~CLocalCounter() {m_counter.Decrement();} +private: + CCounter& m_counter; +}; + +using CSafeCounter = CSafeCounterT; +using CSafeBigCounter = CSafeCounterT; +using CUnsafeCounter = CUnsafeCounterT; +using CUnsafeBigCounter = CUnsafeCounterT; + +using CLocalSafeCounter = CLocalCounter; +using CLocalSafeBigCounter = CLocalCounter; +using CLocalUnsafeCounter = CLocalCounter; +using CLocalUnsafeBigCounter = CLocalCounter; diff --git a/common/Event.cpp b/common/Event.cpp new file mode 100644 index 0000000..9cc69fe --- /dev/null +++ b/common/Event.cpp @@ -0,0 +1,24 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "Event.h" diff --git a/common/Event.h b/common/Event.h new file mode 100644 index 0000000..5b54fbb --- /dev/null +++ b/common/Event.h @@ -0,0 +1,530 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#pragma once + +#include "hpsocket/GlobalDef.h" +#include "Singleton.h" +#include "FuncHelper.h" +#include "PollHelper.h" + +#include +#include +#include + +#include +#include +#include + +class CPipeEvent +{ +public: + + enum + { + EVT_1 = 0x01, + EVT_WAKEUP = EVT_1, + EVT_EXIT = 0x7F, + EVT_SIG_0 = 0x80, + EVT_SIG_MAX = EVT_SIG_0 + _NSIG, + }; + +public: + + int Wait(long lTimeout = INFINITE, const sigset_t* pSigSet = nullptr) + { + pollfd pfd = {m_fd[0], POLLIN}; + + while(TRUE) + { + int rs = (int)::PollForSingleObject(pfd, lTimeout, pSigSet); + + if(rs <= TIMEOUT) return rs; + + if(pfd.revents & POLLIN) + { + BYTE v; + + if(!Get(v)) + return HAS_ERROR; + + if(v == 0) + continue; + + return (int)v; + } + + if(pfd.revents & _POLL_ALL_ERROR_EVENTS) + { + ::SetLastError(ERROR_BROKEN_PIPE); + return HAS_ERROR; + } + + ASSERT(FALSE); + } + } + + BOOL Set(BYTE bVal = EVT_WAKEUP) + { + ASSERT_CHECK_EINVAL(bVal != 0); + + return VERIFY(write(m_fd[1], &bVal, 1) > 0); + } + + BOOL Get(BYTE& v) + { + ASSERT(IsValid()); + + int rs = (int)read(m_fd[0], &v, 1); + + if(IS_HAS_ERROR(rs)) + { + if(IS_WOULDBLOCK_ERROR()) + v = 0; + else + return FALSE; + } + else if(rs == 0) + { + ::SetLastError(ERROR_BROKEN_PIPE); + return FALSE; + } + + return TRUE; + } + + BOOL Reset() + { + BYTE v; + + while(TRUE) + { + if(!Get(v)) + return FALSE; + + if(v == 0) + break; + } + + return TRUE; + } + + BOOL SetSignal(BYTE bSigVal) + { + ASSERT_CHECK_EINVAL(bSigVal > 0 && bSigVal < _NSIG); + + return Set((BYTE)(EVT_SIG_0 + bSigVal)); + } + + static inline BYTE ToSignalValue(int iWaitResult) + { + if(iWaitResult <= EVT_SIG_0 || iWaitResult >= EVT_SIG_MAX) + return 0; + + return (BYTE)(iWaitResult - EVT_SIG_0); + } + + BOOL IsValid() {return IS_VALID_FD(m_fd[0]) && IS_VALID_FD(m_fd[1]);} + + operator FD () {return m_fd[0];} + FD GetFD () {return m_fd[0];} + +public: + CPipeEvent() + { + VERIFY_IS_NO_ERROR(pipe2(m_fd, O_NONBLOCK | O_CLOEXEC)); + VERIFY(::fcntl_SETFL(m_fd[0], O_NOATIME)); + VERIFY(::fcntl_SETFL(m_fd[1], O_NOATIME)); + } + + ~CPipeEvent() + { + close(m_fd[1]); + close(m_fd[0]); + } + + DECLARE_NO_COPY_CLASS(CPipeEvent) + +private: + FD m_fd[2] = {INVALID_FD, INVALID_FD}; +}; + +template class CCounterEvent +{ +public: + + eventfd_t Wait(long lTimeout = INFINITE, const sigset_t* pSigSet = nullptr) + { + pollfd pfd = {m_evt, POLLIN}; + + while(TRUE) + { + long rs = ::PollForSingleObject(pfd, lTimeout, pSigSet); + + if(rs <= TIMEOUT) return (eventfd_t)rs; + + if(pfd.revents & POLLIN) + { + eventfd_t v; + + if(!Get(v)) + return HAS_ERROR; + + if(v == 0) + continue; + + return v; + } + + if(pfd.revents & _POLL_ALL_ERROR_EVENTS) + { + ::SetLastError(ERROR_HANDLES_CLOSED); + return HAS_ERROR; + } + + ASSERT(FALSE); + } + } + + BOOL Set(eventfd_t val = 1) + { + ASSERT_CHECK_EINVAL(val > 0); + + int rs = eventfd_write(m_evt, val); + return VERIFY_IS_NO_ERROR(rs); + } + + BOOL Get(eventfd_t& v) + { + ASSERT(IsValid()); + + if(IS_HAS_ERROR(eventfd_read(m_evt, &v))) + { + if(IS_WOULDBLOCK_ERROR()) + v = 0; + else + return FALSE; + } + + return TRUE; + } + + BOOL Reset() + { + eventfd_t v; + + while(TRUE) + { + if(!Get(v)) + return FALSE; + + if(v == 0) + break; + } + + return TRUE; + } + + BOOL IsValid() {return IS_VALID_FD(m_evt);} + + operator FD () {return m_evt;} + FD GetFD () {return m_evt;} + +public: + CCounterEvent(int iInitCount = 0) + { + int iFlag = EFD_NONBLOCK | EFD_CLOEXEC | (is_sem_mode ? EFD_SEMAPHORE : 0); + m_evt = eventfd(iInitCount, iFlag); + + VERIFY(IsValid()); + } + + ~CCounterEvent() + { + if(IsValid()) close(m_evt); + } + + DECLARE_NO_COPY_CLASS(CCounterEvent) + +private: + FD m_evt = INVALID_FD; +}; + +using CSimpleEvent = CCounterEvent; +using CSemaphoreEvent = CCounterEvent; +using CEvt = CSimpleEvent; + +class CTimerEvent +{ +public: + + ULLONG Wait(long lTimeout = INFINITE, const sigset_t* pSigSet = nullptr) + { + pollfd pfd = {m_tmr, POLLIN}; + + while(TRUE) + { + SSIZE_T rs = ::PollForSingleObject(pfd, lTimeout, pSigSet); + + if(rs <= TIMEOUT) return (ULLONG)rs; + + if(pfd.revents & POLLIN) + { + BOOL ok; + ULLONG v; + + if(!Get(v, ok)) + return HAS_ERROR; + + if(!ok) + continue; + + return v; + } + + if(pfd.revents & _POLL_ALL_ERROR_EVENTS) + { + ::SetLastError(ERROR_HANDLES_CLOSED); + return HAS_ERROR; + } + + ASSERT(FALSE); + } + } + + BOOL Set(LLONG llInterval, LLONG llStart = -1) + { + ASSERT_CHECK_EINVAL(llInterval >= 0L); + + if(llStart < 0) + llStart = llInterval; + + itimerspec its; + + ::MillisecondToTimespec(llStart, its.it_value); + ::MillisecondToTimespec(llInterval, its.it_interval); + + int rs = timerfd_settime(m_tmr, 0, &its, nullptr); + return VERIFY_IS_NO_ERROR(rs); + } + + BOOL Get(ULLONG &v, BOOL& ok) + { + ASSERT(IsValid()); + + return ::ReadTimer(m_tmr, &v, &ok); + } + + BOOL Reset() + { + BOOL ok; + ULLONG v; + + while(TRUE) + { + if(!Get(v, ok)) + return FALSE; + + if(!ok) + break; + } + + return TRUE; + } + + BOOL GetTime(LLONG& lStart, LLONG& lInterval) + { + itimerspec its; + + if(IS_HAS_ERROR(timerfd_gettime(m_tmr, &its))) + return FALSE; + + lStart = ::TimespecToMillisecond(its.it_value); + lInterval = ::TimespecToMillisecond(its.it_interval); + + return TRUE; + } + + BOOL IsValid() {return IS_VALID_FD(m_tmr);} + + operator FD () {return m_tmr;} + FD GetFD () {return m_tmr;} + +public: + CTimerEvent(bool bRealTimeClock = FALSE) + { + int iCID = (bRealTimeClock ? CLOCK_REALTIME : CLOCK_MONOTONIC); + m_tmr = timerfd_create(iCID, TFD_NONBLOCK | TFD_CLOEXEC); + + VERIFY(IsValid()); + } + + ~CTimerEvent() + { + if(IsValid()) close(m_tmr); + } + + DECLARE_NO_COPY_CLASS(CTimerEvent) + +private: + FD m_tmr = INVALID_FD; +}; + +class CSignalEvent +{ +public: + + int Wait(signalfd_siginfo& sgInfo, long lTimeout = INFINITE, const sigset_t* pSigSet = nullptr) + { + m_dwTID = SELF_THREAD_ID; + pollfd pfd = {m_sig, POLLIN}; + + while(TRUE) + { + long rs = ::PollForSingleObject(pfd, lTimeout, pSigSet); + + if(rs <= TIMEOUT) return (int)rs; + + if(pfd.revents & POLLIN) + { + BOOL ok; + + if(!Get(sgInfo, ok)) + return HAS_ERROR; + + if(!ok) + continue; + + return sgInfo.ssi_signo; + } + + if(pfd.revents & _POLL_ALL_ERROR_EVENTS) + { + ::SetLastError(ERROR_HANDLES_CLOSED); + return HAS_ERROR; + } + + ASSERT(FALSE); + } + + m_dwTID = 0; + } + + BOOL Set(int iSig, const sigval sgVal, THR_ID dwTID = 0) + { + if(dwTID == 0) + { + dwTID = m_dwTID; + + if(dwTID == 0) + { + ::SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + } + +#if !defined(__ANDROID__) + int rs = pthread_sigqueue(dwTID, iSig, sgVal); +#else + int rs = pthread_kill(dwTID, iSig); +#endif + + return IS_NO_ERROR(rs); + } + + BOOL Get(signalfd_siginfo& v, BOOL& ok) + { + ASSERT(IsValid()); + + static const SSIZE_T SIZE = sizeof(signalfd_siginfo); + + if(read(m_sig, &v, SIZE) == SIZE) + ok = TRUE; + { + if(IS_WOULDBLOCK_ERROR()) + ok = FALSE; + else + return FALSE; + } + + return ok; + } + + BOOL Reset() + { + BOOL ok; + signalfd_siginfo v; + + while(TRUE) + { + if(!Get(v, ok)) + return FALSE; + + if(!ok) + break; + } + + return TRUE; + } + + BOOL Mask(const sigset_t* pSigMask) + { + if(!pSigMask) + { + if(!IsValid()) return TRUE; + return IS_NO_ERROR(close(m_sig)); + } + + FD sig = signalfd(m_sig, pSigMask, SFD_NONBLOCK | SFD_CLOEXEC); + + if(IS_VALID_FD(sig)) + { + m_sig = sig; + return TRUE; + } + + return FALSE; + } + + BOOL IsValid() {return IS_VALID_FD(m_sig);} + + operator FD () {return m_sig;} + FD GetFD () {return m_sig;} + +public: + CSignalEvent(const sigset_t* pSigMask = nullptr) + { + if(pSigMask) VERIFY(Mask(pSigMask)); + } + + ~CSignalEvent() + { + if(IsValid()) close(m_sig); + } + + DECLARE_NO_COPY_CLASS(CSignalEvent) + +private: + FD m_sig = INVALID_FD; + THR_ID m_dwTID = 0; +}; diff --git a/common/FileHelper.cpp b/common/FileHelper.cpp new file mode 100644 index 0000000..a8154ca --- /dev/null +++ b/common/FileHelper.cpp @@ -0,0 +1,237 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "FileHelper.h" + +#include + +CString GetCurrentDirectory() +{ + char szPath[MAX_PATH]; + + if(getcwd(szPath, sizeof(szPath) - 1) == nullptr) + szPath[0] = 0; + + return szPath; +} + +CString GetModuleFileName(pid_t pid) +{ + if(pid == 0) + pid = SELF_PROCESS_ID; + + char szLink[MAX_PATH]; + char szPath[MAX_PATH]; + + sprintf(szLink, "/proc/%d/exe", pid); + + SSIZE_T rs = readlink(szLink, szPath, sizeof(szPath) - 1); + + if(rs < 0) rs = 0; + szPath[rs] = 0; + + return szPath; +} + +BOOL SetCurrentPathToModulePath(pid_t pid) +{ + CString strPath = GetModuleFileName(pid); + + if(strPath.IsEmpty()) + return FALSE; + + CString::size_type pos = strPath.rfind('/'); + + if(pos == CString::npos) + return FALSE; + + return IS_NO_ERROR(chdir(strPath.substr(0, pos + 1))); +} + +BOOL CFile::Open(LPCTSTR lpszFilePath, int iFlag, mode_t iMode) +{ + CHECK_ERROR(!IsValid(), ERROR_INVALID_STATE); + + m_fd = open(lpszFilePath, iFlag, iMode); + + return IS_VALID_FD(m_fd); +} + +BOOL CFile::Close() +{ + CHECK_ERROR(IsValid(), ERROR_INVALID_STATE); + + if(IS_NO_ERROR(close(m_fd))) + { + m_fd = INVALID_FD; + return TRUE; + } + + return FALSE; +} + +BOOL CFile::Stat(struct stat& st) +{ + CHECK_ERROR_INVOKE(fstat(m_fd, &st)); + return TRUE; +} + +BOOL CFile::GetSize(SIZE_T& dwSize) +{ + struct stat st; + CHECK_IS_OK(Stat(st)); + + dwSize = st.st_size; + + return TRUE; +} + +BOOL CFile::IsDirectory() +{ + struct stat st; + CHECK_IS_OK(Stat(st)); + + return S_ISDIR(st.st_mode); +} + +BOOL CFile::IsFile() +{ + struct stat st; + CHECK_IS_OK(Stat(st)); + + return S_ISREG(st.st_mode); +} + +BOOL CFile::IsExist(LPCTSTR lpszFilePath) +{ + return IS_NO_ERROR(access(lpszFilePath, F_OK)); +} + +BOOL CFile::IsDirectory(LPCTSTR lpszFilePath) +{ + struct stat st; + CHECK_ERROR_INVOKE(stat(lpszFilePath, &st)); + + return S_ISDIR(st.st_mode); +} + +BOOL CFile::IsFile(LPCTSTR lpszFilePath) +{ + struct stat st; + CHECK_ERROR_INVOKE(stat(lpszFilePath, &st)); + + return S_ISREG(st.st_mode); +} + +BOOL CFile::IsLink(LPCTSTR lpszFilePath) +{ + struct stat st; + CHECK_ERROR_INVOKE(lstat(lpszFilePath, &st)); + + return S_ISLNK(st.st_mode); +} + +BOOL CFileMapping::Map(LPCTSTR lpszFilePath, SIZE_T dwSize, SIZE_T dwOffset, int iProtected, int iFlag) +{ + CHECK_ERROR(!IsValid(), ERROR_INVALID_STATE); + + FD fd = INVALID_FD; + + if(lpszFilePath != nullptr) + { + int iFileFlag = O_RDONLY; + + if(iProtected & PROT_WRITE) + { + if(iProtected & PROT_READ) + iFileFlag = O_RDWR; + else + iFileFlag = O_WRONLY; + } + + fd = open(lpszFilePath, iFileFlag); + CHECK_ERROR_FD(fd); + } + + BOOL isOK = Map(fd, dwSize, dwOffset, iProtected, iFlag); + + if(IS_VALID_FD(fd)) EXECUTE_RESTORE_ERROR(close(fd)); + + return isOK; +} + +BOOL CFileMapping::Map(FD fd, SIZE_T dwSize, SIZE_T dwOffset, int iProtected, int iFlag) +{ + CHECK_ERROR(!IsValid(), ERROR_INVALID_STATE); + + if(IS_INVALID_FD(fd)) + { + CHECK_EINVAL((iFlag & MAP_ANONYMOUS) && (dwSize > 0)); + } + else + { + CHECK_EINVAL((iFlag & MAP_ANONYMOUS) == 0); + + struct stat st; + CHECK_ERROR_INVOKE(fstat(fd, &st)); + + CHECK_ERROR(S_ISREG(st.st_mode), ERROR_BAD_FILE_TYPE); + + if(dwSize == 0) + dwSize = st.st_size; + } + + m_pv = (PBYTE)mmap(nullptr, dwSize, iProtected, iFlag, fd, dwOffset); + + if(IsValid()) + { + m_dwSize = dwSize; + return TRUE; + } + + return FALSE; +} + +BOOL CFileMapping::Unmap() +{ + CHECK_ERROR(IsValid(), ERROR_INVALID_STATE); + + if(IS_NO_ERROR(munmap(m_pv, m_dwSize))) + { + m_pv = INVALID_MAP_ADDR; + m_dwSize = 0; + + return TRUE; + } + + return FALSE; +} + +BOOL CFileMapping::MSync(int iFlag, SIZE_T dwSize) +{ + CHECK_ERROR(IsValid(), ERROR_INVALID_STATE); + + if(dwSize == 0) dwSize = m_dwSize; + + return IS_NO_ERROR(msync(m_pv, dwSize, iFlag)); +} diff --git a/common/FileHelper.h b/common/FileHelper.h new file mode 100644 index 0000000..4f71c8e --- /dev/null +++ b/common/FileHelper.h @@ -0,0 +1,123 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#pragma once + +#include "FuncHelper.h" +#include "StringT.h" + +#include +#include +#include + +#define INVALID_MAP_ADDR ((PBYTE)(MAP_FAILED)) + +CString GetCurrentDirectory(); +CString GetModuleFileName(pid_t pid = 0); +BOOL SetCurrentPathToModulePath(pid_t pid = 0); + +class CFile +{ +public: + BOOL Open(LPCTSTR lpszFilePath, int iFlag, mode_t iMode = 0); + BOOL Close(); + BOOL Stat(struct stat& st); + BOOL GetSize(SIZE_T& dwSize); + + SSIZE_T Read(PVOID pBuffer, SIZE_T dwCount) + {return read(m_fd, pBuffer, dwCount);} + SSIZE_T Write(PVOID pBuffer, SIZE_T dwCount) + {return write(m_fd, pBuffer, dwCount);} + SSIZE_T PRead(PVOID pBuffer, SIZE_T dwCount, SIZE_T dwOffset) + {return pread(m_fd, pBuffer, dwCount, dwOffset);} + SSIZE_T PWrite(PVOID pBuffer, SIZE_T dwCount, SIZE_T dwOffset) + {return pwrite(m_fd, pBuffer, dwCount, dwOffset);} + SSIZE_T ReadV(const iovec* pVec, int iVecCount) + {return readv(m_fd, pVec, iVecCount);} + SSIZE_T WriteV(const iovec* pVec, int iVecCount) + {return writev(m_fd, pVec, iVecCount);} + SSIZE_T Seek(SSIZE_T lOffset, int iWhence) + {return lseek(m_fd, lOffset, iWhence);} + + BOOL IsValid() {return IS_VALID_FD(m_fd);} + operator FD () {return m_fd;} + + BOOL IsExist() {return IsValid();} + + BOOL IsDirectory(); + BOOL IsFile(); + + static BOOL IsExist(LPCTSTR lpszFilePath); + static BOOL IsDirectory(LPCTSTR lpszFilePath); + static BOOL IsFile(LPCTSTR lpszFilePath); + static BOOL IsLink(LPCTSTR lpszFilePath); + +public: + CFile(LPCTSTR lpszFilePath = nullptr, int iFlag = O_RDONLY, mode_t iMode = 0) + : m_fd(INVALID_FD) + { + if(lpszFilePath != nullptr) + Open(lpszFilePath, iFlag, iMode); + } + + ~CFile() + { + if(IsValid()) + Close(); + } + +private: + FD m_fd; +}; + +class CFileMapping +{ +public: + BOOL Map(LPCTSTR lpszFilePath, SIZE_T dwSize = 0, SIZE_T dwOffset = 0, int iProtected = PROT_READ, int iFlag = MAP_PRIVATE); + BOOL Map(FD fd, SIZE_T dwSize = 0, SIZE_T dwOffset = 0, int iProtected = PROT_READ, int iFlag = MAP_PRIVATE); + BOOL Unmap(); + BOOL MSync(int iFlag = MS_SYNC, SIZE_T dwSize = 0); + + BOOL IsValid () {return m_pv != INVALID_MAP_ADDR;} + SIZE_T Size () {return m_dwSize;} + LPBYTE Ptr () {return m_pv;} + operator LPBYTE () {return Ptr();} + +public: + CFileMapping() + : m_pv(INVALID_MAP_ADDR) + , m_dwSize(0) + { + + } + + ~CFileMapping() + { + if(IsValid()) + Unmap(); + } + +private: + PBYTE m_pv; + SIZE_T m_dwSize; +}; diff --git a/common/FuncHelper.cpp b/common/FuncHelper.cpp new file mode 100644 index 0000000..85c24e3 --- /dev/null +++ b/common/FuncHelper.cpp @@ -0,0 +1,393 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "FuncHelper.h" +#include "Thread.h" + +#include +#include +#include +#include +#include + +#if !defined(__ANDROID__) + #include + #include + #ifdef __GNUC__ + #include + #endif +#endif + +INT WaitFor(DWORD dwMillSecond, DWORD dwSecond, BOOL bExceptThreadInterrupted) +{ + timeval tv {(time_t)dwSecond, (suseconds_t)(dwMillSecond * 1000)}; + + if(bExceptThreadInterrupted) + return NO_EINTR_EXCEPT_THR_INTR_INT(select(0, nullptr, nullptr, nullptr, &tv)); + + return NO_EINTR_INT(select(0, nullptr, nullptr, nullptr, &tv)); +} + +INT Sleep(DWORD dwMillSecond, DWORD dwSecond, BOOL bExceptThreadInterrupted) +{ + timespec ts_req = {(time_t)dwSecond, (long)(dwMillSecond * 1000000)}; + timespec ts_rem = ts_req; + INT rs = NO_ERROR; + + while(IS_HAS_ERROR(rs = nanosleep(&ts_req, &ts_rem))) + { + if(!IS_INTR_ERROR()) + break; + else + { + if(bExceptThreadInterrupted && ::IsThreadInterrupted()) + break; + } + + ts_req = ts_rem; + } + + return rs; +} + +__time64_t _time64(time_t* ptm) +{ + return (__time64_t)time(ptm); +} + +__time64_t _mkgmtime64(tm* ptm) +{ + return (__time64_t)timegm(ptm); +} + +tm* _gmtime64(tm* ptm, __time64_t* pt) +{ + time_t t = (time_t)(*pt); + + return gmtime_r(&t, ptm); +} + +DWORD TimeGetTime() +{ + return (DWORD)TimeGetTime64(); +} + +ULLONG TimeGetTime64() +{ +#if !defined(__ANDROID__) + + timeb tb; + + if(ftime(&tb) == NO_ERROR) + return (((ULLONG)(tb.time)) * 1000 + tb.millitm); + +#else + + timespec ts; + + if(clock_gettime(CLOCK_MONOTONIC, &ts) == NO_ERROR) + return (((ULLONG)(ts.tv_sec)) * 1000 + ts.tv_nsec / 1000000); + +#endif + + return 0ull; +} + +DWORD GetTimeGap32(DWORD dwOriginal, DWORD dwCurrent) +{ + if(dwCurrent == 0) + dwCurrent = ::TimeGetTime(); + + return dwCurrent - dwOriginal; +} + +ULLONG GetTimeGap64(ULLONG ullOriginal, ULONGLONG ullCurrent) +{ + if(ullCurrent == 0) + ullCurrent = ::TimeGetTime64(); + + return ullCurrent - ullOriginal; +} + +LLONG TimevalToMillisecond(const timeval& tv) +{ + return tv.tv_sec * 1000 + tv.tv_usec / 1000; +} + +timeval& MillisecondToTimeval(LLONG ms, timeval& tv) +{ + tv.tv_sec = (time_t)(ms / 1000); + tv.tv_usec = (suseconds_t)((ms % 1000) * 1000); + + return tv; +} + +LLONG TimespecToMillisecond(const timespec& ts) +{ + return ts.tv_sec * 1000 + ts.tv_nsec / 1000000; +} + +timespec& MillisecondToTimespec(LLONG ms, timespec& ts) +{ + ts.tv_sec = (time_t)(ms / 1000); + ts.tv_nsec = (long)((ms % 1000) * 1000000); + + return ts; +} + +timeval& GetFutureTimeval(LLONG ms, timeval& tv, struct timezone* ptz) +{ + gettimeofday(&tv, ptz); + + tv.tv_sec += (time_t)(ms / 1000); + tv.tv_usec += (suseconds_t)((ms % 1000) * 1000); + + return tv; +} + +timespec& GetFutureTimespec(LLONG ms, timespec& ts, clockid_t clkid) +{ + clock_gettime(clkid, &ts); + + ts.tv_sec += (time_t)(ms / 1000); + ts.tv_nsec += (long)((ms % 1000) * 1000000); + + return ts; +} + +FD CreateTimer(LLONG llInterval, LLONG llStart, BOOL bRealTimeClock) +{ + ASSERT_CHECK_EINVAL(llInterval >= 0L); + + if(llStart < 0) + llStart = llInterval; + + FD fdTimer = timerfd_create((bRealTimeClock ? CLOCK_REALTIME : CLOCK_MONOTONIC), TFD_NONBLOCK | TFD_CLOEXEC); + + itimerspec its; + + ::MillisecondToTimespec(llStart, its.it_value); + ::MillisecondToTimespec(llInterval, its.it_interval); + + if(IS_HAS_ERROR(timerfd_settime(fdTimer, 0, &its, nullptr))) + { + close(fdTimer); + fdTimer = INVALID_FD; + } + + return fdTimer; +} + +BOOL ReadTimer(FD tmr, ULLONG* pVal, BOOL* pRs) +{ + static const SSIZE_T SIZE = sizeof(ULLONG); + + if(pVal == nullptr) + pVal = CreateLocalObject(ULLONG); + if(pRs == nullptr) + pRs = CreateLocalObject(BOOL); + + if(read(tmr, pVal, SIZE) == SIZE) + *pRs = TRUE; + else + { + *pRs = FALSE; + + if(!IS_WOULDBLOCK_ERROR()) + return FALSE; + } + + return TRUE; +} + +BOOL fcntl_SETFL(FD fd, INT fl, BOOL bSet) +{ + int val = fcntl(fd, F_GETFL); + + if(IS_HAS_ERROR(val)) + return FALSE; + + val = bSet ? (val | fl) : (val & (~fl)); + + return IS_NO_ERROR(fcntl(fd, F_SETFL , val)); +} + +void PrintStackTrace() +{ +#if !defined(__ANDROID__) + + const int MAX_SIZE = 51; + void* arr[MAX_SIZE]; + + int size = backtrace(arr, MAX_SIZE); + char** messages = backtrace_symbols(arr, size); + + for(int i = 1; i < size && messages != nullptr; i++) + { + char* mangled_name = nullptr; + char* offset_end = nullptr; + const char* offset_begin = nullptr; + + for(char* p = messages[i]; *p; ++p) + { + if(*p == '(') + { + mangled_name = p; + } + else if(*p == '+') + { + offset_begin = p; + } + else if(*p == ')') + { + offset_end = p; + break; + } + } + + if(mangled_name && offset_end && mangled_name < offset_end) + { + *mangled_name++ = 0; + *offset_end++ = 0; + + if(offset_begin == nullptr) + offset_begin = ""; + else + *(char*)offset_begin++ = 0; + + while(*offset_end == ' ') + ++offset_end; + +#ifdef __GNUC__ + int status; + char* real_name = abi::__cxa_demangle(mangled_name, nullptr, nullptr, &status); + + if(status == 0) + FPRINTLN(stderr, " -> [%02d] %s : (%s+%s) %s", i, messages[i], real_name, offset_begin, offset_end); + else + FPRINTLN(stderr, " -> [%02d] %s : (%s+%s) %s", i, messages[i], mangled_name, offset_begin, offset_end); + + if(real_name != nullptr) + free(real_name); +#else + FPRINTLN(stderr, " -> [%02d] %s : (%s+%s) %s", i, messages[i], mangled_name, offset_begin, offset_end); +#endif + } + else + { + FPRINTLN(stderr, " -> [%02d] %s", i, messages[i]); + } + } + + free(messages); + +#endif +} + +void __EXIT_FN_(void (*fn)(int), LPCSTR lpszFnName, int* lpiExitCode, int iErrno, LPCSTR lpszFile, int iLine, LPCSTR lpszFunc, LPCSTR lpszTitle) +{ + if(iErrno >= 0) + SetLastError(iErrno); + else + iErrno = GetLastError(); + + if(!lpszTitle) + { + lpszTitle = CreateLocalObjects(char, 64); + + if(lpiExitCode) + sprintf((LPSTR)lpszTitle, "(#%d, 0x%zX) > %s(%d) [%d]", SELF_PROCESS_ID, (SIZE_T)SELF_THREAD_ID, lpszFnName, *lpiExitCode, iErrno); + else + sprintf((LPSTR)lpszTitle, "(#%d, 0x%zX) > %s() [%d]", SELF_PROCESS_ID, (SIZE_T)SELF_THREAD_ID, lpszFnName, iErrno); + } + + if(lpszFile && iLine > 0) + FPRINTLN(stderr, "%s : %s\n => %s (%d) : %s", lpszTitle, strerror(iErrno), lpszFile, iLine, lpszFunc ? lpszFunc : ""); + else + FPRINTLN(stderr, "%s : %s", lpszTitle, strerror(iErrno)); + + if(lpiExitCode) + fn(*lpiExitCode); + else + ((void (*)())fn)(); +} + +void EXIT(int iExitCode, int iErrno, LPCSTR lpszFile, int iLine, LPCSTR lpszFunc, LPCSTR lpszTitle) +{ + __EXIT_FN_(exit, "exit", &iExitCode, iErrno, lpszFile, iLine, lpszFunc, lpszTitle); +} + +void _EXIT(int iExitCode, int iErrno, LPCSTR lpszFile, int iLine, LPCSTR lpszFunc, LPCSTR lpszTitle) +{ + __EXIT_FN_(_exit, "_exit", &iExitCode, iErrno, lpszFile, iLine, lpszFunc, lpszTitle); +} + +void ABORT(int iErrno, LPCSTR lpszFile, int iLine, LPCSTR lpszFunc, LPCSTR lpszTitle) +{ + __EXIT_FN_((void (*)(int))abort, "abort", nullptr, iErrno, lpszFile, iLine, lpszFunc, lpszTitle); +} + +BOOL SetSequenceThreadName(THR_ID tid, LPCTSTR lpszPrefix, volatile UINT& vuiSeq) +{ + UINT uiSequence = InterlockedIncrement(&vuiSeq); + return SetThreadName(tid, lpszPrefix, uiSequence); +} + +BOOL SetThreadName(THR_ID tid, LPCTSTR lpszPrefix, UINT uiSequence) +{ + int iMaxSeqLength = (int)(MAX_THREAD_NAME_LENGTH - lstrlen(lpszPrefix)); + + ASSERT(iMaxSeqLength > 0); + + if(iMaxSeqLength <= 0) + { + ::SetLastError(ERROR_OUT_OF_RANGE); + return FALSE; + } + + ULONGLONG uiDiv = 1; + + for(int i = 0; i < iMaxSeqLength; i++) + uiDiv *= 10; + + uiSequence = (UINT)(uiSequence % uiDiv); + + CString strName; + strName.Format(_T("%s%u"), lpszPrefix, uiSequence); + + return SetThreadName(tid, strName); +} + +BOOL SetThreadName(THR_ID tid, LPCTSTR lpszName) +{ + ASSERT(lstrlen(lpszName) <= MAX_THREAD_NAME_LENGTH); + + if(tid == 0) + tid = SELF_THREAD_ID; + + int rs = pthread_setname_np(tid, CT2A(lpszName)); + + CHECK_ERROR_CODE(rs) + + return TRUE; +} diff --git a/common/FuncHelper.h b/common/FuncHelper.h new file mode 100644 index 0000000..e522994 --- /dev/null +++ b/common/FuncHelper.h @@ -0,0 +1,426 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#pragma once + +#include "hpsocket/GlobalDef.h" +#include "hpsocket/GlobalErrno.h" +#include "SysHelper.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace std; + +#if !defined(__ANDROID__) + typedef atomic_ulong atomic_tid; + + #define FPRINTLN(fd, fmt, ...) fprintf((fd), fmt "\n", ##__VA_ARGS__) +#else + typedef atomic_long atomic_tid; + + #if defined(stdout) + #undef stdout + #endif + + #if defined(stderr) + #undef stderr + #endif + + #define stdout nullptr + #define stderr nullptr + + #define FPRINTLN(fd, fmt, ...) printf(fmt "\n", ##__VA_ARGS__) +#endif + +#define PRINTLN(fmt, ...) FPRINTLN(stdout, fmt, ##__VA_ARGS__) + +#if defined(DEBUG) && defined(DEBUG_TRACE) + #define TRACE(fmt, ...) PRINTLN("> TRC (0x%zX, %d) " fmt, (SIZE_T)SELF_THREAD_ID, SELF_NATIVE_THREAD_ID, ##__VA_ARGS__) + #define ASSERT(expr) ((expr) ? TRUE : (::PrintStackTrace(), assert(FALSE), FALSE)) +#else + #define TRACE(fmt, ...) + #define ASSERT(expr) assert(expr) +#endif + +#define VERIFY(expr) ((expr) ? TRUE : (::PrintStackTrace(), ERROR_ABORT2(ERROR_VERIFY_CHECK), FALSE)) +#define ASSERT_IS_NO_ERROR(expr) ASSERT(IS_NO_ERROR(expr)) +#define VERIFY_IS_NO_ERROR(expr) VERIFY(IS_NO_ERROR(expr)) +#define ENSURE(expr) VERIFY(expr) +#define ENSURE_IS_NO_ERROR(expr) VERIFY_IS_NO_ERROR(expr) + +#define TEMP_FAILURE_RETRY_INT(exp) ((int)TEMP_FAILURE_RETRY(exp)) + +#define NO_EINTR TEMP_FAILURE_RETRY +#define NO_EINTR_INT TEMP_FAILURE_RETRY_INT + +#define CHECK_IS_OK(expr) {if(IS_NOT_OK(expr)) return FALSE;} +#define CHECK_ERROR_FD(fd) {if(IS_INVALID_FD(fd)) return FALSE;} +#define CHECK_ERROR_INVOKE(expr) {if(!IS_NO_ERROR(expr)) return FALSE;} +#define CHECK_ERROR_CODE(rs) {if(!IS_NO_ERROR(rs)) {::SetLastError(rs); return FALSE;}} +#define CHECK_ERROR(expr, code) {if(!(expr)) {::SetLastError(code); return FALSE;}} +#define CHECK_EINVAL(expr) CHECK_ERROR(expr, ERROR_INVALID_PARAMETER) +#define ASSERT_CHECK_ERROR(expr, code) {ASSERT(expr); CHECK_ERROR(expr, code);} +#define ASSERT_CHECK_EINVAL(expr) {ASSERT(expr); CHECK_EINVAL(expr);} + +#define SUCCEEDED(rs) IS_NO_ERROR(rs) +#define FAILED(rs) (!SUCCEEDED(rs)) +#define IS_OK(rs) ((BOOL)(rs)) +#define IS_NOT_OK(rs) (!IS_OK(rs)) + +#define IS_ERROR(code) (::GetLastError() == (code)) +#define CONTINUE_IF_ERROR(code) {if(IS_ERROR(code)) continue;} +#define BREAK_IF_ERROR(code) {if(IS_ERROR(code)) break;} + +#define IS_WOULDBLOCK_ERROR() IS_ERROR(ERROR_WOULDBLOCK) +#define CONTINUE_WOULDBLOCK_ERROR() CONTINUE_IF_ERROR(ERROR_WOULDBLOCK) +#define BREAK_WOULDBLOCK_ERROR() BREAK_IF_ERROR(ERROR_WOULDBLOCK) +#define IS_IO_PENDING_ERROR() IS_ERROR(ERROR_IO_PENDING) +#define CONTINUE_IO_PENDING_ERROR() CONTINUE_IF_ERROR(ERROR_IO_PENDING) +#define BREAK_IO_PENDING_ERROR() BREAK_IF_ERROR(ERROR_IO_PENDING) +#define IS_INTR_ERROR() IS_ERROR(ERROR_INTR) +#define CONTINUE_INTR_ERROR() CONTINUE_IF_ERROR(ERROR_INTR) +#define BREAK_INTR_ERROR() BREAK_IF_ERROR(ERROR_INTR) + +#define EqualMemory(dest, src, len) (!memcmp((dest), (src), (len))) +#define MoveMemory(dest, src, len) memmove((dest), (src), (len)) +#define CopyMemory(dest, src, len) memcpy((dest), (src), (len)) +#define FillMemory(dest, len, ch) memset((dest), (ch), (len)) +#define ZeroMemory(dest, len) FillMemory((dest), (len), 0) +#define ZeroObject(obj) ZeroMemory((&(obj)), sizeof(obj)) + +inline void SetLastError(int code) {errno = code;} +inline int GetLastError() {return errno;} +inline LPCSTR GetErrorStr(int code) {return strerror(code);} +inline LPCSTR GetLastErrorStr() {return GetErrorStr(errno);} +inline void PrintError(LPCSTR subject) {perror(subject);} + +#define EXECUTE_RESET_ERROR(expr) (::SetLastError(0), (expr)) +#define EXECUTE_RESTORE_ERROR(expr) {int __le_ = ::GetLastError(); (expr); ::SetLastError(__le_);} +#define EXECUTE_RESTORE_ERROR_RT(T, expr)\ + ({int __le_ = ::GetLastError(); T __rs_ = (expr); ::SetLastError(__le_); __rs_;}) +#define ENSURE_ERROR(def_code) ({int __le_ = ::GetLastError(); if(__le_ == NO_ERROR) __le_ = (def_code); __le_;}) +#define ENSURE_ERROR_CANCELLED ENSURE_ERROR(ERROR_CANCELLED) +#define TRIGGER(expr) EXECUTE_RESET_ERROR((expr)) + +#define _msize(p) malloc_usable_size(p) +#define CreateLocalObjects(T, n) ((T*)alloca(sizeof(T) * (n))) +#define CreateLocalObject(T) CreateLocalObjects(T, 1) +#define CallocObjects(T, n) ((T*)calloc((n), sizeof(T))) + +#define MALLOC(T, n) ((T*)malloc(sizeof(T) * (n))) +#define REALLOC(T, p, n) ((T*)realloc((PVOID)(p), sizeof(T) * (n))) +#define FREE(p) free((PVOID)(p)) +#define CALLOC(n, s) calloc((n), (s)) + +#define InterlockedExchangeAdd(p, n) __atomic_fetch_add((p), (n), memory_order_seq_cst) +#define InterlockedExchangeSub(p, n) __atomic_fetch_sub((p), (n), memory_order_seq_cst) +#define InterlockedAdd(p, n) __atomic_add_fetch((p), (n), memory_order_seq_cst) +#define InterlockedSub(p, n) __atomic_sub_fetch((p), (n), memory_order_seq_cst) +#define InterlockedIncrement(p) InterlockedAdd((p), 1) +#define InterlockedDecrement(p) InterlockedSub((p), 1) + +#define ERROR_EXIT2(code, err) EXIT((code), (err), __FILE__, __LINE__, __PRETTY_FUNCTION__) +#define ERROR__EXIT2(code, err) _EXIT((code), (err), __FILE__, __LINE__, __PRETTY_FUNCTION__) +#define ERROR_ABORT2(err) ABORT((err), __FILE__, __LINE__, __PRETTY_FUNCTION__) + +#define ERROR_EXIT(code) ERROR_EXIT2((code), -1) +#define ERROR__EXIT(code) ERROR__EXIT2((code), -1) +#define ERROR_ABORT() ERROR_ABORT2(-1) + +#define IS_VALID_FD(fd) ((fd) != INVALID_FD) +#define IS_INVALID_FD(fd) (!IS_VALID_FD(fd)) + +#define IS_VALID_PVOID(pv) ((pv) != INVALID_PVOID) +#define IS_INVALID_PVOID(pv) (!IS_VALID_PVOID(pv)) + +#define TO_PVOID(v) ((PVOID)(UINT_PTR)(v)) +#define FROM_PVOID(T, pv) ((T)(UINT_PTR)(pv)) + +#define IS_NULL(v) ((v) == nullptr) +#define IS_NOT_NULL(v) (!IS_NULL(v)) + +#define stricmp strcasecmp +#define strnicmp strncasecmp +#define wcsicmp wcscasecmp +#define wcsnicmp wcsncasecmp + +#ifdef _UNICODE + #define tstrchr wcschr + #define tstrrchr wcsrchr + #define tstrstr wcsstr + #define tstrpbrk wcspbrk + #define tstrtok wcstok + + #define stscanf swscanf + #define tstrlen wcslen + #define tstrcpy wcscpy + #define tstrcmp wcscmp + #define tstricmp wcsicmp + #define tstrncpy wcsncpy + #define tstrncmp wcsncmp + #define tstrnicmp wcsnicmp + #define tstrspn wcsspn + #define tstrcspn wcscspn + #define wsprintf swprintf +#else + #define tstrchr strchr + #define tstrrchr strrchr + #define tstrstr strstr + #define tstrpbrk strpbrk + #define tstrtok strtok_r + + #define stscanf sscanf + #define tstrlen strlen + #define tstrcpy strcpy + #define tstrcmp strcmp + #define tstricmp stricmp + #define tstrncpy strncpy + #define tstrncmp strncmp + #define tstrnicmp strnicmp + #define tstrspn strspn + #define tstrcspn strcspn + #define wsprintf sprintf +#endif + +inline const char* StrChr(const char* s, char c) {return strchr(s, c);} +inline const char* StrRChr(const char* s, char c) {return strrchr(s, c);} +inline const char* StrStr(const char* h, const char* n) {return strstr(h, n);} +inline const char* StrPBrk(const char* s, const char* a) {return strpbrk(s, a);} +inline const wchar_t* StrChr(const wchar_t* s, wchar_t c) {return wcschr(s, c);} +inline const wchar_t* StrRChr(const wchar_t* s, wchar_t c) {return wcsrchr(s, c);} +inline const wchar_t* StrStr(const wchar_t* h, const wchar_t* n) {return wcsstr(h, n);} +inline const wchar_t* StrPBrk(const wchar_t* s, const wchar_t* a) {return wcspbrk(s, a);} +inline LPSTR StrSep2(LPSTR* lpStr, LPCSTR lpDelim = " \t\r\n") {LPSTR lpTok; while((lpTok = strsep(lpStr, lpDelim)) != nullptr && lpTok[0] == 0); return lpTok;} +inline LPSTR TrimLeft(LPSTR* lpStr, LPCSTR lpDelim = " \t\r\n") {while((*lpStr)[0] != 0 && ::StrChr(lpDelim, (*lpStr)[0]) != nullptr) ++(*lpStr); return (*lpStr);} +inline LPSTR TrimRitht(LPSTR* lpStr, LPCSTR lpDelim = " \t\r\n") +{ + LPSTR lpEnd = (*lpStr) + strlen(*lpStr) - 1; + LPSTR lpCur = lpEnd; + + while(lpCur >= (*lpStr) && ::StrChr(lpDelim, lpCur[0]) != nullptr) + --lpCur; + + if(lpCur != lpEnd) + lpCur[1] = 0; + + return (*lpStr); +} + +inline BOOL IsStrEmptyA(LPCSTR lpsz) {return (lpsz == nullptr || lpsz[0] == 0);} +inline BOOL IsStrEmptyW(LPCWSTR lpsz) {return (lpsz == nullptr || lpsz[0] == 0);} +inline BOOL IsStrNotEmptyA(LPCSTR lpsz) {return !IsStrEmptyA(lpsz);} +inline BOOL IsStrNotEmptyW(LPCWSTR lpsz){return !IsStrEmptyW(lpsz);} +inline LPCSTR SafeStrA(LPCSTR lpsz) {return (lpsz != nullptr) ? lpsz : "";} +inline LPCWSTR SafeStrW(LPCWSTR lpsz) {return (lpsz != nullptr) ? lpsz : L"";} + +#ifdef _UNICODE + #define IsStrEmpty(lpsz) IsStrEmptyW(lpsz) + #define IsStrNotEmpty(lpsz) IsStrNotEmptyW(lpsz) + #define SafeStr(lpsz) SafeStrW(lpsz) +#else + #define IsStrEmpty(lpsz) IsStrEmptyA(lpsz) + #define IsStrNotEmpty(lpsz) IsStrNotEmptyA(lpsz) + #define SafeStr(lpsz) SafeStrA(lpsz) +#endif + +inline int lstrlen(LPCTSTR p) {return (int)tstrlen(p);} +inline LPTSTR lstrcpy(LPTSTR d, LPCTSTR s) {return tstrcpy(d, s);} +inline LPTSTR lstrncpy(LPTSTR d, LPCTSTR s, size_t n) {return tstrncpy(d, s, n);} +inline int lstrcmp(LPCTSTR s1, LPCTSTR s2) {return tstrcmp(s1, s2);} +inline int lstrncmp(LPCTSTR s1, LPCTSTR s2, size_t n) {return tstrncmp(s1, s2, n);} +inline int lstricmp(LPCTSTR s1, LPCTSTR s2) {return tstricmp(s1, s2);} +inline int lstrnicmp(LPCTSTR s1, LPCTSTR s2, size_t n) {return tstrnicmp(s1, s2, n);} +inline int lstrspn(LPCTSTR s, LPCTSTR accept) {return (int)tstrspn(s, accept);} +inline int lstrcspn(LPCTSTR s, LPCTSTR accept) {return (int)tstrcspn(s, accept);} + +template char (&_ArraySizeHelper(T(&arr)[N]))[N]; +template char (&_ArraySizeHelper(const T(&arr)[N]))[N]; + +#define ARRAY_SIZE(arr) (sizeof(_ArraySizeHelper(arr))) + +#ifndef _countof + #define _countof(arr) ARRAY_SIZE(arr) +#endif + +#ifndef __countof + #define __countof(arr) ARRAY_SIZE(arr) +#endif + +#define THREAD_YIELD_CYCLE 63 +#define THREAD_SWITCH_CYCLE 4095 + +inline void YieldThread(UINT i = THREAD_YIELD_CYCLE) +{ + if((i & THREAD_SWITCH_CYCLE) == THREAD_SWITCH_CYCLE) + ::SwitchToThread(); + else if((i & THREAD_YIELD_CYCLE) == THREAD_YIELD_CYCLE) + ::YieldProcessor(); +} + +INT WaitFor(DWORD dwMillSecond, DWORD dwSecond = 0, BOOL bExceptThreadInterrupted = FALSE); +INT Sleep(DWORD dwMillSecond, DWORD dwSecond = 0, BOOL bExceptThreadInterrupted = FALSE); + +__time64_t _time64(time_t* ptm = nullptr); +__time64_t _mkgmtime64(tm* ptm); +tm* _gmtime64(tm* ptm, __time64_t* pt); + +DWORD TimeGetTime(); +ULLONG TimeGetTime64(); +DWORD GetTimeGap32(DWORD dwOriginal, DWORD dwCurrent = 0); +ULLONG GetTimeGap64(ULLONG ullOriginal, ULONGLONG ullCurrent = 0); +LLONG TimevalToMillisecond(const timeval& tv); +timeval& MillisecondToTimeval(LLONG ms, timeval& tv); +LLONG TimespecToMillisecond(const timespec& ts); +timespec& MillisecondToTimespec(LLONG ms, timespec& ts); +timeval& GetFutureTimeval(LLONG ms, timeval& tv, struct timezone* ptz = nullptr); +timespec& GetFutureTimespec(LLONG ms, timespec& ts, clockid_t clkid = CLOCK_MONOTONIC); + +FD CreateTimer(LLONG llInterval, LLONG llStart = -1, BOOL bRealTimeClock = FALSE); +BOOL ReadTimer(FD tmr, ULLONG* pVal = nullptr, BOOL* pRs = nullptr); + +BOOL fcntl_SETFL(FD fd, INT fl, BOOL bSet = TRUE); + +void PrintStackTrace(); +void EXIT(int iExitCode = 0, int iErrno = -1, LPCSTR lpszFile = nullptr, int iLine = 0, LPCSTR lpszFunc = nullptr, LPCSTR lpszTitle = nullptr); +void _EXIT(int iExitCode = 0, int iErrno = -1, LPCSTR lpszFile = nullptr, int iLine = 0, LPCSTR lpszFunc = nullptr, LPCSTR lpszTitle = nullptr); +void ABORT(int iErrno = -1, LPCSTR lpszFile = nullptr, int iLine = 0, LPCSTR lpszFunc = nullptr, LPCSTR lpszTitle = nullptr); + +/* ߳󳤶 */ +#define MAX_THREAD_NAME_LENGTH 15 + +BOOL SetSequenceThreadName(THR_ID tid, LPCTSTR lpszPrefix, volatile UINT& vuiSeq); +BOOL SetThreadName(THR_ID tid, LPCTSTR lpszPrefix, UINT uiSequence); +BOOL SetThreadName(THR_ID tid, LPCTSTR lpszName); + +template::value>> +inline bool IS_INFINITE(T v) +{ + return v == (T)INFINITE; +} + +template::value>> +inline bool IS_HAS_ERROR(T v) +{ + return v == (T)HAS_ERROR; +} + +template::value>> +inline bool IS_NO_ERROR(T v) +{ + return v == (T)NO_ERROR; +} + +template +inline T InterlockedCompareExchange(volatile T* _Tgt, T _Value, T _Exp, BOOL _bWeek = FALSE, memory_order m1 = memory_order_seq_cst, memory_order m2 = memory_order_seq_cst) +{ + __atomic_compare_exchange_n(_Tgt, &_Exp, _Value, _bWeek, m1, m2); + return _Exp; +} + +template, decay_t>::value && is_same, decay_t>::value>> +inline V* InterlockedCompareExchangePointer(volatile T** _Tgt, V* _Value, E* _Exp, BOOL _bWeek = FALSE, memory_order m1 = memory_order_seq_cst, memory_order m2 = memory_order_seq_cst) +{ + return (V*)(ULONG_PTR)InterlockedCompareExchange((volatile ULONG_PTR*)(volatile PVOID*)_Tgt, (ULONG_PTR)(PVOID)_Value, (ULONG_PTR)(PVOID)_Exp, _bWeek, m1, m2); +} + +template +inline T* ConstructObject(T* p, A&& ... args) +{ + return new (p) T(forward(args) ...); +} + +template +inline void DestructObject(T* p) +{ + p->T::~T(); +} + +template, decay_t>::value>> +inline void CopyPlainObject(T1* p1, const T2* p2) +{ + CopyMemory(p1, p2, sizeof(T1)); +} + +template::value && (is_same::value || is_same::value)>> +C* _n_2_c(T value, C* lpszDest, int radix) +{ + static const C* dig = "0123456789abcdefghijklmnopqrstuvwxyz"; + + bool neg = false; + + if(is_signed::value && value < 0) + { + value = -value; + neg = true; + } + + int n = 0; + + do + { + lpszDest[n++] = dig[value % radix]; + value /= radix; + } while(value); + + if(neg) lpszDest[n++] = '-'; + lpszDest[n] = 0; + + C c, *p, *q; + for(p = lpszDest, q = p + n - 1; p < q; ++p, --q) + c = *p, *p = *q, *q = c; + + return lpszDest; +} + +#define itoa(v, p, r) _n_2_c((v), (p), (r)) +#define ltoa(v, p, r) _n_2_c((v), (p), (r)) +#define lltoa(v, p, r) _n_2_c((v), (p), (r)) +#define uitoa(v, p, r) _n_2_c((v), (p), (r)) +#define ultoa(v, p, r) _n_2_c((v), (p), (r)) +#define ulltoa(v, p, r) _n_2_c((v), (p), (r)) +#define itow(v, p, r) _n_2_c((v), (p), (r)) +#define ltow(v, p, r) _n_2_c((v), (p), (r)) +#define lltow(v, p, r) _n_2_c((v), (p), (r)) +#define uitow(v, p, r) _n_2_c((v), (p), (r)) +#define ultow(v, p, r) _n_2_c((v), (p), (r)) +#define ulltow(v, p, r) _n_2_c((v), (p), (r)) + +#define HEX_CHAR_TO_VALUE(c) (c <= '9' ? c - '0' : (c <= 'F' ? c - 'A' + 0x0A : c - 'a' + 0X0A)) +#define HEX_DOUBLE_CHAR_TO_VALUE(pc) ((BYTE)(((HEX_CHAR_TO_VALUE(*(pc))) << 4) | (HEX_CHAR_TO_VALUE(*((pc) + 1))))) +#define HEX_VALUE_TO_CHAR(n) (n <= 9 ? n + '0' : (n <= 'F' ? n + 'A' - 0X0A : n + 'a' - 0X0A)) +#define HEX_VALUE_TO_DOUBLE_CHAR(pc, n) {*(pc) = (BYTE)HEX_VALUE_TO_CHAR((n >> 4)); *((pc) + 1) = (BYTE)HEX_VALUE_TO_CHAR((n & 0X0F));} diff --git a/common/GeneralHelper.h b/common/GeneralHelper.h new file mode 100644 index 0000000..a683fb1 --- /dev/null +++ b/common/GeneralHelper.h @@ -0,0 +1,40 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#pragma once + +#include "hpsocket/GlobalDef.h" +#include "hpsocket/GlobalErrno.h" +#include "Singleton.h" +#include "STLHelper.h" +#include "FuncHelper.h" +#include "StringT.h" +#include "SysHelper.h" +#include "PrivateHeap.h" +#include "Semaphore.h" +#include "RWLock.h" +#include "BufferPtr.h" +#include "Event.h" +#include "CriSec.h" +#include "Thread.h" +#include "SignalHandler.h" diff --git a/common/IODispatcher.cpp b/common/IODispatcher.cpp new file mode 100644 index 0000000..45719c5 --- /dev/null +++ b/common/IODispatcher.cpp @@ -0,0 +1,348 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "IODispatcher.h" +#include "FuncHelper.h" + +#include +#include + +volatile UINT CIODispatcher::sm_uiNum = MAXUINT; +LPCTSTR CIODispatcher::WORKER_THREAD_PREFIX = _T("io-disp-"); + +BOOL CIODispatcher::Start(IIOHandler* pHandler, int iWorkerMaxEvents, int iWorkers) +{ + ASSERT_CHECK_EINVAL(pHandler && iWorkerMaxEvents >= 0 && iWorkers >= 0); + CHECK_ERROR(!HasStarted(), ERROR_INVALID_STATE); + + if(iWorkerMaxEvents == 0) iWorkerMaxEvents = DEF_WORKER_MAX_EVENTS; + if(iWorkers == 0) iWorkers = DEFAULT_WORKER_THREAD_COUNT; + + m_iMaxEvents = iWorkerMaxEvents; + m_iWorkers = iWorkers; + m_pHandler = pHandler; + + m_evExit = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC | EFD_SEMAPHORE); + + if(IS_INVALID_FD(m_evExit)) + goto START_ERROR; + + m_pContexts = make_unique(m_iWorkers); + + for(int i = 0; i < m_iWorkers; i++) + { + TDispContext& ctx = m_pContexts[i]; + + ctx.m_iIndex = i; + + ctx.m_epoll = epoll_create1(EPOLL_CLOEXEC); + CHECK_ERROR_FD(ctx.m_epoll); + + ctx.m_evCmd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); + + if(IS_INVALID_FD(ctx.m_evCmd)) + goto START_ERROR; + + if(!VERIFY(AddFD(i, ctx.m_evCmd, EPOLLIN | EPOLLET, &ctx.m_evCmd))) + goto START_ERROR; + + if(!VERIFY(AddFD(i, m_evExit, EPOLLIN, &m_evExit))) + goto START_ERROR; + + sigset_t ss; + sigemptyset(&ss); + sigaddset(&ss, SIGPIPE); + + VERIFY_IS_NO_ERROR(pthread_sigmask(SIG_BLOCK, &ss, nullptr)); + + ctx.m_pWorker = make_unique(); + + if(!VERIFY(ctx.m_pWorker->Start(this, &CIODispatcher::WorkerProc, &ctx))) + goto START_ERROR; + } + + return TRUE; + +START_ERROR: + EXECUTE_RESTORE_ERROR(Stop(FALSE)); + return FALSE; +} + +BOOL CIODispatcher::Stop(BOOL bCheck) +{ + if(bCheck) CHECK_ERROR(HasStarted(), ERROR_INVALID_STATE); + + BOOL isOK = TRUE; + + if(m_pContexts) + { + isOK &= IS_NO_ERROR(eventfd_write(m_evExit, m_iWorkers)); + + for(int i = 0; i < m_iWorkers; i++) + { + TDispContext& ctx = m_pContexts[i]; + + if(ctx.m_pWorker) + isOK &= ctx.m_pWorker->Join(); + + if(!ctx.m_queue.IsEmpty()) + { + TDispCommand* pCmd = nullptr; + + while(ctx.m_queue.PopFront(&pCmd)) + TDispCommand::Destruct(pCmd); + + VERIFY(ctx.m_queue.IsEmpty()); + } + + if(IS_VALID_FD(ctx.m_evCmd)) + isOK &= IS_NO_ERROR(close(ctx.m_evCmd)); + + if(IS_VALID_FD(ctx.m_epoll)) + isOK &= IS_NO_ERROR(close(ctx.m_epoll)); + } + } + + if(IS_VALID_FD(m_evExit)) + isOK &= IS_NO_ERROR(close(m_evExit)); + + Reset(); + + return isOK; +} + +VOID CIODispatcher::Reset() +{ + m_uiSeq = MAXUINT; + m_iWorkers = 0; + m_iMaxEvents= 0; + m_evExit = INVALID_FD; + m_pHandler = nullptr; + m_pContexts = nullptr; +} + +VOID CIODispatcher::MakePrefix() +{ + UINT uiNumber = ::InterlockedIncrement(&sm_uiNum); + + m_strPrefix.Format(_T("%s%u-"), WORKER_THREAD_PREFIX, uiNumber); +} + +TDispContext& CIODispatcher::GetContext(int idx, FD fd) +{ + if(idx < 0) idx = fd; + ASSERT(idx >= 0); + if(idx >= m_iWorkers) idx %= m_iWorkers; + + return m_pContexts[idx]; +} + +BOOL CIODispatcher::SendCommandByIndex(int idx, USHORT t, UINT_PTR wp, UINT_PTR lp) +{ + return SendCommandByIndex(idx, TDispCommand::Construct(t, wp, lp)); +} + +BOOL CIODispatcher::SendCommandByIndex(int idx, TDispCommand* pCmd) +{ + TDispContext& ctx = GetContextByIndex(idx); + return SendCommand(ctx, pCmd); +} + +BOOL CIODispatcher::SendCommandByFD(FD fd, USHORT t, UINT_PTR wp, UINT_PTR lp) +{ + return SendCommandByFD(fd, TDispCommand::Construct(t, wp, lp)); +} + +BOOL CIODispatcher::SendCommandByFD(FD fd, TDispCommand* pCmd) +{ + TDispContext& ctx = GetContextByFD(fd); + return SendCommand(ctx, pCmd); +} + +BOOL CIODispatcher::SendCommand(TDispContext& ctx, TDispCommand* pCmd) +{ + ctx.m_queue.PushBack(pCmd); + return VERIFY_IS_NO_ERROR(eventfd_write(ctx.m_evCmd, 1)); +} + +BOOL CIODispatcher::CtlFD(int idx, FD fd, int op, UINT mask, PVOID pv) +{ + const TDispContext& ctx = GetContext(idx, fd); + + epoll_event evt = {mask, pv}; + return IS_NO_ERROR(epoll_ctl(ctx.m_epoll, op, fd, &evt)); +} + +int CIODispatcher::WorkerProc(TDispContext* pContext) +{ + ::SetSequenceThreadName(SELF_THREAD_ID, m_strPrefix, m_uiSeq); + + m_pHandler->OnDispatchThreadStart(SELF_THREAD_ID); + + BOOL bRun = TRUE; + unique_ptr pEvents = make_unique(m_iMaxEvents); + + while(bRun) + { + int rs = NO_EINTR_INT(epoll_pwait(pContext->m_epoll, pEvents.get(), m_iMaxEvents, INFINITE, nullptr)); + + if(rs <= TIMEOUT) + ERROR_ABORT(); + + for(int i = 0; i < rs; i++) + { + UINT events = pEvents[i].events; + PVOID ptr = pEvents[i].data.ptr; + + if(ptr == &pContext->m_evCmd) + ProcessCommand(pContext, events); + else if(ptr == &m_evExit) + bRun = ProcessExit(pContext, events); + else + ProcessIo(pContext, ptr, events); + } + } + + m_pHandler->OnDispatchThreadEnd(SELF_THREAD_ID); + + return 0; +} + +BOOL CIODispatcher::ProcessCommand(TDispContext* pContext, UINT events) +{ + if(events & _EPOLL_ALL_ERROR_EVENTS) + ERROR_ABORT(); + + if(!(events & EPOLLIN)) + return FALSE; + + BOOL isOK = TRUE; + + eventfd_t v; + + int rs = eventfd_read(pContext->m_evCmd, &v); + + if(IS_NO_ERROR(rs)) + { + ASSERT(v > 0); + + TDispCommand* pCmd = nullptr; + + while(pContext->m_queue.PopFront(&pCmd)) + { + m_pHandler->OnCommand(pContext, pCmd); + TDispCommand::Destruct(pCmd); + } + } + else if(IS_HAS_ERROR(rs)) + { + ASSERT(IS_WOULDBLOCK_ERROR()); + + isOK = FALSE; + } + + return isOK; +} + +BOOL CIODispatcher::ProcessExit(const TDispContext* pContext, UINT events) +{ + if(events & _EPOLL_ALL_ERROR_EVENTS) + ERROR_ABORT(); + + if(!(events & EPOLLIN)) + return TRUE; + + BOOL bRun = TRUE; + + eventfd_t v; + + int rs = eventfd_read(m_evExit, &v); + + if(IS_HAS_ERROR(rs)) + ASSERT(IS_WOULDBLOCK_ERROR()); + else + { + ASSERT(v == 1); + bRun = FALSE; + } + + return bRun; +} + +BOOL CIODispatcher::ProcessIo(const TDispContext* pContext, PVOID pv, UINT events) +{ + if(!m_pHandler->OnBeforeProcessIo(pContext, pv, events)) + return FALSE; + + BOOL rs = DoProcessIo(pContext, pv, events); + m_pHandler->OnAfterProcessIo(pContext, pv, events, rs); + + return rs; +} + +BOOL CIODispatcher::DoProcessIo(const TDispContext* pContext, PVOID pv, UINT events) +{ + if(events & EPOLLERR) + return m_pHandler->OnError(pContext, pv, events); + if((events & EPOLLPRI) && !m_pHandler->OnReadyPrivilege(pContext, pv, events)) + return FALSE; + if((events & EPOLLIN) && !m_pHandler->OnReadyRead(pContext, pv, events)) + return FALSE; + if((events & EPOLLOUT) && !m_pHandler->OnReadyWrite(pContext, pv, events)) + return FALSE; + if((events & (_EPOLL_HUNGUP_EVENTS)) && !m_pHandler->OnHungUp(pContext, pv, events)) + return FALSE; + + return TRUE; +} + +FD CIODispatcher::AddTimer(int idx, LLONG llInterval, PVOID pv) +{ + FD fdTimer = ::CreateTimer(llInterval); + + if(IS_VALID_FD(fdTimer)) + { + if(!AddFD(idx, fdTimer, EPOLLIN | EPOLLET, pv)) + { + close(fdTimer); + fdTimer = INVALID_FD; + } + } + + return fdTimer; +} + +BOOL CIODispatcher::DelTimer(int idx, FD fdTimer) +{ + BOOL isOK = FALSE; + + if(IS_VALID_FD(fdTimer)) + { + if(DelFD(idx, fdTimer)) + isOK = TRUE; + + close(fdTimer); + } + + return isOK; +} diff --git a/common/IODispatcher.h b/common/IODispatcher.h new file mode 100644 index 0000000..2bc6622 --- /dev/null +++ b/common/IODispatcher.h @@ -0,0 +1,272 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#pragma once + +#include "hpsocket/GlobalDef.h" +#include "Singleton.h" +#include "RingBuffer.h" +#include "Thread.h" + +#include +#include +#include + +#include + +using namespace std; + +#define _EPOLL_READ_PRI_EVENTS (EPOLLPRI | EPOLLRDHUP) +#define _EPOLL_READ_EVENTS (EPOLLIN | EPOLLRDHUP) +#define _EPOLL_ALL_READ_EVENTS (_EPOLL_READ_EVENTS | _EPOLL_READ_PRI_EVENTS) +#define _EPOLL_WRITE_EVENTS (EPOLLOUT) +#define _EPOLL_NORMAL_RW_EVENTS (_EPOLL_READ_EVENTS | _EPOLL_WRITE_EVENTS) +#define _EPOLL_ALL_RW_EVENTS (_EPOLL_ALL_READ_EVENTS | _EPOLL_WRITE_EVENTS) +#define _EPOLL_ERROR_EVENTS (EPOLLERR) +#define _EPOLL_HUNGUP_EVENTS (EPOLLHUP | EPOLLRDHUP) +#define _EPOLL_ALL_ERROR_EVENTS (_EPOLL_ERROR_EVENTS | _EPOLL_HUNGUP_EVENTS) +#define _EPOLL_ALL_NORMAL_EVENTS (_EPOLL_NORMAL_RW_EVENTS | _EPOLL_ALL_ERROR_EVENTS) +#define _EPOLL_ALL_EVENTS (_EPOLL_ALL_RW_EVENTS | _EPOLL_ALL_ERROR_EVENTS) + +#define DISP_EVENT_FLAG_R 0x1 +#define DISP_EVENT_FLAG_W 0x2 +#define DISP_EVENT_FLAG_H 0x4 + +#define RETRIVE_EVENT_FLAG_R(evt) ((evt) & (_EPOLL_ALL_READ_EVENTS) ? DISP_EVENT_FLAG_R : 0) +#define RETRIVE_EVENT_FLAG_W(evt) ((evt) & (_EPOLL_WRITE_EVENTS) ? DISP_EVENT_FLAG_W : 0) +#define RETRIVE_EVENT_FLAG_RW(evt) (RETRIVE_EVENT_FLAG_R(evt) | RETRIVE_EVENT_FLAG_W(evt)) +#define RETRIVE_EVENT_FLAG_H(evt) ((evt) & (_EPOLL_HUNGUP_EVENTS) ? DISP_EVENT_FLAG_H : 0) + +#ifndef EPOLLEXCLUSIVE + #define EPOLLEXCLUSIVE (1u << 28) +#endif + +#define MAYBE_EPOLLEXCLUSIVE (::IsKernelVersionAbove(4, 5, 0) ? EPOLLEXCLUSIVE : 0) + +// ------------------------------------------------------------------------------------------------------------------------------------------------------- // + +struct TDispCommand; +class CIODispatcher; + +struct TDispContext +{ + friend class CIODispatcher; + + using CCommandQueue = CCASQueue; + using CWorkerThread = CThread; + +public: + int GetIndex() const {return m_iIndex;} + THR_ID GetThreadId() const {return m_pWorker != nullptr ? m_pWorker->GetThreadID() : 0;} + +public: + TDispContext() {Reset();} + ~TDispContext() = default; + + DECLARE_NO_COPY_CLASS(TDispContext) + +private: + VOID Reset() + { + m_iIndex = -1; + m_epoll = INVALID_FD; + m_evCmd = INVALID_FD; + m_pWorker = nullptr; + } + +private: + int m_iIndex; + FD m_epoll; + FD m_evCmd; + + CCommandQueue m_queue; + unique_ptr m_pWorker; +}; + +struct TDispCommand +{ + USHORT type; + UINT_PTR wParam; + UINT_PTR lParam; + + static TDispCommand* Construct(USHORT t, UINT_PTR wp = 0, UINT_PTR lp = 0) + {return new TDispCommand(t, wp, lp);} + + static VOID Destruct(TDispCommand* p) + {if(p) delete p;} + +private: + TDispCommand(USHORT t, UINT_PTR wp = 0, UINT_PTR lp = 0) + : type(t), wParam(wp), lParam(lp) + { + } + + ~TDispCommand() = default; +}; + +// ------------------------------------------------------------------------------------------------------------------------------------------------------- // + +class IIOHandler +{ +public: + virtual VOID OnCommand(const TDispContext* pContext, TDispCommand* pCmd) = 0; + + virtual BOOL OnBeforeProcessIo(const TDispContext* pContext, PVOID pv, UINT events) = 0; + virtual VOID OnAfterProcessIo(const TDispContext* pContext, PVOID pv, UINT events, BOOL rs) = 0; + virtual BOOL OnReadyRead(const TDispContext* pContext, PVOID pv, UINT events) = 0; + virtual BOOL OnReadyWrite(const TDispContext* pContext, PVOID pv, UINT events) = 0; + virtual BOOL OnHungUp(const TDispContext* pContext, PVOID pv, UINT events) = 0; + virtual BOOL OnError(const TDispContext* pContext, PVOID pv, UINT events) = 0; + virtual BOOL OnReadyPrivilege(const TDispContext* pContext, PVOID pv, UINT events) = 0; + + virtual VOID OnDispatchThreadStart(THR_ID tid) = 0; + virtual VOID OnDispatchThreadEnd(THR_ID tid) = 0; + +public: + virtual ~IIOHandler() = default; +}; + +class CIOHandler : public IIOHandler +{ +public: + virtual VOID OnCommand(const TDispContext* pContext, TDispCommand* pCmd) override {} + + virtual BOOL OnBeforeProcessIo(const TDispContext* pContext, PVOID pv, UINT events) override {return TRUE;} + virtual VOID OnAfterProcessIo(const TDispContext* pContext, PVOID pv, UINT events, BOOL rs) override {} + virtual BOOL OnReadyWrite(const TDispContext* pContext, PVOID pv, UINT events) override {return TRUE;} + virtual BOOL OnHungUp(const TDispContext* pContext, PVOID pv, UINT events) override {return TRUE;} + virtual BOOL OnError(const TDispContext* pContext, PVOID pv, UINT events) override {return TRUE;} + virtual BOOL OnReadyPrivilege(const TDispContext* pContext, PVOID pv, UINT events) override {return TRUE;} + + virtual VOID OnDispatchThreadStart(THR_ID tid) override {} + virtual VOID OnDispatchThreadEnd(THR_ID tid) override {} +}; + +// ------------------------------------------------------------------------------------------------------------------------------------------------------- // + +class CIODispatcher +{ +public: + static const int DEF_WORKER_MAX_EVENTS = 64; + + using CCommandQueue = TDispContext::CCommandQueue; + using CWorkerThread = TDispContext::CWorkerThread; + +public: + BOOL Start(IIOHandler* pHandler, int iWorkerMaxEvents = DEF_WORKER_MAX_EVENTS, int iWorkers = 0); + BOOL Stop(BOOL bCheck = TRUE); + + BOOL SendCommandByIndex(int idx, TDispCommand* pCmd); + BOOL SendCommandByIndex(int idx, USHORT t, UINT_PTR wp = 0, UINT_PTR lp = 0); + BOOL SendCommandByFD(FD fd, TDispCommand* pCmd); + BOOL SendCommandByFD(FD fd, USHORT t, UINT_PTR wp = 0, UINT_PTR lp = 0); + BOOL SendCommand(TDispContext& ctx, TDispCommand* pCmd); + + template, TDispCommand*>::value>> + BOOL SendCommandsByIndex(int idx, const _List& cmds) + { + TDispContext& ctx = GetContextByIndex(idx); + return SendCommands(ctx, cmds); + } + + template, TDispCommand*>::value>> + BOOL SendCommandsByFD(FD fd, const _List& cmds) + { + TDispContext& ctx = GetContextByFD(fd); + return SendCommands(ctx, cmds); + } + + template, TDispCommand*>::value>> + BOOL SendCommands(TDispContext& ctx, const _List& cmds) + { + size_t size = cmds.size(); + if(size == 0) return FALSE; + + for(auto it = cmds.begin(), end = cmds.end(); it != end; ++it) + ctx.m_queue.PushBack(*it); + + return VERIFY_IS_NO_ERROR(eventfd_write(ctx.m_evCmd, size)); + } + + BOOL AddFD(int idx, FD fd, UINT mask, PVOID pv) {return CtlFD(idx, fd, EPOLL_CTL_ADD, mask, pv);} + BOOL ModFD(int idx, FD fd, UINT mask, PVOID pv) {return CtlFD(idx, fd, EPOLL_CTL_MOD, mask, pv);} + BOOL DelFD(int idx, FD fd) {return CtlFD(idx, fd, EPOLL_CTL_DEL, 0, nullptr);} + BOOL CtlFD(int idx, FD fd, int op, UINT mask, PVOID pv); + + + BOOL AddFD(FD fd, UINT mask, PVOID pv) {return CtlFD(-1, fd, EPOLL_CTL_ADD, mask, pv);} + BOOL ModFD(FD fd, UINT mask, PVOID pv) {return CtlFD(-1, fd, EPOLL_CTL_MOD, mask, pv);} + BOOL DelFD(FD fd) {return CtlFD(-1, fd, EPOLL_CTL_DEL, 0, nullptr);} + BOOL CtlFD(FD fd, int op, UINT mask, PVOID pv) {return CtlFD(-1, fd, op, mask, pv);} + + BOOL ProcessIo(const TDispContext* pContext, PVOID pv, UINT events); + + FD AddTimer (int idx, LLONG llInterval, PVOID pv); + BOOL DelTimer (int idx, FD fdTimer); + + FD AddTimer (LLONG llInterval, PVOID pv) {return AddTimer(-1, llInterval, pv);} + BOOL DelTimer (FD fdTimer) {return DelTimer(-1, fdTimer);} + +private: + int WorkerProc(TDispContext* pContext); + BOOL ProcessExit(const TDispContext* pContext, UINT events); + BOOL ProcessCommand(TDispContext* pContext, UINT events); + BOOL DoProcessIo(const TDispContext* pContext, PVOID pv, UINT events); + + VOID Reset(); + VOID MakePrefix(); + + TDispContext& GetContextByIndex(int idx) {return GetContext(idx, -1);} + TDispContext& GetContextByFD(FD fd) {return GetContext(-1, fd);} + TDispContext& GetContext(int idx, FD fd); + +public: + const TDispContext& GetContextRefByIndex(int idx) {return GetContextByIndex(idx);} + const TDispContext& GetContextRefByFD(FD fd) {return GetContextByFD(fd);} + const TDispContext& GetContextRef(int idx, FD fd) {return GetContext(idx, fd);} + + BOOL HasStarted() {return m_pHandler && m_pContexts;} + int GetWorkers() {return m_iWorkers;} + const TDispContext* GetContexts() {return m_pContexts.get();} + + CIODispatcher() {MakePrefix(); Reset();} + ~CIODispatcher() {if(HasStarted()) Stop();} + + DECLARE_NO_COPY_CLASS(CIODispatcher) + +private: + static LPCTSTR WORKER_THREAD_PREFIX; + static volatile UINT sm_uiNum; + + volatile UINT m_uiSeq; + CString m_strPrefix; + +private: + int m_iWorkers; + int m_iMaxEvents; + + FD m_evExit; + + IIOHandler* m_pHandler; + unique_ptr m_pContexts; +}; diff --git a/common/PollHelper.cpp b/common/PollHelper.cpp new file mode 100644 index 0000000..57b3cf8 --- /dev/null +++ b/common/PollHelper.cpp @@ -0,0 +1,62 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "PollHelper.h" +#include "FuncHelper.h" + +long PollForSingleObject(pollfd& pfd, long lTimeout, const sigset_t* pSigSet) +{ + return PollForMultipleObjects(&pfd, 1, lTimeout, pSigSet); +} + +long PollForMultipleObjects(pollfd pfds[], int iCount, long lTimeout, const sigset_t* pSigSet) +{ + ASSERT(iCount > 0 && iCount < (int)(sizeof(LONG) * 8)); + + timespec* pts = nullptr; + + if(!IS_INFINITE(lTimeout)) + { + pts = CreateLocalObject(timespec); + ::MillisecondToTimespec(lTimeout, *pts); + } + + while(TRUE) + { + int rs = NO_EINTR_INT(ppoll(pfds, iCount, pts, pSigSet)); + + if(rs <= TIMEOUT) return rs; + + LONG lValue = 0L; + + for(int i = 0; i < iCount; i++) + { + pollfd& pfd = pfds[i]; + + if(pfd.revents & _POLL_ALL_EVENTS) + lValue |= (1 << i); + } + + return lValue; + } +} diff --git a/common/PollHelper.h b/common/PollHelper.h new file mode 100644 index 0000000..d06119c --- /dev/null +++ b/common/PollHelper.h @@ -0,0 +1,51 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#pragma once + +#include "hpsocket/GlobalDef.h" + +#include +#include + +#if __USE_GNU + #define _POLL_READ_PRI_EVENTS (POLLPRI | POLLRDHUP) + #define _POLL_READ_EVENTS (POLLIN | POLLRDHUP) + #define _POLL_HUNGUP_EVENTS (POLLHUP | POLLRDHUP) +#else + #define _POLL_READ_PRI_EVENTS (POLLPRI) + #define _POLL_READ_EVENTS (POLLIN) + #define _POLL_HUNGUP_EVENTS (POLLHUP) +#endif + +#define _POLL_ALL_READ_EVENTS (_POLL_READ_EVENTS | _POLL_READ_PRI_EVENTS) +#define _POLL_WRITE_EVENTS (POLLOUT) +#define _POLL_NORMAL_RW_EVENTS (_POLL_READ_EVENTS | _POLL_WRITE_EVENTS) +#define _POLL_ALL_RW_EVENTS (_POLL_ALL_READ_EVENTS | _POLL_WRITE_EVENTS) +#define _POLL_ERROR_EVENTS (POLLERR | POLLNVAL) +#define _POLL_ALL_ERROR_EVENTS (_POLL_ERROR_EVENTS | _POLL_HUNGUP_EVENTS) +#define _POLL_ALL_NORMAL_EVENTS (_POLL_NORMAL_RW_EVENTS | _POLL_ALL_ERROR_EVENTS) +#define _POLL_ALL_EVENTS (_POLL_ALL_RW_EVENTS | _POLL_ALL_ERROR_EVENTS) + +long PollForSingleObject(pollfd& pfd, long lTimeout = INFINITE, const sigset_t* pSigSet = nullptr); +long PollForMultipleObjects(pollfd pfds[], int iCount, long lTimeout = INFINITE, const sigset_t* pSigSet = nullptr); diff --git a/common/PrivateHeap.h b/common/PrivateHeap.h new file mode 100644 index 0000000..b865cf5 --- /dev/null +++ b/common/PrivateHeap.h @@ -0,0 +1,152 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#pragma once + +#include "hpsocket/GlobalDef.h" +#include "Singleton.h" + +#include + +#define HEAP_ZERO_MEMORY 0x08 + +class CGlobalHeapImpl +{ +public: + PVOID Alloc(SIZE_T dwSize, DWORD dwFlags = 0) + { + PVOID pv = malloc(dwSize); + + if(!pv) + throw std::bad_alloc(); + + if(dwFlags & HEAP_ZERO_MEMORY) + ZeroMemory(pv, dwSize); + + return pv; + } + + PVOID ReAlloc(PVOID pvMemory, SIZE_T dwSize, DWORD dwFlags = 0) + { + PVOID pv = realloc(pvMemory, dwSize); + + if(!pv) + { + if(pvMemory) + free(pvMemory); + + throw std::bad_alloc(); + } + + if(dwFlags & HEAP_ZERO_MEMORY) + ZeroMemory(pv, dwSize); + + return pv; + } + + BOOL Free(PVOID pvMemory, DWORD dwFlags = 0) + { + if(pvMemory) + { + free(pvMemory); + return TRUE; + } + + return FALSE; + } + + SIZE_T Compact (DWORD dwFlags = 0) {return -1;} + SIZE_T Size (PVOID pvMemory, DWORD dwFlags = 0) {return _msize(pvMemory);} + + BOOL IsValid() {return TRUE;} + BOOL Reset() {return TRUE;} + +public: + CGlobalHeapImpl (DWORD dwOptions = 0, SIZE_T dwInitSize = 0, SIZE_T dwMaxSize = 0) {} + ~CGlobalHeapImpl() {} + + DECLARE_NO_COPY_CLASS(CGlobalHeapImpl) +}; + +#if !defined (_USE_CUSTOM_PRIVATE_HEAP) + using CPrivateHeap = CGlobalHeapImpl; +#endif + +template class CPrivateHeapBuffer +{ +public: + CPrivateHeapBuffer(CPrivateHeap& hpPrivate, SIZE_T dwSize = 0) + : m_hpPrivate (hpPrivate) + , m_pvMemory (nullptr) + { + ASSERT(m_hpPrivate.IsValid()); + Alloc(dwSize); + } + + ~CPrivateHeapBuffer() {Free();} + +public: + + T* Alloc(SIZE_T dwSize, DWORD dwFlags = 0) + { + if(IsValid()) + Free(); + + if(dwSize > 0) + m_pvMemory = (T*)m_hpPrivate.Alloc(dwSize * sizeof(T), dwFlags); + + return m_pvMemory; + } + + T* ReAlloc(SIZE_T dwSize, DWORD dwFlags = 0) + {return m_pvMemory = (T*)m_hpPrivate.ReAlloc(m_pvMemory, dwSize * sizeof(T), dwFlags);} + + SIZE_T Size(DWORD dwFlags = 0) + {return m_hpPrivate.Size(m_pvMemory, dwFlags) / sizeof(T);} + + BOOL Free(DWORD dwFlags = 0) + { + BOOL isOK = TRUE; + + if(IsValid()) + { + isOK = m_hpPrivate.Free(m_pvMemory, dwFlags); + m_pvMemory = nullptr; + } + + return isOK; + } + + BOOL IsValid() {return m_pvMemory != nullptr;} + operator T* () const {return m_pvMemory;} + T& operator [] (int i) const {return *(m_pvMemory + i);} + +private: + CPrivateHeap& m_hpPrivate; + T* m_pvMemory; + + DECLARE_NO_COPY_CLASS(CPrivateHeapBuffer) +}; + +using CPrivateHeapByteBuffer = CPrivateHeapBuffer; +using CPrivateHeapStrBuffer = CPrivateHeapBuffer; diff --git a/common/RWLock.cpp b/common/RWLock.cpp new file mode 100644 index 0000000..e7a0687 --- /dev/null +++ b/common/RWLock.cpp @@ -0,0 +1,228 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "RWLock.h" + +CMutexRWLock::CMutexRWLock() + : m_nActive (0) + , m_dwWriterTID (0) +{ + +} + +CMutexRWLock::~CMutexRWLock() +{ + ASSERT(m_nActive == 0); + ASSERT(m_dwWriterTID == 0); +} + +VOID CMutexRWLock::WaitToRead() +{ + BOOL bWait = FALSE; + + { + CSpinLock locallock(m_cs); + + if(m_nActive > 0) + ++m_nActive; + else if(m_nActive == 0) + { + if(m_mtx.try_lock_shared()) + ++m_nActive; + else + bWait = TRUE; + } + else if(!IsOwner()) + bWait = TRUE; + } + + if(bWait) + { + m_mtx.lock_shared(); + + CSpinLock locallock(m_cs); + ++m_nActive; + } +} + +VOID CMutexRWLock::WaitToWrite() +{ + BOOL bWait = FALSE; + + { + CSpinLock locallock(m_cs); + + if(m_nActive > 0) + bWait = TRUE; + else if(m_nActive == 0) + { + if(m_mtx.try_lock()) + { + SetOwner(); + --m_nActive; + } + else + bWait = TRUE; + } + else + { + if(IsOwner()) + --m_nActive; + else + bWait = TRUE; + } + } + + if(bWait) + { + m_mtx.lock(); + + SetOwner(); + --m_nActive; + } +} + +VOID CMutexRWLock::ReadDone() +{ + ASSERT(m_nActive != 0); + + if(m_nActive > 0) + { + { + CSpinLock locallock(m_cs); + --m_nActive; + } + + m_mtx.unlock_shared(); + } + else + ASSERT(IsOwner()); +} + +VOID CMutexRWLock::WriteDone() +{ + ASSERT(IsOwner()); + ASSERT(m_nActive < 0); + + BOOL bDone; + + { + CSpinLock locallock(m_cs); + bDone = (++m_nActive == 0); + } + + if(bDone) + { + DetachOwner(); + m_mtx.unlock(); + } + else + ASSERT(IsOwner()); +} + +CSEMRWLock::CSEMRWLock() + : m_nWaitingReaders (0) + , m_nWaitingWriters (0) + , m_nActive (0) + , m_dwWriterTID (0) +{ + +} + +CSEMRWLock::~CSEMRWLock() +{ + ASSERT(m_nActive == 0); + ASSERT(m_dwWriterTID == 0); +} + +VOID CSEMRWLock::WaitToRead() +{ + CMutexLock2 lock(m_mtx); + + if(IsOwner()) + return; + + ++m_nWaitingReaders; + + m_cvRead.wait(lock, [=]() -> BOOL + { + return m_nActive >= 0 && m_nWaitingWriters == 0; + }); + + --m_nWaitingReaders; + ++m_nActive; +} + +VOID CSEMRWLock::WaitToWrite() +{ + CMutexLock2 lock(m_mtx); + + if(IsOwner()) + { + --m_nActive; + + return; + } + + ++m_nWaitingWriters; + + m_cvWrite.wait(lock, [=]() -> BOOL + { + return m_nActive == 0; + }); + + --m_nWaitingWriters; + --m_nActive; + + SetOwner(); +} + +VOID CSEMRWLock::ReadDone() +{ + CMutexLock2 locallock(m_mtx); + + if(IsOwner()) + return; + + ASSERT(m_nActive > 0); + + if(--m_nActive == 0 && m_nWaitingWriters > 0) + m_cvWrite.notify_one(); +} + +VOID CSEMRWLock::WriteDone() +{ + ASSERT(IsOwner()); + + CMutexLock2 lock(m_mtx); + + if(++m_nActive == 0) + { + DetachOwner(); + + if(m_nWaitingWriters > 0) + m_cvWrite.notify_one(); + else if(m_nWaitingReaders > 0) + m_cvRead.notify_all(); + } +} diff --git a/common/RWLock.h b/common/RWLock.h new file mode 100644 index 0000000..8294056 --- /dev/null +++ b/common/RWLock.h @@ -0,0 +1,128 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#pragma once + +#include "hpsocket/GlobalDef.h" +#include "CriSec.h" + +#include +#include + +using namespace std; + +class CMutexRWLock +{ +public: + VOID WaitToRead(); + VOID WaitToWrite(); + VOID ReadDone(); + VOID WriteDone(); + +private: + BOOL IsOwner() {return ::IsSelfThread(m_dwWriterTID);} + VOID SetOwner() {m_dwWriterTID = SELF_THREAD_ID;} + VOID DetachOwner() {m_dwWriterTID = 0;} + +public: + CMutexRWLock(); + ~CMutexRWLock(); + + DECLARE_NO_COPY_CLASS(CMutexRWLock) + +private: + int m_nActive; + THR_ID m_dwWriterTID; + + CSpinGuard m_cs; + shared_timed_mutex m_mtx; +}; + +class CSEMRWLock +{ +public: + VOID WaitToRead(); + VOID WaitToWrite(); + VOID ReadDone(); + VOID WriteDone(); + +private: + BOOL IsOwner() {BOOL bOwner = ::IsSelfThread(m_dwWriterTID); ASSERT(!bOwner || m_nActive < 0); return bOwner;} + VOID SetOwner() {m_dwWriterTID = SELF_THREAD_ID;} + VOID DetachOwner() {m_dwWriterTID = 0;} + +public: + CSEMRWLock(); + ~CSEMRWLock(); + + DECLARE_NO_COPY_CLASS(CSEMRWLock) + +private: + int m_nWaitingReaders; + int m_nWaitingWriters; + int m_nActive; + THR_ID m_dwWriterTID; + + CMTX m_mtx; + condition_variable m_cvRead; + condition_variable m_cvWrite; +}; + +template class CLocalReadLock +{ +public: + CLocalReadLock(CLockObj& obj) : m_wait(obj) {m_wait.WaitToRead();} + ~CLocalReadLock() {m_wait.ReadDone();} + + DECLARE_NO_COPY_CLASS(CLocalReadLock) + +private: + CLockObj& m_wait; +}; + +template class CLocalWriteLock +{ +public: + CLocalWriteLock(CLockObj& obj) : m_wait(obj) {m_wait.WaitToWrite();} + ~CLocalWriteLock() {m_wait.WriteDone();} + + DECLARE_NO_COPY_CLASS(CLocalWriteLock) + +private: + CLockObj& m_wait; +}; + + +using CSimpleRWLock = shared_timed_mutex; +using CReadLock = shared_lock; +using CWriteLock = lock_guard; +using CWriteLock2 = unique_lock; + +#if !defined(_USE_MUTEX_RW_LOCK) + using CRWLock = CSEMRWLock; +#else + using CRWLock = CMutexRWLock; +#endif + +using CReentrantReadLock = CLocalReadLock; +using CReentrantWriteLock = CLocalWriteLock; diff --git a/common/RingBuffer.h b/common/RingBuffer.h new file mode 100644 index 0000000..3b9b281 --- /dev/null +++ b/common/RingBuffer.h @@ -0,0 +1,1731 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#pragma once + +#include "hpsocket/GlobalDef.h" +#include "Singleton.h" +#include "STLHelper.h" +#include "FuncHelper.h" +#include "RWLock.h" + +using namespace std; + +#define CACHE_LINE 64 +#define PACK_SIZE_OF(T) (CACHE_LINE - sizeof(T) % CACHE_LINE) + +#if __WORDSIZE == 32 + #pragma pack(push, 4) +#endif + +// ------------------------------------------------------------------------------------------------------------- // + +template class CRingCache +{ +public: + + enum EnGetResult {GR_FAIL = -1, GR_INVALID = 0, GR_VALID = 1}; + + typedef T* TPTR; + typedef volatile T* VTPTR; + + typedef unordered_set IndexSet; + typedef typename IndexSet::const_iterator IndexSetCI; + typedef typename IndexSet::iterator IndexSetI; + + static TPTR const E_EMPTY; + static TPTR const E_LOCKED; + static TPTR const E_MAX_STATUS; + +public: + + static index_type& INDEX_INC(index_type& dwIndex) {if(adjust_index) ++dwIndex; return dwIndex;} + static index_type& INDEX_DEC(index_type& dwIndex) {if(adjust_index) --dwIndex; return dwIndex;} + +private: + + index_type& INDEX_V2R(index_type& dwIndex) {dwIndex %= m_dwSize; if(dwIndex == 0) dwIndex = m_dwSize; return dwIndex;} + VTPTR& INDEX_VAL(index_type dwIndex) {return *(m_pv + dwIndex);} + +public: + + BOOL Put(TPTR pElement, index_type& dwIndex) + { + ASSERT(pElement != nullptr); + + if(!IsValid()) return FALSE; + + BOOL isOK = FALSE; + + while(true) + { + if(!HasSpace()) + break; + + DWORD dwCurSeq = m_dwCurSeq; + index_type dwCurIndex = dwCurSeq % m_dwSize; + VTPTR& pValue = INDEX_VAL(dwCurIndex); + + if(pValue == E_EMPTY) + { + if(::InterlockedCompareExchangePointer(&pValue, pElement, E_EMPTY) == E_EMPTY) + { + ::InterlockedIncrement(&m_dwCount); + ::InterlockedCompareExchange(&m_dwCurSeq, dwCurSeq + 1, dwCurSeq); + + dwIndex = INDEX_INC(dwCurIndex); + isOK = TRUE; + + if(pElement != E_LOCKED) + EmplaceIndex(dwIndex); + + break; + } + } + + ::InterlockedCompareExchange(&m_dwCurSeq, dwCurSeq + 1, dwCurSeq); + } + + return isOK; + } + + EnGetResult GetEx(index_type dwIndex, TPTR* ppElement) + { + return Get(INDEX_V2R(dwIndex), ppElement); + } + + EnGetResult Get(index_type dwIndex, TPTR* ppElement) + { + ASSERT(dwIndex <= m_dwSize); + ASSERT(ppElement != nullptr); + + if(!IsValid() || INDEX_DEC(dwIndex) >= m_dwSize) + { + *ppElement = nullptr; + return GR_FAIL; + } + + *ppElement = (TPTR)INDEX_VAL(dwIndex); + + return IsValidElement(*ppElement) ? GR_VALID : GR_INVALID; + } + + BOOL SetEx(index_type dwIndex, TPTR pElement, TPTR* ppOldElement = nullptr) + { + return Set(INDEX_V2R(dwIndex), pElement, ppOldElement); + } + + BOOL Set(index_type dwIndex, TPTR pElement, TPTR* ppOldElement = nullptr) + { + TPTR pElement2 = nullptr; + + if(Get(dwIndex, &pElement2) == GR_FAIL) + return FALSE; + + if(ppOldElement != nullptr) + *ppOldElement = pElement2; + + if(pElement == pElement2) + return FALSE; + + int f1 = 0; + int f2 = 0; + + if(pElement == E_EMPTY) + { + if(pElement2 == E_LOCKED) + f1 = -1; + else + f1 = f2 = -1; + } + else if(pElement == E_LOCKED) + { + if(pElement2 == E_EMPTY) + f1 = 1; + else + f2 = -1; + } + else + { + if(pElement2 == E_EMPTY) + f1 = f2 = 1; + else if(pElement2 == E_LOCKED) + f2 = 1; + } + + BOOL bSetValueFirst = (f1 + f2 >= 0); + index_type dwOuterIndex = dwIndex; + + INDEX_DEC(dwIndex); + + if(bSetValueFirst) INDEX_VAL(dwIndex) = pElement; + if(f1 > 0) ::InterlockedIncrement(&m_dwCount); + if(f2 != 0) (f2 > 0) ? EmplaceIndex(dwOuterIndex) : EraseIndex(dwOuterIndex); + if(f1 < 0) ::InterlockedDecrement(&m_dwCount); + if(!bSetValueFirst) INDEX_VAL(dwIndex) = pElement; + + ASSERT(Spaces() <= Size()); + + return TRUE; + } + + BOOL RemoveEx(index_type dwIndex, TPTR* ppElement = nullptr) + { + return Remove(INDEX_V2R(dwIndex), ppElement); + } + + BOOL Remove(index_type dwIndex, TPTR* ppElement = nullptr) + { + return Set(dwIndex, E_EMPTY, ppElement); + } + + BOOL AcquireLock(index_type& dwIndex) + { + return Put(E_LOCKED, dwIndex); + } + + BOOL ReleaseLock(index_type dwIndex, TPTR pElement) + { + ASSERT(pElement == nullptr || IsValidElement(pElement)); + + TPTR pElement2 = nullptr; + Get(dwIndex, &pElement2); + + ASSERT(pElement2 == E_LOCKED); + + if(pElement2 != E_LOCKED) + return FALSE; + + return Set(dwIndex, pElement); + } + +public: + + void Reset(DWORD dwSize = 0) + { + if(IsValid()) + Destroy(); + if(dwSize > 0) + Create(dwSize); + } + + BOOL GetAllElementIndexes(index_type ids[], DWORD& dwCount, BOOL bCopy = TRUE) + { + DWORD dwSize = Elements(); + + if(ids == nullptr || dwCount == 0) + { + dwCount = dwSize; + return FALSE; + } + + if(dwSize == 0) + { + dwCount = 0; + return TRUE; + } + + IndexSet* pIndexes = &m_indexes; + + if(bCopy) + { + pIndexes = new IndexSet; + CopyIndexes(*pIndexes); + } + + DWORD i = 0; + + for(auto it = pIndexes->begin(), end = pIndexes->end(); i < dwCount && it != end; ++i, ++it) + ids[i] = *it; + + if(bCopy) delete pIndexes; + + dwCount = i; + return TRUE; + } + + unique_ptr GetAllElementIndexes(DWORD& dwCount, BOOL bCopy = TRUE) + { + dwCount = (DWORD)m_indexes.size(); + unique_ptr ids(new index_type[dwCount]); + + if(dwCount > 0) + GetAllElementIndexes(ids.get(), dwCount, bCopy); + + return ids; + } + + IndexSet& CopyIndexes(IndexSet& indexes) + { + { + CReadLock locallock(m_cs); + indexes = m_indexes; + } + + return indexes; + } + + static BOOL IsValidElement(TPTR pElement) {return pElement > E_MAX_STATUS;} + + IndexSet& Indexes () { return m_indexes;} + DWORD Size () {return m_dwSize;} + DWORD Elements () {return (DWORD)m_indexes.size();} + DWORD Spaces () {return m_dwSize - m_dwCount;} + BOOL HasSpace () {return m_dwCount < m_dwSize;} + BOOL IsEmpty () {return m_dwCount == 0;} + BOOL IsValid () {return m_pv != nullptr;} + +private: + + void Create(DWORD dwSize) + { + ASSERT(!IsValid() && dwSize > 0); + + m_dwCurSeq = 0; + m_dwCount = 0; + m_dwSize = dwSize; + m_pv = (VTPTR*)malloc(m_dwSize * sizeof(TPTR)); + + ::ZeroMemory(m_pv, m_dwSize * sizeof(TPTR)); + } + + void Destroy() + { + ASSERT(IsValid()); + + m_indexes.clear(); + free((void*)m_pv); + + m_pv = nullptr; + m_dwSize = 0; + m_dwCount = 0; + m_dwCurSeq = 0; + } + + void EmplaceIndex(index_type dwIndex) + { + CWriteLock locallock(m_cs); + m_indexes.emplace(dwIndex); + } + + void EraseIndex(index_type dwIndex) + { + CWriteLock locallock(m_cs); + m_indexes.erase(dwIndex); + } + +public: + CRingCache (DWORD dwSize = 0) + : m_pv (nullptr) + , m_dwSize (0) + , m_dwCount (0) + , m_dwCurSeq(0) + { + Reset(dwSize); + } + + ~CRingCache() + { + Reset(0); + } + +private: + CRingCache(const CRingCache&); + CRingCache operator = (const CRingCache&); + +private: + DWORD m_dwSize; + VTPTR* m_pv; + char pack1[PACK_SIZE_OF(VTPTR*)]; + volatile DWORD m_dwCurSeq; + char pack2[PACK_SIZE_OF(DWORD)]; + volatile DWORD m_dwCount; + char pack3[PACK_SIZE_OF(DWORD)]; + + CSimpleRWLock m_cs; + IndexSet m_indexes; +}; + +template T* const CRingCache::E_EMPTY = (T*)0x00; +template T* const CRingCache::E_LOCKED = (T*)0x01; +template T* const CRingCache::E_MAX_STATUS = (T*)0x0F; + +// ------------------------------------------------------------------------------------------------------------- // + +template class CRingCache2 +{ +public: + + enum EnGetResult {GR_FAIL = -1, GR_INVALID = 0, GR_VALID = 1}; + + typedef T* TPTR; + typedef volatile T* VTPTR; + + typedef unordered_set IndexSet; + typedef typename IndexSet::const_iterator IndexSetCI; + typedef typename IndexSet::iterator IndexSetI; + + static TPTR const E_EMPTY; + static TPTR const E_LOCKED; + static TPTR const E_MAX_STATUS; + static DWORD const MAX_SIZE; + +public: + + static index_type& INDEX_INC(index_type& dwIndex) {if(adjust_index) ++dwIndex; return dwIndex;} + static index_type& INDEX_DEC(index_type& dwIndex) {if(adjust_index) --dwIndex; return dwIndex;} + + index_type& INDEX_R2V(index_type& dwIndex) {dwIndex += *(m_px + dwIndex) * m_dwSize; return dwIndex;} + + BOOL INDEX_V2R(index_type& dwIndex) + { + index_type m = dwIndex % m_dwSize; + BYTE x = *(m_px + m); + + if(dwIndex / m_dwSize != x) + return FALSE; + + dwIndex = m; + return TRUE; + } + + +private: + + VTPTR& INDEX_VAL(index_type dwIndex) {return *(m_pv + dwIndex);} + +public: + + BOOL Put(TPTR pElement, index_type& dwIndex) + { + ASSERT(pElement != nullptr); + + if(!IsValid()) return FALSE; + + BOOL isOK = FALSE; + + while(true) + { + if(!HasSpace()) + break; + + DWORD dwCurSeq = m_dwCurSeq; + index_type dwCurIndex = dwCurSeq % m_dwSize; + VTPTR& pValue = INDEX_VAL(dwCurIndex); + + if(pValue == E_EMPTY) + { + if(::InterlockedCompareExchangePointer(&pValue, pElement, E_EMPTY) == E_EMPTY) + { + ::InterlockedIncrement(&m_dwCount); + ::InterlockedCompareExchange(&m_dwCurSeq, dwCurSeq + 1, dwCurSeq); + + dwIndex = INDEX_INC(INDEX_R2V(dwCurIndex)); + isOK = TRUE; + + if(pElement != E_LOCKED) + EmplaceIndex(dwIndex); + + break; + } + } + + ::InterlockedCompareExchange(&m_dwCurSeq, dwCurSeq + 1, dwCurSeq); + } + + return isOK; + } + + EnGetResult Get(index_type dwIndex, TPTR* ppElement, index_type* pdwRealIndex = nullptr) + { + ASSERT(ppElement != nullptr); + + if(!IsValid() || !INDEX_V2R(INDEX_DEC(dwIndex))) + { + *ppElement = nullptr; + return GR_FAIL; + } + + *ppElement = (TPTR)INDEX_VAL(dwIndex); + if(pdwRealIndex) *pdwRealIndex = dwIndex; + + return IsValidElement(*ppElement) ? GR_VALID : GR_INVALID; + } + + BOOL Set(index_type dwIndex, TPTR pElement, TPTR* ppOldElement = nullptr, index_type* pdwRealIndex = nullptr) + { + TPTR pElement2 = nullptr; + + if(pdwRealIndex == nullptr) + pdwRealIndex = CreateLocalObject(index_type); + + if(Get(dwIndex, &pElement2, pdwRealIndex) == GR_FAIL) + return FALSE; + + if(ppOldElement != nullptr) + *ppOldElement = pElement2; + + if(pElement == pElement2) + return FALSE; + + int f1 = 0; + int f2 = 0; + + if(pElement == E_EMPTY) + { + if(pElement2 == E_LOCKED) + f1 = -1; + else + f1 = f2 = -1; + } + else if(pElement == E_LOCKED) + { + if(pElement2 == E_EMPTY) + f1 = 1; + else + f2 = -1; + } + else + { + if(pElement2 == E_EMPTY) + f1 = f2 = 1; + else if(pElement2 == E_LOCKED) + f2 = 1; + } + + BOOL bSetValueFirst = (f1 + f2 >= 0); + index_type dwRealIndex = *pdwRealIndex; + + if(bSetValueFirst) INDEX_VAL(dwRealIndex) = pElement; + if(f1 > 0) ::InterlockedIncrement(&m_dwCount); + if(f2 != 0) (f2 > 0) ? EmplaceIndex(dwIndex) : EraseIndex(dwIndex); + if(f1 < 0) {::InterlockedDecrement(&m_dwCount); ++(*(m_px + dwRealIndex));} + if(!bSetValueFirst) INDEX_VAL(dwRealIndex) = pElement; + + ASSERT(Spaces() <= Size()); + + return TRUE; + } + + BOOL Remove(index_type dwIndex, TPTR* ppElement = nullptr) + { + return Set(dwIndex, E_EMPTY, ppElement); + } + + BOOL AcquireLock(index_type& dwIndex) + { + return Put(E_LOCKED, dwIndex); + } + + BOOL ReleaseLock(index_type dwIndex, TPTR pElement) + { + ASSERT(pElement == nullptr || IsValidElement(pElement)); + + TPTR pElement2 = nullptr; + Get(dwIndex, &pElement2); + + ASSERT(pElement2 == E_LOCKED); + + if(pElement2 != E_LOCKED) + return FALSE; + + return Set(dwIndex, pElement); + } + +public: + + void Reset(DWORD dwSize = 0) + { + if(IsValid()) + Destroy(); + if(dwSize > 0) + Create(dwSize); + } + + BOOL GetAllElementIndexes(index_type ids[], DWORD& dwCount, BOOL bCopy = TRUE) + { + DWORD dwSize = Elements(); + + if(ids == nullptr || dwCount == 0) + { + dwCount = dwSize; + return FALSE; + } + + if(dwSize == 0) + { + dwCount = 0; + return TRUE; + } + + IndexSet* pIndexes = &m_indexes; + + if(bCopy) + { + pIndexes = new IndexSet; + CopyIndexes(*pIndexes); + } + + DWORD i = 0; + + for(auto it = pIndexes->begin(), end = pIndexes->end(); i < dwCount && it != end; ++i, ++it) + ids[i] = *it; + + if(bCopy) delete pIndexes; + + dwCount = i; + return TRUE; + } + + unique_ptr GetAllElementIndexes(DWORD& dwCount, BOOL bCopy = TRUE) + { + dwCount = (DWORD)m_indexes.size(); + unique_ptr ids(new index_type[dwCount]); + + if(dwCount > 0) + GetAllElementIndexes(ids.get(), dwCount, bCopy); + + return ids; + } + + IndexSet& CopyIndexes(IndexSet& indexes) + { + { + CReadLock locallock(m_cs); + indexes = m_indexes; + } + + return indexes; + } + + static BOOL IsValidElement(TPTR pElement) {return pElement > E_MAX_STATUS;} + + IndexSet& Indexes () {return m_indexes;} + DWORD Size () {return m_dwSize;} + DWORD Elements () {return (DWORD)m_indexes.size();} + DWORD Spaces () {return m_dwSize - m_dwCount;} + BOOL HasSpace () {return m_dwCount < m_dwSize;} + BOOL IsEmpty () {return m_dwCount == 0;} + BOOL IsValid () {return m_pv != nullptr;} + +private: + + void Create(DWORD dwSize) + { + ASSERT(!IsValid() && dwSize > 0 && dwSize <= MAX_SIZE); + + m_dwCurSeq = 0; + m_dwCount = 0; + m_dwSize = dwSize; + m_pv = (VTPTR*)malloc(m_dwSize * sizeof(TPTR)); + m_px = (BYTE*)malloc(m_dwSize * sizeof(BYTE)); + + ::ZeroMemory(m_pv, m_dwSize * sizeof(TPTR)); + ::ZeroMemory(m_px, m_dwSize * sizeof(BYTE)); + } + + void Destroy() + { + ASSERT(IsValid()); + + m_indexes.clear(); + free((void*)m_pv); + free((void*)m_px); + + m_pv = nullptr; + m_px = nullptr; + m_dwSize = 0; + m_dwCount = 0; + m_dwCurSeq = 0; + } + + void EmplaceIndex(index_type dwIndex) + { + CWriteLock locallock(m_cs); + m_indexes.emplace(dwIndex); + } + + void EraseIndex(index_type dwIndex) + { + CWriteLock locallock(m_cs); + m_indexes.erase(dwIndex); + } + +public: + CRingCache2 (DWORD dwSize = 0) + : m_pv (nullptr) + , m_px (nullptr) + , m_dwSize (0) + , m_dwCount (0) + , m_dwCurSeq(0) + { + Reset(dwSize); + } + + ~CRingCache2() + { + Reset(0); + } + + DECLARE_NO_COPY_CLASS(CRingCache2) + +private: + DWORD m_dwSize; + VTPTR* m_pv; + char pack1[PACK_SIZE_OF(VTPTR*)]; + BYTE* m_px; + char pack2[PACK_SIZE_OF(BYTE*)]; + volatile DWORD m_dwCurSeq; + char pack3[PACK_SIZE_OF(DWORD)]; + volatile DWORD m_dwCount; + char pack4[PACK_SIZE_OF(DWORD)]; + + CSimpleRWLock m_cs; + IndexSet m_indexes; +}; + +template T* const CRingCache2::E_EMPTY = (T*)0x00; +template T* const CRingCache2::E_LOCKED = (T*)0x01; +template T* const CRingCache2::E_MAX_STATUS = (T*)0x0F; + +template DWORD const CRingCache2::MAX_SIZE = +#if __WORDSIZE == 32 + 0x00FFFFFF +#else + 0xFFFFFFFF +#endif + ; +// ------------------------------------------------------------------------------------------------------------- // + +template class CRingPool +{ +private: + + typedef T* TPTR; + typedef volatile T* VTPTR; + + static TPTR const E_EMPTY; + static TPTR const E_LOCKED; + static TPTR const E_MAX_STATUS; + +private: + + VTPTR& INDEX_VAL(DWORD dwIndex) {return *(m_pv + dwIndex);} + +public: + + BOOL TryPut(TPTR pElement) + { + ASSERT(pElement != nullptr); + + if(!IsValid()) return FALSE; + + BOOL isOK = FALSE; + + for(DWORD i = 0; i < m_dwSize; i++) + { + DWORD seqPut = m_seqPut; + + if(!HasPutSpace(seqPut)) + break; + + DWORD dwIndex = seqPut % m_dwSize; + VTPTR& pValue = INDEX_VAL(dwIndex); + TPTR pCurrent = (TPTR)pValue; + + if(pCurrent == E_EMPTY) + { + if(::InterlockedCompareExchangePointer(&pValue, pElement, pCurrent) == pCurrent) + { + ::InterlockedCompareExchange(&m_seqPut, seqPut + 1, seqPut); + + isOK = TRUE; + + break; + } + } + + ::InterlockedCompareExchange(&m_seqPut, seqPut + 1, seqPut); + } + + return isOK; + } + + BOOL TryGet(TPTR* ppElement) + { + ASSERT(ppElement != nullptr); + + if(!IsValid()) return FALSE; + + BOOL isOK = FALSE; + + while(true) + { + DWORD seqGet = m_seqGet; + + if(!HasGetSpace(seqGet)) + break; + + DWORD dwIndex = seqGet % m_dwSize; + VTPTR& pValue = INDEX_VAL(dwIndex); + TPTR pCurrent = (TPTR)pValue; + + if(pCurrent > E_MAX_STATUS) + { + if(::InterlockedCompareExchangePointer(&pValue, E_EMPTY, pCurrent) == pCurrent) + { + ::InterlockedCompareExchange(&m_seqGet, seqGet + 1, seqGet); + + *(ppElement) = pCurrent; + isOK = TRUE; + + break; + } + } + + ::InterlockedCompareExchange(&m_seqGet, seqGet + 1, seqGet); + } + + return isOK; + } + + BOOL TryLock(TPTR* ppElement, DWORD& dwIndex) + { + ASSERT(ppElement != nullptr); + + if(!IsValid()) return FALSE; + + BOOL isOK = FALSE; + + while(true) + { + DWORD seqGet = m_seqGet; + + if(!HasGetSpace(seqGet)) + break; + + dwIndex = seqGet % m_dwSize; + VTPTR& pValue = INDEX_VAL(dwIndex); + TPTR pCurrent = (TPTR)pValue; + + if(pCurrent > E_MAX_STATUS) + { + if(::InterlockedCompareExchangePointer(&pValue, E_LOCKED, pCurrent) == pCurrent) + { + ::InterlockedCompareExchange(&m_seqGet, seqGet + 1, seqGet); + + *(ppElement) = pCurrent; + isOK = TRUE; + + break; + } + } + + ::InterlockedCompareExchange(&m_seqGet, seqGet + 1, seqGet); + } + + return isOK; + } + + BOOL ReleaseLock(TPTR pElement, DWORD dwIndex) + { + ASSERT(dwIndex < m_dwSize); + ASSERT(pElement == nullptr || pElement > E_MAX_STATUS); + + if(!IsValid()) return FALSE; + + VTPTR& pValue = INDEX_VAL(dwIndex); + ENSURE(pValue == E_LOCKED); + + if(pElement == nullptr) + pValue = E_EMPTY; + else + pValue = pElement; + + return TRUE; + } + +public: + + void Reset(DWORD dwSize = 0) + { + if(IsValid()) + Destroy(); + if(dwSize > 0) + Create(dwSize); + } + + void Clear() + { + for(DWORD dwIndex = 0; dwIndex < m_dwSize; dwIndex++) + { + VTPTR& pValue = INDEX_VAL(dwIndex); + + if(pValue > E_MAX_STATUS) + { + T::Destruct((TPTR)pValue); + pValue = E_EMPTY; + } + } + + Reset(); + } + + DWORD Size() {return m_dwSize;} + DWORD Elements() {return m_seqPut - m_seqGet;} + BOOL IsFull() {return Elements() == Size();} + BOOL IsEmpty() {return Elements() == 0;} + BOOL IsValid() {return m_pv != nullptr;} + +private: + + BOOL HasPutSpace(DWORD seqPut) + { + return ((int)(seqPut - m_seqGet) < (int)m_dwSize); + } + + BOOL HasGetSpace(DWORD seqGet) + { + return ((int)(m_seqPut - seqGet) > 0); + } + + void Create(DWORD dwSize) + { + ASSERT(!IsValid() && dwSize > 0); + + m_seqPut = 0; + m_seqGet = 0; + m_dwSize = dwSize; + m_pv = (VTPTR*)malloc(m_dwSize * sizeof(TPTR)); + + ::ZeroMemory(m_pv, m_dwSize * sizeof(TPTR)); + } + + void Destroy() + { + ASSERT(IsValid()); + + free((void*)m_pv); + m_pv = nullptr; + m_dwSize = 0; + m_seqPut = 0; + m_seqGet = 0; + } + +public: + CRingPool(DWORD dwSize = 0) + : m_pv(nullptr) + , m_dwSize(0) + , m_seqPut(0) + , m_seqGet(0) + { + Reset(dwSize); + } + + ~CRingPool() + { + Reset(0); + } + +private: + CRingPool(const CRingPool&); + CRingPool operator = (const CRingPool&); + +private: + DWORD m_dwSize; + VTPTR* m_pv; + char pack1[PACK_SIZE_OF(VTPTR*)]; + volatile DWORD m_seqPut; + char pack2[PACK_SIZE_OF(DWORD)]; + volatile DWORD m_seqGet; + char pack3[PACK_SIZE_OF(DWORD)]; +}; + +template T* const CRingPool::E_EMPTY = (T*)0x00; +template T* const CRingPool::E_LOCKED = (T*)0x01; +template T* const CRingPool::E_MAX_STATUS = (T*)0x0F; + +// ------------------------------------------------------------------------------------------------------------- // + +template class CCASQueueX +{ +private: + struct Node; + typedef Node* NPTR; + typedef volatile Node* VNPTR; + typedef volatile UINT VUINT; + + struct Node + { + T* pValue; + VNPTR pNext; + + Node(T* val, NPTR next = nullptr) + : pValue(val), pNext(next) + { + + } + }; + +public: + + void PushBack(T* pVal) + { + ASSERT(pVal != nullptr); + + VNPTR pTail = nullptr; + NPTR pNode = new Node(pVal); + + while(true) + { + pTail = m_pTail; + + if(::InterlockedCompareExchangePointer(&m_pTail, pNode, pTail) == pTail) + { + pTail->pNext = pNode; + break; + } + } + + ::InterlockedIncrement(&m_iSize); + } + + void UnsafePushBack(T* pVal) + { + ASSERT(pVal != nullptr); + + NPTR pNode = new Node(pVal); + m_pTail->pNext = pNode; + m_pTail = pNode; + + ::InterlockedIncrement(&m_iSize); + } + + BOOL PopFront(T** ppVal) + { + ASSERT(ppVal != nullptr); + + if(IsEmpty()) + return FALSE; + + BOOL isOK = FALSE; + NPTR pHead = nullptr; + NPTR pNext = nullptr; + + while(true) + { + Lock(); + + pHead = (NPTR)m_pHead; + pNext = (NPTR)pHead->pNext; + + if(pNext == nullptr) + { + Unlock(); + break; + } + + *ppVal = pNext->pValue; + m_pHead = pNext; + + Unlock(); + + isOK = TRUE; + + ::InterlockedDecrement(&m_iSize); + + delete pHead; + break; + } + + return isOK; + } + + BOOL UnsafePopFront(T** ppVal) + { + if(!UnsafePeekFront(ppVal)) + return FALSE; + + UnsafePopFrontNotCheck(); + + return TRUE; + } + + BOOL UnsafePeekFront(T** ppVal) + { + ASSERT(ppVal != nullptr); + + NPTR pNext = (NPTR)m_pHead->pNext; + + if(pNext == nullptr) + return FALSE; + + *ppVal = pNext->pValue; + + return TRUE; + } + + void UnsafePopFrontNotCheck() + { + NPTR pHead = (NPTR)m_pHead; + NPTR pNext = (NPTR)pHead->pNext; + m_pHead = pNext; + + ::InterlockedDecrement(&m_iSize); + + delete pHead; + } + + void UnsafeClear() + { + ASSERT(m_pHead != nullptr); + + m_dwCheckTime = 0; + + while(m_pHead->pNext != nullptr) + UnsafePopFrontNotCheck(); + } + +public: + + UINT Size() {return m_iSize;} + BOOL IsEmpty() {return m_iSize == 0;} + + void Lock() {while(!TryLock()) ::YieldProcessor();} + void Unlock() {m_iLock = 0;} + BOOL TryLock() {return (::InterlockedCompareExchange(&m_iLock, 1u, 0u) == 0);} + + DWORD GetCheckTime() + { + return m_dwCheckTime; + } + + void UpdateCheckTime(DWORD dwCurrent = 0) + { + if(dwCurrent == 0) + dwCurrent = ::TimeGetTime(); + + m_dwCheckTime = dwCurrent; + } + + int GetCheckTimeGap(DWORD dwCurrent = 0) + { + int rs = (int)GetTimeGap32(m_dwCheckTime, dwCurrent); + + if(rs < -60 * 1000) + rs = MAXINT; + + return rs; + } + +public: + + CCASQueueX() : m_iLock(0), m_iSize(0), m_dwCheckTime(0) + { + m_pHead = m_pTail = new Node(nullptr); + } + + ~CCASQueueX() + { + ASSERT(m_iLock == 0); + ASSERT(m_iSize == 0); + ASSERT(m_pTail == m_pHead); + ASSERT(m_pHead != nullptr); + ASSERT(m_pHead->pNext == nullptr); + + UnsafeClear(); + + delete m_pHead; + } + + DECLARE_NO_COPY_CLASS(CCASQueueX) + +private: + VUINT m_iLock; + VUINT m_iSize; + VNPTR m_pHead; + VNPTR m_pTail; + + volatile DWORD m_dwCheckTime; +}; + +template class CCASSimpleQueueX +{ +private: + struct Node; + typedef Node* NPTR; + typedef volatile Node* VNPTR; + typedef volatile UINT VUINT; + + struct Node + { + T tValue; + VNPTR pNext; + + Node(T val, NPTR next = nullptr) + : tValue(val), pNext(next) + { + + } + }; + +public: + + void PushBack(T tVal) + { + VNPTR pTail = nullptr; + NPTR pNode = new Node(tVal); + + while(true) + { + pTail = m_pTail; + + if(::InterlockedCompareExchangePointer(&m_pTail, pNode, pTail) == pTail) + { + pTail->pNext = pNode; + break; + } + } + + ::InterlockedIncrement(&m_iSize); + } + + void UnsafePushBack(T tVal) + { + NPTR pNode = new Node(tVal); + m_pTail->pNext = pNode; + m_pTail = pNode; + + ::InterlockedIncrement(&m_iSize); + } + + BOOL PopFront(T* ptVal) + { + ASSERT(ptVal != nullptr); + + if(IsEmpty()) + return FALSE; + + BOOL isOK = FALSE; + NPTR pHead = nullptr; + NPTR pNext = nullptr; + + while(true) + { + Lock(); + + pHead = (NPTR)m_pHead; + pNext = (NPTR)pHead->pNext; + + if(pNext == nullptr) + { + Unlock(); + break; + } + + *ptVal = pNext->tValue; + m_pHead = pNext; + + Unlock(); + + isOK = TRUE; + + ::InterlockedDecrement(&m_iSize); + + delete pHead; + break; + } + + return isOK; + } + + BOOL UnsafePopFront(T* ptVal) + { + if(!UnsafePeekFront(ptVal)) + return FALSE; + + UnsafePopFrontNotCheck(); + + return TRUE; + } + + BOOL UnsafePeekFront(T* ptVal) + { + ASSERT(ptVal != nullptr); + + NPTR pNext = (NPTR)m_pHead->pNext; + + if(pNext == nullptr) + return FALSE; + + *ptVal = pNext->pValue; + + return TRUE; + } + + void UnsafePopFrontNotCheck() + { + NPTR pHead = (NPTR)m_pHead; + NPTR pNext = (NPTR)pHead->pNext; + m_pHead = pNext; + + ::InterlockedDecrement(&m_iSize); + + delete pHead; + } + + void UnsafeClear() + { + ASSERT(m_pHead != nullptr); + + m_dwCheckTime = 0; + + while(m_pHead->pNext != nullptr) + UnsafePopFrontNotCheck(); + } + +public: + + UINT Size() {return m_iSize;} + BOOL IsEmpty() {return m_iSize == 0;} + + void Lock() {while(!TryLock()) ::YieldProcessor();} + void Unlock() {m_iLock = 0;} + BOOL TryLock() {return (::InterlockedCompareExchange(&m_iLock, 1u, 0u) == 0);} + + DWORD GetCheckTime() + { + return m_dwCheckTime; + } + + void UpdateCheckTime(DWORD dwCurrent = 0) + { + if(dwCurrent == 0) + dwCurrent = ::TimeGetTime(); + + m_dwCheckTime = dwCurrent; + } + + int GetCheckTimeGap(DWORD dwCurrent = 0) + { + int rs = (int)GetTimeGap32(m_dwCheckTime, dwCurrent); + + if(rs < -60 * 1000) + rs = MAXINT; + + return rs; + } + +public: + + CCASSimpleQueueX() : m_iLock(0), m_iSize(0), m_dwCheckTime(0) + { + m_pHead = m_pTail = new Node(0); + } + + ~CCASSimpleQueueX() + { + ASSERT(m_iLock == 0); + ASSERT(m_iSize == 0); + ASSERT(m_pTail == m_pHead); + ASSERT(m_pHead != nullptr); + ASSERT(m_pHead->pNext == nullptr); + + UnsafeClear(); + + delete m_pHead; + } + + DECLARE_NO_COPY_CLASS(CCASSimpleQueueX) + +private: + VUINT m_iLock; + VUINT m_iSize; + VNPTR m_pHead; + VNPTR m_pTail; + + volatile DWORD m_dwCheckTime; +}; + +template class CCASQueueY +{ +public: + + void PushBack(T* pVal) + { + CCriSecLock locallock(m_csGuard); + + UnsafePushBack(pVal); + } + + void UnsafePushBack(T* pVal) + { + ASSERT(pVal != nullptr); + + m_lsItems.push_back(pVal); + } + + void PushFront(T* pVal) + { + CCriSecLock locallock(m_csGuard); + + UnsafePushFront(pVal); + } + + void UnsafePushFront(T* pVal) + { + ASSERT(pVal != nullptr); + + m_lsItems.push_front(pVal); + } + + BOOL PopFront(T** ppVal) + { + CCriSecLock locallock(m_csGuard); + + return UnsafePopFront(ppVal); + } + + BOOL UnsafePopFront(T** ppVal) + { + if(!UnsafePeekFront(ppVal)) + return FALSE; + + UnsafePopFrontNotCheck(); + + return TRUE; + } + + BOOL PeekFront(T** ppVal) + { + CCriSecLock locallock(m_csGuard); + + return UnsafePeekFront(ppVal); + } + + BOOL UnsafePeekFront(T** ppVal) + { + ASSERT(ppVal != nullptr); + + if(m_lsItems.empty()) + return FALSE; + + *ppVal = m_lsItems.front(); + + return TRUE; + } + + void UnsafePopFrontNotCheck() + { + m_lsItems.pop_front(); + } + + void Clear() + { + CCriSecLock locallock(m_csGuard); + + UnsafeClear(); + } + + void UnsafeClear() + { + m_dwCheckTime = 0; + + m_lsItems.clear(); + } + +public: + + ULONG Size() {return (ULONG)m_lsItems.size();} + BOOL IsEmpty() {return (BOOL)m_lsItems.empty();} + + void Lock() {m_csGuard.lock();} + void Unlock() {m_csGuard.unlock();} + BOOL TryLock() {return m_csGuard.try_lock();} + CCriSec& Guard(){return m_csGuard;} + + DWORD GetCheckTime() + { + return m_dwCheckTime; + } + + void UpdateCheckTime(DWORD dwCurrent = 0) + { + if(dwCurrent == 0) + dwCurrent = ::TimeGetTime(); + + m_dwCheckTime = dwCurrent; + } + + int GetCheckTimeGap(DWORD dwCurrent = 0) + { + int rs = (int)GetTimeGap32(m_dwCheckTime, dwCurrent); + + if(rs < -60 * 1000) + rs = MAXINT; + + return rs; + } + +public: + + CCASQueueY() + : m_dwCheckTime(0) + { + + } + + ~CCASQueueY() + { + ASSERT(IsEmpty()); + + UnsafeClear(); + } + + DECLARE_NO_COPY_CLASS(CCASQueueY) + +private: + CCriSec m_csGuard; + deque m_lsItems; + + volatile DWORD m_dwCheckTime; +}; + +template class CCASSimpleQueueY +{ +public: + + void PushBack(T tVal) + { + CCriSecLock locallock(m_csGuard); + + UnsafePushBack(tVal); + } + + void UnsafePushBack(T tVal) + { + m_lsItems.push_back(tVal); + } + + void PushFront(T tVal) + { + CCriSecLock locallock(m_csGuard); + + UnsafePushFront(tVal); + } + + void UnsafePushFront(T tVal) + { + m_lsItems.push_front(tVal); + } + + BOOL PopFront(T* ptVal) + { + CCriSecLock locallock(m_csGuard); + + return UnsafePopFront(ptVal); + } + + BOOL UnsafePopFront(T* ptVal) + { + if(!UnsafePeekFront(ptVal)) + return FALSE; + + UnsafePopFrontNotCheck(); + + return TRUE; + } + + BOOL PeekFront(T* ptVal) + { + CCriSecLock locallock(m_csGuard); + + return UnsafePeekFront(ptVal); + } + + BOOL UnsafePeekFront(T* ptVal) + { + ASSERT(ptVal != nullptr); + + if(m_lsItems.empty()) + return FALSE; + + *ptVal = m_lsItems.front(); + + return TRUE; + } + + void UnsafePopFrontNotCheck() + { + m_lsItems.pop_front(); + } + + void Clear() + { + CCriSecLock locallock(m_csGuard); + + UnsafeClear(); + } + + void UnsafeClear() + { + m_dwCheckTime = 0; + + m_lsItems.clear(); + } + +public: + + ULONG Size() {return (ULONG)m_lsItems.size();} + BOOL IsEmpty() {return (BOOL)m_lsItems.empty();} + + void Lock() {m_csGuard.lock();} + void Unlock() {m_csGuard.unlock();} + BOOL TryLock() {return m_csGuard.try_lock();} + CCriSec& Guard(){return m_csGuard;} + + DWORD GetCheckTime() + { + return m_dwCheckTime; + } + + void UpdateCheckTime(DWORD dwCurrent = 0) + { + if(dwCurrent == 0) + dwCurrent = ::TimeGetTime(); + + m_dwCheckTime = dwCurrent; + } + + int GetCheckTimeGap(DWORD dwCurrent = 0) + { + int rs = (int)GetTimeGap32(m_dwCheckTime, dwCurrent); + + if(rs < -60 * 1000) + rs = MAXINT; + + return rs; + } + +public: + + CCASSimpleQueueY() + : m_dwCheckTime(0) + { + + } + + ~CCASSimpleQueueY() + { + ASSERT(IsEmpty()); + + UnsafeClear(); + } + + DECLARE_NO_COPY_CLASS(CCASSimpleQueueY) + +private: + CCriSec m_csGuard; + deque m_lsItems; + + volatile DWORD m_dwCheckTime; +}; + +template using CCASQueue = CCASQueueX; +template using CCASSimpleQueue = CCASSimpleQueueX; + +template +void ReleaseGCObj(CCASQueue& lsGC, DWORD dwLockTime, BOOL bForce = FALSE) +{ + static const int MIN_CHECK_INTERVAL = 1 * 1000; + static const int MAX_CHECK_INTERVAL = 15 * 1000; + + T* pObj = nullptr; + + if(bForce) + { + CLocalLock> locallock(lsGC); + + while(lsGC.UnsafePeekFront(&pObj)) + { + lsGC.UnsafePopFrontNotCheck(); + T::Destruct(pObj); + } + } + else + { + if(lsGC.IsEmpty() || lsGC.GetCheckTimeGap() < MAX(MIN((int)(dwLockTime / 3), MAX_CHECK_INTERVAL), MIN_CHECK_INTERVAL)) + return; + + T* pFirst = nullptr; + BOOL bFirst = TRUE; + DWORD now = 0; + + while(TRUE) + { + ASSERT((pObj = nullptr) == nullptr); + + { + CLocalTryLock> locallock(lsGC); + + if(!locallock.IsValid()) + break; + + if(bFirst) + { + bFirst = FALSE; + now = ::TimeGetTime(); + + lsGC.UpdateCheckTime(now); + } + + if(!lsGC.UnsafePeekFront(&pObj)) + break; + + if((int)(now - pObj->GetFreeTime()) < (int)dwLockTime) + break; + + lsGC.UnsafePopFrontNotCheck(); + + if(pObj->GetCount() > 0) + { + lsGC.PushBack(pObj); + + if(pFirst == nullptr) + pFirst = pObj; + else if(pFirst == pObj) + break; + + continue; + } + } + + ASSERT(pObj != nullptr); + T::Destruct(pObj); + } + } +} + +#if __WORDSIZE == 32 + #pragma pack(pop) +#endif diff --git a/common/STLHelper.h b/common/STLHelper.h new file mode 100644 index 0000000..675e514 --- /dev/null +++ b/common/STLHelper.h @@ -0,0 +1,1068 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#pragma once + +#include "hpsocket/GlobalDef.h" +#include "Singleton.h" +#include "StringT.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define hash_set unordered_set +#define hash_map unordered_map +#define hash_multimap unordered_multimap + +using namespace std; + +typedef list short_list; +typedef list int_list; +typedef list long_list; +typedef list int64_list; +typedef list ushort_list; +typedef list uint_list; +typedef list ulong_list; +typedef list uint64_list; +typedef list float_list; +typedef list double_list; +typedef stack short_stack; +typedef stack int_stack; +typedef stack long_stack; +typedef stack int64_stack; +typedef stack ushort_stack; +typedef stack uint_stack; +typedef stack ulong_stack; +typedef stack uint64_stack; +typedef stack float_stack; +typedef stack double_stack; +typedef queue short_queue; +typedef queue int_queue; +typedef queue long_queue; +typedef queue int64_queue; +typedef queue ushort_queue; +typedef queue uint_queue; +typedef queue ulong_queue; +typedef queue uint64_queue; +typedef queue float_queue; +typedef queue double_queue; +typedef deque short_deque; +typedef deque int_deque; +typedef deque long_deque; +typedef deque int64_deque; +typedef deque ushort_deque; +typedef deque uint_deque; +typedef deque ulong_deque; +typedef deque uint64_deque; +typedef deque float_deque; +typedef deque double_deque; +typedef vector short_vector; +typedef vector int_vector; +typedef vector long_vector; +typedef vector int64_vector; +typedef vector ushort_vector; +typedef vector uint_vector; +typedef vector ulong_vector; +typedef vector uint64_vector; +typedef vector float_vector; +typedef vector double_vector; +typedef set short_set; +typedef set int_set; +typedef set long_set; +typedef set int64_set; +typedef set ushort_set; +typedef set uint_set; +typedef set ulong_set; +typedef set uint64_set; +typedef set float_set; +typedef set double_set; +typedef hash_set short_hash_set; +typedef hash_set int_hash_set; +typedef hash_set long_hash_set; +typedef hash_set int64_hash_set; +typedef hash_set ushort_hash_set; +typedef hash_set uint_hash_set; +typedef hash_set ulong_hash_set; +typedef hash_set uint64_hash_set; +typedef hash_set float_hash_set; +typedef hash_set double_hash_set; +typedef unordered_set short_unordered_set; +typedef unordered_set int_unordered_set; +typedef unordered_set long_unordered_set; +typedef unordered_set int64_unordered_set; +typedef unordered_set ushort_unordered_set; +typedef unordered_set uint_unordered_set; +typedef unordered_set ulong_unordered_set; +typedef unordered_set uint64_unordered_set; +typedef unordered_set float_unordered_set; +typedef unordered_set double_unordered_set; + +typedef list int_ptr_list; +typedef list long_ptr_list; +typedef list uint_ptr_list; +typedef list ulong_ptr_list; +typedef stack int_ptr_stack; +typedef stack long_ptr_stack; +typedef stack uint_ptr_stack; +typedef stack ulong_ptr_stack; +typedef queue int_ptr_queue; +typedef queue long_ptr_queue; +typedef queue uint_ptr_queue; +typedef queue ulong_ptr_queue; +typedef deque int_ptr_deque; +typedef deque long_ptr_deque; +typedef deque uint_ptr_deque; +typedef deque ulong_ptr_deque; +typedef vector int_ptr_vector; +typedef vector long_ptr_vector; +typedef vector uint_ptr_vector; +typedef vector ulong_ptr_vector; +typedef set int_ptr_set; +typedef set long_ptr_set; +typedef set uint_ptr_set; +typedef set ulong_ptr_set; +typedef hash_set int_ptr_hash_set; +typedef hash_set long_ptr_hash_set; +typedef hash_set uint_ptr_hash_set; +typedef hash_set ulong_ptr_hash_set; +typedef unordered_set int_ptr_unordered_set; +typedef unordered_set long_ptr_unordered_set; +typedef unordered_set uint_ptr_unordered_set; +typedef unordered_set ulong_ptr_unordered_set; + +/*****************************************************************************/ +/******************************** 容器操作函数 *******************************/ + +/********************************** +描述: 清除普通集合 , 适用于 vector / list +参数: + v : vector / list / set + +返回值: +**********************************/ +template void ClearSet(Set& v) +{ + v.clear(); +} + +template struct Set_Cleaner +{ + static void Clear(Set& v) {ClearSet(v);} +}; + +/********************************** +描述: 清除指针集合 (清除前先释放指针), 适用于 vector / list +参数: + v : vector / list / set + +返回值: +**********************************/ +template void ClearPtrSet(PtrSet& v) +{ + for(auto it = v.begin(), + end = v.end(); + it != end; + ++it) + delete (*it); + + v.clear(); +} + +template struct PtrSet_Cleaner +{ + static void Clear(PtrSet& v) {ClearPtrSet(v);} +}; + +/********************************** +描述: 清除指针集合 (指针同时又指向数组), 适用于 vector / list +参数: + v : vector / list / set + +返回值: +**********************************/ +template void ClearPtrArraySet(PtrArraySet& v) +{ + for(auto it = v.begin(), + end = v.end(); + it != end; + ++it) + delete[] (*it); + + v.clear(); +} + +template struct PtrArraySet_Cleaner +{ + static void Clear(PtrArraySet& v) {ClearPtrArraySet(v);} +}; + +/********************************** +描述: 清除普通影射 , 适用于 map +参数: + v : map + +返回值: +**********************************/ +template void ClearMap(Map& v) +{ + v.clear(); +} + +template struct Map_Cleaner +{ + static void Clear(Map& v) {ClearMap(v);} +}; + +/********************************** +描述: 清除指针影射 (清除前先释放指针), 适用于 map +参数: + v : map + +返回值: +**********************************/ +template void ClearPtrMap(PtrMap& v) +{ + for(auto it = v.begin(), + end = v.end(); + it != end; + ++it) + delete it->second; + + v.clear(); +} + +template struct PtrMap_Cleaner +{ + static void Clear(PtrMap& v) {ClearPtrMap(v);} +}; + +/********************************** +描述: 清除指针影射 (指针同时又指向数组), 适用于 map +参数: + v : map + +返回值: +**********************************/ +template void ClearPtrArrayMap(PtrArrayMap& v) +{ + for(auto it = v.begin(), + end = v.end(); + it != end; + ++it) + delete[] it->second; + + v.clear(); +} + +template struct PtrArrayMap_Cleaner +{ + static void Clear(PtrArrayMap& v) {ClearPtrArrayMap(v);} +}; + +/********************************** +描述: 清除集合-集合 (清除前先清除内部集合), 适用于 set*> +参数: + v : vector / list / set + +返回值: +**********************************/ +template void ClearSetSet(SetSet& v) +{ + for(auto it = v.begin(), + end = v.end(); + it != end; + ++it) + { + (*it)->clear(); + delete (*it); + } + + v.clear(); +} + +template struct SetSet_Cleaner +{ + static void Clear(SetSet& v) {ClearSetSet(v);} +}; + +/********************************** +描述: 清除指针集合-集合 (清除前先清除内部指针集合), 适用于 set*> +参数: + v : vector / list / set + +返回值: +**********************************/ +template void ClearPtrSetSet(PtrSetSet& v) +{ + for(auto it = v.begin(), + end = v.end(); + it != end; + ++it) + { + ClearPtrSet(**it); + delete (*it); + } + + v.clear(); +} + +template struct PtrSetSet_Cleaner +{ + static void Clear(PtrSetSet& v) {ClearPtrSetSet(v);} +}; + +/********************************** +描述: 清除指针数组集合影射 (清除前先清除指针数组集合), 适用于 map*> +参数: + v : vector / list / set + +返回值: +**********************************/ +template void ClearPtrArraySetSet(PtrArraySetSet& v) +{ + for(auto it = v.begin(), + end = v.end(); + it != end; + ++it) + { + ClearPtrArraySet(**it); + delete (*it); + } + + v.clear(); +} + +template struct PtrArraySetSet_Cleaner +{ + static void Clear(PtrArraySetSet& v) {ClearPtrArraySetSet(v);} +}; + +/********************************** +描述: 清除集合影射 (清除前先清除集合), 适用于 map*> +参数: + v : map + +返回值: +**********************************/ +template void ClearSetMap(SetMap& v) +{ + for(auto it = v.begin(), + end = v.end(); + it != end; + ++it) + { + it->second->clear(); + delete it->second; + } + + v.clear(); +} + +template struct SetMap_Cleaner +{ + static void Clear(SetMap& v) {ClearSetMap(v);} +}; + +/********************************** +描述: 清除指针集合影射 (清除前先清除指针集合), 适用于 map*> +参数: + v : map + +返回值: +**********************************/ +template void ClearPtrSetMap(PtrSetMap& v) +{ + for(auto it = v.begin(), + end = v.end(); + it != end; + ++it) + { + ClearPtrSet(*(it->second)); + delete it->second; + } + + v.clear(); +} + +template struct PtrSetMap_Cleaner +{ + static void Clear(PtrSetMap& v) {ClearPtrSetMap(v);} +}; + +/********************************** +描述: 清除指针数组集合影射 (清除前先清除指针数组集合), 适用于 map*> +参数: + v : map + +返回值: +**********************************/ +template void ClearPtrArraySetMap(PtrArraySetMap& v) +{ + for(auto it = v.begin(), + end = v.end(); + it != end; + ++it) + { + ClearPtrArraySet(*(it->second)); + delete it->second; + } + + v.clear(); +} + +template struct PtrArraySetMap_Cleaner +{ + static void Clear(PtrArraySetMap& v) {ClearPtrArraySetMap(v);} +}; + +/********************************** +描述: 清除映射-影射 (清除前先清除内部映射), 适用于 map*> +参数: +v : map + +返回值: +**********************************/ +template void ClearMapMap(MapMap& v) +{ + for(auto it = v.begin(), + end = v.end(); + it != end; + ++it) + { + it->second->clear(); + delete it->second; + } + + v.clear(); +} + +template struct MapMap_Cleaner +{ + static void Clear(MapMap& v) {ClearMapMap(v);} +}; + +/********************************** +描述: 清除指针映射-影射 (清除前先清除指针内部映射), 适用于 map*> +参数: + v : map + +返回值: +**********************************/ +template void ClearPtrMapMap(PtrMapMap& v) +{ + for(auto it = v.begin(), + end = v.end(); + it != end; + ++it) + { + ClearPtrMap(*(it->second)); + delete it->second; + } + + v.clear(); +} + +template struct PtrMapMap_Cleaner +{ + static void Clear(PtrMapMap& v) {ClearPtrMapMap(v);} +}; + +/********************************** +描述: 清除指针映射-影射 (清除前先清除指针数组内部映射), 适用于 map*> +参数: + v : map + +返回值: +**********************************/ +template void ClearPtrArrayMapMap(PtrArrayMapMap& v) +{ + for(auto it = v.begin(), + end = v.end(); + it != end; + ++it) + { + ClearPtrArrayMap(*(it->second)); + delete it->second; + } + + v.clear(); +} + +template struct PtrArrayMapMap_Cleaner +{ + static void Clear(PtrArrayMapMap& v) {ClearPtrArrayMapMap(v);} +}; + +/************************************************************************/ +/* 指针集合容器 */ +/************************************************************************/ +template struct SetWrapper +{ + typedef typename Set::iterator iterator; + typedef typename Set::const_iterator const_iterator; + typedef typename Set::value_type value_type; + typedef typename Set::reference reference; + typedef typename Set::const_reference const_reference; + typedef typename Set::pointer pointer; + typedef typename Set::const_pointer const_pointer; + typedef typename Set::size_type size_type; + typedef typename Set::difference_type difference_type; + + SetWrapper() + { + } + + virtual ~SetWrapper() + { + Clear(); + } + + void Clear() + { + if(!IsEmpty()) + { + Cleaner::Clear(m_set); + } + } + + Set& operator * () {return m_set;} + const Set& operator * () const {return m_set;} + Set* operator -> () {return &m_set;} + const Set* operator -> () const {return &m_set;} + Set& Get () {return m_set;} + operator Set& () {return m_set;} + bool IsEmpty () const {return m_set.empty();} + size_t Size () const {return m_set.size();} + +protected: + Set m_set; + + DECLARE_NO_COPY_CLASS(SetWrapper) +}; + +template struct VectorWrapper : public SetWrapper +{ + typedef SetWrapper __super; + typedef typename __super::reference reference; + typedef typename __super::const_reference const_reference; + typedef typename __super::size_type size_type; + + VectorWrapper() + { + } + + reference operator [] (size_type i) {return __super::m_set[i];} + const_reference operator [] (size_type i) const {return __super::m_set[i];} + + DECLARE_NO_COPY_CLASS(VectorWrapper) +}; + +/************************************************************************/ +/* 指针数组集合容器 */ +/************************************************************************/ + + +/************************************************************************/ +/* 指针映射容器 */ +/************************************************************************/ +template struct MapWrapper +{ + typedef typename Map::iterator iterator; + typedef typename Map::const_iterator const_iterator; + typedef typename Map::key_type key_type; + typedef typename Map::mapped_type mapped_type; + typedef typename Map::value_type value_type; + typedef typename Map::reference reference; + typedef typename Map::const_reference const_reference; + typedef typename Map::pointer pointer; + typedef typename Map::size_type size_type; + typedef typename Map::difference_type difference_type; + + MapWrapper() + { + } + + ~MapWrapper() + { + Clear(); + } + + void Clear() + { + if(!IsEmpty()) + { + Cleaner::Clear(m_map); + } + } + + Map& operator * () {return m_map;} + const Map& operator * () const {return m_map;} + Map* operator -> () {return &m_map;} + const Map* operator -> () const {return &m_map;} + mapped_type& operator [] (const key_type& key) {return m_map[key];} + const mapped_type& operator [] (const key_type& key) const {return m_map[key];} + Map& Get () {return m_map;} + operator Map& () {return m_map;} + bool IsEmpty () const {return m_map.empty();} + size_t Size () const {return m_map.size();} + +private: + Map m_map; + + DECLARE_NO_COPY_CLASS(MapWrapper) +}; + +/************************************************************************/ +/* 比较仿函数 */ +/************************************************************************/ + +template struct char_comparator +{ + typedef T row_type; + static row_type row_type_value(const T& v) {return (row_type)v;} + static bool equal_to(const T& v1, const T& v2) {return strcmp(v1, v2) == 0;} +}; + +template struct char_nc_comparator +{ + typedef T row_type; + static row_type row_type_value(const T& v) {return (row_type)v;} + static bool equal_to(const T& v1, const T& v2) {return stricmp(v1, v2) == 0;} +}; + +template struct wchar_comparator +{ + typedef T row_type; + static row_type row_type_value(const T& v) {return (row_type)v;} + static bool equal_to(const T& v1, const T& v2) {return wcscmp(v1, v2) == 0;} +}; + +template struct wchar_nc_comparator +{ + typedef T row_type; + static row_type row_type_value(const T& v) {return (row_type)v;} + static bool equal_to(const T& v1, const T& v2) {return wcsicmp(v1, v2) == 0;} +}; + +template struct cstring_comparator +{ + typedef typename T::PCXSTR row_type; + static row_type row_type_value(const T& v) {return (row_type)v;} + static bool equal_to(const T& v1, const T& v2) {return v1.Compare(v2) == 0;} +}; + +template struct cstring_nc_comparator +{ + typedef typename T::PCXSTR row_type; + static row_type row_type_value(const T& v) {return (row_type)v;} + static bool equal_to(const T& v1, const T& v2) {return v1.CompareNoCase(v2) == 0;} +}; + +// char/wchar_t/CStringX hash function +template struct str_hash_func_t +{ + struct hash + { + size_t operator() (const T& t) const + { + return hash_value(H::row_type_value(t)); + } + }; + + struct equal_to + { + bool operator() (const T& t1, const T& t2) const + { + return H::equal_to(t1, t2); + } + }; + +}; + +// char/wchar_t/CStringX hash function (no case) +template struct str_nc_hash_func_t +{ + struct hash + { + size_t operator() (const T& t) const + { + size_t _Val = 2166136261U; + typename H::row_type lpsz = H::row_type_value(t); + B c; + + while((c = *lpsz++) != 0) + { + if(c >= 'A' && c <= 'Z') + c = (char)(c + 32); + + _Val = 16777619U * _Val ^ c; + + } + + return _Val; + } + }; + + struct equal_to + { + bool operator() (const T& t1, const T& t2) const + { + return H::equal_to(t1, t2); + } + }; + +}; + +typedef str_hash_func_t> str_hash_func; +typedef str_hash_func_t> wstr_hash_func; +typedef str_hash_func_t> cstringa_hash_func; +typedef str_hash_func_t> cstringw_hash_func; +typedef str_nc_hash_func_t, char> str_nc_hash_func; +typedef str_nc_hash_func_t, wchar_t> wstr_nc_hash_func; +typedef str_nc_hash_func_t, char> cstringa_nc_hash_func; +typedef str_nc_hash_func_t, wchar_t> cstringw_nc_hash_func; + +#ifdef _UNICODE + typedef cstringw_hash_func cstring_hash_func; + typedef cstringw_nc_hash_func cstring_nc_hash_func; +#else + typedef cstringa_hash_func cstring_hash_func; + typedef cstringa_nc_hash_func cstring_nc_hash_func; +#endif + +struct bool_comp_func +{ + bool operator() (bool v1, bool v2) const + { + if(!v1) + return false; + if(v1 == v2) + return false; + + return true; + } +}; + +template +// T -> (signed / unsigned) short / int / long / int64_t +struct integer_comp_func +{ + bool operator() (T v1, T v2) const + { + return v1 < v2; + } +}; + +typedef integer_comp_func short_comp_func; +typedef integer_comp_func int_comp_func; +typedef integer_comp_func long_comp_func; +typedef integer_comp_func int64_comp_func; +typedef integer_comp_func ushort_comp_func; +typedef integer_comp_func uint_comp_func; +typedef integer_comp_func ulong_comp_func; +typedef integer_comp_func uint64_comp_func; + +struct float_comp_func +{ + bool operator() (float v1, float v2) const + { + float disc = v1 - v2; + if(fabsf(disc) < 1E-5) + return false; + + return disc < 0; + } +}; + +struct double_comp_func +{ + bool operator() (double v1, double v2) const + { + double disc = v1 - v2; + if(fabs(disc) < 1E-8) + return false; + + return disc < 0; + } +}; + +template +// T -> (unsigned) char / wchar_t +struct character_comp_func +{ + bool operator() (T v1, T v2) const + { + if(!CASE) + { + if(v1 >= 'A' && v1 <= 'Z') v1 += 32; + if(v2 >= 'A' && v2 <= 'Z') v2 += 32; + } + + return v1 < v2; + } +}; + +typedef character_comp_func char_case_comp_func; +typedef character_comp_func uchar_case_comp_func; +typedef character_comp_func wchar_case_comp_func; +typedef character_comp_func char_ucase_comp_func; +typedef character_comp_func uchar_ucase_comp_func; +typedef character_comp_func wchar_ucase_comp_func; + +template +// T -> TCHAR* / CString +struct str_comp_func +{ + //比较函数。 + bool operator() (const T &A, const T &B) const + { + if(!CASE) + return lstricmp((LPCTSTR)A, (LPCTSTR)B) < 0; + else + return lstrcmp((LPCTSTR)A, (LPCTSTR)B) < 0; + } +}; + +typedef str_comp_func case_tchar_comp_func; +typedef str_comp_func uncase_tchar_comp_func; +typedef str_comp_func case_string_comp_func; +typedef str_comp_func uncase_string_comp_func; +typedef case_tchar_comp_func tchar_ptr_case_comp_func; +typedef uncase_tchar_comp_func tchar_ptr_ucase_comp_func; +typedef case_string_comp_func string_case_comp_func; +typedef uncase_string_comp_func string_ucase_comp_func; +/************************************************************************/ +/* 排序仿函数 */ +/************************************************************************/ +template +struct bool_sort_func +{ + bool operator() (bool v1, bool v2) const + { + if(v1 == v2) + return false; + + bool result = !v1; + return ASC ? result : !result; + } +}; + +typedef bool_sort_func bool_asc_sort_func; +typedef bool_sort_func bool_desc_sort_func; + +template +// T -> (signed / unsigned) short / int / long / int64_t +struct integer_sort_func +{ + bool operator() (T v1, T v2) const + { + if(v1 == v2) + return false; + + bool result = v1 < v2; + return ASC ? result : !result; + } +}; + +typedef integer_sort_func short_asc_sort_func; +typedef integer_sort_func ushort_asc_sort_func; +typedef integer_sort_func int_asc_sort_func; +typedef integer_sort_func uint_asc_sort_func; +typedef integer_sort_func long_asc_sort_func; +typedef integer_sort_func ulong_asc_sort_func; +typedef integer_sort_func int64_asc_sort_func; +typedef integer_sort_func uint64_asc_sort_func; +typedef integer_sort_func short_desc_sort_func; +typedef integer_sort_func ushort_desc_sort_func; +typedef integer_sort_func int_desc_sort_func; +typedef integer_sort_func uint_desc_sort_func; +typedef integer_sort_func long_desc_sort_func; +typedef integer_sort_func ulong_desc_sort_func; +typedef integer_sort_func int64_desc_sort_func; +typedef integer_sort_func uint64_desc_sort_func; + +template +struct float_sort_func +{ + bool operator() (float v1, float v2) const + { + float disc = v1 - v2; + if(fabsf(disc) < 1E-5) + return false; + + bool result = disc < 0; + return ASC ? result : !result; + } +}; + +typedef float_sort_func float_asc_sort_func; +typedef float_sort_func float_desc_sort_func; + +template +struct double_sort_func +{ + bool operator() (double v1, double v2) const + { + double disc = v1 - v2; + if(fabs(disc) < 1E-8) + return false; + + bool result = disc < 0; + return ASC ? result : !result; + } +}; + +typedef double_sort_func double_asc_sort_func; +typedef double_sort_func double_desc_sort_func; + +template +// T -> (unsigned) char / wchar_t +struct character_sort_func +{ + bool operator() (T v1, T v2) const + { + if(!CASE) + { + if(v1 >= 'A' && v1 <= 'Z') v1 += 32; + if(v2 >= 'A' && v2 <= 'Z') v2 += 32; + } + + if(v1 == v2) + return false; + + bool result = v1 < v2; + return ASC ? result : !result; + } +}; + +typedef character_sort_func char_asc_case_sort_func; +typedef character_sort_func uchar_asc_case_sort_func; +typedef character_sort_func wchar_asc_case_sort_func; +typedef character_sort_func char_asc_ucase_sort_func; +typedef character_sort_func uchar_asc_ucase_sort_func; +typedef character_sort_func wchar_asc_ucase_sort_func; +typedef character_sort_func char_desc_case_sort_func; +typedef character_sort_func uchar_desc_case_sort_func; +typedef character_sort_func wchar_desc_case_sort_func; +typedef character_sort_func char_desc_ucase_sort_func; +typedef character_sort_func uchar_desc_ucase_sort_func; +typedef character_sort_func wchar_desc_ucase_sort_func; + +template +// T -> TCHAR* / CString +struct str_sort_func +{ + bool operator() (const T& v1, const T& v2) const + { + bool result; + + if(CASE) + { + int v = lstrcmp((LPCTSTR)v1, (LPCTSTR)v2); + if(v == 0) + result = false; + else + result = v < 0; + } + else + { + int v = tstricmp((LPCTSTR)v1, (LPCTSTR)v2); + if(v == 0) + result = false; + else + result = v < 0; + } + + return ASC ? result : !result; + } +}; + +typedef str_sort_func tchar_ptr_asc_case_sort_func; +typedef str_sort_func string_asc_case_sort_func; +typedef str_sort_func tchar_ptr_asc_ucase_sort_func; +typedef str_sort_func string_asc_ucase_sort_func; +typedef str_sort_func tchar_ptr_desc_case_sort_func; +typedef str_sort_func string_desc_case_sort_func; +typedef str_sort_func tchar_ptr_desc_ucase_sort_func; +typedef str_sort_func string_desc_ucase_sort_func; + +template::value>> +class CRandomIntegralT +{ +public: + _IntType Generate() {return dist(gen);} + _IntType operator()() {return Generate();} + +public: + CRandomIntegralT(_IntType from, _IntType to) : gen(rd()), dist(from, to) + { + + } + +private: + random_device rd; + mt19937 gen; + uniform_int_distribution<_IntType> dist; +}; + +template::value>> +class CRandomRealTypeT +{ +public: + _RealType Generate() {return dist(gen);} + _RealType operator()() {return Generate();} + +public: + CRandomRealTypeT(_RealType from, _RealType to) : gen(rd()), dist(from, to) + { + + } + +private: + random_device rd; + mt19937 gen; + uniform_real_distribution<_RealType> dist; +}; + +typedef CRandomIntegralT CRandomInt; +typedef CRandomIntegralT CRandomLong; +typedef CRandomIntegralT CRandomUint; +typedef CRandomIntegralT CRandomUlong; +typedef CRandomIntegralT CRandomInt64; +typedef CRandomIntegralT CRandomUint64; + +typedef CRandomRealTypeT CRandomFloat; +typedef CRandomRealTypeT CRandomDouble; diff --git a/common/Semaphore.h b/common/Semaphore.h new file mode 100644 index 0000000..0d897f8 --- /dev/null +++ b/common/Semaphore.h @@ -0,0 +1,117 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#pragma once + +#include "hpsocket/GlobalDef.h" +#include "CriSec.h" + +#include + +using namespace std; + +class CSEM +{ +public: + void Wait() + { + CMutexLock2 lock(m_mtx); + + m_cv.wait(lock); + } + + template + void Wait(_Predicate p) + { + CMutexLock2 lock(m_mtx); + + m_cv.wait(lock, p); + } + + template + cv_status WaitFor(const chrono::duration<_Rep, _Period>& t) + { + CMutexLock2 lock(m_mtx); + + return m_cv.wait_for(lock, t); + } + + cv_status WaitFor(DWORD dwMilliseconds) + { + return WaitFor(chrono::milliseconds(dwMilliseconds)); + } + + template + bool WaitFor(const chrono::duration<_Rep, _Period>& t, _Predicate p) + { + CMutexLock2 lock(m_mtx); + + return m_cv.wait_for(lock, t, p); + } + + template + bool WaitFor(DWORD dwMilliseconds, _Predicate p) + { + if(IS_INFINITE(dwMilliseconds)) + { + Wait(p); + return true; + } + + return WaitFor(chrono::milliseconds(dwMilliseconds), p); + + } + + void NotifyOne() + { + m_cv.notify_one(); + } + + void NotifyAll() + { + m_cv.notify_all(); + } + + void SyncNotifyOne() + { + CMutexLock2 lock(m_mtx); + + NotifyOne(); + } + + void SyncNotifyAll() + { + CMutexLock2 lock(m_mtx); + + NotifyAll(); + } + +private: + CMTX m_mtx; + condition_variable m_cv; + + DECLARE_NO_COPY_CLASS(CSEM) + DECLARE_PUBLIC_DEFAULT_CONSTRUCTOR(CSEM) +}; + +using CCVLock = CSEM; diff --git a/common/SignalHandler.h b/common/SignalHandler.h new file mode 100644 index 0000000..dd44ce2 --- /dev/null +++ b/common/SignalHandler.h @@ -0,0 +1,183 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#pragma once + +#include "hpsocket/GlobalDef.h" +#include "Thread.h" + +#include + +#include + +using namespace std; + +template class CSignalHandler +{ +public: + using MT = CSignalHandler; + using SI = siginfo_t; + using SS = sigset_t; + using CHandlerThread = CThread; + using F = VOID (T::*)(const SI*); + using SF = VOID (*)(const SI*); + using SSPTR = unique_ptr; + friend CHandlerThread; + +public: + + BOOL Setup(SF pFunc, const SS* pSigSet, BOOL bRestorOnCancel = TRUE) + { + return Setup((__CFakeRunnerClass_*)nullptr, *(F*)&pFunc, pSigSet, bRestorOnCancel); + } + + BOOL Setup(T* pRunner, F pFunc, const SS* pSigSet, BOOL bRestorOnCancel = TRUE) + { + ASSERT_CHECK_EINVAL(pSigSet != nullptr); + + m_pssPre = make_unique(); + int rs = pthread_sigmask(SIG_BLOCK, pSigSet, m_pssPre.get()); + + if(rs != NO_ERROR) + { + m_pssPre = nullptr; + ::SetLastError(rs); + + return FALSE; + } + + m_pRunner = pRunner; + m_pFunc = pFunc; + m_pssCur = make_unique(); + + ::CopyPlainObject(m_pssCur.get(), pSigSet); + + BOOL isOK = m_thHandler.Start(this, &MT::ThreadFunc, m_pssCur.get()); + + if(isOK && !bRestorOnCancel) + m_pssPre = nullptr; + else if(!isOK) + EXECUTE_RESTORE_ERROR(Reset()); + + return isOK; + } + + BOOL Cancel() + { + BOOL isOK = m_thHandler.IsRunning(); + + if(isOK) + { + isOK = m_thHandler.Interrupt(); + isOK &= m_thHandler.Join(); + } + + isOK &= Reset(); + + return isOK; + } + + BOOL IsRunning () {return m_thHandler.IsRunning();} + T* GetRunner () {return m_pRunner;} + F GetFunc () {return m_pFunc;} + SF GetSFunc () {return *(SF*)&m_pFunc;} + THR_ID GetThreadID () {return m_thHandler.GetThreadID();} + NTHR_ID GetNativeID () {return m_thHandler.GetNativeID();} + + +private: + VOID ThreadFunc(const SS* pSigSet) + { + ASSERT(pSigSet == m_pssCur.get()); + + SI si; + + ZeroObject(si); + + while(!::IsThreadInterrupted()) + { +#if !defined(__ANDROID__) + int rs = NO_EINTR_EXCEPT_THR_INTR_INT(sigwaitinfo(pSigSet, &si)); +#else + int rs = NO_EINTR_EXCEPT_THR_INTR_INT(sigwait(pSigSet, &si.si_signo)); +#endif + + if(IS_HAS_ERROR(rs)) + { + if(IS_ERROR(EINTR)) + { + ASSERT(::IsThreadInterrupted()); + break; + } + + ERROR_ABORT(); + } + + Run((T*)nullptr, &si); + } + } + + template::value>> + VOID Run(T_*, const SI* pSigSet) {(m_pRunner->*m_pFunc)(pSigSet);} + + VOID Run(__CFakeRunnerClass_*, const SI* pSigSet) {(*(SF*)&m_pFunc)(pSigSet);} + + + BOOL Reset() + { + BOOL isOK = TRUE; + + if(m_pssPre) + { + isOK = (pthread_sigmask(SIG_SETMASK, m_pssPre.get(), nullptr) == NO_ERROR); + m_pssPre = nullptr; + } + + m_pssCur = nullptr; + m_pRunner = nullptr; + m_pFunc = nullptr; + + return isOK; + } + +public: + CSignalHandler() + { + } + + virtual ~CSignalHandler() + { + Cancel(); + } + + DECLARE_NO_COPY_CLASS(CSignalHandler) + +private: + T* m_pRunner; + F m_pFunc; + SSPTR m_pssCur; + SSPTR m_pssPre; + CHandlerThread m_thHandler; +}; + +using CStaticSignalHandler = CSignalHandler<__CFakeRunnerClass_>; diff --git a/common/Singleton.h b/common/Singleton.h new file mode 100644 index 0000000..81592a3 --- /dev/null +++ b/common/Singleton.h @@ -0,0 +1,117 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#pragma once + +#include "hpsocket/GlobalDef.h" + +#define SINGLETON_THIS(ClassName) ClassName::GetThis() +#define SINGLETON_INSTANCE(ClassName) ClassName::GetInstance() +#define SINGLETON_OBJECT(ObjName) SINGLETON_INSTANCE(C##ObjName) + +#define DEFINE_SINGLETON(ClassName) \ + ClassName* ClassName::m_pThis = nullptr; + +#define DEFINE_P_THIS(ClassName) \ + DEFINE_SINGLETON(ClassName) + +#define DECLARE_SINGLETON_INTERFACE(ClassName) \ +public: \ + static ClassName* GetThis() {return m_pThis;} \ + static ClassName& GetInstance() {return *m_pThis;} \ +protected: \ + static ClassName* m_pThis; + +#define DECLARE_SINGLETON_CREATE_INSTANCE(ClassName) \ +public: \ + static BOOL CreateInstance() \ + { \ + if(!m_pThis) \ + m_pThis = new ClassName; \ + \ + return m_pThis != nullptr; \ + } \ + \ + static BOOL DeleteInstance() \ + { \ + if(m_pThis) \ + { \ + delete m_pThis; \ + m_pThis = nullptr; \ + } \ + \ + return m_pThis == nullptr; \ + } + +#define DECLARE_PUBLIC_DEFAULT_CONSTRUCTOR(ClassName) \ +public: \ + ClassName() = default; + +#define DECLARE_PRIVATE_DEFAULT_CONSTRUCTOR(ClassName) \ +private: \ + ClassName() = default; + +#define DECLARE_PRIVATE_COPY_CONSTRUCTOR(ClassName) \ +private: \ + ClassName(const ClassName&); \ + ClassName& operator = (const ClassName&); + +#define DECLARE_NO_COPY_CLASS(ClassName) \ +private: \ + ClassName(const ClassName&) = delete; \ + ClassName& operator = (const ClassName&) = delete; + + +#define DECLARE_SINGLETON_IMPLEMENT_NO_CREATE_INSTANCE(ClassName) \ + DECLARE_SINGLETON_INTERFACE(ClassName) \ + DECLARE_PRIVATE_DEFAULT_CONSTRUCTOR(ClassName) \ + DECLARE_NO_COPY_CLASS(ClassName) + +#define DECLARE_SINGLETON_IMPLEMENT_NO_DEFAULT_CONSTRUCTOR(ClassName) \ + DECLARE_SINGLETON_CREATE_INSTANCE(ClassName) \ + DECLARE_NO_COPY_CLASS(ClassName) + +#define DECLARE_SINGLETON_IMPLEMENT(ClassName) \ + DECLARE_SINGLETON_IMPLEMENT_NO_DEFAULT_CONSTRUCTOR(ClassName) \ + DECLARE_PRIVATE_DEFAULT_CONSTRUCTOR(ClassName) + +#define DECLARE_SINGLETON_NO_DEFAULT_CONSTRUCTOR(ClassName) \ + DECLARE_SINGLETON_INTERFACE(ClassName) \ + DECLARE_SINGLETON_IMPLEMENT_NO_DEFAULT_CONSTRUCTOR(ClassName) + +#define DECLARE_SINGLETON(ClassName) \ + DECLARE_SINGLETON_NO_DEFAULT_CONSTRUCTOR(ClassName) \ + DECLARE_PRIVATE_DEFAULT_CONSTRUCTOR(ClassName) + + +template class CSingleObject +{ +public: + CSingleObject () {T::CreateInstance();} + ~CSingleObject () {T::DeleteInstance();} + T* GetPointer () {return T::GetThis();} + T& GetObject () {return T::GetInstance();} + BOOL IsValid () {return GetPointer() != nullptr;} +}; + +#define DECLARE_SINGLE_OBJECT(ClassName) CSingleObject _##ClassName##_Single_Object_; \ No newline at end of file diff --git a/common/StringT.h b/common/StringT.h new file mode 100644 index 0000000..55096bd --- /dev/null +++ b/common/StringT.h @@ -0,0 +1,930 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#pragma once + +#include "FuncHelper.h" + +#include +#include +#include + +using namespace std; + +template, typename _Alloc = allocator<_CharT>> +class CStringT : public basic_string<_CharT, _Traits, _Alloc> +{ +public: + + using __super = basic_string<_CharT, _Traits, _Alloc>; + + using XCHAR = _CharT; + using PXSTR = _CharT*; + using PCXSTR = const _CharT*; + + using traits_type = typename __super::traits_type; + using value_type = typename __super::value_type; + using allocator_type = typename __super::allocator_type; + using size_type = typename __super::size_type; + using difference_type = typename __super::difference_type; + using reference = typename __super::reference; + using const_reference = typename __super::const_reference; + using pointer = typename __super::pointer; + using const_pointer = typename __super::const_pointer; + using iterator = typename __super::iterator; + using const_iterator = typename __super::const_iterator; + using const_reverse_iterator = typename __super::const_reverse_iterator; + using reverse_iterator = typename __super::reverse_iterator; + + using __super::clear; + using __super::empty; + using __super::size; + using __super::resize; + using __super::data; + using __super::c_str; + +private: + + constexpr static PCXSTR SPACE_CHARS = _T(" \t\r\n\f\v"); + +public: + + void Empty() {clear();} + bool IsEmpty() const {return empty();} + int GetLength() const {return (int)size();} + const _CharT* GetString() const {return c_str();} + + operator const _CharT* () const {return __super::c_str();} + + _CharT* GetBuffer(int length) + { + resize((size_type)length); + return (_CharT*)data(); + } + + void ReleaseBuffer(int length = -1) + { + if(length == -1) + length = lstrlen(data()); + + resize(length); + } + + void ReleaseBufferSetLength(int length) + { + ASSERT(length >=0); + ReleaseBuffer(length); + } + + void Truncate(int length) + { + if(length >= GetLength()) + return; + + ReleaseBuffer(length); + } + + int Format(const _CharT* format, ...) + { + int rs; + va_list ap; + + va_start(ap, format); + rs = VASprintf(0, format, ap); + va_end(ap); + + return rs; + } + + int AppendFormat(const _CharT* format, ...) + { + int rs; + va_list ap; + + va_start(ap, format); + rs = VASprintf(GetLength(), format, ap); + va_end(ap); + + return rs; + } + + int VASprintf(int offset, const _CharT* format, va_list ap) + { + va_list ap_cpy; + va_copy(ap_cpy, ap); + + int count = vsnprintf(nullptr, 0, format, ap); + + if(count >= 0) + { + _CharT* p = GetBuffer(count + offset); + vsnprintf(p + offset, count + 1, format, ap_cpy); + } + + va_end(ap_cpy); + + return count; + } + + CStringT& Append(const _CharT* __s) + { + append(__s); + return *this; + } + + CStringT& Append(const _CharT* __s, int __n) + { + append(__s, __n); + return *this; + } + + CStringT& AppendChar(_CharT __c) + { + push_back(__c); + return *this; + } + + int Compare(const _CharT* __s) const + { + return lstrcmp(c_str(), __s); + } + + int CompareNoCase(const _CharT* __s) const + { + return lstricmp(c_str(), __s); + } + + bool Equals(const _CharT* __s) const + { + return (Compare(__s) == 0); + } + + bool EqualsNoCase(const _CharT* __s) const + { + return (CompareNoCase(__s) == 0); + } + + CStringT& MakeLower() + { + size_type s = size(); + _CharT* p = (_CharT*)c_str(); + _CharT c; + + for(size_type i = 0; i < s; i++) + { + c = p[i]; + + if(c >= 'A' && c <= 'Z') + p[i] = (_CharT)(c + 32); + } + + return *this; + } + + CStringT& MakeUpper() + { + size_type s = size(); + _CharT* p = (_CharT*)c_str(); + _CharT c; + + for(size_type i = 0; i < s; i++) + { + c = p[i]; + + if(c >= 'a' && c <= 'z') + p[i] = (_CharT)(c - 32); + } + + return *this; + } + + CStringT Mid(int iFirst, int nCount = (int)__super::npos) const + { + return substr(iFirst, nCount); + } + + CStringT Left(int nCount) const + { + return Mid(0, nCount); + } + + CStringT Right(int nCount) const + { + int nLength = GetLength(); + + if(nCount >= nLength) + return *this; + + return Mid(nLength - nCount, nCount); + } + + CStringT Tokenize(PCXSTR lpszTokens, int& iStart) const + { + ASSERT(iStart >= 0); + + if((lpszTokens == nullptr) || (*lpszTokens == (_CharT)0)) + { + if(iStart < GetLength()) + return CStringT(GetString() + iStart); + } + else + { + PCXSTR pszPlace = GetString() + iStart; + PCXSTR pszEnd = GetString() + GetLength(); + + if(pszPlace < pszEnd) + { + int nIncluding = lstrspn(pszPlace, lpszTokens); + + if((pszPlace + nIncluding) < pszEnd) + { + pszPlace += nIncluding; + int nExcluding = lstrcspn(pszPlace, lpszTokens); + + int iFrom = iStart + nIncluding; + int nUntil = nExcluding; + iStart = iFrom + nUntil + 1; + + return Mid(iFrom, nUntil); + } + } + } + + iStart = -1; + + return CStringT(); + } + + CStringT& Trim() + { + return Trim(SPACE_CHARS); + } + + CStringT& TrimRight() + { + return TrimRight(SPACE_CHARS); + } + + CStringT& TrimLeft() + { + return TrimLeft(SPACE_CHARS); + } + + CStringT& Trim(XCHAR c) + { + return(TrimRight(c).TrimLeft(c)); + } + + CStringT& TrimRight(XCHAR c) + { + int iLength = GetLength(); + + if(iLength == 0) + return *this; + + PCXSTR lpszBegin = GetString(); + PCXSTR lpszEnd = lpszBegin + iLength; + + while(lpszEnd > lpszBegin) + { + if(*(lpszEnd - 1) != c) + break; + + --lpszEnd; + } + + int iNewLength = (int)(lpszEnd - lpszBegin); + + if(iNewLength < iLength) + Truncate(iNewLength); + + return *this; + } + + CStringT& TrimLeft(XCHAR c) + { + int iLength = GetLength(); + + if(iLength == 0) + return *this; + + PCXSTR lpszBegin = GetString(); + PCXSTR lpszEnd = lpszBegin; + int iOffset = 0; + + while(*lpszEnd == c) + { + ++lpszEnd; + ++iOffset; + + if(iOffset == iLength) + break; + } + + if(iOffset != 0) + { + int iNewLength = iLength - iOffset; + + if(iNewLength > 0) + memcpy((PXSTR)lpszBegin, lpszEnd, (iLength - iOffset) * sizeof(XCHAR)); + + ReleaseBufferSetLength(iNewLength); + } + + return *this; + } + + CStringT& Trim(PCXSTR lpszChars) + { + return(TrimRight(lpszChars).TrimLeft(lpszChars)); + } + + CStringT& TrimRight(PCXSTR lpszChars) + { + ASSERT(!::IsStrEmpty(lpszChars)); + + if(::IsStrEmpty(lpszChars)) + return *this; + + int iLength = GetLength(); + + if(iLength == 0) + return *this; + + PCXSTR lpszBegin = GetString(); + PCXSTR lpszEnd = lpszBegin + iLength; + + while(lpszEnd > lpszBegin) + { + if(::StrChr(lpszChars, *(lpszEnd - 1)) == nullptr) + break; + + --lpszEnd; + } + + int iNewLength = (int)(lpszEnd - lpszBegin); + + if(iNewLength < iLength) + Truncate(iNewLength); + + return *this; + } + + CStringT& TrimLeft(PCXSTR lpszChars) + { + ASSERT(!::IsStrEmpty(lpszChars)); + + if(::IsStrEmpty(lpszChars)) + return *this; + + int iLength = GetLength(); + + if(iLength == 0) + return *this; + + PCXSTR lpszBegin = GetString(); + PCXSTR lpszEnd = lpszBegin; + int iOffset = 0; + + while(::StrChr(lpszChars, *lpszEnd) != nullptr) + { + ++lpszEnd; + ++iOffset; + + if(iOffset == iLength) + break; + } + + if(iOffset != 0) + { + int iNewLength = iLength - iOffset; + + if(iNewLength > 0) + memcpy((PXSTR)lpszBegin, lpszEnd, (iLength - iOffset) * sizeof(XCHAR)); + + ReleaseBufferSetLength(iNewLength); + } + + return *this; + } + + int Find(XCHAR c, int iStart = 0) const + { + ASSERT(iStart >= 0); + + int iLength = GetLength(); + + if(iStart < 0 || iStart >= iLength) + return -1; + + PCXSTR lpszBegin = GetString(); + PCXSTR lpszFind = ::StrChr(lpszBegin + iStart, c); + + return ((lpszFind == nullptr) ? -1 : (int)(lpszFind - lpszBegin)); + } + + int Find(PCXSTR lpszSub, int iStart = 0) const + { + ASSERT(iStart >= 0 && !::IsStrEmpty(lpszSub)); + + int iLength = GetLength(); + + if(lpszSub == nullptr || iStart < 0 || iStart > iLength) + return -1; + + PCXSTR lpszBegin = GetString(); + PCXSTR lpszFind = ::StrStr(lpszBegin + iStart, lpszSub); + + return ((lpszFind == nullptr) ? -1 : (int)(lpszFind - lpszBegin)); + } + + int FindOneOf(PCXSTR lpszChars) const + { + ASSERT(!::IsStrEmpty(lpszChars)); + + if(lpszChars == nullptr) + return -1; + + PCXSTR lpszBegin = GetString(); + PCXSTR lpszFind = ::StrPBrk(lpszBegin, lpszChars); + + return ((lpszFind == nullptr) ? -1 : (int)(lpszFind - lpszBegin)); + } + + int ReverseFind(XCHAR c) const + { + PCXSTR lpszBegin = GetString(); + PCXSTR lpszFind = ::StrRChr(lpszBegin, c); + + return ((lpszFind == nullptr) ? -1 : (int)(lpszFind - lpszBegin)); + } + + int Remove(XCHAR c) + { + int iLength = GetLength(); + + if(iLength == 0) + return 0; + + PCXSTR lpszBegin = GetString(); + PXSTR lpszCur = (PXSTR)lpszBegin; + PCXSTR lpszEnd = lpszBegin + iLength; + int iRemoved = 0; + + while(lpszCur < lpszEnd) + { + if(*lpszCur == c) + ++iRemoved; + else if(iRemoved > 0) + *(lpszCur - iRemoved) = *lpszCur; + + ++lpszCur; + } + + if(iRemoved > 0) + ReleaseBufferSetLength(iLength - iRemoved); + + return iRemoved; + } + + XCHAR GetAt(int i) const + { + return (*this)[i]; + } + + void SetAt(int i, XCHAR c) + { + (*this)[i] = c; + } + + XCHAR operator[](int i) const + { + ASSERT(i >= 0 && i < GetLength()); + + return *(GetString() + i); + } + + XCHAR& operator[](int i) + { + ASSERT(i >= 0 && i < GetLength()); + + return *(PXSTR)(GetString() + i); + } + + CStringT& Insert(int i, XCHAR c) + { + return insert((size_type)i, 1, c); + } + + CStringT& Insert(int i, PCXSTR lpszChars) + { + return insert((size_type)i, lpszChars); + } + + CStringT& SetString(PCXSTR lpszStr) + { + return assign(lpszStr); + } + + CStringT& SetString(PCXSTR lpszStr, int iLength) + { + return assign(lpszStr, iLength); + } + + friend bool operator==(const CStringT& str1, const CStringT& str2) + { + return (str1.Compare(str2) == 0); + } + + friend bool operator==(const CStringT& str1, const _CharT* psz2) + { + return (str1.Compare(psz2) == 0); + } + + friend bool operator==(const _CharT* psz1, const CStringT& str2) + { + return (str2.Compare(psz1) == 0); + } + + friend bool operator!=(const CStringT& str1, const CStringT& str2) + { + return !(str1 == str2); + } + + friend bool operator!=(const CStringT& str1, const _CharT* psz2) + { + return !(str1 == psz2); + } + + friend bool operator!=(const _CharT* psz1, const CStringT& str2) + { + return !(psz1 == str2); + } + +public: + CStringT() : __super() {}; + + explicit CStringT(const _Alloc& __a) + : __super(__a) {} + + CStringT(const __super& __str) + : __super(__str) {} + + CStringT(const CStringT& __str) + : __super(__str) {} + + CStringT(const __super& __str, size_type __pos, size_type __n = __super::npos) + : __super(__str, __pos, __n) {} + + CStringT(const __super& __str, size_type __pos, size_type __n, const _Alloc& __a) + : __super(__str, __pos, __n, __a) {} + + CStringT(const _CharT* __s, size_type __n, const _Alloc& __a = _Alloc()) + : __super(::SafeStr(__s), __n, __a) {} + + CStringT(const _CharT* __s, const _Alloc& __a = _Alloc()) + : __super(::SafeStr(__s), __a) {} + + CStringT(size_type __n, _CharT __c, const _Alloc& __a = _Alloc()) + : __super(__n, __c, __a) {} + +#if __cplusplus >= 201103L + CStringT(__super&& __str) + : __super(__str) {} + + CStringT(CStringT&& __str) + : __super(__str) {} + + CStringT(initializer_list<_CharT> __l, const _Alloc& __a = _Alloc()) + : __super(__l, __a) {} +#endif // C++11 + + template + CStringT(_InputIterator __beg, _InputIterator __end, const _Alloc& __a = _Alloc()) + : __super(__beg, __end, __a) {} + + ~CStringT() = default; + + CStringT& operator=(const __super& __str) + {__super::operator=(__str); return *this;} + + CStringT& operator=(const CStringT& __str) + {__super::operator=(__str); return *this;} + + CStringT& operator=(const _CharT* __s) + {__super::operator=(::SafeStr(__s)); return *this;} + + CStringT& operator=(_CharT __c) + {__super::operator=(__c); return *this;} + +#if __cplusplus >= 201103L + CStringT& operator=(__super&& __str) + {__super::operator=(__str); return *this;} + + CStringT& operator=(CStringT&& __str) + {__super::operator=(__str); return *this;} + + CStringT& operator=(initializer_list<_CharT> __l) + {__super::operator=(__l); return *this;} +#endif // C++11 + +public: + CStringT& operator+=(const __super& __str) + {__super::operator+=(__str); return *this;} + + CStringT& operator+=(const _CharT* __s) + {__super::operator+=(::SafeStr(__s)); return *this;} + + CStringT& operator+=(_CharT __c) + {__super::operator+=(__c); return *this;} + +#if __cplusplus >= 201103L + CStringT& operator+=(initializer_list<_CharT> __l) + {__super::operator+=(__l); return *this;} +#endif // C++11 + + CStringT& append(const __super& __str) + {__super::append(__str); return *this;} + + CStringT& append(const __super& __str, size_type __pos, size_type __n) + {__super::append(__str, __pos, __n); return *this;} + + CStringT& append(const _CharT* __s, size_type __n) + {__super::append(::SafeStr(__s), __n); return *this;} + + CStringT& append(const _CharT* __s) + {__super::append(::SafeStr(__s)); return *this;} + + CStringT& append(size_type __n, _CharT __c) + {__super::append(__n, __c); return *this;} + +#if __cplusplus >= 201103L + CStringT& append(initializer_list<_CharT> __l) + {__super::append(__l); return *this;} +#endif // C++11 + + template + CStringT& append(_InputIterator __first, _InputIterator __last) + {__super::append(__first, __last); return *this;} + + void push_back(_CharT __c) + {__super::push_back(__c);} + + CStringT& assign(const __super& __str) + {__super::assign(__str); return *this;} + +#if __cplusplus >= 201103L + CStringT& assign(__super&& __str) + {__super::assign(__str); return *this;} +#endif // C++11 + + CStringT& assign(const __super& __str, size_type __pos, size_type __n) + {__super::assign(__str, __pos, __n); return *this;} + + CStringT& assign(const _CharT* __s, size_type __n) + {__super::assign(::SafeStr(__s), __n); return *this;} + + CStringT& assign(const _CharT* __s) + {__super::assign(::SafeStr(__s)); return *this;} + + CStringT& assign(size_type __n, _CharT __c) + {__super::assign(__n, __c); return *this;} + + template + CStringT& assign(_InputIterator __first, _InputIterator __last) + {__super::assign(__first, __last); return *this;} + +#if __cplusplus >= 201103L + CStringT& assign(initializer_list<_CharT> __l) + {__super::assign(__l); return *this;} +#endif // C++11 + + CStringT& insert(size_type __pos1, const __super& __str) + {__super::insert(__pos1, __str); return *this;} + + CStringT& insert(size_type __pos1, const __super& __str, size_type __pos2, size_type __n) + {__super::insert(__pos1, __str, __pos2, __n); return *this;} + + CStringT& insert(size_type __pos, const _CharT* __s, size_type __n) + {__super::insert(__pos, __s, __n); return *this;} + + CStringT& insert(size_type __pos, const _CharT* __s) + {__super::insert(__pos, __s); return *this;} + + CStringT& insert(size_type __pos, size_type __n, _CharT __c) + {__super::insert(__pos, __n, __c); return *this;} + + CStringT& erase(size_type __pos = 0, size_type __n = __super::npos) + {__super::erase(__pos, __n); return *this;} + + CStringT& replace(size_type __pos, size_type __n, const __super& __str) + {__super::replace(__pos, __n, __str); return *this;} + + CStringT& replace(size_type __pos1, size_type __n1, const __super& __str, size_type __pos2, size_type __n2) + {__super::replace(__pos1, __n1, __str, __pos2, __n2); return *this;} + + CStringT& replace(size_type __pos, size_type __n1, const _CharT* __s, size_type __n2) + {__super::replace(__pos, __n1, __s, __n2); return *this;} + + CStringT& replace(size_type __pos, size_type __n1, const _CharT* __s) + {__super::replace(__pos, __n1, __s); return *this;} + + CStringT& replace(size_type __pos, size_type __n1, size_type __n2, _CharT __c) + {__super::replace(__pos, __n1, __n2, __c); return *this;} + + CStringT& replace(iterator __i1, iterator __i2, const __super& __str) + {__super::replace(__i1, __i2, __str); return *this;} + + CStringT& replace(iterator __i1, iterator __i2, const _CharT* __s, size_type __n) + {__super::replace(__i1, __i2, __s, __n); return *this;} + + CStringT& replace(iterator __i1, iterator __i2, const _CharT* __s) + {__super::replace(__i1, __i2, __s); return *this;} + + CStringT& replace(iterator __i1, iterator __i2, size_type __n, _CharT __c) + {__super::replace(__i1, __i2, __n, __c); return *this;} + + template + CStringT& replace(iterator __i1, iterator __i2, _InputIterator __k1, _InputIterator __k2) + {__super::replace(__i1, __i2, __k1, __k2); return *this;} + + CStringT& replace(iterator __i1, iterator __i2, _CharT* __k1, _CharT* __k2) + {__super::replace(__i1, __i2, __k1, __k2); return *this;} + + CStringT& replace(iterator __i1, iterator __i2, const _CharT* __k1, const _CharT* __k2) + {__super::replace(__i1, __i2, __k1, __k2); return *this;} + + CStringT& replace(iterator __i1, iterator __i2, iterator __k1, iterator __k2) + {__super::replace(__i1, __i2, __k1, __k2); return *this;} + + CStringT& replace(iterator __i1, iterator __i2, const_iterator __k1, const_iterator __k2) + {__super::replace(__i1, __i2, __k1, __k2); return *this;} + +#if __cplusplus >= 201103L + CStringT& replace(iterator __i1, iterator __i2, initializer_list<_CharT> __l) + {__super::replace(__i1, __i2, __l); return *this;} +#endif // C++11 + + CStringT substr(size_type __pos = 0, size_type __n = __super::npos) const + {return __super::substr(__pos, __n);} + +}; + +template +CStringT<_CharT, _Traits, _Alloc> + operator+(const CStringT<_CharT, _Traits, _Alloc>& __lhs, const CStringT<_CharT, _Traits, _Alloc>& __rhs) +{ + CStringT<_CharT, _Traits, _Alloc> __str(__lhs); + __str.append(__rhs); + return __str; +} + +template +CStringT<_CharT,_Traits,_Alloc> + operator+(const _CharT* __lhs, const CStringT<_CharT,_Traits,_Alloc>& __rhs); + +template +CStringT<_CharT,_Traits,_Alloc> + operator+(_CharT __lhs, const CStringT<_CharT,_Traits,_Alloc>& __rhs); + +template +inline CStringT<_CharT, _Traits, _Alloc> + operator+(const CStringT<_CharT, _Traits, _Alloc>& __lhs, const _CharT* __rhs) +{ + CStringT<_CharT, _Traits, _Alloc> __str(__lhs); + __str.append(__rhs); + return __str; +} + +template +inline CStringT<_CharT, _Traits, _Alloc> + operator+(const CStringT<_CharT, _Traits, _Alloc>& __lhs, _CharT __rhs) +{ + typedef CStringT<_CharT, _Traits, _Alloc> __string_type; + typedef typename __string_type::size_type __size_type; + __string_type __str(__lhs); + __str.append(__size_type(1), __rhs); + return __str; +} + +#if __cplusplus >= 201103L +template +inline CStringT<_CharT, _Traits, _Alloc> + operator+(CStringT<_CharT, _Traits, _Alloc>&& __lhs, const CStringT<_CharT, _Traits, _Alloc>& __rhs) +{return std::move(__lhs.append(__rhs));} + +template +inline CStringT<_CharT, _Traits, _Alloc> + operator+(const CStringT<_CharT, _Traits, _Alloc>& __lhs, CStringT<_CharT, _Traits, _Alloc>&& __rhs) +{return std::move(__rhs.insert(0, __lhs));} + +template +inline CStringT<_CharT, _Traits, _Alloc> + operator+(CStringT<_CharT, _Traits, _Alloc>&& __lhs, CStringT<_CharT, _Traits, _Alloc>&& __rhs) +{ + const auto __size = __lhs.size() + __rhs.size(); + const bool __cond = (__size > __lhs.capacity() + && __size <= __rhs.capacity()); + return __cond ? std::move(__rhs.insert(0, __lhs)) + : std::move(__lhs.append(__rhs)); +} + +template +inline CStringT<_CharT, _Traits, _Alloc> + operator+(const _CharT* __lhs, CStringT<_CharT, _Traits, _Alloc>&& __rhs) +{return std::move(__rhs.insert(0, __lhs));} + +template +inline CStringT<_CharT, _Traits, _Alloc> + operator+(_CharT __lhs, CStringT<_CharT, _Traits, _Alloc>&& __rhs) +{return std::move(__rhs.insert(0, 1, __lhs));} + +template +inline CStringT<_CharT, _Traits, _Alloc> + operator+(CStringT<_CharT, _Traits, _Alloc>&& __lhs, const _CharT* __rhs) +{return std::move(__lhs.append(__rhs));} + +template +inline CStringT<_CharT, _Traits, _Alloc> + operator+(CStringT<_CharT, _Traits, _Alloc>&& __lhs, _CharT __rhs) +{return std::move(__lhs.append(1, __rhs));} +#endif + +using CStringA = CStringT; +using CStringW = CStringT; + +using CStdStringA = string; +using CStdStringW = wstring; + +#ifdef _UNICODE + using CString = CStringW; + using CStdString = CStdStringW; +#else + using CString = CStringA; + using CStdString = CStdStringA; +#endif + +#define _HASH_SEED (size_t)0xdeadbeef + +template +inline size_t hash_value(const _Kty& _Keyval) +{ + return ((size_t)_Keyval ^ _HASH_SEED); +} + +template +inline size_t _Hash_value(_InIt _Begin, _InIt _End) +{ + size_t _Val = 2166136261U; + + while(_Begin != _End) + _Val = 16777619U * _Val ^ (size_t)*_Begin++; + + return (_Val); +} + +template +inline size_t hash_value(const basic_string<_Elem, _Traits, _Alloc>& _Str) +{ + const _Elem *_Ptr = _Str.c_str(); + + return (_Hash_value(_Ptr, _Ptr + _Str.size())); +} + +template +inline size_t hash_value(const CStringT<_Elem>& _Str) +{ + const _Elem *_Ptr = _Str.c_str(); + + return (_Hash_value(_Ptr, _Ptr + _Str.size())); +} + +inline size_t hash_value(const char *_Str) +{ + return (_Hash_value(_Str, _Str + strlen(_Str))); +} + +inline size_t hash_value(const wchar_t *_Str) +{ + return (_Hash_value(_Str, _Str + wcslen(_Str))); +} diff --git a/common/SysHelper.cpp b/common/SysHelper.cpp new file mode 100644 index 0000000..e12cbad --- /dev/null +++ b/common/SysHelper.cpp @@ -0,0 +1,66 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "SysHelper.h" + +#include +#include + +DWORD _GetKernelVersion() +{ + utsname uts; + + if(uname(&uts) == RS_FAIL) + return 0; + + char c; + int major, minor, revise; + + if(sscanf(uts.release, "%d.%d.%d%c", &major, &minor, &revise, &c) < 3) + return 0; + + return (DWORD)((major << 16) | (minor << 8) | revise); +} + +DWORD GetSysPageSize() +{ + static const DWORD _s_page_size = (DWORD)SysGetPageSize(); + return _s_page_size; +} + +DWORD GetKernelVersion() +{ + static const DWORD _s_kernel_version = _GetKernelVersion(); + return _s_kernel_version; +} + +BOOL IsKernelVersionAbove(BYTE major, BYTE minor, BYTE revise) +{ + return GetKernelVersion() >= (DWORD)((major << 16) | (minor << 8) | revise); +} + +DWORD GetDefaultWorkerThreadCount() +{ + static const DWORD _s_dwtc = MIN((PROCESSOR_COUNT * 2 + 2), MAX_WORKER_THREAD_COUNT); + return _s_dwtc; +} diff --git a/common/SysHelper.h b/common/SysHelper.h new file mode 100644 index 0000000..4a35e12 --- /dev/null +++ b/common/SysHelper.h @@ -0,0 +1,186 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#pragma once + +#include "hpsocket/GlobalDef.h" + +#include +#include +#include + +#include +#include + +using namespace std; + +/* 最大工作线程数 */ +#define MAX_WORKER_THREAD_COUNT 512 +/* 默认对象缓存锁定时间 */ +#define DEFAULT_OBJECT_CACHE_LOCK_TIME (30 * 1000) +/* 默认对象缓存池大小 */ +#define DEFAULT_OBJECT_CACHE_POOL_SIZE 600 +/* 默认对象缓存池回收阀值 */ +#define DEFAULT_OBJECT_CACHE_POOL_HOLD 600 +/* 默认内存块缓存容量 */ +#define DEFAULT_BUFFER_CACHE_CAPACITY 4096 +/* 默认内存块缓存池大小 */ +#define DEFAULT_BUFFER_CACHE_POOL_SIZE 1024 +/* 默认内存块缓存池回收阀值 */ +#define DEFAULT_BUFFER_CACHE_POOL_HOLD 1024 + +/* 使用外部垃圾回收 */ +#define USE_EXTERNAL_GC 1 + + +#define SysGetSystemConfig sysconf +#define SysGetSystemInfo sysinfo + +#if !defined(__ANDROID__) + #define SysGetPageSize getpagesize + #define SysGetNumberOfProcessors get_nprocs +#else + #define SysGetPageSize() sysconf(_SC_PAGESIZE) + #define SysGetNumberOfProcessors() sysconf(_SC_NPROCESSORS_ONLN) +#endif + +#define SYS_PAGE_SIZE GetSysPageSize() + +#define PROCESSOR_COUNT (::SysGetNumberOfProcessors()) +#define GetCurrentProcessId getpid +#define SELF_PROCESS_ID (::GetCurrentProcessId()) +#define gettid() syscall(__NR_gettid) +#define GetCurrentNativeThreadId() gettid() +#define SELF_NATIVE_THREAD_ID (::GetCurrentNativeThreadId()) +#define GetCurrentThreadId pthread_self +#define SELF_THREAD_ID (::GetCurrentThreadId()) +#define IsSameThread(tid1, tid2) pthread_equal((tid1), (tid2)) +#define IsSelfThread(tid) IsSameThread((tid), SELF_THREAD_ID) +inline BOOL IsSameNativeThread(pid_t pid1, pid_t pid2) + {return (pid1 == pid2);} +#define IsSelfNativeThread(pid) IsSameNativeThread((pid), SELF_PROCESS_ID) +#define DEFAULT_WORKER_THREAD_COUNT GetDefaultWorkerThreadCount() + +// Yield +#if defined(__cplusplus) +#include +static inline void __atomic_yield() +{ + std::this_thread::yield(); +} +#elif defined(_WIN32) +#define WIN32_LEAN_AND_MEAN +#include +static inline void __atomic_yield() +{ + YieldProcessor(); +} +#elif defined(__SSE2__) +#include +static inline void __atomic_yield() +{ + _mm_pause(); +} +#elif (defined(__GNUC__) || defined(__clang__)) && \ + (defined(__x86_64__) || defined(__i386__) || defined(__arm__) || defined(__armel__) || defined(__ARMEL__) || \ + defined(__aarch64__) || defined(__powerpc__) || defined(__ppc__) || defined(__PPC__)) +#if defined(__x86_64__) || defined(__i386__) +static inline void __atomic_yield() +{ + __asm__ volatile ("pause" ::: "memory"); +} +#elif defined(__aarch64__) +static inline void __atomic_yield() +{ + __asm__ volatile("wfe"); +} +#elif (defined(__arm__) && __ARM_ARCH__ >= 7) +static inline void __atomic_yield() +{ + __asm__ volatile("yield" ::: "memory"); +} +#elif defined(__powerpc__) || defined(__ppc__) || defined(__PPC__) +static inline void __atomic_yield() +{ + __asm__ __volatile__ ("or 27,27,27" ::: "memory"); +} +#elif defined(__armel__) || defined(__ARMEL__) +static inline void __atomic_yield() +{ + __asm__ volatile ("nop" ::: "memory"); +} +#endif +#elif defined(__sun) +// Fallback for other archs +#include +static inline void __atomic_yield() +{ + smt_pause(); +} +#elif defined(__wasi__) +#include +static inline void __atomic_yield() +{ + sched_yield(); +} +#else +#include +static inline void __atomic_yield() +{ + sleep(0); +} +#endif // Yield + +#define YieldProcessor __atomic_yield +#define SwitchToThread sched_yield + +inline void __asm_nop() {__asm__ __volatile__("nop" : : : "memory");} +inline void __asm_rep_nop() {__asm__ __volatile__("rep; nop" : : : "memory");} + +DWORD GetSysPageSize(); +DWORD GetKernelVersion(); +BOOL IsKernelVersionAbove(BYTE major, BYTE minor, BYTE revise); +DWORD GetDefaultWorkerThreadCount(); + + +#if defined(__ANDROID__) + +#include + +#if !defined(EFD_SEMAPHORE) + #define EFD_SEMAPHORE 00000001 +#endif + +#define pthread_cancel(t) + +#if defined(__ANDROID_API__) +#if (__ANDROID_API__ < 21) + + #define ppoll(fd, nfds, ptmspec, sig) poll((fd), (nfds), ((ptmspec) == nullptr) ? -1 : ((ptmspec)->tv_sec * 1000 + (ptmspec)->tv_nsec / 1000000)) + #define epoll_create1(flag) epoll_create(32) + #define epoll_pwait(epfd, events, maxevents, timeout, sigmask) epoll_wait((epfd), (events), (maxevents), (timeout)) + +#endif +#endif + +#endif diff --git a/common/Thread.cpp b/common/Thread.cpp new file mode 100644 index 0000000..059d607 --- /dev/null +++ b/common/Thread.cpp @@ -0,0 +1,50 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "Thread.h" + +static __thread BOOL s_tlsInterrupt = FALSE; +BOOL __CThread_Interrupt_::sm_bInitFlag = __CThread_Interrupt_::InitSigAction(); + +__CThread_Interrupt_::~__CThread_Interrupt_ () {s_tlsInterrupt = FALSE;} +BOOL __CThread_Interrupt_::IsInterrupted () {return s_tlsInterrupt;} + +BOOL __CThread_Interrupt_::InitSigAction() +{ + struct sigaction act; + sigemptyset(&act.sa_mask); + + act.sa_handler = SignalHandler; + act.sa_flags = 0; + + if(IS_HAS_ERROR(sigaction(SIG_NO_INTERRUPT, &act, nullptr))) + ERROR_ABORT(); + + return TRUE; +} + +void __CThread_Interrupt_::SignalHandler(int sig) +{ + if(sig == SIG_NO_INTERRUPT) + s_tlsInterrupt = TRUE; +} diff --git a/common/Thread.h b/common/Thread.h new file mode 100644 index 0000000..d070bc8 --- /dev/null +++ b/common/Thread.h @@ -0,0 +1,612 @@ +/* +* Copyright: JessMA Open Source (ldcsaa@gmail.com) +* +* Author : Bruce Liang +* Website : https://github.com/ldcsaa +* Project : https://github.com/ldcsaa/HP-Socket +* Blog : http://www.cnblogs.com/ldcsaa +* Wiki : http://www.oschina.net/p/hp-socket +* QQ Group : 44636872, 75375912 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#pragma once + +#include "hpsocket/GlobalDef.h" +#include "hpsocket/GlobalErrno.h" +#include "RWLock.h" +#include "STLHelper.h" + +#include +#include +#include + +using namespace std; + +/* Used to retry syscalls that can return EINTR. */ +#define NO_EINTR_EXCEPT_THR_INTR(exp) ({ \ + long int _rc; \ + do {_rc = (long int)(exp);} \ + while (IS_HAS_ERROR(_rc) && IS_INTR_ERROR() \ + && !::IsThreadInterrupted()); \ + _rc; }) + +#define NO_EINTR_EXCEPT_THR_INTR_INT(exp) ((int)NO_EINTR_EXCEPT_THR_INTR(exp)) + +class __CThread_Interrupt_ +{ +public: + static const int SIG_NO_INTERRUPT = (_NSIG - 5); + +private: + friend BOOL IsThreadInterrupted(); + template friend class CThread; + +private: + static BOOL IsInterrupted(); + static BOOL InitSigAction(); + static void SignalHandler(int sig); + +private: + ~__CThread_Interrupt_(); + +private: + static BOOL sm_bInitFlag; +}; + +inline BOOL IsThreadInterrupted() {return __CThread_Interrupt_::IsInterrupted();} + +class __CFakeRunnerClass_ {}; + +template class CThread +{ +public: + + using F = R (T::*)(P*); + using SF = R (*)(P*); + + struct TWorker + { + CThread* m_pThread; + BOOL m_bDetach; + + T* m_pRunner; + F m_pFunc; + P* m_pArg; + + public: + + TWorker(CThread* pThread, BOOL bDetach = FALSE, T* pRunner = nullptr, F pFunc = nullptr, P* pArg = nullptr) + : m_pThread(pThread) + { + Reset(bDetach, pRunner, pFunc, pArg); + } + + void Reset(BOOL bDetach = FALSE, T* pRunner = nullptr, F pFunc = nullptr, P* pArg = nullptr) + { + m_bDetach = bDetach; + m_pRunner = pRunner; + m_pFunc = pFunc; + m_pArg = pArg; + } + + public: + + template::value && !is_void::value>> + PVOID Run(T_*, R_*) + { + return (PVOID)(UINT_PTR)((m_pRunner->*m_pFunc)(m_pArg)); + } + + template::value>> + PVOID Run(T_*, PVOID) + { + (m_pRunner->*m_pFunc)(m_pArg); + + return nullptr; + } + + template::value>> + PVOID Run(__CFakeRunnerClass_*, R_*) + { + return (PVOID)(UINT_PTR)(*(SF*)&m_pFunc)(m_pArg); + } + + PVOID Run(__CFakeRunnerClass_*, VOID*) + { + (*(SF*)&m_pFunc)(m_pArg); + + return nullptr; + } + }; + + friend struct TWorker; + +public: + + BOOL Start(SF pFunc, P* pArg = nullptr, BOOL bDetach = FALSE, const pthread_attr_t* pAttr = nullptr) + { + return Start((__CFakeRunnerClass_*)nullptr, *(F*)&pFunc, pArg, bDetach, pAttr); + } + + BOOL Start(T* pRunner, F pFunc, P* pArg = nullptr, BOOL bDetach = FALSE, const pthread_attr_t* pAttr = nullptr) + { + int rs = ERROR_INVALID_STATE; + + if(IsRunning()) + ::SetLastError(rs); + else + { + m_Worker.Reset(bDetach, pRunner, pFunc, pArg); + + SetRunning(TRUE); + + rs = pthread_create(&m_ulThreadID, pAttr, ThreadProc, (PVOID)(&m_Worker)); + + if(rs != NO_ERROR) + { + Reset(); + ::SetLastError(rs); + } + } + + return (rs == NO_ERROR); + } + +#if !defined(__ANDROID__) + + BOOL Cancel() + { + int rs = NO_ERROR; + + if(!IsRunning() || ::IsSelfThread(m_ulThreadID)) + rs = ERROR_INVALID_STATE; + else + rs = pthread_cancel(m_ulThreadID); + + if(rs != NO_ERROR) + ::SetLastError(rs); + + return (rs == NO_ERROR); + } + + BOOL Join(R* pResult = nullptr, BOOL bWait = TRUE, LONG lWaitMillsec = INFINITE) + { + int rs = NO_ERROR; + + if(!IsRunning() || ::IsSelfThread(m_ulThreadID)) + rs = ERROR_INVALID_STATE; + else + { + if(!bWait) + rs = pthread_tryjoin_np(m_ulThreadID, (PVOID*)pResult); + else if(IS_INFINITE(lWaitMillsec)) + rs = pthread_join(m_ulThreadID, (PVOID*)pResult); + else + { + timespec ts; + ::GetFutureTimespec(lWaitMillsec, ts, CLOCK_REALTIME); + + rs = pthread_timedjoin_np(m_ulThreadID, (PVOID*)pResult, &ts); + } + } + + if(rs == NO_ERROR) + SetRunning(FALSE); + else + ::SetLastError(rs); + + return (rs == NO_ERROR); + } + +#else + + BOOL Cancel() + { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; + } + + BOOL Join(R* pResult = nullptr) + { + int rs = NO_ERROR; + + if(!IsRunning() || ::IsSelfThread(m_ulThreadID)) + rs = ERROR_INVALID_STATE; + else + rs = pthread_join(m_ulThreadID, (PVOID*)pResult); + + if(rs == NO_ERROR) + SetRunning(FALSE); + else + ::SetLastError(rs); + + return (rs == NO_ERROR); + } + +#endif + + BOOL Detach() + { + int rs = NO_ERROR; + + if(!IsRunning()) + rs = ERROR_INVALID_STATE; + else + rs = pthread_detach(m_ulThreadID); + + if(rs == NO_ERROR) + Reset(); + else + ::SetLastError(rs); + + return (rs == NO_ERROR); + } + + void Reset() + { + SetRunning(FALSE); + + m_ulThreadID = 0; + m_lNativeID = 0; + + m_Worker.Reset(); + } + + BOOL Interrupt() + { + if(!IsRunning()) + { + SetLastError(ERROR_INVALID_STATE); + return FALSE; + } + + return (IS_NO_ERROR(pthread_kill(m_ulThreadID, __CThread_Interrupt_::SIG_NO_INTERRUPT))); + } + + void SetRunning(BOOL bRunning) {m_bRunning = bRunning;} + + BOOL IsRunning () const {return m_bRunning;} + T* GetRunner () const {return m_Worker.m_pRunner;} + F GetFunc () const {return m_Worker.m_pFunc;} + SF GetSFunc () const {return *(SF*)&m_Worker.m_pFunc;} + P* GetArg () const {return m_Worker.m_pArg;} + THR_ID GetThreadID () const {return m_ulThreadID;} + NTHR_ID GetNativeID () const {return m_lNativeID;} + + BOOL IsInMyThread () const {return IsMyThreadID(SELF_THREAD_ID);} + BOOL IsMyThreadID (THR_ID ulThreadID) const {return ::IsSameThread(ulThreadID, m_ulThreadID);} + BOOL IsMyNativeThreadID (NTHR_ID lNativeID) const {return ::IsSameNativeThread(lNativeID, m_lNativeID);} + +private: + + static PVOID ThreadProc(LPVOID pv) + { + UnmaskInterruptSignal(); + + __CThread_Interrupt_ tlsInterrupt; + + TWorker* pWorker = (TWorker*)pv; + + if(pWorker->m_bDetach) + pWorker->m_pThread->Detach(); + else + pWorker->m_pThread->m_lNativeID = SELF_NATIVE_THREAD_ID; + + PVOID pResult = pWorker->Run((T*)nullptr, (R*)nullptr); + + return pResult; + } + + static void UnmaskInterruptSignal() + { + sigset_t ss; + + sigemptyset(&ss); + sigaddset(&ss, __CThread_Interrupt_::SIG_NO_INTERRUPT); + + pthread_sigmask(SIG_UNBLOCK, &ss, nullptr); + } + +public: + + CThread() + : m_Worker(this) + { + Reset(); + } + + virtual ~CThread() + { + if(IsRunning()) + { + Interrupt(); + Join(nullptr); + } + + ASSERT(!IsRunning()); + } + + DECLARE_NO_COPY_CLASS(CThread) + +private: + + THR_ID m_ulThreadID; + NTHR_ID m_lNativeID; + BOOL m_bRunning; + + TWorker m_Worker; +}; + +template using CStaticThread = CThread<__CFakeRunnerClass_, P, R>; + +template class CTlsObj +{ + using TLocalMap = unordered_map; + +public: + + T* TryGet() + { + T* pValue = nullptr; + + { + CReadLock locallock(m_lock); + auto it = m_map.find(SELF_THREAD_ID); + + if(it != m_map.end()) + pValue = it->second; + } + + return pValue; + } + + template T* Get(_Con_Param&& ... construct_args) + { + T* pValue = TryGet(); + + if(pValue == nullptr) + { + pValue = Construct(forward<_Con_Param>(construct_args) ...); + + CWriteLock locallock(m_lock); + m_map[SELF_THREAD_ID] = pValue; + } + + return pValue; + } + + template T& GetRef(_Con_Param&& ... construct_args) + { + return *Get(forward<_Con_Param>(construct_args) ...); + } + + T* SetNewAndGetOld(T* pValue) + { + T* pOldValue = TryGet(); + + if(pValue != pOldValue) + { + if(pValue == nullptr) + DoRemove(); + else + { + CWriteLock locallock(m_lock); + m_map[SELF_THREAD_ID] = pValue; + } + } + + return pOldValue; + } + + void Set(T* pValue) + { + T* pOldValue = SetNewAndGetOld(pValue); + + if(pValue != pOldValue) + DoDelete(pOldValue); + } + + void Remove() + { + T* pValue = TryGet(); + + if(pValue != nullptr) + { + DoDelete(pValue); + DoRemove(); + } + } + + void Clear() + { + CWriteLock locallock(m_lock); + + if(!IsEmpty()) + { + for(auto it = m_map.begin(), end = m_map.end(); it != end; ++it) + DoDelete(it->second); + + m_map.clear(); + } + } + + TLocalMap& GetLocalMap() {return m_map;} + const TLocalMap& GetLocalMap() const {return m_map;} + + CTlsObj& operator = (T* p) {Set(p); return *this;} + T* operator -> () {return Get();} + const T* operator -> () const {return Get();} + T& operator * () {return GetRef();} + const T& operator * () const {return GetRef();} + size_t Size () const {return m_map.size();} + bool IsEmpty() const {return m_map.empty();} + +private: + + inline void DoRemove() + { + CWriteLock locallock(m_lock); + m_map.erase(SELF_THREAD_ID); + } + + static inline void DoDelete(T* pValue) + { + if(pValue != nullptr) + delete pValue; + } + + template static inline T* Construct(_Con_Param&& ... construct_args) + { + return new T(forward<_Con_Param>(construct_args) ...); + } + +public: + + CTlsObj() + { + + } + + CTlsObj(T* pValue) + { + Set(pValue); + } + + ~CTlsObj() + { + Clear(); + } + +private: + + CSimpleRWLock m_lock; + TLocalMap m_map; + + DECLARE_NO_COPY_CLASS(CTlsObj) +}; + +template class CTlsSimple +{ + using TLocalMap = unordered_map; + + static const T DEFAULT = (T)(0); + +public: + + BOOL TryGet(T& tValue) + { + BOOL isOK = FALSE; + + { + CReadLock locallock(m_lock); + auto it = m_map.find(SELF_THREAD_ID); + + if(it != m_map.end()) + { + tValue = it->second; + isOK = TRUE; + } + } + + return isOK; + } + + T Get(T tDefault = DEFAULT) + { + T tValue; + + if(TryGet(tValue)) + return tValue; + + Set(tDefault); + + return tDefault; + } + + T SetNewAndGetOld(T tValue) + { + T tOldValue; + + if(!TryGet(tOldValue)) + tOldValue = DEFAULT; + else if(tValue != tOldValue) + Set(tValue); + + return tOldValue; + } + + void Set(T tValue) + { + CWriteLock locallock(m_lock); + m_map[SELF_THREAD_ID] = tValue; + } + + void Remove() + { + T tValue; + + if(TryGet(tValue)) + { + CWriteLock locallock(m_lock); + m_map.erase(SELF_THREAD_ID); + } + } + + void Clear() + { + CWriteLock locallock(m_lock); + + if(!IsEmpty()) + m_map.clear(); + } + + TLocalMap& GetLocalMap() {return m_map;} + const TLocalMap& GetLocalMap() const {return m_map;} + + CTlsSimple& operator = (T t) {Set(t); return *this;} + BOOL operator == (T t) {return Get() == t;} + BOOL operator != (T t) {return Get() != t;} + BOOL operator >= (T t) {return Get() >= t;} + BOOL operator <= (T t) {return Get() <= t;} + BOOL operator > (T t) {return Get() > t;} + BOOL operator < (T t) {return Get() < t;} + + size_t Size () const {return m_map.size();} + bool IsEmpty() const {return m_map.empty();} + +public: + + CTlsSimple() + { + + } + + CTlsSimple(T tValue) + { + Set(tValue); + } + + ~CTlsSimple() + { + Clear(); + } + + DECLARE_NO_COPY_CLASS(CTlsSimple) + +private: + + CSimpleRWLock m_lock; + TLocalMap m_map; +}; diff --git a/common/http/Readme.txt b/common/http/Readme.txt new file mode 100644 index 0000000..b6dc35b --- /dev/null +++ b/common/http/Readme.txt @@ -0,0 +1,11 @@ +http_parser Modifications +-------------------- +1. move 'enum state' from http_parser.c to http_parser.h +3. http_parser.c ignore warning: "-Wconversion", "-Wsign-conversion" + +llhttp Modifications +-------------------- +1. llhttp_api.c ignore warning: "-Wconversion", "-Wsign-conversion" +1. llhttp_url.c ignore warning: "-Wconversion", "-Wsign-conversion" +2. llhttp_internal.c ignore warning: "-Wconversion", "-Wsign-conversion" "-Wunused-variable" "-Wunreachable-code" +3. llhttp.h, llhttp_url.h : LLHTTP_STRICT_MODE set default value 1 \ No newline at end of file diff --git a/common/http/llhttp.h b/common/http/llhttp.h new file mode 100644 index 0000000..63ad1af --- /dev/null +++ b/common/http/llhttp.h @@ -0,0 +1,903 @@ + +#ifndef INCLUDE_LLHTTP_H_ +#define INCLUDE_LLHTTP_H_ + +#define LLHTTP_VERSION_MAJOR 9 +#define LLHTTP_VERSION_MINOR 2 +#define LLHTTP_VERSION_PATCH 1 + +#ifndef INCLUDE_LLHTTP_ITSELF_H_ +#define INCLUDE_LLHTTP_ITSELF_H_ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef struct llhttp__internal_s llhttp__internal_t; +struct llhttp__internal_s { + int32_t _index; + void* _span_pos0; + void* _span_cb0; + int32_t error; + const char* reason; + const char* error_pos; + void* data; + void* _current; + uint64_t content_length; + uint8_t type; + uint8_t method; + uint8_t http_major; + uint8_t http_minor; + uint8_t header_state; + uint16_t lenient_flags; + uint8_t upgrade; + uint8_t finish; + uint16_t flags; + uint16_t status_code; + uint8_t initial_message_completed; + void* settings; +}; + +int llhttp__internal_init(llhttp__internal_t* s); +int llhttp__internal_execute(llhttp__internal_t* s, const char* p, const char* endp); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* INCLUDE_LLHTTP_ITSELF_H_ */ + + +#ifndef LLLLHTTP_C_HEADERS_ +#define LLLLHTTP_C_HEADERS_ +#ifdef __cplusplus +extern "C" { +#endif + +enum llhttp_errno { + HPE_OK = 0, + HPE_INTERNAL = 1, + HPE_STRICT = 2, + HPE_CR_EXPECTED = 25, + HPE_LF_EXPECTED = 3, + HPE_UNEXPECTED_CONTENT_LENGTH = 4, + HPE_UNEXPECTED_SPACE = 30, + HPE_CLOSED_CONNECTION = 5, + HPE_INVALID_METHOD = 6, + HPE_INVALID_URL = 7, + HPE_INVALID_CONSTANT = 8, + HPE_INVALID_VERSION = 9, + HPE_INVALID_HEADER_TOKEN = 10, + HPE_INVALID_CONTENT_LENGTH = 11, + HPE_INVALID_CHUNK_SIZE = 12, + HPE_INVALID_STATUS = 13, + HPE_INVALID_EOF_STATE = 14, + HPE_INVALID_TRANSFER_ENCODING = 15, + HPE_CB_MESSAGE_BEGIN = 16, + HPE_CB_HEADERS_COMPLETE = 17, + HPE_CB_MESSAGE_COMPLETE = 18, + HPE_CB_CHUNK_HEADER = 19, + HPE_CB_CHUNK_COMPLETE = 20, + HPE_PAUSED = 21, + HPE_PAUSED_UPGRADE = 22, + HPE_PAUSED_H2_UPGRADE = 23, + HPE_USER = 24, + HPE_CB_URL_COMPLETE = 26, + HPE_CB_STATUS_COMPLETE = 27, + HPE_CB_METHOD_COMPLETE = 32, + HPE_CB_VERSION_COMPLETE = 33, + HPE_CB_HEADER_FIELD_COMPLETE = 28, + HPE_CB_HEADER_VALUE_COMPLETE = 29, + HPE_CB_CHUNK_EXTENSION_NAME_COMPLETE = 34, + HPE_CB_CHUNK_EXTENSION_VALUE_COMPLETE = 35, + HPE_CB_RESET = 31 +}; +typedef enum llhttp_errno llhttp_errno_t; + +enum llhttp_flags { + F_CONNECTION_KEEP_ALIVE = 0x1, + F_CONNECTION_CLOSE = 0x2, + F_CONNECTION_UPGRADE = 0x4, + F_CHUNKED = 0x8, + F_UPGRADE = 0x10, + F_CONTENT_LENGTH = 0x20, + F_SKIPBODY = 0x40, + F_TRAILING = 0x80, + F_TRANSFER_ENCODING = 0x200 +}; +typedef enum llhttp_flags llhttp_flags_t; + +enum llhttp_lenient_flags { + LENIENT_HEADERS = 0x1, + LENIENT_CHUNKED_LENGTH = 0x2, + LENIENT_KEEP_ALIVE = 0x4, + LENIENT_TRANSFER_ENCODING = 0x8, + LENIENT_VERSION = 0x10, + LENIENT_DATA_AFTER_CLOSE = 0x20, + LENIENT_OPTIONAL_LF_AFTER_CR = 0x40, + LENIENT_OPTIONAL_CRLF_AFTER_CHUNK = 0x80, + LENIENT_OPTIONAL_CR_BEFORE_LF = 0x100, + LENIENT_SPACES_AFTER_CHUNK_SIZE = 0x200 +}; +typedef enum llhttp_lenient_flags llhttp_lenient_flags_t; + +enum llhttp_type { + HTTP_BOTH = 0, + HTTP_REQUEST = 1, + HTTP_RESPONSE = 2 +}; +typedef enum llhttp_type llhttp_type_t; + +enum llhttp_finish { + HTTP_FINISH_SAFE = 0, + HTTP_FINISH_SAFE_WITH_CB = 1, + HTTP_FINISH_UNSAFE = 2 +}; +typedef enum llhttp_finish llhttp_finish_t; + +enum llhttp_method { + HTTP_DELETE = 0, + HTTP_GET = 1, + HTTP_HEAD = 2, + HTTP_POST = 3, + HTTP_PUT = 4, + HTTP_CONNECT = 5, + HTTP_OPTIONS = 6, + HTTP_TRACE = 7, + HTTP_COPY = 8, + HTTP_LOCK = 9, + HTTP_MKCOL = 10, + HTTP_MOVE = 11, + HTTP_PROPFIND = 12, + HTTP_PROPPATCH = 13, + HTTP_SEARCH = 14, + HTTP_UNLOCK = 15, + HTTP_BIND = 16, + HTTP_REBIND = 17, + HTTP_UNBIND = 18, + HTTP_ACL = 19, + HTTP_REPORT = 20, + HTTP_MKACTIVITY = 21, + HTTP_CHECKOUT = 22, + HTTP_MERGE = 23, + HTTP_MSEARCH = 24, + HTTP_NOTIFY = 25, + HTTP_SUBSCRIBE = 26, + HTTP_UNSUBSCRIBE = 27, + HTTP_PATCH = 28, + HTTP_PURGE = 29, + HTTP_MKCALENDAR = 30, + HTTP_LINK = 31, + HTTP_UNLINK = 32, + HTTP_SOURCE = 33, + HTTP_PRI = 34, + HTTP_DESCRIBE = 35, + HTTP_ANNOUNCE = 36, + HTTP_SETUP = 37, + HTTP_PLAY = 38, + HTTP_PAUSE = 39, + HTTP_TEARDOWN = 40, + HTTP_GET_PARAMETER = 41, + HTTP_SET_PARAMETER = 42, + HTTP_REDIRECT = 43, + HTTP_RECORD = 44, + HTTP_FLUSH = 45, + HTTP_QUERY = 46 +}; +typedef enum llhttp_method llhttp_method_t; + +enum llhttp_status { + HTTP_STATUS_CONTINUE = 100, + HTTP_STATUS_SWITCHING_PROTOCOLS = 101, + HTTP_STATUS_PROCESSING = 102, + HTTP_STATUS_EARLY_HINTS = 103, + HTTP_STATUS_RESPONSE_IS_STALE = 110, + HTTP_STATUS_REVALIDATION_FAILED = 111, + HTTP_STATUS_DISCONNECTED_OPERATION = 112, + HTTP_STATUS_HEURISTIC_EXPIRATION = 113, + HTTP_STATUS_MISCELLANEOUS_WARNING = 199, + HTTP_STATUS_OK = 200, + HTTP_STATUS_CREATED = 201, + HTTP_STATUS_ACCEPTED = 202, + HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION = 203, + HTTP_STATUS_NO_CONTENT = 204, + HTTP_STATUS_RESET_CONTENT = 205, + HTTP_STATUS_PARTIAL_CONTENT = 206, + HTTP_STATUS_MULTI_STATUS = 207, + HTTP_STATUS_ALREADY_REPORTED = 208, + HTTP_STATUS_TRANSFORMATION_APPLIED = 214, + HTTP_STATUS_IM_USED = 226, + HTTP_STATUS_MISCELLANEOUS_PERSISTENT_WARNING = 299, + HTTP_STATUS_MULTIPLE_CHOICES = 300, + HTTP_STATUS_MOVED_PERMANENTLY = 301, + HTTP_STATUS_FOUND = 302, + HTTP_STATUS_SEE_OTHER = 303, + HTTP_STATUS_NOT_MODIFIED = 304, + HTTP_STATUS_USE_PROXY = 305, + HTTP_STATUS_SWITCH_PROXY = 306, + HTTP_STATUS_TEMPORARY_REDIRECT = 307, + HTTP_STATUS_PERMANENT_REDIRECT = 308, + HTTP_STATUS_BAD_REQUEST = 400, + HTTP_STATUS_UNAUTHORIZED = 401, + HTTP_STATUS_PAYMENT_REQUIRED = 402, + HTTP_STATUS_FORBIDDEN = 403, + HTTP_STATUS_NOT_FOUND = 404, + HTTP_STATUS_METHOD_NOT_ALLOWED = 405, + HTTP_STATUS_NOT_ACCEPTABLE = 406, + HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED = 407, + HTTP_STATUS_REQUEST_TIMEOUT = 408, + HTTP_STATUS_CONFLICT = 409, + HTTP_STATUS_GONE = 410, + HTTP_STATUS_LENGTH_REQUIRED = 411, + HTTP_STATUS_PRECONDITION_FAILED = 412, + HTTP_STATUS_PAYLOAD_TOO_LARGE = 413, + HTTP_STATUS_URI_TOO_LONG = 414, + HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE = 415, + HTTP_STATUS_RANGE_NOT_SATISFIABLE = 416, + HTTP_STATUS_EXPECTATION_FAILED = 417, + HTTP_STATUS_IM_A_TEAPOT = 418, + HTTP_STATUS_PAGE_EXPIRED = 419, + HTTP_STATUS_ENHANCE_YOUR_CALM = 420, + HTTP_STATUS_MISDIRECTED_REQUEST = 421, + HTTP_STATUS_UNPROCESSABLE_ENTITY = 422, + HTTP_STATUS_LOCKED = 423, + HTTP_STATUS_FAILED_DEPENDENCY = 424, + HTTP_STATUS_TOO_EARLY = 425, + HTTP_STATUS_UPGRADE_REQUIRED = 426, + HTTP_STATUS_PRECONDITION_REQUIRED = 428, + HTTP_STATUS_TOO_MANY_REQUESTS = 429, + HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE_UNOFFICIAL = 430, + HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 431, + HTTP_STATUS_LOGIN_TIMEOUT = 440, + HTTP_STATUS_NO_RESPONSE = 444, + HTTP_STATUS_RETRY_WITH = 449, + HTTP_STATUS_BLOCKED_BY_PARENTAL_CONTROL = 450, + HTTP_STATUS_UNAVAILABLE_FOR_LEGAL_REASONS = 451, + HTTP_STATUS_CLIENT_CLOSED_LOAD_BALANCED_REQUEST = 460, + HTTP_STATUS_INVALID_X_FORWARDED_FOR = 463, + HTTP_STATUS_REQUEST_HEADER_TOO_LARGE = 494, + HTTP_STATUS_SSL_CERTIFICATE_ERROR = 495, + HTTP_STATUS_SSL_CERTIFICATE_REQUIRED = 496, + HTTP_STATUS_HTTP_REQUEST_SENT_TO_HTTPS_PORT = 497, + HTTP_STATUS_INVALID_TOKEN = 498, + HTTP_STATUS_CLIENT_CLOSED_REQUEST = 499, + HTTP_STATUS_INTERNAL_SERVER_ERROR = 500, + HTTP_STATUS_NOT_IMPLEMENTED = 501, + HTTP_STATUS_BAD_GATEWAY = 502, + HTTP_STATUS_SERVICE_UNAVAILABLE = 503, + HTTP_STATUS_GATEWAY_TIMEOUT = 504, + HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED = 505, + HTTP_STATUS_VARIANT_ALSO_NEGOTIATES = 506, + HTTP_STATUS_INSUFFICIENT_STORAGE = 507, + HTTP_STATUS_LOOP_DETECTED = 508, + HTTP_STATUS_BANDWIDTH_LIMIT_EXCEEDED = 509, + HTTP_STATUS_NOT_EXTENDED = 510, + HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED = 511, + HTTP_STATUS_WEB_SERVER_UNKNOWN_ERROR = 520, + HTTP_STATUS_WEB_SERVER_IS_DOWN = 521, + HTTP_STATUS_CONNECTION_TIMEOUT = 522, + HTTP_STATUS_ORIGIN_IS_UNREACHABLE = 523, + HTTP_STATUS_TIMEOUT_OCCURED = 524, + HTTP_STATUS_SSL_HANDSHAKE_FAILED = 525, + HTTP_STATUS_INVALID_SSL_CERTIFICATE = 526, + HTTP_STATUS_RAILGUN_ERROR = 527, + HTTP_STATUS_SITE_IS_OVERLOADED = 529, + HTTP_STATUS_SITE_IS_FROZEN = 530, + HTTP_STATUS_IDENTITY_PROVIDER_AUTHENTICATION_ERROR = 561, + HTTP_STATUS_NETWORK_READ_TIMEOUT = 598, + HTTP_STATUS_NETWORK_CONNECT_TIMEOUT = 599 +}; +typedef enum llhttp_status llhttp_status_t; + +#define HTTP_ERRNO_MAP(XX) \ + XX(0, OK, OK) \ + XX(1, INTERNAL, INTERNAL) \ + XX(2, STRICT, STRICT) \ + XX(25, CR_EXPECTED, CR_EXPECTED) \ + XX(3, LF_EXPECTED, LF_EXPECTED) \ + XX(4, UNEXPECTED_CONTENT_LENGTH, UNEXPECTED_CONTENT_LENGTH) \ + XX(30, UNEXPECTED_SPACE, UNEXPECTED_SPACE) \ + XX(5, CLOSED_CONNECTION, CLOSED_CONNECTION) \ + XX(6, INVALID_METHOD, INVALID_METHOD) \ + XX(7, INVALID_URL, INVALID_URL) \ + XX(8, INVALID_CONSTANT, INVALID_CONSTANT) \ + XX(9, INVALID_VERSION, INVALID_VERSION) \ + XX(10, INVALID_HEADER_TOKEN, INVALID_HEADER_TOKEN) \ + XX(11, INVALID_CONTENT_LENGTH, INVALID_CONTENT_LENGTH) \ + XX(12, INVALID_CHUNK_SIZE, INVALID_CHUNK_SIZE) \ + XX(13, INVALID_STATUS, INVALID_STATUS) \ + XX(14, INVALID_EOF_STATE, INVALID_EOF_STATE) \ + XX(15, INVALID_TRANSFER_ENCODING, INVALID_TRANSFER_ENCODING) \ + XX(16, CB_MESSAGE_BEGIN, CB_MESSAGE_BEGIN) \ + XX(17, CB_HEADERS_COMPLETE, CB_HEADERS_COMPLETE) \ + XX(18, CB_MESSAGE_COMPLETE, CB_MESSAGE_COMPLETE) \ + XX(19, CB_CHUNK_HEADER, CB_CHUNK_HEADER) \ + XX(20, CB_CHUNK_COMPLETE, CB_CHUNK_COMPLETE) \ + XX(21, PAUSED, PAUSED) \ + XX(22, PAUSED_UPGRADE, PAUSED_UPGRADE) \ + XX(23, PAUSED_H2_UPGRADE, PAUSED_H2_UPGRADE) \ + XX(24, USER, USER) \ + XX(26, CB_URL_COMPLETE, CB_URL_COMPLETE) \ + XX(27, CB_STATUS_COMPLETE, CB_STATUS_COMPLETE) \ + XX(32, CB_METHOD_COMPLETE, CB_METHOD_COMPLETE) \ + XX(33, CB_VERSION_COMPLETE, CB_VERSION_COMPLETE) \ + XX(28, CB_HEADER_FIELD_COMPLETE, CB_HEADER_FIELD_COMPLETE) \ + XX(29, CB_HEADER_VALUE_COMPLETE, CB_HEADER_VALUE_COMPLETE) \ + XX(34, CB_CHUNK_EXTENSION_NAME_COMPLETE, CB_CHUNK_EXTENSION_NAME_COMPLETE) \ + XX(35, CB_CHUNK_EXTENSION_VALUE_COMPLETE, CB_CHUNK_EXTENSION_VALUE_COMPLETE) \ + XX(31, CB_RESET, CB_RESET) \ + + +#define HTTP_METHOD_MAP(XX) \ + XX(0, DELETE, DELETE) \ + XX(1, GET, GET) \ + XX(2, HEAD, HEAD) \ + XX(3, POST, POST) \ + XX(4, PUT, PUT) \ + XX(5, CONNECT, CONNECT) \ + XX(6, OPTIONS, OPTIONS) \ + XX(7, TRACE, TRACE) \ + XX(8, COPY, COPY) \ + XX(9, LOCK, LOCK) \ + XX(10, MKCOL, MKCOL) \ + XX(11, MOVE, MOVE) \ + XX(12, PROPFIND, PROPFIND) \ + XX(13, PROPPATCH, PROPPATCH) \ + XX(14, SEARCH, SEARCH) \ + XX(15, UNLOCK, UNLOCK) \ + XX(16, BIND, BIND) \ + XX(17, REBIND, REBIND) \ + XX(18, UNBIND, UNBIND) \ + XX(19, ACL, ACL) \ + XX(20, REPORT, REPORT) \ + XX(21, MKACTIVITY, MKACTIVITY) \ + XX(22, CHECKOUT, CHECKOUT) \ + XX(23, MERGE, MERGE) \ + XX(24, MSEARCH, M-SEARCH) \ + XX(25, NOTIFY, NOTIFY) \ + XX(26, SUBSCRIBE, SUBSCRIBE) \ + XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ + XX(28, PATCH, PATCH) \ + XX(29, PURGE, PURGE) \ + XX(30, MKCALENDAR, MKCALENDAR) \ + XX(31, LINK, LINK) \ + XX(32, UNLINK, UNLINK) \ + XX(33, SOURCE, SOURCE) \ + XX(46, QUERY, QUERY) \ + + +#define RTSP_METHOD_MAP(XX) \ + XX(1, GET, GET) \ + XX(3, POST, POST) \ + XX(6, OPTIONS, OPTIONS) \ + XX(35, DESCRIBE, DESCRIBE) \ + XX(36, ANNOUNCE, ANNOUNCE) \ + XX(37, SETUP, SETUP) \ + XX(38, PLAY, PLAY) \ + XX(39, PAUSE, PAUSE) \ + XX(40, TEARDOWN, TEARDOWN) \ + XX(41, GET_PARAMETER, GET_PARAMETER) \ + XX(42, SET_PARAMETER, SET_PARAMETER) \ + XX(43, REDIRECT, REDIRECT) \ + XX(44, RECORD, RECORD) \ + XX(45, FLUSH, FLUSH) \ + + +#define HTTP_ALL_METHOD_MAP(XX) \ + XX(0, DELETE, DELETE) \ + XX(1, GET, GET) \ + XX(2, HEAD, HEAD) \ + XX(3, POST, POST) \ + XX(4, PUT, PUT) \ + XX(5, CONNECT, CONNECT) \ + XX(6, OPTIONS, OPTIONS) \ + XX(7, TRACE, TRACE) \ + XX(8, COPY, COPY) \ + XX(9, LOCK, LOCK) \ + XX(10, MKCOL, MKCOL) \ + XX(11, MOVE, MOVE) \ + XX(12, PROPFIND, PROPFIND) \ + XX(13, PROPPATCH, PROPPATCH) \ + XX(14, SEARCH, SEARCH) \ + XX(15, UNLOCK, UNLOCK) \ + XX(16, BIND, BIND) \ + XX(17, REBIND, REBIND) \ + XX(18, UNBIND, UNBIND) \ + XX(19, ACL, ACL) \ + XX(20, REPORT, REPORT) \ + XX(21, MKACTIVITY, MKACTIVITY) \ + XX(22, CHECKOUT, CHECKOUT) \ + XX(23, MERGE, MERGE) \ + XX(24, MSEARCH, M-SEARCH) \ + XX(25, NOTIFY, NOTIFY) \ + XX(26, SUBSCRIBE, SUBSCRIBE) \ + XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ + XX(28, PATCH, PATCH) \ + XX(29, PURGE, PURGE) \ + XX(30, MKCALENDAR, MKCALENDAR) \ + XX(31, LINK, LINK) \ + XX(32, UNLINK, UNLINK) \ + XX(33, SOURCE, SOURCE) \ + XX(34, PRI, PRI) \ + XX(35, DESCRIBE, DESCRIBE) \ + XX(36, ANNOUNCE, ANNOUNCE) \ + XX(37, SETUP, SETUP) \ + XX(38, PLAY, PLAY) \ + XX(39, PAUSE, PAUSE) \ + XX(40, TEARDOWN, TEARDOWN) \ + XX(41, GET_PARAMETER, GET_PARAMETER) \ + XX(42, SET_PARAMETER, SET_PARAMETER) \ + XX(43, REDIRECT, REDIRECT) \ + XX(44, RECORD, RECORD) \ + XX(45, FLUSH, FLUSH) \ + XX(46, QUERY, QUERY) \ + + +#define HTTP_STATUS_MAP(XX) \ + XX(100, CONTINUE, CONTINUE) \ + XX(101, SWITCHING_PROTOCOLS, SWITCHING_PROTOCOLS) \ + XX(102, PROCESSING, PROCESSING) \ + XX(103, EARLY_HINTS, EARLY_HINTS) \ + XX(110, RESPONSE_IS_STALE, RESPONSE_IS_STALE) \ + XX(111, REVALIDATION_FAILED, REVALIDATION_FAILED) \ + XX(112, DISCONNECTED_OPERATION, DISCONNECTED_OPERATION) \ + XX(113, HEURISTIC_EXPIRATION, HEURISTIC_EXPIRATION) \ + XX(199, MISCELLANEOUS_WARNING, MISCELLANEOUS_WARNING) \ + XX(200, OK, OK) \ + XX(201, CREATED, CREATED) \ + XX(202, ACCEPTED, ACCEPTED) \ + XX(203, NON_AUTHORITATIVE_INFORMATION, NON_AUTHORITATIVE_INFORMATION) \ + XX(204, NO_CONTENT, NO_CONTENT) \ + XX(205, RESET_CONTENT, RESET_CONTENT) \ + XX(206, PARTIAL_CONTENT, PARTIAL_CONTENT) \ + XX(207, MULTI_STATUS, MULTI_STATUS) \ + XX(208, ALREADY_REPORTED, ALREADY_REPORTED) \ + XX(214, TRANSFORMATION_APPLIED, TRANSFORMATION_APPLIED) \ + XX(226, IM_USED, IM_USED) \ + XX(299, MISCELLANEOUS_PERSISTENT_WARNING, MISCELLANEOUS_PERSISTENT_WARNING) \ + XX(300, MULTIPLE_CHOICES, MULTIPLE_CHOICES) \ + XX(301, MOVED_PERMANENTLY, MOVED_PERMANENTLY) \ + XX(302, FOUND, FOUND) \ + XX(303, SEE_OTHER, SEE_OTHER) \ + XX(304, NOT_MODIFIED, NOT_MODIFIED) \ + XX(305, USE_PROXY, USE_PROXY) \ + XX(306, SWITCH_PROXY, SWITCH_PROXY) \ + XX(307, TEMPORARY_REDIRECT, TEMPORARY_REDIRECT) \ + XX(308, PERMANENT_REDIRECT, PERMANENT_REDIRECT) \ + XX(400, BAD_REQUEST, BAD_REQUEST) \ + XX(401, UNAUTHORIZED, UNAUTHORIZED) \ + XX(402, PAYMENT_REQUIRED, PAYMENT_REQUIRED) \ + XX(403, FORBIDDEN, FORBIDDEN) \ + XX(404, NOT_FOUND, NOT_FOUND) \ + XX(405, METHOD_NOT_ALLOWED, METHOD_NOT_ALLOWED) \ + XX(406, NOT_ACCEPTABLE, NOT_ACCEPTABLE) \ + XX(407, PROXY_AUTHENTICATION_REQUIRED, PROXY_AUTHENTICATION_REQUIRED) \ + XX(408, REQUEST_TIMEOUT, REQUEST_TIMEOUT) \ + XX(409, CONFLICT, CONFLICT) \ + XX(410, GONE, GONE) \ + XX(411, LENGTH_REQUIRED, LENGTH_REQUIRED) \ + XX(412, PRECONDITION_FAILED, PRECONDITION_FAILED) \ + XX(413, PAYLOAD_TOO_LARGE, PAYLOAD_TOO_LARGE) \ + XX(414, URI_TOO_LONG, URI_TOO_LONG) \ + XX(415, UNSUPPORTED_MEDIA_TYPE, UNSUPPORTED_MEDIA_TYPE) \ + XX(416, RANGE_NOT_SATISFIABLE, RANGE_NOT_SATISFIABLE) \ + XX(417, EXPECTATION_FAILED, EXPECTATION_FAILED) \ + XX(418, IM_A_TEAPOT, IM_A_TEAPOT) \ + XX(419, PAGE_EXPIRED, PAGE_EXPIRED) \ + XX(420, ENHANCE_YOUR_CALM, ENHANCE_YOUR_CALM) \ + XX(421, MISDIRECTED_REQUEST, MISDIRECTED_REQUEST) \ + XX(422, UNPROCESSABLE_ENTITY, UNPROCESSABLE_ENTITY) \ + XX(423, LOCKED, LOCKED) \ + XX(424, FAILED_DEPENDENCY, FAILED_DEPENDENCY) \ + XX(425, TOO_EARLY, TOO_EARLY) \ + XX(426, UPGRADE_REQUIRED, UPGRADE_REQUIRED) \ + XX(428, PRECONDITION_REQUIRED, PRECONDITION_REQUIRED) \ + XX(429, TOO_MANY_REQUESTS, TOO_MANY_REQUESTS) \ + XX(430, REQUEST_HEADER_FIELDS_TOO_LARGE_UNOFFICIAL, REQUEST_HEADER_FIELDS_TOO_LARGE_UNOFFICIAL) \ + XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, REQUEST_HEADER_FIELDS_TOO_LARGE) \ + XX(440, LOGIN_TIMEOUT, LOGIN_TIMEOUT) \ + XX(444, NO_RESPONSE, NO_RESPONSE) \ + XX(449, RETRY_WITH, RETRY_WITH) \ + XX(450, BLOCKED_BY_PARENTAL_CONTROL, BLOCKED_BY_PARENTAL_CONTROL) \ + XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, UNAVAILABLE_FOR_LEGAL_REASONS) \ + XX(460, CLIENT_CLOSED_LOAD_BALANCED_REQUEST, CLIENT_CLOSED_LOAD_BALANCED_REQUEST) \ + XX(463, INVALID_X_FORWARDED_FOR, INVALID_X_FORWARDED_FOR) \ + XX(494, REQUEST_HEADER_TOO_LARGE, REQUEST_HEADER_TOO_LARGE) \ + XX(495, SSL_CERTIFICATE_ERROR, SSL_CERTIFICATE_ERROR) \ + XX(496, SSL_CERTIFICATE_REQUIRED, SSL_CERTIFICATE_REQUIRED) \ + XX(497, HTTP_REQUEST_SENT_TO_HTTPS_PORT, HTTP_REQUEST_SENT_TO_HTTPS_PORT) \ + XX(498, INVALID_TOKEN, INVALID_TOKEN) \ + XX(499, CLIENT_CLOSED_REQUEST, CLIENT_CLOSED_REQUEST) \ + XX(500, INTERNAL_SERVER_ERROR, INTERNAL_SERVER_ERROR) \ + XX(501, NOT_IMPLEMENTED, NOT_IMPLEMENTED) \ + XX(502, BAD_GATEWAY, BAD_GATEWAY) \ + XX(503, SERVICE_UNAVAILABLE, SERVICE_UNAVAILABLE) \ + XX(504, GATEWAY_TIMEOUT, GATEWAY_TIMEOUT) \ + XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP_VERSION_NOT_SUPPORTED) \ + XX(506, VARIANT_ALSO_NEGOTIATES, VARIANT_ALSO_NEGOTIATES) \ + XX(507, INSUFFICIENT_STORAGE, INSUFFICIENT_STORAGE) \ + XX(508, LOOP_DETECTED, LOOP_DETECTED) \ + XX(509, BANDWIDTH_LIMIT_EXCEEDED, BANDWIDTH_LIMIT_EXCEEDED) \ + XX(510, NOT_EXTENDED, NOT_EXTENDED) \ + XX(511, NETWORK_AUTHENTICATION_REQUIRED, NETWORK_AUTHENTICATION_REQUIRED) \ + XX(520, WEB_SERVER_UNKNOWN_ERROR, WEB_SERVER_UNKNOWN_ERROR) \ + XX(521, WEB_SERVER_IS_DOWN, WEB_SERVER_IS_DOWN) \ + XX(522, CONNECTION_TIMEOUT, CONNECTION_TIMEOUT) \ + XX(523, ORIGIN_IS_UNREACHABLE, ORIGIN_IS_UNREACHABLE) \ + XX(524, TIMEOUT_OCCURED, TIMEOUT_OCCURED) \ + XX(525, SSL_HANDSHAKE_FAILED, SSL_HANDSHAKE_FAILED) \ + XX(526, INVALID_SSL_CERTIFICATE, INVALID_SSL_CERTIFICATE) \ + XX(527, RAILGUN_ERROR, RAILGUN_ERROR) \ + XX(529, SITE_IS_OVERLOADED, SITE_IS_OVERLOADED) \ + XX(530, SITE_IS_FROZEN, SITE_IS_FROZEN) \ + XX(561, IDENTITY_PROVIDER_AUTHENTICATION_ERROR, IDENTITY_PROVIDER_AUTHENTICATION_ERROR) \ + XX(598, NETWORK_READ_TIMEOUT, NETWORK_READ_TIMEOUT) \ + XX(599, NETWORK_CONNECT_TIMEOUT, NETWORK_CONNECT_TIMEOUT) \ + + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* LLLLHTTP_C_HEADERS_ */ + + +#ifndef INCLUDE_LLHTTP_API_H_ +#define INCLUDE_LLHTTP_API_H_ +#ifdef __cplusplus +extern "C" { +#endif +#include + +#if defined(__wasm__) +#define LLHTTP_EXPORT __attribute__((visibility("default"))) +//#elif defined(_WIN32) +//#define LLHTTP_EXPORT __declspec(dllexport) +#else +#define LLHTTP_EXPORT +#endif + +typedef llhttp__internal_t llhttp_t; +typedef struct llhttp_settings_s llhttp_settings_t; + +typedef int (*llhttp_data_cb)(llhttp_t*, const char *at, size_t length); +typedef int (*llhttp_cb)(llhttp_t*); + +struct llhttp_settings_s { + /* Possible return values 0, -1, `HPE_PAUSED` */ + llhttp_cb on_message_begin; + + /* Possible return values 0, -1, HPE_USER */ + llhttp_data_cb on_url; + llhttp_data_cb on_status; + llhttp_data_cb on_method; + llhttp_data_cb on_version; + llhttp_data_cb on_header_field; + llhttp_data_cb on_header_value; + llhttp_data_cb on_chunk_extension_name; + llhttp_data_cb on_chunk_extension_value; + + /* Possible return values: + * 0 - Proceed normally + * 1 - Assume that request/response has no body, and proceed to parsing the + * next message + * 2 - Assume absence of body (as above) and make `llhttp_execute()` return + * `HPE_PAUSED_UPGRADE` + * -1 - Error + * `HPE_PAUSED` + */ + llhttp_cb on_headers_complete; + + /* Possible return values 0, -1, HPE_USER */ + llhttp_data_cb on_body; + + /* Possible return values 0, -1, `HPE_PAUSED` */ + llhttp_cb on_message_complete; + llhttp_cb on_url_complete; + llhttp_cb on_status_complete; + llhttp_cb on_method_complete; + llhttp_cb on_version_complete; + llhttp_cb on_header_field_complete; + llhttp_cb on_header_value_complete; + llhttp_cb on_chunk_extension_name_complete; + llhttp_cb on_chunk_extension_value_complete; + + /* When on_chunk_header is called, the current chunk length is stored + * in parser->content_length. + * Possible return values 0, -1, `HPE_PAUSED` + */ + llhttp_cb on_chunk_header; + llhttp_cb on_chunk_complete; + llhttp_cb on_reset; +}; + +/* Initialize the parser with specific type and user settings. + * + * NOTE: lifetime of `settings` has to be at least the same as the lifetime of + * the `parser` here. In practice, `settings` has to be either a static + * variable or be allocated with `malloc`, `new`, etc. + */ +LLHTTP_EXPORT +void llhttp_init(llhttp_t* parser, llhttp_type_t type, + const llhttp_settings_t* settings); + +LLHTTP_EXPORT +llhttp_t* llhttp_alloc(llhttp_type_t type); + +LLHTTP_EXPORT +void llhttp_free(llhttp_t* parser); + +LLHTTP_EXPORT +uint8_t llhttp_get_type(llhttp_t* parser); + +LLHTTP_EXPORT +uint8_t llhttp_get_http_major(llhttp_t* parser); + +LLHTTP_EXPORT +uint8_t llhttp_get_http_minor(llhttp_t* parser); + +LLHTTP_EXPORT +uint8_t llhttp_get_method(llhttp_t* parser); + +LLHTTP_EXPORT +int llhttp_get_status_code(llhttp_t* parser); + +LLHTTP_EXPORT +uint8_t llhttp_get_upgrade(llhttp_t* parser); + +/* Reset an already initialized parser back to the start state, preserving the + * existing parser type, callback settings, user data, and lenient flags. + */ +LLHTTP_EXPORT +void llhttp_reset(llhttp_t* parser); + +/* Initialize the settings object */ +LLHTTP_EXPORT +void llhttp_settings_init(llhttp_settings_t* settings); + +/* Parse full or partial request/response, invoking user callbacks along the + * way. + * + * If any of `llhttp_data_cb` returns errno not equal to `HPE_OK` - the parsing + * interrupts, and such errno is returned from `llhttp_execute()`. If + * `HPE_PAUSED` was used as a errno, the execution can be resumed with + * `llhttp_resume()` call. + * + * In a special case of CONNECT/Upgrade request/response `HPE_PAUSED_UPGRADE` + * is returned after fully parsing the request/response. If the user wishes to + * continue parsing, they need to invoke `llhttp_resume_after_upgrade()`. + * + * NOTE: if this function ever returns a non-pause type error, it will continue + * to return the same error upon each successive call up until `llhttp_init()` + * is called. + */ +LLHTTP_EXPORT +llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len); + +/* This method should be called when the other side has no further bytes to + * send (e.g. shutdown of readable side of the TCP connection.) + * + * Requests without `Content-Length` and other messages might require treating + * all incoming bytes as the part of the body, up to the last byte of the + * connection. This method will invoke `on_message_complete()` callback if the + * request was terminated safely. Otherwise a error code would be returned. + */ +LLHTTP_EXPORT +llhttp_errno_t llhttp_finish(llhttp_t* parser); + +/* Returns `1` if the incoming message is parsed until the last byte, and has + * to be completed by calling `llhttp_finish()` on EOF + */ +LLHTTP_EXPORT +int llhttp_message_needs_eof(const llhttp_t* parser); + +/* Returns `1` if there might be any other messages following the last that was + * successfully parsed. + */ +LLHTTP_EXPORT +int llhttp_should_keep_alive(const llhttp_t* parser); + +/* Make further calls of `llhttp_execute()` return `HPE_PAUSED` and set + * appropriate error reason. + * + * Important: do not call this from user callbacks! User callbacks must return + * `HPE_PAUSED` if pausing is required. + */ +LLHTTP_EXPORT +void llhttp_pause(llhttp_t* parser); + +/* Might be called to resume the execution after the pause in user's callback. + * See `llhttp_execute()` above for details. + * + * Call this only if `llhttp_execute()` returns `HPE_PAUSED`. + */ +LLHTTP_EXPORT +void llhttp_resume(llhttp_t* parser); + +/* Might be called to resume the execution after the pause in user's callback. + * See `llhttp_execute()` above for details. + * + * Call this only if `llhttp_execute()` returns `HPE_PAUSED_UPGRADE` + */ +LLHTTP_EXPORT +void llhttp_resume_after_upgrade(llhttp_t* parser); + +/* Returns the latest return error */ +LLHTTP_EXPORT +llhttp_errno_t llhttp_get_errno(const llhttp_t* parser); + +/* Returns the verbal explanation of the latest returned error. + * + * Note: User callback should set error reason when returning the error. See + * `llhttp_set_error_reason()` for details. + */ +LLHTTP_EXPORT +const char* llhttp_get_error_reason(const llhttp_t* parser); + +/* Assign verbal description to the returned error. Must be called in user + * callbacks right before returning the errno. + * + * Note: `HPE_USER` error code might be useful in user callbacks. + */ +LLHTTP_EXPORT +void llhttp_set_error_reason(llhttp_t* parser, const char* reason); + +/* Returns the pointer to the last parsed byte before the returned error. The + * pointer is relative to the `data` argument of `llhttp_execute()`. + * + * Note: this method might be useful for counting the number of parsed bytes. + */ +LLHTTP_EXPORT +const char* llhttp_get_error_pos(const llhttp_t* parser); + +/* Returns textual name of error code */ +LLHTTP_EXPORT +const char* llhttp_errno_name(llhttp_errno_t err); + +/* Returns textual name of HTTP method */ +LLHTTP_EXPORT +const char* llhttp_method_name(llhttp_method_t method); + +/* Returns textual name of HTTP status */ +LLHTTP_EXPORT +const char* llhttp_status_name(llhttp_status_t status); + +/* Enables/disables lenient header value parsing (disabled by default). + * + * Lenient parsing disables header value token checks, extending llhttp's + * protocol support to highly non-compliant clients/server. No + * `HPE_INVALID_HEADER_TOKEN` will be raised for incorrect header values when + * lenient parsing is "on". + * + * **Enabling this flag can pose a security issue since you will be exposed to + * request smuggling attacks. USE WITH CAUTION!** + */ +LLHTTP_EXPORT +void llhttp_set_lenient_headers(llhttp_t* parser, int enabled); + + +/* Enables/disables lenient handling of conflicting `Transfer-Encoding` and + * `Content-Length` headers (disabled by default). + * + * Normally `llhttp` would error when `Transfer-Encoding` is present in + * conjunction with `Content-Length`. This error is important to prevent HTTP + * request smuggling, but may be less desirable for small number of cases + * involving legacy servers. + * + * **Enabling this flag can pose a security issue since you will be exposed to + * request smuggling attacks. USE WITH CAUTION!** + */ +LLHTTP_EXPORT +void llhttp_set_lenient_chunked_length(llhttp_t* parser, int enabled); + + +/* Enables/disables lenient handling of `Connection: close` and HTTP/1.0 + * requests responses. + * + * Normally `llhttp` would error on (in strict mode) or discard (in loose mode) + * the HTTP request/response after the request/response with `Connection: close` + * and `Content-Length`. This is important to prevent cache poisoning attacks, + * but might interact badly with outdated and insecure clients. With this flag + * the extra request/response will be parsed normally. + * + * **Enabling this flag can pose a security issue since you will be exposed to + * poisoning attacks. USE WITH CAUTION!** + */ +LLHTTP_EXPORT +void llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled); + +/* Enables/disables lenient handling of `Transfer-Encoding` header. + * + * Normally `llhttp` would error when a `Transfer-Encoding` has `chunked` value + * and another value after it (either in a single header or in multiple + * headers whose value are internally joined using `, `). + * This is mandated by the spec to reliably determine request body size and thus + * avoid request smuggling. + * With this flag the extra value will be parsed normally. + * + * **Enabling this flag can pose a security issue since you will be exposed to + * request smuggling attacks. USE WITH CAUTION!** + */ +LLHTTP_EXPORT +void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, int enabled); + +/* Enables/disables lenient handling of HTTP version. + * + * Normally `llhttp` would error when the HTTP version in the request or status line + * is not `0.9`, `1.0`, `1.1` or `2.0`. + * With this flag the invalid value will be parsed normally. + * + * **Enabling this flag can pose a security issue since you will allow unsupported + * HTTP versions. USE WITH CAUTION!** + */ +LLHTTP_EXPORT +void llhttp_set_lenient_version(llhttp_t* parser, int enabled); + +/* Enables/disables lenient handling of additional data received after a message ends + * and keep-alive is disabled. + * + * Normally `llhttp` would error when additional unexpected data is received if the message + * contains the `Connection` header with `close` value. + * With this flag the extra data will discarded without throwing an error. + * + * **Enabling this flag can pose a security issue since you will be exposed to + * poisoning attacks. USE WITH CAUTION!** + */ +LLHTTP_EXPORT +void llhttp_set_lenient_data_after_close(llhttp_t* parser, int enabled); + +/* Enables/disables lenient handling of incomplete CRLF sequences. + * + * Normally `llhttp` would error when a CR is not followed by LF when terminating the + * request line, the status line, the headers or a chunk header. + * With this flag only a CR is required to terminate such sections. + * + * **Enabling this flag can pose a security issue since you will be exposed to + * request smuggling attacks. USE WITH CAUTION!** + */ +LLHTTP_EXPORT +void llhttp_set_lenient_optional_lf_after_cr(llhttp_t* parser, int enabled); + +/* + * Enables/disables lenient handling of line separators. + * + * Normally `llhttp` would error when a LF is not preceded by CR when terminating the + * request line, the status line, the headers, a chunk header or a chunk data. + * With this flag only a LF is required to terminate such sections. + * + * **Enabling this flag can pose a security issue since you will be exposed to + * request smuggling attacks. USE WITH CAUTION!** + */ +LLHTTP_EXPORT +void llhttp_set_lenient_optional_cr_before_lf(llhttp_t* parser, int enabled); + +/* Enables/disables lenient handling of chunks not separated via CRLF. + * + * Normally `llhttp` would error when after a chunk data a CRLF is missing before + * starting a new chunk. + * With this flag the new chunk can start immediately after the previous one. + * + * **Enabling this flag can pose a security issue since you will be exposed to + * request smuggling attacks. USE WITH CAUTION!** + */ +LLHTTP_EXPORT +void llhttp_set_lenient_optional_crlf_after_chunk(llhttp_t* parser, int enabled); + +/* Enables/disables lenient handling of spaces after chunk size. + * + * Normally `llhttp` would error when after a chunk size is followed by one or more + * spaces are present instead of a CRLF or `;`. + * With this flag this check is disabled. + * + * **Enabling this flag can pose a security issue since you will be exposed to + * request smuggling attacks. USE WITH CAUTION!** + */ +LLHTTP_EXPORT +void llhttp_set_lenient_spaces_after_chunk_size(llhttp_t* parser, int enabled); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* INCLUDE_LLHTTP_API_H_ */ + + +#endif /* INCLUDE_LLHTTP_H_ */ diff --git a/common/http/llhttp_api.c b/common/http/llhttp_api.c new file mode 100644 index 0000000..adf35ff --- /dev/null +++ b/common/http/llhttp_api.c @@ -0,0 +1,520 @@ +#include +#include +#include + +#include "llhttp.h" + +#ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wconversion" + #pragma GCC diagnostic ignored "-Wsign-conversion" +#endif + +#define CALLBACK_MAYBE(PARSER, NAME) \ + do { \ + const llhttp_settings_t* settings; \ + settings = (const llhttp_settings_t*) (PARSER)->settings; \ + if (settings == NULL || settings->NAME == NULL) { \ + err = 0; \ + break; \ + } \ + err = settings->NAME((PARSER)); \ + } while (0) + +#define SPAN_CALLBACK_MAYBE(PARSER, NAME, START, LEN) \ + do { \ + const llhttp_settings_t* settings; \ + settings = (const llhttp_settings_t*) (PARSER)->settings; \ + if (settings == NULL || settings->NAME == NULL) { \ + err = 0; \ + break; \ + } \ + err = settings->NAME((PARSER), (START), (LEN)); \ + if (err == -1) { \ + err = HPE_USER; \ + llhttp_set_error_reason((PARSER), "Span callback error in " #NAME); \ + } \ + } while (0) + +void llhttp_init(llhttp_t* parser, llhttp_type_t type, + const llhttp_settings_t* settings) { + llhttp__internal_init(parser); + + parser->type = type; + parser->settings = (void*) settings; +} + + +#if defined(__wasm__) + +extern int wasm_on_message_begin(llhttp_t * p); +extern int wasm_on_url(llhttp_t* p, const char* at, size_t length); +extern int wasm_on_status(llhttp_t* p, const char* at, size_t length); +extern int wasm_on_header_field(llhttp_t* p, const char* at, size_t length); +extern int wasm_on_header_value(llhttp_t* p, const char* at, size_t length); +extern int wasm_on_headers_complete(llhttp_t * p, int status_code, + uint8_t upgrade, int should_keep_alive); +extern int wasm_on_body(llhttp_t* p, const char* at, size_t length); +extern int wasm_on_message_complete(llhttp_t * p); + +static int wasm_on_headers_complete_wrap(llhttp_t* p) { + return wasm_on_headers_complete(p, p->status_code, p->upgrade, + llhttp_should_keep_alive(p)); +} + +const llhttp_settings_t wasm_settings = { + wasm_on_message_begin, + wasm_on_url, + wasm_on_status, + NULL, + NULL, + wasm_on_header_field, + wasm_on_header_value, + NULL, + NULL, + wasm_on_headers_complete_wrap, + wasm_on_body, + wasm_on_message_complete, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, +}; + + +llhttp_t* llhttp_alloc(llhttp_type_t type) { + llhttp_t* parser = malloc(sizeof(llhttp_t)); + llhttp_init(parser, type, &wasm_settings); + return parser; +} + +void llhttp_free(llhttp_t* parser) { + free(parser); +} + +#endif // defined(__wasm__) + +/* Some getters required to get stuff from the parser */ + +uint8_t llhttp_get_type(llhttp_t* parser) { + return parser->type; +} + +uint8_t llhttp_get_http_major(llhttp_t* parser) { + return parser->http_major; +} + +uint8_t llhttp_get_http_minor(llhttp_t* parser) { + return parser->http_minor; +} + +uint8_t llhttp_get_method(llhttp_t* parser) { + return parser->method; +} + +int llhttp_get_status_code(llhttp_t* parser) { + return parser->status_code; +} + +uint8_t llhttp_get_upgrade(llhttp_t* parser) { + return parser->upgrade; +} + + +void llhttp_reset(llhttp_t* parser) { + llhttp_type_t type = parser->type; + const llhttp_settings_t* settings = parser->settings; + void* data = parser->data; + uint16_t lenient_flags = parser->lenient_flags; + + llhttp__internal_init(parser); + + parser->type = type; + parser->settings = (void*) settings; + parser->data = data; + parser->lenient_flags = lenient_flags; +} + + +llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len) { + return llhttp__internal_execute(parser, data, data + len); +} + + +void llhttp_settings_init(llhttp_settings_t* settings) { + memset(settings, 0, sizeof(*settings)); +} + + +llhttp_errno_t llhttp_finish(llhttp_t* parser) { + int err; + + /* We're in an error state. Don't bother doing anything. */ + if (parser->error != 0) { + return 0; + } + + switch (parser->finish) { + case HTTP_FINISH_SAFE_WITH_CB: + CALLBACK_MAYBE(parser, on_message_complete); + if (err != HPE_OK) return err; + + /* FALLTHROUGH */ + case HTTP_FINISH_SAFE: + return HPE_OK; + case HTTP_FINISH_UNSAFE: + parser->reason = "Invalid EOF state"; + return HPE_INVALID_EOF_STATE; + default: + abort(); + } +} + + +void llhttp_pause(llhttp_t* parser) { + if (parser->error != HPE_OK) { + return; + } + + parser->error = HPE_PAUSED; + parser->reason = "Paused"; +} + + +void llhttp_resume(llhttp_t* parser) { + if (parser->error != HPE_PAUSED) { + return; + } + + parser->error = 0; +} + + +void llhttp_resume_after_upgrade(llhttp_t* parser) { + if (parser->error != HPE_PAUSED_UPGRADE) { + return; + } + + parser->error = 0; +} + + +llhttp_errno_t llhttp_get_errno(const llhttp_t* parser) { + return parser->error; +} + + +const char* llhttp_get_error_reason(const llhttp_t* parser) { + return parser->reason; +} + + +void llhttp_set_error_reason(llhttp_t* parser, const char* reason) { + parser->reason = reason; +} + + +const char* llhttp_get_error_pos(const llhttp_t* parser) { + return parser->error_pos; +} + + +const char* llhttp_errno_name(llhttp_errno_t err) { +#define HTTP_ERRNO_GEN(CODE, NAME, _) case HPE_##NAME: return "HPE_" #NAME; + switch (err) { + HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) + default: abort(); + } +#undef HTTP_ERRNO_GEN +} + + +const char* llhttp_method_name(llhttp_method_t method) { +#define HTTP_METHOD_GEN(NUM, NAME, STRING) case HTTP_##NAME: return #STRING; + switch (method) { + HTTP_ALL_METHOD_MAP(HTTP_METHOD_GEN) + default: abort(); + } +#undef HTTP_METHOD_GEN +} + +const char* llhttp_status_name(llhttp_status_t status) { +#define HTTP_STATUS_GEN(NUM, NAME, STRING) case HTTP_STATUS_##NAME: return #STRING; + switch (status) { + HTTP_STATUS_MAP(HTTP_STATUS_GEN) + default: abort(); + } +#undef HTTP_STATUS_GEN +} + + +void llhttp_set_lenient_headers(llhttp_t* parser, int enabled) { + if (enabled) { + parser->lenient_flags |= LENIENT_HEADERS; + } else { + parser->lenient_flags &= ~LENIENT_HEADERS; + } +} + + +void llhttp_set_lenient_chunked_length(llhttp_t* parser, int enabled) { + if (enabled) { + parser->lenient_flags |= LENIENT_CHUNKED_LENGTH; + } else { + parser->lenient_flags &= ~LENIENT_CHUNKED_LENGTH; + } +} + + +void llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled) { + if (enabled) { + parser->lenient_flags |= LENIENT_KEEP_ALIVE; + } else { + parser->lenient_flags &= ~LENIENT_KEEP_ALIVE; + } +} + +void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, int enabled) { + if (enabled) { + parser->lenient_flags |= LENIENT_TRANSFER_ENCODING; + } else { + parser->lenient_flags &= ~LENIENT_TRANSFER_ENCODING; + } +} + +void llhttp_set_lenient_version(llhttp_t* parser, int enabled) { + if (enabled) { + parser->lenient_flags |= LENIENT_VERSION; + } else { + parser->lenient_flags &= ~LENIENT_VERSION; + } +} + +void llhttp_set_lenient_data_after_close(llhttp_t* parser, int enabled) { + if (enabled) { + parser->lenient_flags |= LENIENT_DATA_AFTER_CLOSE; + } else { + parser->lenient_flags &= ~LENIENT_DATA_AFTER_CLOSE; + } +} + +void llhttp_set_lenient_optional_lf_after_cr(llhttp_t* parser, int enabled) { + if (enabled) { + parser->lenient_flags |= LENIENT_OPTIONAL_LF_AFTER_CR; + } else { + parser->lenient_flags &= ~LENIENT_OPTIONAL_LF_AFTER_CR; + } +} + +void llhttp_set_lenient_optional_crlf_after_chunk(llhttp_t* parser, int enabled) { + if (enabled) { + parser->lenient_flags |= LENIENT_OPTIONAL_CRLF_AFTER_CHUNK; + } else { + parser->lenient_flags &= ~LENIENT_OPTIONAL_CRLF_AFTER_CHUNK; + } +} + +void llhttp_set_lenient_optional_cr_before_lf(llhttp_t* parser, int enabled) { + if (enabled) { + parser->lenient_flags |= LENIENT_OPTIONAL_CR_BEFORE_LF; + } else { + parser->lenient_flags &= ~LENIENT_OPTIONAL_CR_BEFORE_LF; + } +} + +void llhttp_set_lenient_spaces_after_chunk_size(llhttp_t* parser, int enabled) { + if (enabled) { + parser->lenient_flags |= LENIENT_SPACES_AFTER_CHUNK_SIZE; + } else { + parser->lenient_flags &= ~LENIENT_SPACES_AFTER_CHUNK_SIZE; + } +} + +/* Callbacks */ + + +int llhttp__on_message_begin(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_message_begin); + return err; +} + + +int llhttp__on_url(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_url, p, endp - p); + return err; +} + + +int llhttp__on_url_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_url_complete); + return err; +} + + +int llhttp__on_status(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_status, p, endp - p); + return err; +} + + +int llhttp__on_status_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_status_complete); + return err; +} + + +int llhttp__on_method(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_method, p, endp - p); + return err; +} + + +int llhttp__on_method_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_method_complete); + return err; +} + + +int llhttp__on_version(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_version, p, endp - p); + return err; +} + + +int llhttp__on_version_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_version_complete); + return err; +} + + +int llhttp__on_header_field(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_header_field, p, endp - p); + return err; +} + + +int llhttp__on_header_field_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_header_field_complete); + return err; +} + + +int llhttp__on_header_value(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_header_value, p, endp - p); + return err; +} + + +int llhttp__on_header_value_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_header_value_complete); + return err; +} + + +int llhttp__on_headers_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_headers_complete); + return err; +} + + +int llhttp__on_message_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_message_complete); + return err; +} + + +int llhttp__on_body(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_body, p, endp - p); + return err; +} + + +int llhttp__on_chunk_header(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_chunk_header); + return err; +} + + +int llhttp__on_chunk_extension_name(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_chunk_extension_name, p, endp - p); + return err; +} + + +int llhttp__on_chunk_extension_name_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_chunk_extension_name_complete); + return err; +} + + +int llhttp__on_chunk_extension_value(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_chunk_extension_value, p, endp - p); + return err; +} + + +int llhttp__on_chunk_extension_value_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_chunk_extension_value_complete); + return err; +} + + +int llhttp__on_chunk_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_chunk_complete); + return err; +} + + +int llhttp__on_reset(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_reset); + return err; +} + + +/* Private */ + + +void llhttp__debug(llhttp_t* s, const char* p, const char* endp, + const char* msg) { + if (p == endp) { + fprintf(stderr, "p=%p type=%d flags=%02x next=null debug=%s\n", s, s->type, + s->flags, msg); + } else { + fprintf(stderr, "p=%p type=%d flags=%02x next=%02x debug=%s\n", s, + s->type, s->flags, *p, msg); + } +} + +#ifdef __GNUC__ + #pragma GCC diagnostic pop +#endif diff --git a/common/http/llhttp_internal.c b/common/http/llhttp_internal.c new file mode 100644 index 0000000..999ec50 --- /dev/null +++ b/common/http/llhttp_internal.c @@ -0,0 +1,10180 @@ +#ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wconversion" + #pragma GCC diagnostic ignored "-Wsign-conversion" + #pragma GCC diagnostic ignored "-Wunused-variable" + #pragma GCC diagnostic ignored "-Wunreachable-code" +#endif + +#include +#include +#include + +#ifdef __SSE4_2__ + #ifdef _MSC_VER + #include + #else /* !_MSC_VER */ + #include + #endif /* _MSC_VER */ +#endif /* __SSE4_2__ */ + +#ifdef _MSC_VER + #define ALIGN(n) _declspec(align(n)) +#else /* !_MSC_VER */ + #define ALIGN(n) __attribute__((aligned(n))) +#endif /* _MSC_VER */ + +#include "llhttp.h" + +typedef int (*llhttp__internal__span_cb)( + llhttp__internal_t*, const char*, const char*); + +static const unsigned char llparse_blob0[] = { + 'o', 'n' +}; +static const unsigned char llparse_blob1[] = { + 'e', 'c', 't', 'i', 'o', 'n' +}; +static const unsigned char llparse_blob2[] = { + 'l', 'o', 's', 'e' +}; +static const unsigned char llparse_blob3[] = { + 'e', 'e', 'p', '-', 'a', 'l', 'i', 'v', 'e' +}; +static const unsigned char llparse_blob4[] = { + 'p', 'g', 'r', 'a', 'd', 'e' +}; +static const unsigned char llparse_blob5[] = { + 'c', 'h', 'u', 'n', 'k', 'e', 'd' +}; +#ifdef __SSE4_2__ +static const unsigned char ALIGN(16) llparse_blob6[] = { + 0x9, 0x9, ' ', '~', 0x80, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0 +}; +#endif /* __SSE4_2__ */ +#ifdef __SSE4_2__ +static const unsigned char ALIGN(16) llparse_blob7[] = { + '!', '!', '#', '\'', '*', '+', '-', '.', '0', '9', 'A', + 'Z', '^', 'z', '|', '|' +}; +#endif /* __SSE4_2__ */ +#ifdef __SSE4_2__ +static const unsigned char ALIGN(16) llparse_blob8[] = { + '~', '~', 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0 +}; +#endif /* __SSE4_2__ */ +static const unsigned char llparse_blob9[] = { + 'e', 'n', 't', '-', 'l', 'e', 'n', 'g', 't', 'h' +}; +static const unsigned char llparse_blob10[] = { + 'r', 'o', 'x', 'y', '-', 'c', 'o', 'n', 'n', 'e', 'c', + 't', 'i', 'o', 'n' +}; +static const unsigned char llparse_blob11[] = { + 'r', 'a', 'n', 's', 'f', 'e', 'r', '-', 'e', 'n', 'c', + 'o', 'd', 'i', 'n', 'g' +}; +static const unsigned char llparse_blob12[] = { + 'p', 'g', 'r', 'a', 'd', 'e' +}; +static const unsigned char llparse_blob13[] = { + 'T', 'T', 'P', '/' +}; +static const unsigned char llparse_blob14[] = { + 0xd, 0xa, 0xd, 0xa, 'S', 'M', 0xd, 0xa, 0xd, 0xa +}; +static const unsigned char llparse_blob15[] = { + 'C', 'E', '/' +}; +static const unsigned char llparse_blob16[] = { + 'T', 'S', 'P', '/' +}; +static const unsigned char llparse_blob17[] = { + 'N', 'O', 'U', 'N', 'C', 'E' +}; +static const unsigned char llparse_blob18[] = { + 'I', 'N', 'D' +}; +static const unsigned char llparse_blob19[] = { + 'E', 'C', 'K', 'O', 'U', 'T' +}; +static const unsigned char llparse_blob20[] = { + 'N', 'E', 'C', 'T' +}; +static const unsigned char llparse_blob21[] = { + 'E', 'T', 'E' +}; +static const unsigned char llparse_blob22[] = { + 'C', 'R', 'I', 'B', 'E' +}; +static const unsigned char llparse_blob23[] = { + 'L', 'U', 'S', 'H' +}; +static const unsigned char llparse_blob24[] = { + 'E', 'T' +}; +static const unsigned char llparse_blob25[] = { + 'P', 'A', 'R', 'A', 'M', 'E', 'T', 'E', 'R' +}; +static const unsigned char llparse_blob26[] = { + 'E', 'A', 'D' +}; +static const unsigned char llparse_blob27[] = { + 'N', 'K' +}; +static const unsigned char llparse_blob28[] = { + 'C', 'K' +}; +static const unsigned char llparse_blob29[] = { + 'S', 'E', 'A', 'R', 'C', 'H' +}; +static const unsigned char llparse_blob30[] = { + 'R', 'G', 'E' +}; +static const unsigned char llparse_blob31[] = { + 'C', 'T', 'I', 'V', 'I', 'T', 'Y' +}; +static const unsigned char llparse_blob32[] = { + 'L', 'E', 'N', 'D', 'A', 'R' +}; +static const unsigned char llparse_blob33[] = { + 'V', 'E' +}; +static const unsigned char llparse_blob34[] = { + 'O', 'T', 'I', 'F', 'Y' +}; +static const unsigned char llparse_blob35[] = { + 'P', 'T', 'I', 'O', 'N', 'S' +}; +static const unsigned char llparse_blob36[] = { + 'C', 'H' +}; +static const unsigned char llparse_blob37[] = { + 'S', 'E' +}; +static const unsigned char llparse_blob38[] = { + 'A', 'Y' +}; +static const unsigned char llparse_blob39[] = { + 'S', 'T' +}; +static const unsigned char llparse_blob40[] = { + 'I', 'N', 'D' +}; +static const unsigned char llparse_blob41[] = { + 'A', 'T', 'C', 'H' +}; +static const unsigned char llparse_blob42[] = { + 'G', 'E' +}; +static const unsigned char llparse_blob43[] = { + 'U', 'E', 'R', 'Y' +}; +static const unsigned char llparse_blob44[] = { + 'I', 'N', 'D' +}; +static const unsigned char llparse_blob45[] = { + 'O', 'R', 'D' +}; +static const unsigned char llparse_blob46[] = { + 'I', 'R', 'E', 'C', 'T' +}; +static const unsigned char llparse_blob47[] = { + 'O', 'R', 'T' +}; +static const unsigned char llparse_blob48[] = { + 'R', 'C', 'H' +}; +static const unsigned char llparse_blob49[] = { + 'P', 'A', 'R', 'A', 'M', 'E', 'T', 'E', 'R' +}; +static const unsigned char llparse_blob50[] = { + 'U', 'R', 'C', 'E' +}; +static const unsigned char llparse_blob51[] = { + 'B', 'S', 'C', 'R', 'I', 'B', 'E' +}; +static const unsigned char llparse_blob52[] = { + 'A', 'R', 'D', 'O', 'W', 'N' +}; +static const unsigned char llparse_blob53[] = { + 'A', 'C', 'E' +}; +static const unsigned char llparse_blob54[] = { + 'I', 'N', 'D' +}; +static const unsigned char llparse_blob55[] = { + 'N', 'K' +}; +static const unsigned char llparse_blob56[] = { + 'C', 'K' +}; +static const unsigned char llparse_blob57[] = { + 'U', 'B', 'S', 'C', 'R', 'I', 'B', 'E' +}; +static const unsigned char llparse_blob58[] = { + 'H', 'T', 'T', 'P', '/' +}; +static const unsigned char llparse_blob59[] = { + 'A', 'D' +}; +static const unsigned char llparse_blob60[] = { + 'T', 'P', '/' +}; + +enum llparse_match_status_e { + kMatchComplete, + kMatchPause, + kMatchMismatch +}; +typedef enum llparse_match_status_e llparse_match_status_t; + +struct llparse_match_s { + llparse_match_status_t status; + const unsigned char* current; +}; +typedef struct llparse_match_s llparse_match_t; + +static llparse_match_t llparse__match_sequence_to_lower( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp, + const unsigned char* seq, uint32_t seq_len) { + uint32_t index; + llparse_match_t res; + + index = s->_index; + for (; p != endp; p++) { + unsigned char current; + + current = ((*p) >= 'A' && (*p) <= 'Z' ? (*p | 0x20) : (*p)); + if (current == seq[index]) { + if (++index == seq_len) { + res.status = kMatchComplete; + goto reset; + } + } else { + res.status = kMatchMismatch; + goto reset; + } + } + s->_index = index; + res.status = kMatchPause; + res.current = p; + return res; +reset: + s->_index = 0; + res.current = p; + return res; +} + +static llparse_match_t llparse__match_sequence_to_lower_unsafe( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp, + const unsigned char* seq, uint32_t seq_len) { + uint32_t index; + llparse_match_t res; + + index = s->_index; + for (; p != endp; p++) { + unsigned char current; + + current = ((*p) | 0x20); + if (current == seq[index]) { + if (++index == seq_len) { + res.status = kMatchComplete; + goto reset; + } + } else { + res.status = kMatchMismatch; + goto reset; + } + } + s->_index = index; + res.status = kMatchPause; + res.current = p; + return res; +reset: + s->_index = 0; + res.current = p; + return res; +} + +static llparse_match_t llparse__match_sequence_id( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp, + const unsigned char* seq, uint32_t seq_len) { + uint32_t index; + llparse_match_t res; + + index = s->_index; + for (; p != endp; p++) { + unsigned char current; + + current = *p; + if (current == seq[index]) { + if (++index == seq_len) { + res.status = kMatchComplete; + goto reset; + } + } else { + res.status = kMatchMismatch; + goto reset; + } + } + s->_index = index; + res.status = kMatchPause; + res.current = p; + return res; +reset: + s->_index = 0; + res.current = p; + return res; +} + +enum llparse_state_e { + s_error, + s_n_llhttp__internal__n_closed, + s_n_llhttp__internal__n_invoke_llhttp__after_message_complete, + s_n_llhttp__internal__n_pause_1, + s_n_llhttp__internal__n_invoke_is_equal_upgrade, + s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2, + s_n_llhttp__internal__n_chunk_data_almost_done_1, + s_n_llhttp__internal__n_chunk_data_almost_done, + s_n_llhttp__internal__n_consume_content_length, + s_n_llhttp__internal__n_span_start_llhttp__on_body, + s_n_llhttp__internal__n_invoke_is_equal_content_length, + s_n_llhttp__internal__n_chunk_size_almost_done, + s_n_llhttp__internal__n_invoke_test_lenient_flags_9, + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete, + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_1, + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_2, + s_n_llhttp__internal__n_invoke_test_lenient_flags_10, + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete, + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_1, + s_n_llhttp__internal__n_chunk_extension_quoted_value_done, + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_2, + s_n_llhttp__internal__n_error_30, + s_n_llhttp__internal__n_chunk_extension_quoted_value_quoted_pair, + s_n_llhttp__internal__n_error_31, + s_n_llhttp__internal__n_chunk_extension_quoted_value, + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_3, + s_n_llhttp__internal__n_error_33, + s_n_llhttp__internal__n_chunk_extension_value, + s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value, + s_n_llhttp__internal__n_error_34, + s_n_llhttp__internal__n_chunk_extension_name, + s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_name, + s_n_llhttp__internal__n_chunk_extensions, + s_n_llhttp__internal__n_chunk_size_otherwise, + s_n_llhttp__internal__n_chunk_size, + s_n_llhttp__internal__n_chunk_size_digit, + s_n_llhttp__internal__n_invoke_update_content_length_1, + s_n_llhttp__internal__n_consume_content_length_1, + s_n_llhttp__internal__n_span_start_llhttp__on_body_1, + s_n_llhttp__internal__n_eof, + s_n_llhttp__internal__n_span_start_llhttp__on_body_2, + s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete, + s_n_llhttp__internal__n_error_5, + s_n_llhttp__internal__n_headers_almost_done, + s_n_llhttp__internal__n_header_field_colon_discard_ws, + s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete, + s_n_llhttp__internal__n_span_start_llhttp__on_header_value, + s_n_llhttp__internal__n_header_value_discard_lws, + s_n_llhttp__internal__n_header_value_discard_ws_almost_done, + s_n_llhttp__internal__n_header_value_lws, + s_n_llhttp__internal__n_header_value_almost_done, + s_n_llhttp__internal__n_invoke_test_lenient_flags_17, + s_n_llhttp__internal__n_header_value_lenient, + s_n_llhttp__internal__n_error_54, + s_n_llhttp__internal__n_header_value_otherwise, + s_n_llhttp__internal__n_header_value_connection_token, + s_n_llhttp__internal__n_header_value_connection_ws, + s_n_llhttp__internal__n_header_value_connection_1, + s_n_llhttp__internal__n_header_value_connection_2, + s_n_llhttp__internal__n_header_value_connection_3, + s_n_llhttp__internal__n_header_value_connection, + s_n_llhttp__internal__n_error_56, + s_n_llhttp__internal__n_error_57, + s_n_llhttp__internal__n_header_value_content_length_ws, + s_n_llhttp__internal__n_header_value_content_length, + s_n_llhttp__internal__n_error_59, + s_n_llhttp__internal__n_error_58, + s_n_llhttp__internal__n_header_value_te_token_ows, + s_n_llhttp__internal__n_header_value, + s_n_llhttp__internal__n_header_value_te_token, + s_n_llhttp__internal__n_header_value_te_chunked_last, + s_n_llhttp__internal__n_header_value_te_chunked, + s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1, + s_n_llhttp__internal__n_header_value_discard_ws, + s_n_llhttp__internal__n_invoke_load_header_state, + s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete, + s_n_llhttp__internal__n_header_field_general_otherwise, + s_n_llhttp__internal__n_header_field_general, + s_n_llhttp__internal__n_header_field_colon, + s_n_llhttp__internal__n_header_field_3, + s_n_llhttp__internal__n_header_field_4, + s_n_llhttp__internal__n_header_field_2, + s_n_llhttp__internal__n_header_field_1, + s_n_llhttp__internal__n_header_field_5, + s_n_llhttp__internal__n_header_field_6, + s_n_llhttp__internal__n_header_field_7, + s_n_llhttp__internal__n_header_field, + s_n_llhttp__internal__n_span_start_llhttp__on_header_field, + s_n_llhttp__internal__n_header_field_start, + s_n_llhttp__internal__n_headers_start, + s_n_llhttp__internal__n_url_to_http_09, + s_n_llhttp__internal__n_url_skip_to_http09, + s_n_llhttp__internal__n_url_skip_lf_to_http09_1, + s_n_llhttp__internal__n_url_skip_lf_to_http09, + s_n_llhttp__internal__n_req_pri_upgrade, + s_n_llhttp__internal__n_req_http_complete_crlf, + s_n_llhttp__internal__n_req_http_complete, + s_n_llhttp__internal__n_invoke_load_method_1, + s_n_llhttp__internal__n_invoke_llhttp__on_version_complete, + s_n_llhttp__internal__n_error_66, + s_n_llhttp__internal__n_error_73, + s_n_llhttp__internal__n_req_http_minor, + s_n_llhttp__internal__n_error_74, + s_n_llhttp__internal__n_req_http_dot, + s_n_llhttp__internal__n_error_75, + s_n_llhttp__internal__n_req_http_major, + s_n_llhttp__internal__n_span_start_llhttp__on_version, + s_n_llhttp__internal__n_req_http_start_1, + s_n_llhttp__internal__n_req_http_start_2, + s_n_llhttp__internal__n_req_http_start_3, + s_n_llhttp__internal__n_req_http_start, + s_n_llhttp__internal__n_url_to_http, + s_n_llhttp__internal__n_url_skip_to_http, + s_n_llhttp__internal__n_url_fragment, + s_n_llhttp__internal__n_span_end_stub_query_3, + s_n_llhttp__internal__n_url_query, + s_n_llhttp__internal__n_url_query_or_fragment, + s_n_llhttp__internal__n_url_path, + s_n_llhttp__internal__n_span_start_stub_path_2, + s_n_llhttp__internal__n_span_start_stub_path, + s_n_llhttp__internal__n_span_start_stub_path_1, + s_n_llhttp__internal__n_url_server_with_at, + s_n_llhttp__internal__n_url_server, + s_n_llhttp__internal__n_url_schema_delim_1, + s_n_llhttp__internal__n_url_schema_delim, + s_n_llhttp__internal__n_span_end_stub_schema, + s_n_llhttp__internal__n_url_schema, + s_n_llhttp__internal__n_url_start, + s_n_llhttp__internal__n_span_start_llhttp__on_url_1, + s_n_llhttp__internal__n_url_entry_normal, + s_n_llhttp__internal__n_span_start_llhttp__on_url, + s_n_llhttp__internal__n_url_entry_connect, + s_n_llhttp__internal__n_req_spaces_before_url, + s_n_llhttp__internal__n_req_first_space_before_url, + s_n_llhttp__internal__n_invoke_llhttp__on_method_complete_1, + s_n_llhttp__internal__n_after_start_req_2, + s_n_llhttp__internal__n_after_start_req_3, + s_n_llhttp__internal__n_after_start_req_1, + s_n_llhttp__internal__n_after_start_req_4, + s_n_llhttp__internal__n_after_start_req_6, + s_n_llhttp__internal__n_after_start_req_8, + s_n_llhttp__internal__n_after_start_req_9, + s_n_llhttp__internal__n_after_start_req_7, + s_n_llhttp__internal__n_after_start_req_5, + s_n_llhttp__internal__n_after_start_req_12, + s_n_llhttp__internal__n_after_start_req_13, + s_n_llhttp__internal__n_after_start_req_11, + s_n_llhttp__internal__n_after_start_req_10, + s_n_llhttp__internal__n_after_start_req_14, + s_n_llhttp__internal__n_after_start_req_17, + s_n_llhttp__internal__n_after_start_req_16, + s_n_llhttp__internal__n_after_start_req_15, + s_n_llhttp__internal__n_after_start_req_18, + s_n_llhttp__internal__n_after_start_req_20, + s_n_llhttp__internal__n_after_start_req_21, + s_n_llhttp__internal__n_after_start_req_19, + s_n_llhttp__internal__n_after_start_req_23, + s_n_llhttp__internal__n_after_start_req_24, + s_n_llhttp__internal__n_after_start_req_26, + s_n_llhttp__internal__n_after_start_req_28, + s_n_llhttp__internal__n_after_start_req_29, + s_n_llhttp__internal__n_after_start_req_27, + s_n_llhttp__internal__n_after_start_req_25, + s_n_llhttp__internal__n_after_start_req_30, + s_n_llhttp__internal__n_after_start_req_22, + s_n_llhttp__internal__n_after_start_req_31, + s_n_llhttp__internal__n_after_start_req_32, + s_n_llhttp__internal__n_after_start_req_35, + s_n_llhttp__internal__n_after_start_req_36, + s_n_llhttp__internal__n_after_start_req_34, + s_n_llhttp__internal__n_after_start_req_37, + s_n_llhttp__internal__n_after_start_req_38, + s_n_llhttp__internal__n_after_start_req_42, + s_n_llhttp__internal__n_after_start_req_43, + s_n_llhttp__internal__n_after_start_req_41, + s_n_llhttp__internal__n_after_start_req_40, + s_n_llhttp__internal__n_after_start_req_39, + s_n_llhttp__internal__n_after_start_req_45, + s_n_llhttp__internal__n_after_start_req_44, + s_n_llhttp__internal__n_after_start_req_33, + s_n_llhttp__internal__n_after_start_req_46, + s_n_llhttp__internal__n_after_start_req_49, + s_n_llhttp__internal__n_after_start_req_50, + s_n_llhttp__internal__n_after_start_req_51, + s_n_llhttp__internal__n_after_start_req_52, + s_n_llhttp__internal__n_after_start_req_48, + s_n_llhttp__internal__n_after_start_req_47, + s_n_llhttp__internal__n_after_start_req_55, + s_n_llhttp__internal__n_after_start_req_57, + s_n_llhttp__internal__n_after_start_req_58, + s_n_llhttp__internal__n_after_start_req_56, + s_n_llhttp__internal__n_after_start_req_54, + s_n_llhttp__internal__n_after_start_req_59, + s_n_llhttp__internal__n_after_start_req_60, + s_n_llhttp__internal__n_after_start_req_53, + s_n_llhttp__internal__n_after_start_req_62, + s_n_llhttp__internal__n_after_start_req_63, + s_n_llhttp__internal__n_after_start_req_61, + s_n_llhttp__internal__n_after_start_req_66, + s_n_llhttp__internal__n_after_start_req_68, + s_n_llhttp__internal__n_after_start_req_69, + s_n_llhttp__internal__n_after_start_req_67, + s_n_llhttp__internal__n_after_start_req_70, + s_n_llhttp__internal__n_after_start_req_65, + s_n_llhttp__internal__n_after_start_req_64, + s_n_llhttp__internal__n_after_start_req, + s_n_llhttp__internal__n_span_start_llhttp__on_method_1, + s_n_llhttp__internal__n_res_line_almost_done, + s_n_llhttp__internal__n_invoke_test_lenient_flags_30, + s_n_llhttp__internal__n_res_status, + s_n_llhttp__internal__n_span_start_llhttp__on_status, + s_n_llhttp__internal__n_res_status_code_otherwise, + s_n_llhttp__internal__n_res_status_code_digit_3, + s_n_llhttp__internal__n_res_status_code_digit_2, + s_n_llhttp__internal__n_res_status_code_digit_1, + s_n_llhttp__internal__n_res_after_version, + s_n_llhttp__internal__n_invoke_llhttp__on_version_complete_1, + s_n_llhttp__internal__n_error_89, + s_n_llhttp__internal__n_error_103, + s_n_llhttp__internal__n_res_http_minor, + s_n_llhttp__internal__n_error_104, + s_n_llhttp__internal__n_res_http_dot, + s_n_llhttp__internal__n_error_105, + s_n_llhttp__internal__n_res_http_major, + s_n_llhttp__internal__n_span_start_llhttp__on_version_1, + s_n_llhttp__internal__n_start_res, + s_n_llhttp__internal__n_invoke_llhttp__on_method_complete, + s_n_llhttp__internal__n_req_or_res_method_2, + s_n_llhttp__internal__n_invoke_update_type_1, + s_n_llhttp__internal__n_req_or_res_method_3, + s_n_llhttp__internal__n_req_or_res_method_1, + s_n_llhttp__internal__n_req_or_res_method, + s_n_llhttp__internal__n_span_start_llhttp__on_method, + s_n_llhttp__internal__n_start_req_or_res, + s_n_llhttp__internal__n_invoke_load_type, + s_n_llhttp__internal__n_invoke_update_finish, + s_n_llhttp__internal__n_start, +}; +typedef enum llparse_state_e llparse_state_t; + +int llhttp__on_method( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_url( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_version( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_header_field( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_header_value( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_body( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_chunk_extension_name( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_chunk_extension_value( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_status( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_load_initial_message_completed( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return state->initial_message_completed; +} + +int llhttp__on_reset( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_update_finish( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->finish = 2; + return 0; +} + +int llhttp__on_message_begin( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_load_type( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return state->type; +} + +int llhttp__internal__c_store_method( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp, + int match) { + state->method = match; + return 0; +} + +int llhttp__on_method_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_is_equal_method( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return state->method == 5; +} + +int llhttp__internal__c_update_http_major( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->http_major = 0; + return 0; +} + +int llhttp__internal__c_update_http_minor( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->http_minor = 9; + return 0; +} + +int llhttp__on_url_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_test_lenient_flags( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->lenient_flags & 1) == 1; +} + +int llhttp__internal__c_test_lenient_flags_1( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->lenient_flags & 256) == 256; +} + +int llhttp__internal__c_test_flags( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->flags & 128) == 128; +} + +int llhttp__on_chunk_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_message_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_is_equal_upgrade( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return state->upgrade == 1; +} + +int llhttp__after_message_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_update_content_length( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->content_length = 0; + return 0; +} + +int llhttp__internal__c_update_initial_message_completed( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->initial_message_completed = 1; + return 0; +} + +int llhttp__internal__c_update_finish_1( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->finish = 0; + return 0; +} + +int llhttp__internal__c_test_lenient_flags_2( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->lenient_flags & 4) == 4; +} + +int llhttp__internal__c_test_lenient_flags_3( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->lenient_flags & 32) == 32; +} + +int llhttp__before_headers_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_headers_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__after_headers_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_mul_add_content_length( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp, + int match) { + /* Multiplication overflow */ + if (state->content_length > 0xffffffffffffffffULL / 16) { + return 1; + } + + state->content_length *= 16; + + /* Addition overflow */ + if (match >= 0) { + if (state->content_length > 0xffffffffffffffffULL - match) { + return 1; + } + } else { + if (state->content_length < 0ULL - match) { + return 1; + } + } + state->content_length += match; + return 0; +} + +int llhttp__internal__c_test_lenient_flags_4( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->lenient_flags & 512) == 512; +} + +int llhttp__on_chunk_header( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_is_equal_content_length( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return state->content_length == 0; +} + +int llhttp__internal__c_test_lenient_flags_7( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->lenient_flags & 128) == 128; +} + +int llhttp__internal__c_or_flags( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags |= 128; + return 0; +} + +int llhttp__internal__c_test_lenient_flags_8( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->lenient_flags & 64) == 64; +} + +int llhttp__on_chunk_extension_name_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_chunk_extension_value_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_update_finish_3( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->finish = 1; + return 0; +} + +int llhttp__internal__c_or_flags_1( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags |= 64; + return 0; +} + +int llhttp__internal__c_update_upgrade( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->upgrade = 1; + return 0; +} + +int llhttp__internal__c_store_header_state( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp, + int match) { + state->header_state = match; + return 0; +} + +int llhttp__on_header_field_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_load_header_state( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return state->header_state; +} + +int llhttp__internal__c_test_flags_4( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->flags & 512) == 512; +} + +int llhttp__internal__c_test_lenient_flags_22( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->lenient_flags & 2) == 2; +} + +int llhttp__internal__c_or_flags_5( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags |= 1; + return 0; +} + +int llhttp__internal__c_update_header_state( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->header_state = 1; + return 0; +} + +int llhttp__on_header_value_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_or_flags_6( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags |= 2; + return 0; +} + +int llhttp__internal__c_or_flags_7( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags |= 4; + return 0; +} + +int llhttp__internal__c_or_flags_8( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags |= 8; + return 0; +} + +int llhttp__internal__c_update_header_state_3( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->header_state = 6; + return 0; +} + +int llhttp__internal__c_update_header_state_1( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->header_state = 0; + return 0; +} + +int llhttp__internal__c_update_header_state_6( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->header_state = 5; + return 0; +} + +int llhttp__internal__c_update_header_state_7( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->header_state = 7; + return 0; +} + +int llhttp__internal__c_test_flags_2( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->flags & 32) == 32; +} + +int llhttp__internal__c_mul_add_content_length_1( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp, + int match) { + /* Multiplication overflow */ + if (state->content_length > 0xffffffffffffffffULL / 10) { + return 1; + } + + state->content_length *= 10; + + /* Addition overflow */ + if (match >= 0) { + if (state->content_length > 0xffffffffffffffffULL - match) { + return 1; + } + } else { + if (state->content_length < 0ULL - match) { + return 1; + } + } + state->content_length += match; + return 0; +} + +int llhttp__internal__c_or_flags_17( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags |= 32; + return 0; +} + +int llhttp__internal__c_test_flags_3( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->flags & 8) == 8; +} + +int llhttp__internal__c_test_lenient_flags_20( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->lenient_flags & 8) == 8; +} + +int llhttp__internal__c_or_flags_18( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags |= 512; + return 0; +} + +int llhttp__internal__c_and_flags( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags &= -9; + return 0; +} + +int llhttp__internal__c_update_header_state_8( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->header_state = 8; + return 0; +} + +int llhttp__internal__c_or_flags_20( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags |= 16; + return 0; +} + +int llhttp__internal__c_load_method( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return state->method; +} + +int llhttp__internal__c_store_http_major( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp, + int match) { + state->http_major = match; + return 0; +} + +int llhttp__internal__c_store_http_minor( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp, + int match) { + state->http_minor = match; + return 0; +} + +int llhttp__internal__c_test_lenient_flags_24( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->lenient_flags & 16) == 16; +} + +int llhttp__on_version_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_load_http_major( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return state->http_major; +} + +int llhttp__internal__c_load_http_minor( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return state->http_minor; +} + +int llhttp__internal__c_update_status_code( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->status_code = 0; + return 0; +} + +int llhttp__internal__c_mul_add_status_code( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp, + int match) { + /* Multiplication overflow */ + if (state->status_code > 0xffff / 10) { + return 1; + } + + state->status_code *= 10; + + /* Addition overflow */ + if (match >= 0) { + if (state->status_code > 0xffff - match) { + return 1; + } + } else { + if (state->status_code < 0 - match) { + return 1; + } + } + state->status_code += match; + return 0; +} + +int llhttp__on_status_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_update_type( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->type = 1; + return 0; +} + +int llhttp__internal__c_update_type_1( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->type = 2; + return 0; +} + +int llhttp__internal_init(llhttp__internal_t* state) { + memset(state, 0, sizeof(*state)); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_start; + return 0; +} + +static llparse_state_t llhttp__internal__run( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + int match; + switch ((llparse_state_t) (intptr_t) state->_current) { + case s_n_llhttp__internal__n_closed: + s_n_llhttp__internal__n_closed: { + if (p == endp) { + return s_n_llhttp__internal__n_closed; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_closed; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_closed; + } + default: { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_3; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__after_message_complete: + s_n_llhttp__internal__n_invoke_llhttp__after_message_complete: { + switch (llhttp__after_message_complete(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_update_content_length; + default: + goto s_n_llhttp__internal__n_invoke_update_finish_1; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_pause_1: + s_n_llhttp__internal__n_pause_1: { + state->error = 0x16; + state->reason = "Pause on CONNECT/Upgrade"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__after_message_complete; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_is_equal_upgrade: + s_n_llhttp__internal__n_invoke_is_equal_upgrade: { + switch (llhttp__internal__c_is_equal_upgrade(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_llhttp__after_message_complete; + default: + goto s_n_llhttp__internal__n_pause_1; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2: + s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2: { + switch (llhttp__on_message_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_is_equal_upgrade; + case 21: + goto s_n_llhttp__internal__n_pause_13; + default: + goto s_n_llhttp__internal__n_error_38; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_chunk_data_almost_done_1: + s_n_llhttp__internal__n_chunk_data_almost_done_1: { + if (p == endp) { + return s_n_llhttp__internal__n_chunk_data_almost_done_1; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete; + } + default: { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_7; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_chunk_data_almost_done: + s_n_llhttp__internal__n_chunk_data_almost_done: { + if (p == endp) { + return s_n_llhttp__internal__n_chunk_data_almost_done; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_6; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_chunk_data_almost_done_1; + } + default: { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_7; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_consume_content_length: + s_n_llhttp__internal__n_consume_content_length: { + size_t avail; + uint64_t need; + + avail = endp - p; + need = state->content_length; + if (avail >= need) { + p += need; + state->content_length = 0; + goto s_n_llhttp__internal__n_span_end_llhttp__on_body; + } + + state->content_length -= avail; + return s_n_llhttp__internal__n_consume_content_length; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_body: + s_n_llhttp__internal__n_span_start_llhttp__on_body: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_body; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_body; + goto s_n_llhttp__internal__n_consume_content_length; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_is_equal_content_length: + s_n_llhttp__internal__n_invoke_is_equal_content_length: { + switch (llhttp__internal__c_is_equal_content_length(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_span_start_llhttp__on_body; + default: + goto s_n_llhttp__internal__n_invoke_or_flags; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_chunk_size_almost_done: + s_n_llhttp__internal__n_chunk_size_almost_done: { + if (p == endp) { + return s_n_llhttp__internal__n_chunk_size_almost_done; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_header; + } + default: { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_8; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_test_lenient_flags_9: + s_n_llhttp__internal__n_invoke_test_lenient_flags_9: { + switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_chunk_size_almost_done; + default: + goto s_n_llhttp__internal__n_error_20; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete: + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete: { + switch (llhttp__on_chunk_extension_name_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_9; + case 21: + goto s_n_llhttp__internal__n_pause_5; + default: + goto s_n_llhttp__internal__n_error_19; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_1: + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_1: { + switch (llhttp__on_chunk_extension_name_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_chunk_size_almost_done; + case 21: + goto s_n_llhttp__internal__n_pause_6; + default: + goto s_n_llhttp__internal__n_error_21; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_2: + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_2: { + switch (llhttp__on_chunk_extension_name_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_chunk_extensions; + case 21: + goto s_n_llhttp__internal__n_pause_7; + default: + goto s_n_llhttp__internal__n_error_22; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_test_lenient_flags_10: + s_n_llhttp__internal__n_invoke_test_lenient_flags_10: { + switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_chunk_size_almost_done; + default: + goto s_n_llhttp__internal__n_error_25; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete: + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete: { + switch (llhttp__on_chunk_extension_value_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_10; + case 21: + goto s_n_llhttp__internal__n_pause_8; + default: + goto s_n_llhttp__internal__n_error_24; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_1: + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_1: { + switch (llhttp__on_chunk_extension_value_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_chunk_size_almost_done; + case 21: + goto s_n_llhttp__internal__n_pause_9; + default: + goto s_n_llhttp__internal__n_error_26; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_chunk_extension_quoted_value_done: + s_n_llhttp__internal__n_chunk_extension_quoted_value_done: { + if (p == endp) { + return s_n_llhttp__internal__n_chunk_extension_quoted_value_done; + } + switch (*p) { + case 10: { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_11; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_chunk_size_almost_done; + } + case ';': { + p++; + goto s_n_llhttp__internal__n_chunk_extensions; + } + default: { + goto s_n_llhttp__internal__n_error_29; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_2: + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_2: { + switch (llhttp__on_chunk_extension_value_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_chunk_extension_quoted_value_done; + case 21: + goto s_n_llhttp__internal__n_pause_10; + default: + goto s_n_llhttp__internal__n_error_27; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_30: + s_n_llhttp__internal__n_error_30: { + state->error = 0x2; + state->reason = "Invalid quoted-pair in chunk extensions quoted value"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_chunk_extension_quoted_value_quoted_pair: + s_n_llhttp__internal__n_chunk_extension_quoted_value_quoted_pair: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + if (p == endp) { + return s_n_llhttp__internal__n_chunk_extension_quoted_value_quoted_pair; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_chunk_extension_quoted_value; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_3; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_31: + s_n_llhttp__internal__n_error_31: { + state->error = 0x2; + state->reason = "Invalid character in chunk extensions quoted value"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_chunk_extension_quoted_value: + s_n_llhttp__internal__n_chunk_extension_quoted_value: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + if (p == endp) { + return s_n_llhttp__internal__n_chunk_extension_quoted_value; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_chunk_extension_quoted_value; + } + case 2: { + p++; + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_2; + } + case 3: { + p++; + goto s_n_llhttp__internal__n_chunk_extension_quoted_value_quoted_pair; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_4; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_3: + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_3: { + switch (llhttp__on_chunk_extension_value_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_chunk_extensions; + case 21: + goto s_n_llhttp__internal__n_pause_11; + default: + goto s_n_llhttp__internal__n_error_32; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_33: + s_n_llhttp__internal__n_error_33: { + state->error = 0x2; + state->reason = "Invalid character in chunk extensions value"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_chunk_extension_value: + s_n_llhttp__internal__n_chunk_extension_value: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 3, 4, 3, 3, 3, 3, 3, 0, 0, 3, 3, 0, 3, 3, 0, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 5, 0, 0, 0, 0, + 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 3, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_chunk_extension_value; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value; + } + case 2: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_1; + } + case 3: { + p++; + goto s_n_llhttp__internal__n_chunk_extension_value; + } + case 4: { + p++; + goto s_n_llhttp__internal__n_chunk_extension_quoted_value; + } + case 5: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_5; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_6; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value: + s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_chunk_extension_value; + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_3; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_34: + s_n_llhttp__internal__n_error_34: { + state->error = 0x2; + state->reason = "Invalid character in chunk extensions name"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_chunk_extension_name: + s_n_llhttp__internal__n_chunk_extension_name: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 3, 0, 3, 3, 3, 3, 3, 0, 0, 3, 3, 0, 3, 3, 0, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 4, 0, 5, 0, 0, + 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 3, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_chunk_extension_name; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name; + } + case 2: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_1; + } + case 3: { + p++; + goto s_n_llhttp__internal__n_chunk_extension_name; + } + case 4: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_2; + } + case 5: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_3; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_4; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_name: + s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_name: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_name; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_chunk_extension_name; + goto s_n_llhttp__internal__n_chunk_extension_name; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_chunk_extensions: + s_n_llhttp__internal__n_chunk_extensions: { + if (p == endp) { + return s_n_llhttp__internal__n_chunk_extensions; + } + switch (*p) { + case 13: { + p++; + goto s_n_llhttp__internal__n_error_17; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_error_18; + } + default: { + goto s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_name; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_chunk_size_otherwise: + s_n_llhttp__internal__n_chunk_size_otherwise: { + if (p == endp) { + return s_n_llhttp__internal__n_chunk_size_otherwise; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_4; + } + case 10: { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_5; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_chunk_size_almost_done; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_4; + } + case ';': { + p++; + goto s_n_llhttp__internal__n_chunk_extensions; + } + default: { + goto s_n_llhttp__internal__n_error_35; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_chunk_size: + s_n_llhttp__internal__n_chunk_size: { + if (p == endp) { + return s_n_llhttp__internal__n_chunk_size; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'A': { + p++; + match = 10; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'B': { + p++; + match = 11; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'C': { + p++; + match = 12; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'D': { + p++; + match = 13; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'E': { + p++; + match = 14; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'F': { + p++; + match = 15; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'a': { + p++; + match = 10; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'b': { + p++; + match = 11; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'c': { + p++; + match = 12; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'd': { + p++; + match = 13; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'e': { + p++; + match = 14; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'f': { + p++; + match = 15; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + default: { + goto s_n_llhttp__internal__n_chunk_size_otherwise; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_chunk_size_digit: + s_n_llhttp__internal__n_chunk_size_digit: { + if (p == endp) { + return s_n_llhttp__internal__n_chunk_size_digit; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'A': { + p++; + match = 10; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'B': { + p++; + match = 11; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'C': { + p++; + match = 12; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'D': { + p++; + match = 13; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'E': { + p++; + match = 14; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'F': { + p++; + match = 15; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'a': { + p++; + match = 10; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'b': { + p++; + match = 11; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'c': { + p++; + match = 12; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'd': { + p++; + match = 13; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'e': { + p++; + match = 14; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'f': { + p++; + match = 15; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + default: { + goto s_n_llhttp__internal__n_error_37; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_update_content_length_1: + s_n_llhttp__internal__n_invoke_update_content_length_1: { + switch (llhttp__internal__c_update_content_length(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_chunk_size_digit; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_consume_content_length_1: + s_n_llhttp__internal__n_consume_content_length_1: { + size_t avail; + uint64_t need; + + avail = endp - p; + need = state->content_length; + if (avail >= need) { + p += need; + state->content_length = 0; + goto s_n_llhttp__internal__n_span_end_llhttp__on_body_1; + } + + state->content_length -= avail; + return s_n_llhttp__internal__n_consume_content_length_1; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_body_1: + s_n_llhttp__internal__n_span_start_llhttp__on_body_1: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_body_1; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_body; + goto s_n_llhttp__internal__n_consume_content_length_1; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_eof: + s_n_llhttp__internal__n_eof: { + if (p == endp) { + return s_n_llhttp__internal__n_eof; + } + p++; + goto s_n_llhttp__internal__n_eof; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_body_2: + s_n_llhttp__internal__n_span_start_llhttp__on_body_2: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_body_2; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_body; + goto s_n_llhttp__internal__n_eof; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete: + s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete: { + switch (llhttp__after_headers_complete(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_1; + case 2: + goto s_n_llhttp__internal__n_invoke_update_content_length_1; + case 3: + goto s_n_llhttp__internal__n_span_start_llhttp__on_body_1; + case 4: + goto s_n_llhttp__internal__n_invoke_update_finish_3; + case 5: + goto s_n_llhttp__internal__n_error_39; + default: + goto s_n_llhttp__internal__n_invoke_llhttp__on_message_complete; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_5: + s_n_llhttp__internal__n_error_5: { + state->error = 0xa; + state->reason = "Invalid header field char"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_headers_almost_done: + s_n_llhttp__internal__n_headers_almost_done: { + if (p == endp) { + return s_n_llhttp__internal__n_headers_almost_done; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_invoke_test_flags_1; + } + default: { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_12; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_field_colon_discard_ws: + s_n_llhttp__internal__n_header_field_colon_discard_ws: { + if (p == endp) { + return s_n_llhttp__internal__n_header_field_colon_discard_ws; + } + switch (*p) { + case ' ': { + p++; + goto s_n_llhttp__internal__n_header_field_colon_discard_ws; + } + default: { + goto s_n_llhttp__internal__n_header_field_colon; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete: + s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete: { + switch (llhttp__on_header_value_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_header_field_start; + case 21: + goto s_n_llhttp__internal__n_pause_18; + default: + goto s_n_llhttp__internal__n_error_48; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_header_value: + s_n_llhttp__internal__n_span_start_llhttp__on_header_value: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_header_value; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_header_value; + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_discard_lws: + s_n_llhttp__internal__n_header_value_discard_lws: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_discard_lws; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_15; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_15; + } + default: { + goto s_n_llhttp__internal__n_invoke_load_header_state_1; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_discard_ws_almost_done: + s_n_llhttp__internal__n_header_value_discard_ws_almost_done: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_discard_ws_almost_done; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_header_value_discard_lws; + } + default: { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_16; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_lws: + s_n_llhttp__internal__n_header_value_lws: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_lws; + } + switch (*p) { + case 9: { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_18; + } + case ' ': { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_18; + } + default: { + goto s_n_llhttp__internal__n_invoke_load_header_state_5; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_almost_done: + s_n_llhttp__internal__n_header_value_almost_done: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_almost_done; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_header_value_lws; + } + default: { + goto s_n_llhttp__internal__n_error_53; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_test_lenient_flags_17: + s_n_llhttp__internal__n_invoke_test_lenient_flags_17: { + switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_header_value_almost_done; + default: + goto s_n_llhttp__internal__n_error_51; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_lenient: + s_n_llhttp__internal__n_header_value_lenient: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_lenient; + } + switch (*p) { + case 10: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_4; + } + case 13: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_5; + } + default: { + p++; + goto s_n_llhttp__internal__n_header_value_lenient; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_54: + s_n_llhttp__internal__n_error_54: { + state->error = 0xa; + state->reason = "Invalid header value char"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_otherwise: + s_n_llhttp__internal__n_header_value_otherwise: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_otherwise; + } + switch (*p) { + case 10: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_1; + } + case 13: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_2; + } + default: { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_19; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_connection_token: + s_n_llhttp__internal__n_header_value_connection_token: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + if (p == endp) { + return s_n_llhttp__internal__n_header_value_connection_token; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_header_value_connection_token; + } + case 2: { + p++; + goto s_n_llhttp__internal__n_header_value_connection; + } + default: { + goto s_n_llhttp__internal__n_header_value_otherwise; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_connection_ws: + s_n_llhttp__internal__n_header_value_connection_ws: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_connection_ws; + } + switch (*p) { + case 10: { + goto s_n_llhttp__internal__n_header_value_otherwise; + } + case 13: { + goto s_n_llhttp__internal__n_header_value_otherwise; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_header_value_connection_ws; + } + case ',': { + p++; + goto s_n_llhttp__internal__n_invoke_load_header_state_6; + } + default: { + goto s_n_llhttp__internal__n_invoke_update_header_state_5; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_connection_1: + s_n_llhttp__internal__n_header_value_connection_1: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_value_connection_1; + } + match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob2, 4); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_invoke_update_header_state_3; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_value_connection_1; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_header_value_connection_token; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_connection_2: + s_n_llhttp__internal__n_header_value_connection_2: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_value_connection_2; + } + match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob3, 9); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_invoke_update_header_state_6; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_value_connection_2; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_header_value_connection_token; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_connection_3: + s_n_llhttp__internal__n_header_value_connection_3: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_value_connection_3; + } + match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob4, 6); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_invoke_update_header_state_7; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_value_connection_3; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_header_value_connection_token; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_connection: + s_n_llhttp__internal__n_header_value_connection: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_connection; + } + switch (((*p) >= 'A' && (*p) <= 'Z' ? (*p | 0x20) : (*p))) { + case 9: { + p++; + goto s_n_llhttp__internal__n_header_value_connection; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_header_value_connection; + } + case 'c': { + p++; + goto s_n_llhttp__internal__n_header_value_connection_1; + } + case 'k': { + p++; + goto s_n_llhttp__internal__n_header_value_connection_2; + } + case 'u': { + p++; + goto s_n_llhttp__internal__n_header_value_connection_3; + } + default: { + goto s_n_llhttp__internal__n_header_value_connection_token; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_56: + s_n_llhttp__internal__n_error_56: { + state->error = 0xb; + state->reason = "Content-Length overflow"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_57: + s_n_llhttp__internal__n_error_57: { + state->error = 0xb; + state->reason = "Invalid character in Content-Length"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_content_length_ws: + s_n_llhttp__internal__n_header_value_content_length_ws: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_content_length_ws; + } + switch (*p) { + case 10: { + goto s_n_llhttp__internal__n_invoke_or_flags_17; + } + case 13: { + goto s_n_llhttp__internal__n_invoke_or_flags_17; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_header_value_content_length_ws; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_7; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_content_length: + s_n_llhttp__internal__n_header_value_content_length: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_content_length; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + default: { + goto s_n_llhttp__internal__n_header_value_content_length_ws; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_59: + s_n_llhttp__internal__n_error_59: { + state->error = 0xf; + state->reason = "Invalid `Transfer-Encoding` header value"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_58: + s_n_llhttp__internal__n_error_58: { + state->error = 0xf; + state->reason = "Invalid `Transfer-Encoding` header value"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_te_token_ows: + s_n_llhttp__internal__n_header_value_te_token_ows: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_te_token_ows; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_header_value_te_token_ows; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_header_value_te_token_ows; + } + default: { + goto s_n_llhttp__internal__n_header_value_te_chunked; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value: + s_n_llhttp__internal__n_header_value: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + if (p == endp) { + return s_n_llhttp__internal__n_header_value; + } + #ifdef __SSE4_2__ + if (endp - p >= 16) { + __m128i ranges; + __m128i input; + int avail; + int match_len; + + /* Load input */ + input = _mm_loadu_si128((__m128i const*) p); + ranges = _mm_loadu_si128((__m128i const*) llparse_blob6); + + /* Find first character that does not match `ranges` */ + match_len = _mm_cmpestri(ranges, 6, + input, 16, + _SIDD_UBYTE_OPS | _SIDD_CMP_RANGES | + _SIDD_NEGATIVE_POLARITY); + + if (match_len != 0) { + p += match_len; + goto s_n_llhttp__internal__n_header_value; + } + goto s_n_llhttp__internal__n_header_value_otherwise; + } + #endif /* __SSE4_2__ */ + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_header_value; + } + default: { + goto s_n_llhttp__internal__n_header_value_otherwise; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_te_token: + s_n_llhttp__internal__n_header_value_te_token: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + if (p == endp) { + return s_n_llhttp__internal__n_header_value_te_token; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_header_value_te_token; + } + case 2: { + p++; + goto s_n_llhttp__internal__n_header_value_te_token_ows; + } + default: { + goto s_n_llhttp__internal__n_invoke_update_header_state_9; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_te_chunked_last: + s_n_llhttp__internal__n_header_value_te_chunked_last: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_te_chunked_last; + } + switch (*p) { + case 10: { + goto s_n_llhttp__internal__n_invoke_update_header_state_8; + } + case 13: { + goto s_n_llhttp__internal__n_invoke_update_header_state_8; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_header_value_te_chunked_last; + } + case ',': { + goto s_n_llhttp__internal__n_invoke_load_type_1; + } + default: { + goto s_n_llhttp__internal__n_header_value_te_token; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_te_chunked: + s_n_llhttp__internal__n_header_value_te_chunked: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_value_te_chunked; + } + match_seq = llparse__match_sequence_to_lower_unsafe(state, p, endp, llparse_blob5, 7); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_header_value_te_chunked_last; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_value_te_chunked; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_header_value_te_token; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1: + s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_header_value; + goto s_n_llhttp__internal__n_invoke_load_header_state_3; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_discard_ws: + s_n_llhttp__internal__n_header_value_discard_ws: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_discard_ws; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_header_value_discard_ws; + } + case 10: { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_14; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_header_value_discard_ws_almost_done; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_header_value_discard_ws; + } + default: { + goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_load_header_state: + s_n_llhttp__internal__n_invoke_load_header_state: { + switch (llhttp__internal__c_load_header_state(state, p, endp)) { + case 2: + goto s_n_llhttp__internal__n_invoke_test_flags_4; + case 3: + goto s_n_llhttp__internal__n_invoke_test_flags_5; + default: + goto s_n_llhttp__internal__n_header_value_discard_ws; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete: + s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete: { + switch (llhttp__on_header_field_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_load_header_state; + case 21: + goto s_n_llhttp__internal__n_pause_19; + default: + goto s_n_llhttp__internal__n_error_45; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_field_general_otherwise: + s_n_llhttp__internal__n_header_field_general_otherwise: { + if (p == endp) { + return s_n_llhttp__internal__n_header_field_general_otherwise; + } + switch (*p) { + case ':': { + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_field_2; + } + default: { + goto s_n_llhttp__internal__n_error_62; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_field_general: + s_n_llhttp__internal__n_header_field_general: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_header_field_general; + } + #ifdef __SSE4_2__ + if (endp - p >= 16) { + __m128i ranges; + __m128i input; + int avail; + int match_len; + + /* Load input */ + input = _mm_loadu_si128((__m128i const*) p); + ranges = _mm_loadu_si128((__m128i const*) llparse_blob7); + + /* Find first character that does not match `ranges` */ + match_len = _mm_cmpestri(ranges, 16, + input, 16, + _SIDD_UBYTE_OPS | _SIDD_CMP_RANGES | + _SIDD_NEGATIVE_POLARITY); + + if (match_len != 0) { + p += match_len; + goto s_n_llhttp__internal__n_header_field_general; + } + ranges = _mm_loadu_si128((__m128i const*) llparse_blob8); + + /* Find first character that does not match `ranges` */ + match_len = _mm_cmpestri(ranges, 2, + input, 16, + _SIDD_UBYTE_OPS | _SIDD_CMP_RANGES | + _SIDD_NEGATIVE_POLARITY); + + if (match_len != 0) { + p += match_len; + goto s_n_llhttp__internal__n_header_field_general; + } + goto s_n_llhttp__internal__n_header_field_general_otherwise; + } + #endif /* __SSE4_2__ */ + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_header_field_general; + } + default: { + goto s_n_llhttp__internal__n_header_field_general_otherwise; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_field_colon: + s_n_llhttp__internal__n_header_field_colon: { + if (p == endp) { + return s_n_llhttp__internal__n_header_field_colon; + } + switch (*p) { + case ' ': { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_13; + } + case ':': { + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_field_1; + } + default: { + goto s_n_llhttp__internal__n_invoke_update_header_state_10; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_field_3: + s_n_llhttp__internal__n_header_field_3: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_field_3; + } + match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob1, 6); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_store_header_state; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_field_3; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_invoke_update_header_state_11; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_field_4: + s_n_llhttp__internal__n_header_field_4: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_field_4; + } + match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob9, 10); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_store_header_state; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_field_4; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_invoke_update_header_state_11; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_field_2: + s_n_llhttp__internal__n_header_field_2: { + if (p == endp) { + return s_n_llhttp__internal__n_header_field_2; + } + switch (((*p) >= 'A' && (*p) <= 'Z' ? (*p | 0x20) : (*p))) { + case 'n': { + p++; + goto s_n_llhttp__internal__n_header_field_3; + } + case 't': { + p++; + goto s_n_llhttp__internal__n_header_field_4; + } + default: { + goto s_n_llhttp__internal__n_invoke_update_header_state_11; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_field_1: + s_n_llhttp__internal__n_header_field_1: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_field_1; + } + match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob0, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_header_field_2; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_field_1; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_invoke_update_header_state_11; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_field_5: + s_n_llhttp__internal__n_header_field_5: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_field_5; + } + match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob10, 15); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_store_header_state; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_field_5; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_invoke_update_header_state_11; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_field_6: + s_n_llhttp__internal__n_header_field_6: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_field_6; + } + match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob11, 16); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_store_header_state; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_field_6; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_invoke_update_header_state_11; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_field_7: + s_n_llhttp__internal__n_header_field_7: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_field_7; + } + match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob12, 6); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_store_header_state; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_field_7; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_invoke_update_header_state_11; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_field: + s_n_llhttp__internal__n_header_field: { + if (p == endp) { + return s_n_llhttp__internal__n_header_field; + } + switch (((*p) >= 'A' && (*p) <= 'Z' ? (*p | 0x20) : (*p))) { + case 'c': { + p++; + goto s_n_llhttp__internal__n_header_field_1; + } + case 'p': { + p++; + goto s_n_llhttp__internal__n_header_field_5; + } + case 't': { + p++; + goto s_n_llhttp__internal__n_header_field_6; + } + case 'u': { + p++; + goto s_n_llhttp__internal__n_header_field_7; + } + default: { + goto s_n_llhttp__internal__n_invoke_update_header_state_11; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_header_field: + s_n_llhttp__internal__n_span_start_llhttp__on_header_field: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_header_field; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_header_field; + goto s_n_llhttp__internal__n_header_field; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_field_start: + s_n_llhttp__internal__n_header_field_start: { + if (p == endp) { + return s_n_llhttp__internal__n_header_field_start; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_1; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_headers_almost_done; + } + case ':': { + goto s_n_llhttp__internal__n_error_44; + } + default: { + goto s_n_llhttp__internal__n_span_start_llhttp__on_header_field; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_headers_start: + s_n_llhttp__internal__n_headers_start: { + if (p == endp) { + return s_n_llhttp__internal__n_headers_start; + } + switch (*p) { + case ' ': { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags; + } + default: { + goto s_n_llhttp__internal__n_header_field_start; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_to_http_09: + s_n_llhttp__internal__n_url_to_http_09: { + if (p == endp) { + return s_n_llhttp__internal__n_url_to_http_09; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 12: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + default: { + goto s_n_llhttp__internal__n_invoke_update_http_major; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_skip_to_http09: + s_n_llhttp__internal__n_url_skip_to_http09: { + if (p == endp) { + return s_n_llhttp__internal__n_url_skip_to_http09; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 12: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + default: { + p++; + goto s_n_llhttp__internal__n_url_to_http_09; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_skip_lf_to_http09_1: + s_n_llhttp__internal__n_url_skip_lf_to_http09_1: { + if (p == endp) { + return s_n_llhttp__internal__n_url_skip_lf_to_http09_1; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_url_to_http_09; + } + default: { + goto s_n_llhttp__internal__n_error_63; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_skip_lf_to_http09: + s_n_llhttp__internal__n_url_skip_lf_to_http09: { + if (p == endp) { + return s_n_llhttp__internal__n_url_skip_lf_to_http09; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 12: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_url_skip_lf_to_http09_1; + } + default: { + goto s_n_llhttp__internal__n_error_63; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_pri_upgrade: + s_n_llhttp__internal__n_req_pri_upgrade: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_req_pri_upgrade; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob14, 10); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_error_71; + } + case kMatchPause: { + return s_n_llhttp__internal__n_req_pri_upgrade; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_72; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_http_complete_crlf: + s_n_llhttp__internal__n_req_http_complete_crlf: { + if (p == endp) { + return s_n_llhttp__internal__n_req_http_complete_crlf; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_headers_start; + } + default: { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_26; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_http_complete: + s_n_llhttp__internal__n_req_http_complete: { + if (p == endp) { + return s_n_llhttp__internal__n_req_http_complete; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_25; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_req_http_complete_crlf; + } + default: { + goto s_n_llhttp__internal__n_error_70; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_load_method_1: + s_n_llhttp__internal__n_invoke_load_method_1: { + switch (llhttp__internal__c_load_method(state, p, endp)) { + case 34: + goto s_n_llhttp__internal__n_req_pri_upgrade; + default: + goto s_n_llhttp__internal__n_req_http_complete; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__on_version_complete: + s_n_llhttp__internal__n_invoke_llhttp__on_version_complete: { + switch (llhttp__on_version_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_load_method_1; + case 21: + goto s_n_llhttp__internal__n_pause_21; + default: + goto s_n_llhttp__internal__n_error_67; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_66: + s_n_llhttp__internal__n_error_66: { + state->error = 0x9; + state->reason = "Invalid HTTP version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_73: + s_n_llhttp__internal__n_error_73: { + state->error = 0x9; + state->reason = "Invalid minor version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_http_minor: + s_n_llhttp__internal__n_req_http_minor: { + if (p == endp) { + return s_n_llhttp__internal__n_req_http_minor; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_2; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_74: + s_n_llhttp__internal__n_error_74: { + state->error = 0x9; + state->reason = "Expected dot"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_http_dot: + s_n_llhttp__internal__n_req_http_dot: { + if (p == endp) { + return s_n_llhttp__internal__n_req_http_dot; + } + switch (*p) { + case '.': { + p++; + goto s_n_llhttp__internal__n_req_http_minor; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_3; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_75: + s_n_llhttp__internal__n_error_75: { + state->error = 0x9; + state->reason = "Invalid major version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_http_major: + s_n_llhttp__internal__n_req_http_major: { + if (p == endp) { + return s_n_llhttp__internal__n_req_http_major; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_4; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_version: + s_n_llhttp__internal__n_span_start_llhttp__on_version: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_version; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_version; + goto s_n_llhttp__internal__n_req_http_major; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_http_start_1: + s_n_llhttp__internal__n_req_http_start_1: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_req_http_start_1; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob13, 4); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_invoke_load_method; + } + case kMatchPause: { + return s_n_llhttp__internal__n_req_http_start_1; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_78; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_http_start_2: + s_n_llhttp__internal__n_req_http_start_2: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_req_http_start_2; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob15, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_invoke_load_method_2; + } + case kMatchPause: { + return s_n_llhttp__internal__n_req_http_start_2; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_78; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_http_start_3: + s_n_llhttp__internal__n_req_http_start_3: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_req_http_start_3; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob16, 4); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_invoke_load_method_3; + } + case kMatchPause: { + return s_n_llhttp__internal__n_req_http_start_3; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_78; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_http_start: + s_n_llhttp__internal__n_req_http_start: { + if (p == endp) { + return s_n_llhttp__internal__n_req_http_start; + } + switch (*p) { + case ' ': { + p++; + goto s_n_llhttp__internal__n_req_http_start; + } + case 'H': { + p++; + goto s_n_llhttp__internal__n_req_http_start_1; + } + case 'I': { + p++; + goto s_n_llhttp__internal__n_req_http_start_2; + } + case 'R': { + p++; + goto s_n_llhttp__internal__n_req_http_start_3; + } + default: { + goto s_n_llhttp__internal__n_error_78; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_to_http: + s_n_llhttp__internal__n_url_to_http: { + if (p == endp) { + return s_n_llhttp__internal__n_url_to_http; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 12: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + default: { + goto s_n_llhttp__internal__n_invoke_llhttp__on_url_complete_1; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_skip_to_http: + s_n_llhttp__internal__n_url_skip_to_http: { + if (p == endp) { + return s_n_llhttp__internal__n_url_skip_to_http; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 12: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + default: { + p++; + goto s_n_llhttp__internal__n_url_to_http; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_fragment: + s_n_llhttp__internal__n_url_fragment: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 3, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_url_fragment; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 2: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_6; + } + case 3: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_7; + } + case 4: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_8; + } + case 5: { + p++; + goto s_n_llhttp__internal__n_url_fragment; + } + default: { + goto s_n_llhttp__internal__n_error_79; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_end_stub_query_3: + s_n_llhttp__internal__n_span_end_stub_query_3: { + if (p == endp) { + return s_n_llhttp__internal__n_span_end_stub_query_3; + } + p++; + goto s_n_llhttp__internal__n_url_fragment; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_query: + s_n_llhttp__internal__n_url_query: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 3, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_url_query; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 2: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_9; + } + case 3: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_10; + } + case 4: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_11; + } + case 5: { + p++; + goto s_n_llhttp__internal__n_url_query; + } + case 6: { + goto s_n_llhttp__internal__n_span_end_stub_query_3; + } + default: { + goto s_n_llhttp__internal__n_error_80; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_query_or_fragment: + s_n_llhttp__internal__n_url_query_or_fragment: { + if (p == endp) { + return s_n_llhttp__internal__n_url_query_or_fragment; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 10: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_3; + } + case 12: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 13: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_4; + } + case ' ': { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_5; + } + case '#': { + p++; + goto s_n_llhttp__internal__n_url_fragment; + } + case '?': { + p++; + goto s_n_llhttp__internal__n_url_query; + } + default: { + goto s_n_llhttp__internal__n_error_81; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_path: + s_n_llhttp__internal__n_url_path: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_url_path; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 2: { + p++; + goto s_n_llhttp__internal__n_url_path; + } + default: { + goto s_n_llhttp__internal__n_url_query_or_fragment; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_stub_path_2: + s_n_llhttp__internal__n_span_start_stub_path_2: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_stub_path_2; + } + p++; + goto s_n_llhttp__internal__n_url_path; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_stub_path: + s_n_llhttp__internal__n_span_start_stub_path: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_stub_path; + } + p++; + goto s_n_llhttp__internal__n_url_path; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_stub_path_1: + s_n_llhttp__internal__n_span_start_stub_path_1: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_stub_path_1; + } + p++; + goto s_n_llhttp__internal__n_url_path; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_server_with_at: + s_n_llhttp__internal__n_url_server_with_at: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 3, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4, 5, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 0, 7, + 8, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 0, 5, + 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 0, 0, 5, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_url_server_with_at; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 2: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_12; + } + case 3: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_13; + } + case 4: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_14; + } + case 5: { + p++; + goto s_n_llhttp__internal__n_url_server; + } + case 6: { + goto s_n_llhttp__internal__n_span_start_stub_path_1; + } + case 7: { + p++; + goto s_n_llhttp__internal__n_url_query; + } + case 8: { + p++; + goto s_n_llhttp__internal__n_error_82; + } + default: { + goto s_n_llhttp__internal__n_error_83; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_server: + s_n_llhttp__internal__n_url_server: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 3, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4, 5, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 0, 7, + 8, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 0, 5, + 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 0, 0, 5, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_url_server; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 2: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url; + } + case 3: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_1; + } + case 4: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_2; + } + case 5: { + p++; + goto s_n_llhttp__internal__n_url_server; + } + case 6: { + goto s_n_llhttp__internal__n_span_start_stub_path; + } + case 7: { + p++; + goto s_n_llhttp__internal__n_url_query; + } + case 8: { + p++; + goto s_n_llhttp__internal__n_url_server_with_at; + } + default: { + goto s_n_llhttp__internal__n_error_84; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_schema_delim_1: + s_n_llhttp__internal__n_url_schema_delim_1: { + if (p == endp) { + return s_n_llhttp__internal__n_url_schema_delim_1; + } + switch (*p) { + case '/': { + p++; + goto s_n_llhttp__internal__n_url_server; + } + default: { + goto s_n_llhttp__internal__n_error_85; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_schema_delim: + s_n_llhttp__internal__n_url_schema_delim: { + if (p == endp) { + return s_n_llhttp__internal__n_url_schema_delim; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 10: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 12: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case '/': { + p++; + goto s_n_llhttp__internal__n_url_schema_delim_1; + } + default: { + goto s_n_llhttp__internal__n_error_85; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_end_stub_schema: + s_n_llhttp__internal__n_span_end_stub_schema: { + if (p == endp) { + return s_n_llhttp__internal__n_span_end_stub_schema; + } + p++; + goto s_n_llhttp__internal__n_url_schema_delim; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_schema: + s_n_llhttp__internal__n_url_schema: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, + 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, + 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_url_schema; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 2: { + goto s_n_llhttp__internal__n_span_end_stub_schema; + } + case 3: { + p++; + goto s_n_llhttp__internal__n_url_schema; + } + default: { + goto s_n_llhttp__internal__n_error_86; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_start: + s_n_llhttp__internal__n_url_start: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, + 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_url_start; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 2: { + goto s_n_llhttp__internal__n_span_start_stub_path_2; + } + case 3: { + goto s_n_llhttp__internal__n_url_schema; + } + default: { + goto s_n_llhttp__internal__n_error_87; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_url_1: + s_n_llhttp__internal__n_span_start_llhttp__on_url_1: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_url_1; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_url; + goto s_n_llhttp__internal__n_url_start; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_entry_normal: + s_n_llhttp__internal__n_url_entry_normal: { + if (p == endp) { + return s_n_llhttp__internal__n_url_entry_normal; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 12: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + default: { + goto s_n_llhttp__internal__n_span_start_llhttp__on_url_1; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_url: + s_n_llhttp__internal__n_span_start_llhttp__on_url: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_url; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_url; + goto s_n_llhttp__internal__n_url_server; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_entry_connect: + s_n_llhttp__internal__n_url_entry_connect: { + if (p == endp) { + return s_n_llhttp__internal__n_url_entry_connect; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 12: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + default: { + goto s_n_llhttp__internal__n_span_start_llhttp__on_url; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_spaces_before_url: + s_n_llhttp__internal__n_req_spaces_before_url: { + if (p == endp) { + return s_n_llhttp__internal__n_req_spaces_before_url; + } + switch (*p) { + case ' ': { + p++; + goto s_n_llhttp__internal__n_req_spaces_before_url; + } + default: { + goto s_n_llhttp__internal__n_invoke_is_equal_method; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_first_space_before_url: + s_n_llhttp__internal__n_req_first_space_before_url: { + if (p == endp) { + return s_n_llhttp__internal__n_req_first_space_before_url; + } + switch (*p) { + case ' ': { + p++; + goto s_n_llhttp__internal__n_req_spaces_before_url; + } + default: { + goto s_n_llhttp__internal__n_error_88; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__on_method_complete_1: + s_n_llhttp__internal__n_invoke_llhttp__on_method_complete_1: { + switch (llhttp__on_method_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_req_first_space_before_url; + case 21: + goto s_n_llhttp__internal__n_pause_26; + default: + goto s_n_llhttp__internal__n_error_107; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_2: + s_n_llhttp__internal__n_after_start_req_2: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_2; + } + switch (*p) { + case 'L': { + p++; + match = 19; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_3: + s_n_llhttp__internal__n_after_start_req_3: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_3; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob17, 6); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 36; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_3; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_1: + s_n_llhttp__internal__n_after_start_req_1: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_1; + } + switch (*p) { + case 'C': { + p++; + goto s_n_llhttp__internal__n_after_start_req_2; + } + case 'N': { + p++; + goto s_n_llhttp__internal__n_after_start_req_3; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_4: + s_n_llhttp__internal__n_after_start_req_4: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_4; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob18, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 16; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_4; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_6: + s_n_llhttp__internal__n_after_start_req_6: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_6; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob19, 6); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 22; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_6; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_8: + s_n_llhttp__internal__n_after_start_req_8: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_8; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob20, 4); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_8; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_9: + s_n_llhttp__internal__n_after_start_req_9: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_9; + } + switch (*p) { + case 'Y': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_7: + s_n_llhttp__internal__n_after_start_req_7: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_7; + } + switch (*p) { + case 'N': { + p++; + goto s_n_llhttp__internal__n_after_start_req_8; + } + case 'P': { + p++; + goto s_n_llhttp__internal__n_after_start_req_9; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_5: + s_n_llhttp__internal__n_after_start_req_5: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_5; + } + switch (*p) { + case 'H': { + p++; + goto s_n_llhttp__internal__n_after_start_req_6; + } + case 'O': { + p++; + goto s_n_llhttp__internal__n_after_start_req_7; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_12: + s_n_llhttp__internal__n_after_start_req_12: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_12; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob21, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_12; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_13: + s_n_llhttp__internal__n_after_start_req_13: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_13; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob22, 5); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 35; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_13; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_11: + s_n_llhttp__internal__n_after_start_req_11: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_11; + } + switch (*p) { + case 'L': { + p++; + goto s_n_llhttp__internal__n_after_start_req_12; + } + case 'S': { + p++; + goto s_n_llhttp__internal__n_after_start_req_13; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_10: + s_n_llhttp__internal__n_after_start_req_10: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_10; + } + switch (*p) { + case 'E': { + p++; + goto s_n_llhttp__internal__n_after_start_req_11; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_14: + s_n_llhttp__internal__n_after_start_req_14: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_14; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob23, 4); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 45; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_14; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_17: + s_n_llhttp__internal__n_after_start_req_17: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_17; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob25, 9); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 41; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_17; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_16: + s_n_llhttp__internal__n_after_start_req_16: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_16; + } + switch (*p) { + case '_': { + p++; + goto s_n_llhttp__internal__n_after_start_req_17; + } + default: { + match = 1; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_15: + s_n_llhttp__internal__n_after_start_req_15: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_15; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob24, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_after_start_req_16; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_15; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_18: + s_n_llhttp__internal__n_after_start_req_18: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_18; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob26, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_18; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_20: + s_n_llhttp__internal__n_after_start_req_20: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_20; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob27, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 31; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_20; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_21: + s_n_llhttp__internal__n_after_start_req_21: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_21; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob28, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_21; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_19: + s_n_llhttp__internal__n_after_start_req_19: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_19; + } + switch (*p) { + case 'I': { + p++; + goto s_n_llhttp__internal__n_after_start_req_20; + } + case 'O': { + p++; + goto s_n_llhttp__internal__n_after_start_req_21; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_23: + s_n_llhttp__internal__n_after_start_req_23: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_23; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob29, 6); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 24; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_23; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_24: + s_n_llhttp__internal__n_after_start_req_24: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_24; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob30, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 23; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_24; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_26: + s_n_llhttp__internal__n_after_start_req_26: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_26; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob31, 7); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 21; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_26; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_28: + s_n_llhttp__internal__n_after_start_req_28: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_28; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob32, 6); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 30; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_28; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_29: + s_n_llhttp__internal__n_after_start_req_29: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_29; + } + switch (*p) { + case 'L': { + p++; + match = 10; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_27: + s_n_llhttp__internal__n_after_start_req_27: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_27; + } + switch (*p) { + case 'A': { + p++; + goto s_n_llhttp__internal__n_after_start_req_28; + } + case 'O': { + p++; + goto s_n_llhttp__internal__n_after_start_req_29; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_25: + s_n_llhttp__internal__n_after_start_req_25: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_25; + } + switch (*p) { + case 'A': { + p++; + goto s_n_llhttp__internal__n_after_start_req_26; + } + case 'C': { + p++; + goto s_n_llhttp__internal__n_after_start_req_27; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_30: + s_n_llhttp__internal__n_after_start_req_30: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_30; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob33, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 11; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_30; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_22: + s_n_llhttp__internal__n_after_start_req_22: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_22; + } + switch (*p) { + case '-': { + p++; + goto s_n_llhttp__internal__n_after_start_req_23; + } + case 'E': { + p++; + goto s_n_llhttp__internal__n_after_start_req_24; + } + case 'K': { + p++; + goto s_n_llhttp__internal__n_after_start_req_25; + } + case 'O': { + p++; + goto s_n_llhttp__internal__n_after_start_req_30; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_31: + s_n_llhttp__internal__n_after_start_req_31: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_31; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob34, 5); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 25; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_31; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_32: + s_n_llhttp__internal__n_after_start_req_32: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_32; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob35, 6); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_32; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_35: + s_n_llhttp__internal__n_after_start_req_35: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_35; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob36, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 28; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_35; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_36: + s_n_llhttp__internal__n_after_start_req_36: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_36; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob37, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 39; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_36; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_34: + s_n_llhttp__internal__n_after_start_req_34: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_34; + } + switch (*p) { + case 'T': { + p++; + goto s_n_llhttp__internal__n_after_start_req_35; + } + case 'U': { + p++; + goto s_n_llhttp__internal__n_after_start_req_36; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_37: + s_n_llhttp__internal__n_after_start_req_37: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_37; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob38, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 38; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_37; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_38: + s_n_llhttp__internal__n_after_start_req_38: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_38; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob39, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_38; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_42: + s_n_llhttp__internal__n_after_start_req_42: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_42; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob40, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 12; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_42; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_43: + s_n_llhttp__internal__n_after_start_req_43: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_43; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob41, 4); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 13; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_43; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_41: + s_n_llhttp__internal__n_after_start_req_41: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_41; + } + switch (*p) { + case 'F': { + p++; + goto s_n_llhttp__internal__n_after_start_req_42; + } + case 'P': { + p++; + goto s_n_llhttp__internal__n_after_start_req_43; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_40: + s_n_llhttp__internal__n_after_start_req_40: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_40; + } + switch (*p) { + case 'P': { + p++; + goto s_n_llhttp__internal__n_after_start_req_41; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_39: + s_n_llhttp__internal__n_after_start_req_39: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_39; + } + switch (*p) { + case 'I': { + p++; + match = 34; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case 'O': { + p++; + goto s_n_llhttp__internal__n_after_start_req_40; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_45: + s_n_llhttp__internal__n_after_start_req_45: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_45; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob42, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 29; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_45; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_44: + s_n_llhttp__internal__n_after_start_req_44: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_44; + } + switch (*p) { + case 'R': { + p++; + goto s_n_llhttp__internal__n_after_start_req_45; + } + case 'T': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_33: + s_n_llhttp__internal__n_after_start_req_33: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_33; + } + switch (*p) { + case 'A': { + p++; + goto s_n_llhttp__internal__n_after_start_req_34; + } + case 'L': { + p++; + goto s_n_llhttp__internal__n_after_start_req_37; + } + case 'O': { + p++; + goto s_n_llhttp__internal__n_after_start_req_38; + } + case 'R': { + p++; + goto s_n_llhttp__internal__n_after_start_req_39; + } + case 'U': { + p++; + goto s_n_llhttp__internal__n_after_start_req_44; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_46: + s_n_llhttp__internal__n_after_start_req_46: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_46; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob43, 4); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 46; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_46; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_49: + s_n_llhttp__internal__n_after_start_req_49: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_49; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob44, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 17; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_49; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_50: + s_n_llhttp__internal__n_after_start_req_50: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_50; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob45, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 44; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_50; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_51: + s_n_llhttp__internal__n_after_start_req_51: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_51; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob46, 5); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 43; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_51; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_52: + s_n_llhttp__internal__n_after_start_req_52: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_52; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob47, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 20; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_52; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_48: + s_n_llhttp__internal__n_after_start_req_48: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_48; + } + switch (*p) { + case 'B': { + p++; + goto s_n_llhttp__internal__n_after_start_req_49; + } + case 'C': { + p++; + goto s_n_llhttp__internal__n_after_start_req_50; + } + case 'D': { + p++; + goto s_n_llhttp__internal__n_after_start_req_51; + } + case 'P': { + p++; + goto s_n_llhttp__internal__n_after_start_req_52; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_47: + s_n_llhttp__internal__n_after_start_req_47: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_47; + } + switch (*p) { + case 'E': { + p++; + goto s_n_llhttp__internal__n_after_start_req_48; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_55: + s_n_llhttp__internal__n_after_start_req_55: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_55; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob48, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 14; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_55; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_57: + s_n_llhttp__internal__n_after_start_req_57: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_57; + } + switch (*p) { + case 'P': { + p++; + match = 37; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_58: + s_n_llhttp__internal__n_after_start_req_58: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_58; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob49, 9); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 42; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_58; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_56: + s_n_llhttp__internal__n_after_start_req_56: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_56; + } + switch (*p) { + case 'U': { + p++; + goto s_n_llhttp__internal__n_after_start_req_57; + } + case '_': { + p++; + goto s_n_llhttp__internal__n_after_start_req_58; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_54: + s_n_llhttp__internal__n_after_start_req_54: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_54; + } + switch (*p) { + case 'A': { + p++; + goto s_n_llhttp__internal__n_after_start_req_55; + } + case 'T': { + p++; + goto s_n_llhttp__internal__n_after_start_req_56; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_59: + s_n_llhttp__internal__n_after_start_req_59: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_59; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob50, 4); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 33; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_59; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_60: + s_n_llhttp__internal__n_after_start_req_60: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_60; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob51, 7); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 26; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_60; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_53: + s_n_llhttp__internal__n_after_start_req_53: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_53; + } + switch (*p) { + case 'E': { + p++; + goto s_n_llhttp__internal__n_after_start_req_54; + } + case 'O': { + p++; + goto s_n_llhttp__internal__n_after_start_req_59; + } + case 'U': { + p++; + goto s_n_llhttp__internal__n_after_start_req_60; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_62: + s_n_llhttp__internal__n_after_start_req_62: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_62; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob52, 6); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 40; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_62; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_63: + s_n_llhttp__internal__n_after_start_req_63: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_63; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob53, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_63; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_61: + s_n_llhttp__internal__n_after_start_req_61: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_61; + } + switch (*p) { + case 'E': { + p++; + goto s_n_llhttp__internal__n_after_start_req_62; + } + case 'R': { + p++; + goto s_n_llhttp__internal__n_after_start_req_63; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_66: + s_n_llhttp__internal__n_after_start_req_66: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_66; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob54, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 18; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_66; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_68: + s_n_llhttp__internal__n_after_start_req_68: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_68; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob55, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 32; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_68; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_69: + s_n_llhttp__internal__n_after_start_req_69: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_69; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob56, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 15; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_69; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_67: + s_n_llhttp__internal__n_after_start_req_67: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_67; + } + switch (*p) { + case 'I': { + p++; + goto s_n_llhttp__internal__n_after_start_req_68; + } + case 'O': { + p++; + goto s_n_llhttp__internal__n_after_start_req_69; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_70: + s_n_llhttp__internal__n_after_start_req_70: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_70; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob57, 8); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 27; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_70; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_65: + s_n_llhttp__internal__n_after_start_req_65: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_65; + } + switch (*p) { + case 'B': { + p++; + goto s_n_llhttp__internal__n_after_start_req_66; + } + case 'L': { + p++; + goto s_n_llhttp__internal__n_after_start_req_67; + } + case 'S': { + p++; + goto s_n_llhttp__internal__n_after_start_req_70; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_64: + s_n_llhttp__internal__n_after_start_req_64: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_64; + } + switch (*p) { + case 'N': { + p++; + goto s_n_llhttp__internal__n_after_start_req_65; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req: + s_n_llhttp__internal__n_after_start_req: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req; + } + switch (*p) { + case 'A': { + p++; + goto s_n_llhttp__internal__n_after_start_req_1; + } + case 'B': { + p++; + goto s_n_llhttp__internal__n_after_start_req_4; + } + case 'C': { + p++; + goto s_n_llhttp__internal__n_after_start_req_5; + } + case 'D': { + p++; + goto s_n_llhttp__internal__n_after_start_req_10; + } + case 'F': { + p++; + goto s_n_llhttp__internal__n_after_start_req_14; + } + case 'G': { + p++; + goto s_n_llhttp__internal__n_after_start_req_15; + } + case 'H': { + p++; + goto s_n_llhttp__internal__n_after_start_req_18; + } + case 'L': { + p++; + goto s_n_llhttp__internal__n_after_start_req_19; + } + case 'M': { + p++; + goto s_n_llhttp__internal__n_after_start_req_22; + } + case 'N': { + p++; + goto s_n_llhttp__internal__n_after_start_req_31; + } + case 'O': { + p++; + goto s_n_llhttp__internal__n_after_start_req_32; + } + case 'P': { + p++; + goto s_n_llhttp__internal__n_after_start_req_33; + } + case 'Q': { + p++; + goto s_n_llhttp__internal__n_after_start_req_46; + } + case 'R': { + p++; + goto s_n_llhttp__internal__n_after_start_req_47; + } + case 'S': { + p++; + goto s_n_llhttp__internal__n_after_start_req_53; + } + case 'T': { + p++; + goto s_n_llhttp__internal__n_after_start_req_61; + } + case 'U': { + p++; + goto s_n_llhttp__internal__n_after_start_req_64; + } + default: { + goto s_n_llhttp__internal__n_error_108; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_method_1: + s_n_llhttp__internal__n_span_start_llhttp__on_method_1: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_method_1; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_method; + goto s_n_llhttp__internal__n_after_start_req; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_res_line_almost_done: + s_n_llhttp__internal__n_res_line_almost_done: { + if (p == endp) { + return s_n_llhttp__internal__n_res_line_almost_done; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_status_complete; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_status_complete; + } + default: { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_29; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_test_lenient_flags_30: + s_n_llhttp__internal__n_invoke_test_lenient_flags_30: { + switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_llhttp__on_status_complete; + default: + goto s_n_llhttp__internal__n_error_94; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_res_status: + s_n_llhttp__internal__n_res_status: { + if (p == endp) { + return s_n_llhttp__internal__n_res_status; + } + switch (*p) { + case 10: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_status; + } + case 13: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_status_1; + } + default: { + p++; + goto s_n_llhttp__internal__n_res_status; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_status: + s_n_llhttp__internal__n_span_start_llhttp__on_status: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_status; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_status; + goto s_n_llhttp__internal__n_res_status; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_res_status_code_otherwise: + s_n_llhttp__internal__n_res_status_code_otherwise: { + if (p == endp) { + return s_n_llhttp__internal__n_res_status_code_otherwise; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_28; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_res_line_almost_done; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_span_start_llhttp__on_status; + } + default: { + goto s_n_llhttp__internal__n_error_95; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_res_status_code_digit_3: + s_n_llhttp__internal__n_res_status_code_digit_3: { + if (p == endp) { + return s_n_llhttp__internal__n_res_status_code_digit_3; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + default: { + goto s_n_llhttp__internal__n_error_97; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_res_status_code_digit_2: + s_n_llhttp__internal__n_res_status_code_digit_2: { + if (p == endp) { + return s_n_llhttp__internal__n_res_status_code_digit_2; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + default: { + goto s_n_llhttp__internal__n_error_99; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_res_status_code_digit_1: + s_n_llhttp__internal__n_res_status_code_digit_1: { + if (p == endp) { + return s_n_llhttp__internal__n_res_status_code_digit_1; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + default: { + goto s_n_llhttp__internal__n_error_101; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_res_after_version: + s_n_llhttp__internal__n_res_after_version: { + if (p == endp) { + return s_n_llhttp__internal__n_res_after_version; + } + switch (*p) { + case ' ': { + p++; + goto s_n_llhttp__internal__n_invoke_update_status_code; + } + default: { + goto s_n_llhttp__internal__n_error_102; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__on_version_complete_1: + s_n_llhttp__internal__n_invoke_llhttp__on_version_complete_1: { + switch (llhttp__on_version_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_res_after_version; + case 21: + goto s_n_llhttp__internal__n_pause_25; + default: + goto s_n_llhttp__internal__n_error_90; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_89: + s_n_llhttp__internal__n_error_89: { + state->error = 0x9; + state->reason = "Invalid HTTP version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_103: + s_n_llhttp__internal__n_error_103: { + state->error = 0x9; + state->reason = "Invalid minor version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_res_http_minor: + s_n_llhttp__internal__n_res_http_minor: { + if (p == endp) { + return s_n_llhttp__internal__n_res_http_minor; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_7; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_104: + s_n_llhttp__internal__n_error_104: { + state->error = 0x9; + state->reason = "Expected dot"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_res_http_dot: + s_n_llhttp__internal__n_res_http_dot: { + if (p == endp) { + return s_n_llhttp__internal__n_res_http_dot; + } + switch (*p) { + case '.': { + p++; + goto s_n_llhttp__internal__n_res_http_minor; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_8; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_105: + s_n_llhttp__internal__n_error_105: { + state->error = 0x9; + state->reason = "Invalid major version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_res_http_major: + s_n_llhttp__internal__n_res_http_major: { + if (p == endp) { + return s_n_llhttp__internal__n_res_http_major; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_9; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_version_1: + s_n_llhttp__internal__n_span_start_llhttp__on_version_1: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_version_1; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_version; + goto s_n_llhttp__internal__n_res_http_major; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_start_res: + s_n_llhttp__internal__n_start_res: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_start_res; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob58, 5); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_span_start_llhttp__on_version_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_start_res; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_109; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__on_method_complete: + s_n_llhttp__internal__n_invoke_llhttp__on_method_complete: { + switch (llhttp__on_method_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_req_first_space_before_url; + case 21: + goto s_n_llhttp__internal__n_pause_23; + default: + goto s_n_llhttp__internal__n_error_1; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_or_res_method_2: + s_n_llhttp__internal__n_req_or_res_method_2: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_req_or_res_method_2; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob59, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_store_method; + } + case kMatchPause: { + return s_n_llhttp__internal__n_req_or_res_method_2; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_106; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_update_type_1: + s_n_llhttp__internal__n_invoke_update_type_1: { + switch (llhttp__internal__c_update_type_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version_1; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_or_res_method_3: + s_n_llhttp__internal__n_req_or_res_method_3: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_req_or_res_method_3; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob60, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_span_end_llhttp__on_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_req_or_res_method_3; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_106; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_or_res_method_1: + s_n_llhttp__internal__n_req_or_res_method_1: { + if (p == endp) { + return s_n_llhttp__internal__n_req_or_res_method_1; + } + switch (*p) { + case 'E': { + p++; + goto s_n_llhttp__internal__n_req_or_res_method_2; + } + case 'T': { + p++; + goto s_n_llhttp__internal__n_req_or_res_method_3; + } + default: { + goto s_n_llhttp__internal__n_error_106; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_or_res_method: + s_n_llhttp__internal__n_req_or_res_method: { + if (p == endp) { + return s_n_llhttp__internal__n_req_or_res_method; + } + switch (*p) { + case 'H': { + p++; + goto s_n_llhttp__internal__n_req_or_res_method_1; + } + default: { + goto s_n_llhttp__internal__n_error_106; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_method: + s_n_llhttp__internal__n_span_start_llhttp__on_method: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_method; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_method; + goto s_n_llhttp__internal__n_req_or_res_method; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_start_req_or_res: + s_n_llhttp__internal__n_start_req_or_res: { + if (p == endp) { + return s_n_llhttp__internal__n_start_req_or_res; + } + switch (*p) { + case 'H': { + goto s_n_llhttp__internal__n_span_start_llhttp__on_method; + } + default: { + goto s_n_llhttp__internal__n_invoke_update_type_2; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_load_type: + s_n_llhttp__internal__n_invoke_load_type: { + switch (llhttp__internal__c_load_type(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_span_start_llhttp__on_method_1; + case 2: + goto s_n_llhttp__internal__n_start_res; + default: + goto s_n_llhttp__internal__n_start_req_or_res; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_update_finish: + s_n_llhttp__internal__n_invoke_update_finish: { + switch (llhttp__internal__c_update_finish(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_llhttp__on_message_begin; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_start: + s_n_llhttp__internal__n_start: { + if (p == endp) { + return s_n_llhttp__internal__n_start; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_start; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_start; + } + default: { + goto s_n_llhttp__internal__n_invoke_load_initial_message_completed; + } + } + /* UNREACHABLE */; + abort(); + } + default: + /* UNREACHABLE */ + abort(); + } + s_n_llhttp__internal__n_error_2: { + state->error = 0x7; + state->reason = "Invalid characters in url"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_finish_2: { + switch (llhttp__internal__c_update_finish_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_start; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_initial_message_completed: { + switch (llhttp__internal__c_update_initial_message_completed(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_finish_2; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_content_length: { + switch (llhttp__internal__c_update_content_length(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_initial_message_completed; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_8: { + state->error = 0x5; + state->reason = "Data after `Connection: close`"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_3: { + switch (llhttp__internal__c_test_lenient_flags_3(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_closed; + default: + goto s_n_llhttp__internal__n_error_8; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_2: { + switch (llhttp__internal__c_test_lenient_flags_2(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_update_initial_message_completed; + default: + goto s_n_llhttp__internal__n_closed; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_finish_1: { + switch (llhttp__internal__c_update_finish_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_2; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_13: { + state->error = 0x15; + state->reason = "on_message_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_is_equal_upgrade; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_38: { + state->error = 0x12; + state->reason = "`on_message_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_15: { + state->error = 0x15; + state->reason = "on_chunk_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_40: { + state->error = 0x14; + state->reason = "`on_chunk_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete_1: { + switch (llhttp__on_chunk_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2; + case 21: + goto s_n_llhttp__internal__n_pause_15; + default: + goto s_n_llhttp__internal__n_error_40; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_2: { + state->error = 0x15; + state->reason = "on_message_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_pause_1; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_9: { + state->error = 0x12; + state->reason = "`on_message_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_1: { + switch (llhttp__on_message_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_pause_1; + case 21: + goto s_n_llhttp__internal__n_pause_2; + default: + goto s_n_llhttp__internal__n_error_9; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_36: { + state->error = 0xc; + state->reason = "Chunk size overflow"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_10: { + state->error = 0xc; + state->reason = "Invalid character in chunk size"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_4: { + switch (llhttp__internal__c_test_lenient_flags_4(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_chunk_size_otherwise; + default: + goto s_n_llhttp__internal__n_error_10; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_3: { + state->error = 0x15; + state->reason = "on_chunk_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_update_content_length_1; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_14: { + state->error = 0x14; + state->reason = "`on_chunk_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete: { + switch (llhttp__on_chunk_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_update_content_length_1; + case 21: + goto s_n_llhttp__internal__n_pause_3; + default: + goto s_n_llhttp__internal__n_error_14; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_13: { + state->error = 0x19; + state->reason = "Missing expected CR after chunk data"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_6: { + switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete; + default: + goto s_n_llhttp__internal__n_error_13; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_15: { + state->error = 0x2; + state->reason = "Expected LF after chunk data"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_7: { + switch (llhttp__internal__c_test_lenient_flags_7(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete; + default: + goto s_n_llhttp__internal__n_error_15; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_body: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_body(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_data_almost_done; + return s_error; + } + goto s_n_llhttp__internal__n_chunk_data_almost_done; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags: { + switch (llhttp__internal__c_or_flags(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_field_start; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_4: { + state->error = 0x15; + state->reason = "on_chunk_header pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_is_equal_content_length; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_12: { + state->error = 0x13; + state->reason = "`on_chunk_header` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_header: { + switch (llhttp__on_chunk_header(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_is_equal_content_length; + case 21: + goto s_n_llhttp__internal__n_pause_4; + default: + goto s_n_llhttp__internal__n_error_12; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_16: { + state->error = 0x2; + state->reason = "Expected LF after chunk size"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_8: { + switch (llhttp__internal__c_test_lenient_flags_8(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_header; + default: + goto s_n_llhttp__internal__n_error_16; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_11: { + state->error = 0x19; + state->reason = "Missing expected CR after chunk size"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_5: { + switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_chunk_size_almost_done; + default: + goto s_n_llhttp__internal__n_error_11; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_17: { + state->error = 0x2; + state->reason = "Invalid character in chunk extensions"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_18: { + state->error = 0x2; + state->reason = "Invalid character in chunk extensions"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_20: { + state->error = 0x19; + state->reason = "Missing expected CR after chunk extension name"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_5: { + state->error = 0x15; + state->reason = "on_chunk_extension_name pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_test_lenient_flags_9; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_19: { + state->error = 0x22; + state->reason = "`on_chunk_extension_name` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_name(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_6: { + state->error = 0x15; + state->reason = "on_chunk_extension_name pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_size_almost_done; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_21: { + state->error = 0x22; + state->reason = "`on_chunk_extension_name` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_name(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_1; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_1; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_7: { + state->error = 0x15; + state->reason = "on_chunk_extension_name pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_extensions; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_22: { + state->error = 0x22; + state->reason = "`on_chunk_extension_name` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_2: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_name(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_2; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_2; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_25: { + state->error = 0x19; + state->reason = "Missing expected CR after chunk extension value"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_8: { + state->error = 0x15; + state->reason = "on_chunk_extension_value pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_test_lenient_flags_10; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_24: { + state->error = 0x23; + state->reason = "`on_chunk_extension_value` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_9: { + state->error = 0x15; + state->reason = "on_chunk_extension_value pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_size_almost_done; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_26: { + state->error = 0x23; + state->reason = "`on_chunk_extension_value` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_1; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_1; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_28: { + state->error = 0x19; + state->reason = "Missing expected CR after chunk extension value"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_11: { + switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_chunk_size_almost_done; + default: + goto s_n_llhttp__internal__n_error_28; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_29: { + state->error = 0x2; + state->reason = "Invalid character in chunk extensions quote value"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_10: { + state->error = 0x15; + state->reason = "on_chunk_extension_value pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_extension_quoted_value_done; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_27: { + state->error = 0x23; + state->reason = "`on_chunk_extension_value` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_2: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_2; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_2; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_3: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_30; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_error_30; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_4: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_31; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_error_31; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_11: { + state->error = 0x15; + state->reason = "on_chunk_extension_value pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_extensions; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_32: { + state->error = 0x23; + state->reason = "`on_chunk_extension_value` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_5: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_3; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_3; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_6: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_33; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_error_33; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_12: { + state->error = 0x15; + state->reason = "on_chunk_extension_name pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_extension_value; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_23: { + state->error = 0x22; + state->reason = "`on_chunk_extension_name` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_3: { + switch (llhttp__on_chunk_extension_name_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_chunk_extension_value; + case 21: + goto s_n_llhttp__internal__n_pause_12; + default: + goto s_n_llhttp__internal__n_error_23; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_3: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_name(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_4: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_name(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_34; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_error_34; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_35: { + state->error = 0xc; + state->reason = "Invalid character in chunk size"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_mul_add_content_length: { + switch (llhttp__internal__c_mul_add_content_length(state, p, endp, match)) { + case 1: + goto s_n_llhttp__internal__n_error_36; + default: + goto s_n_llhttp__internal__n_chunk_size; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_37: { + state->error = 0xc; + state->reason = "Invalid character in chunk size"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_body_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_body(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_finish_3: { + switch (llhttp__internal__c_update_finish_3(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_span_start_llhttp__on_body_2; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_39: { + state->error = 0xf; + state->reason = "Request has invalid `Transfer-Encoding`"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause: { + state->error = 0x15; + state->reason = "on_message_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__after_message_complete; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_7: { + state->error = 0x12; + state->reason = "`on_message_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__on_message_complete: { + switch (llhttp__on_message_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_llhttp__after_message_complete; + case 21: + goto s_n_llhttp__internal__n_pause; + default: + goto s_n_llhttp__internal__n_error_7; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_1: { + switch (llhttp__internal__c_or_flags_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_2: { + switch (llhttp__internal__c_or_flags_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_upgrade: { + switch (llhttp__internal__c_update_upgrade(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_or_flags_2; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_14: { + state->error = 0x15; + state->reason = "Paused by on_headers_complete"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_6: { + state->error = 0x11; + state->reason = "User callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__on_headers_complete: { + switch (llhttp__on_headers_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; + case 1: + goto s_n_llhttp__internal__n_invoke_or_flags_1; + case 2: + goto s_n_llhttp__internal__n_invoke_update_upgrade; + case 21: + goto s_n_llhttp__internal__n_pause_14; + default: + goto s_n_llhttp__internal__n_error_6; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__before_headers_complete: { + switch (llhttp__before_headers_complete(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_llhttp__on_headers_complete; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_flags: { + switch (llhttp__internal__c_test_flags(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete_1; + default: + goto s_n_llhttp__internal__n_invoke_llhttp__before_headers_complete; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_1: { + switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_test_flags; + default: + goto s_n_llhttp__internal__n_error_5; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_17: { + state->error = 0x15; + state->reason = "on_chunk_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_42: { + state->error = 0x14; + state->reason = "`on_chunk_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete_2: { + switch (llhttp__on_chunk_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2; + case 21: + goto s_n_llhttp__internal__n_pause_17; + default: + goto s_n_llhttp__internal__n_error_42; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_3: { + switch (llhttp__internal__c_or_flags_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_4: { + switch (llhttp__internal__c_or_flags_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_upgrade_1: { + switch (llhttp__internal__c_update_upgrade(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_or_flags_4; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_16: { + state->error = 0x15; + state->reason = "Paused by on_headers_complete"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_41: { + state->error = 0x11; + state->reason = "User callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__on_headers_complete_1: { + switch (llhttp__on_headers_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; + case 1: + goto s_n_llhttp__internal__n_invoke_or_flags_3; + case 2: + goto s_n_llhttp__internal__n_invoke_update_upgrade_1; + case 21: + goto s_n_llhttp__internal__n_pause_16; + default: + goto s_n_llhttp__internal__n_error_41; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__before_headers_complete_1: { + switch (llhttp__before_headers_complete(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_llhttp__on_headers_complete_1; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_flags_1: { + switch (llhttp__internal__c_test_flags(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete_2; + default: + goto s_n_llhttp__internal__n_invoke_llhttp__before_headers_complete_1; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_43: { + state->error = 0x2; + state->reason = "Expected LF after headers"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_12: { + switch (llhttp__internal__c_test_lenient_flags_8(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_test_flags_1; + default: + goto s_n_llhttp__internal__n_error_43; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_44: { + state->error = 0xa; + state->reason = "Invalid header token"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_field: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_field(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_5; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_error_5; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_13: { + switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_header_field_colon_discard_ws; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_field; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_60: { + state->error = 0xb; + state->reason = "Content-Length can't be present with Transfer-Encoding"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_47: { + state->error = 0xa; + state->reason = "Invalid header value char"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_15: { + switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_header_value_discard_ws; + default: + goto s_n_llhttp__internal__n_error_47; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_49: { + state->error = 0xb; + state->reason = "Empty Content-Length"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_18: { + state->error = 0x15; + state->reason = "on_header_value_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_field_start; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_48: { + state->error = 0x1d; + state->reason = "`on_header_value_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_header_state: { + switch (llhttp__internal__c_update_header_state(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_5: { + switch (llhttp__internal__c_or_flags_5(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_6: { + switch (llhttp__internal__c_or_flags_6(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_7: { + switch (llhttp__internal__c_or_flags_7(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_8: { + switch (llhttp__internal__c_or_flags_8(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_header_state_2: { + switch (llhttp__internal__c_load_header_state(state, p, endp)) { + case 5: + goto s_n_llhttp__internal__n_invoke_or_flags_5; + case 6: + goto s_n_llhttp__internal__n_invoke_or_flags_6; + case 7: + goto s_n_llhttp__internal__n_invoke_or_flags_7; + case 8: + goto s_n_llhttp__internal__n_invoke_or_flags_8; + default: + goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_header_state_1: { + switch (llhttp__internal__c_load_header_state(state, p, endp)) { + case 2: + goto s_n_llhttp__internal__n_error_49; + default: + goto s_n_llhttp__internal__n_invoke_load_header_state_2; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_46: { + state->error = 0xa; + state->reason = "Invalid header value char"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_14: { + switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_header_value_discard_lws; + default: + goto s_n_llhttp__internal__n_error_46; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_50: { + state->error = 0x2; + state->reason = "Expected LF after CR"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_16: { + switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_header_value_discard_lws; + default: + goto s_n_llhttp__internal__n_error_50; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_header_state_1: { + switch (llhttp__internal__c_update_header_state_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_header_state_4: { + switch (llhttp__internal__c_load_header_state(state, p, endp)) { + case 8: + goto s_n_llhttp__internal__n_invoke_update_header_state_1; + default: + goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_52: { + state->error = 0xa; + state->reason = "Unexpected whitespace after header value"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_18: { + switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_load_header_state_4; + default: + goto s_n_llhttp__internal__n_error_52; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_header_state_2: { + switch (llhttp__internal__c_update_header_state(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_9: { + switch (llhttp__internal__c_or_flags_5(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state_2; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_10: { + switch (llhttp__internal__c_or_flags_6(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state_2; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_11: { + switch (llhttp__internal__c_or_flags_7(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state_2; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_12: { + switch (llhttp__internal__c_or_flags_8(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_header_state_5: { + switch (llhttp__internal__c_load_header_state(state, p, endp)) { + case 5: + goto s_n_llhttp__internal__n_invoke_or_flags_9; + case 6: + goto s_n_llhttp__internal__n_invoke_or_flags_10; + case 7: + goto s_n_llhttp__internal__n_invoke_or_flags_11; + case 8: + goto s_n_llhttp__internal__n_invoke_or_flags_12; + default: + goto s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_53: { + state->error = 0x3; + state->reason = "Missing expected LF after header value"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_51: { + state->error = 0x19; + state->reason = "Missing expected CR after header value"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_test_lenient_flags_17; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_17; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value_2: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_value_almost_done; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_header_value_almost_done; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value_4: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_value_almost_done; + return s_error; + } + goto s_n_llhttp__internal__n_header_value_almost_done; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value_5: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_value_almost_done; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_header_value_almost_done; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value_3: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_54; + return s_error; + } + goto s_n_llhttp__internal__n_error_54; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_19: { + switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_header_value_lenient; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_3; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_header_state_4: { + switch (llhttp__internal__c_update_header_state(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value_connection; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_13: { + switch (llhttp__internal__c_or_flags_5(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state_4; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_14: { + switch (llhttp__internal__c_or_flags_6(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state_4; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_15: { + switch (llhttp__internal__c_or_flags_7(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state_4; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_16: { + switch (llhttp__internal__c_or_flags_8(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value_connection; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_header_state_6: { + switch (llhttp__internal__c_load_header_state(state, p, endp)) { + case 5: + goto s_n_llhttp__internal__n_invoke_or_flags_13; + case 6: + goto s_n_llhttp__internal__n_invoke_or_flags_14; + case 7: + goto s_n_llhttp__internal__n_invoke_or_flags_15; + case 8: + goto s_n_llhttp__internal__n_invoke_or_flags_16; + default: + goto s_n_llhttp__internal__n_header_value_connection; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_header_state_5: { + switch (llhttp__internal__c_update_header_state_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value_connection_token; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_header_state_3: { + switch (llhttp__internal__c_update_header_state_3(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value_connection_ws; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_header_state_6: { + switch (llhttp__internal__c_update_header_state_6(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value_connection_ws; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_header_state_7: { + switch (llhttp__internal__c_update_header_state_7(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value_connection_ws; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value_6: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_56; + return s_error; + } + goto s_n_llhttp__internal__n_error_56; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_mul_add_content_length_1: { + switch (llhttp__internal__c_mul_add_content_length_1(state, p, endp, match)) { + case 1: + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_6; + default: + goto s_n_llhttp__internal__n_header_value_content_length; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_17: { + switch (llhttp__internal__c_or_flags_17(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value_otherwise; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value_7: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_57; + return s_error; + } + goto s_n_llhttp__internal__n_error_57; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_55: { + state->error = 0x4; + state->reason = "Duplicate Content-Length"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_flags_2: { + switch (llhttp__internal__c_test_flags_2(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_header_value_content_length; + default: + goto s_n_llhttp__internal__n_error_55; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value_9: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_59; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_error_59; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_header_state_8: { + switch (llhttp__internal__c_update_header_state_8(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value_otherwise; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value_8: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_58; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_error_58; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_20: { + switch (llhttp__internal__c_test_lenient_flags_20(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_8; + default: + goto s_n_llhttp__internal__n_header_value_te_chunked; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_type_1: { + switch (llhttp__internal__c_load_type(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_20; + default: + goto s_n_llhttp__internal__n_header_value_te_chunked; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_header_state_9: { + switch (llhttp__internal__c_update_header_state_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_and_flags: { + switch (llhttp__internal__c_and_flags(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value_te_chunked; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_19: { + switch (llhttp__internal__c_or_flags_18(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_and_flags; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_21: { + switch (llhttp__internal__c_test_lenient_flags_20(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_9; + default: + goto s_n_llhttp__internal__n_invoke_or_flags_19; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_type_2: { + switch (llhttp__internal__c_load_type(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_21; + default: + goto s_n_llhttp__internal__n_invoke_or_flags_19; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_18: { + switch (llhttp__internal__c_or_flags_18(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_and_flags; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_flags_3: { + switch (llhttp__internal__c_test_flags_3(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_load_type_2; + default: + goto s_n_llhttp__internal__n_invoke_or_flags_18; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_20: { + switch (llhttp__internal__c_or_flags_20(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state_9; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_header_state_3: { + switch (llhttp__internal__c_load_header_state(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_header_value_connection; + case 2: + goto s_n_llhttp__internal__n_invoke_test_flags_2; + case 3: + goto s_n_llhttp__internal__n_invoke_test_flags_3; + case 4: + goto s_n_llhttp__internal__n_invoke_or_flags_20; + default: + goto s_n_llhttp__internal__n_header_value; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_22: { + switch (llhttp__internal__c_test_lenient_flags_22(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_error_60; + default: + goto s_n_llhttp__internal__n_header_value_discard_ws; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_flags_4: { + switch (llhttp__internal__c_test_flags_4(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_22; + default: + goto s_n_llhttp__internal__n_header_value_discard_ws; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_61: { + state->error = 0xf; + state->reason = "Transfer-Encoding can't be present with Content-Length"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_23: { + switch (llhttp__internal__c_test_lenient_flags_22(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_error_61; + default: + goto s_n_llhttp__internal__n_header_value_discard_ws; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_flags_5: { + switch (llhttp__internal__c_test_flags_2(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_23; + default: + goto s_n_llhttp__internal__n_header_value_discard_ws; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_19: { + state->error = 0x15; + state->reason = "on_header_field_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_load_header_state; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_45: { + state->error = 0x1c; + state->reason = "`on_header_field_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_field_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_field(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_field_2: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_field(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_62: { + state->error = 0xa; + state->reason = "Invalid header token"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_header_state_10: { + switch (llhttp__internal__c_update_header_state_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_field_general; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_store_header_state: { + switch (llhttp__internal__c_store_header_state(state, p, endp, match)) { + default: + goto s_n_llhttp__internal__n_header_field_colon; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_header_state_11: { + switch (llhttp__internal__c_update_header_state_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_field_general; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_4: { + state->error = 0x1e; + state->reason = "Unexpected space after start line"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags: { + switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_header_field_start; + default: + goto s_n_llhttp__internal__n_error_4; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_20: { + state->error = 0x15; + state->reason = "on_url_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_headers_start; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_3: { + state->error = 0x1a; + state->reason = "`on_url_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__on_url_complete: { + switch (llhttp__on_url_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_headers_start; + case 21: + goto s_n_llhttp__internal__n_pause_20; + default: + goto s_n_llhttp__internal__n_error_3; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_http_minor: { + switch (llhttp__internal__c_update_http_minor(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_llhttp__on_url_complete; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_http_major: { + switch (llhttp__internal__c_update_http_major(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_http_minor; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_3: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http09; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_63: { + state->error = 0x7; + state->reason = "Expected CRLF"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_4: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_lf_to_http09; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_71: { + state->error = 0x17; + state->reason = "Pause on PRI/Upgrade"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_72: { + state->error = 0x9; + state->reason = "Expected HTTP/2 Connection Preface"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_69: { + state->error = 0x2; + state->reason = "Expected CRLF after version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_26: { + switch (llhttp__internal__c_test_lenient_flags_8(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_headers_start; + default: + goto s_n_llhttp__internal__n_error_69; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_68: { + state->error = 0x9; + state->reason = "Expected CRLF after version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_25: { + switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_req_http_complete_crlf; + default: + goto s_n_llhttp__internal__n_error_68; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_70: { + state->error = 0x9; + state->reason = "Expected CRLF after version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_21: { + state->error = 0x15; + state->reason = "on_version_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_load_method_1; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_67: { + state->error = 0x21; + state->reason = "`on_version_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_version_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_version_complete; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_version_complete; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_version: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_66; + return s_error; + } + goto s_n_llhttp__internal__n_error_66; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_http_minor: { + switch (llhttp__internal__c_load_http_minor(state, p, endp)) { + case 9: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_1; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_http_minor_1: { + switch (llhttp__internal__c_load_http_minor(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_1; + case 1: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_1; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_http_minor_2: { + switch (llhttp__internal__c_load_http_minor(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_1; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_http_major: { + switch (llhttp__internal__c_load_http_major(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_load_http_minor; + case 1: + goto s_n_llhttp__internal__n_invoke_load_http_minor_1; + case 2: + goto s_n_llhttp__internal__n_invoke_load_http_minor_2; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_24: { + switch (llhttp__internal__c_test_lenient_flags_24(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_1; + default: + goto s_n_llhttp__internal__n_invoke_load_http_major; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_store_http_minor: { + switch (llhttp__internal__c_store_http_minor(state, p, endp, match)) { + default: + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_24; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_version_2: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_73; + return s_error; + } + goto s_n_llhttp__internal__n_error_73; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_version_3: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_74; + return s_error; + } + goto s_n_llhttp__internal__n_error_74; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_store_http_major: { + switch (llhttp__internal__c_store_http_major(state, p, endp, match)) { + default: + goto s_n_llhttp__internal__n_req_http_dot; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_version_4: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_75; + return s_error; + } + goto s_n_llhttp__internal__n_error_75; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_65: { + state->error = 0x8; + state->reason = "Invalid method for HTTP/x.x request"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_method: { + switch (llhttp__internal__c_load_method(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 1: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 2: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 3: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 4: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 5: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 6: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 7: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 8: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 9: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 10: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 11: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 12: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 13: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 14: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 15: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 16: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 17: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 18: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 19: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 20: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 21: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 22: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 23: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 24: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 25: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 26: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 27: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 28: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 29: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 30: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 31: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 32: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 33: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 34: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 46: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + default: + goto s_n_llhttp__internal__n_error_65; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_78: { + state->error = 0x8; + state->reason = "Expected HTTP/"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_76: { + state->error = 0x8; + state->reason = "Expected SOURCE method for ICE/x.x request"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_method_2: { + switch (llhttp__internal__c_load_method(state, p, endp)) { + case 33: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + default: + goto s_n_llhttp__internal__n_error_76; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_77: { + state->error = 0x8; + state->reason = "Invalid method for RTSP/x.x request"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_method_3: { + switch (llhttp__internal__c_load_method(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 3: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 6: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 35: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 36: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 37: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 38: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 39: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 40: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 41: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 42: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 43: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 44: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 45: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + default: + goto s_n_llhttp__internal__n_error_77; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_22: { + state->error = 0x15; + state->reason = "on_url_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_req_http_start; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_64: { + state->error = 0x1a; + state->reason = "`on_url_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__on_url_complete_1: { + switch (llhttp__on_url_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_req_http_start; + case 21: + goto s_n_llhttp__internal__n_pause_22; + default: + goto s_n_llhttp__internal__n_error_64; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_5: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_6: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http09; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_7: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_lf_to_http09; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_8: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_79: { + state->error = 0x7; + state->reason = "Invalid char in url fragment start"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_9: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http09; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_10: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_lf_to_http09; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_11: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_80: { + state->error = 0x7; + state->reason = "Invalid char in url query"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_81: { + state->error = 0x7; + state->reason = "Invalid char in url path"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http09; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_lf_to_http09; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_2: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_12: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http09; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_13: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_lf_to_http09; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_14: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_82: { + state->error = 0x7; + state->reason = "Double @ in url"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_83: { + state->error = 0x7; + state->reason = "Unexpected char in url server"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_84: { + state->error = 0x7; + state->reason = "Unexpected char in url server"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_85: { + state->error = 0x7; + state->reason = "Unexpected char in url schema"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_86: { + state->error = 0x7; + state->reason = "Unexpected char in url schema"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_87: { + state->error = 0x7; + state->reason = "Unexpected start char in url"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_is_equal_method: { + switch (llhttp__internal__c_is_equal_method(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_url_entry_normal; + default: + goto s_n_llhttp__internal__n_url_entry_connect; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_88: { + state->error = 0x6; + state->reason = "Expected space after method"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_26: { + state->error = 0x15; + state->reason = "on_method_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_req_first_space_before_url; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_107: { + state->error = 0x20; + state->reason = "`on_method_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_method_2: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_method(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_method_complete_1; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_method_complete_1; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_store_method_1: { + switch (llhttp__internal__c_store_method(state, p, endp, match)) { + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_method_2; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_108: { + state->error = 0x6; + state->reason = "Invalid method encountered"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_100: { + state->error = 0xd; + state->reason = "Invalid status code"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_98: { + state->error = 0xd; + state->reason = "Invalid status code"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_96: { + state->error = 0xd; + state->reason = "Invalid status code"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_24: { + state->error = 0x15; + state->reason = "on_status_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_headers_start; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_92: { + state->error = 0x1b; + state->reason = "`on_status_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__on_status_complete: { + switch (llhttp__on_status_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_headers_start; + case 21: + goto s_n_llhttp__internal__n_pause_24; + default: + goto s_n_llhttp__internal__n_error_92; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_91: { + state->error = 0xd; + state->reason = "Invalid response status"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_28: { + switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_llhttp__on_status_complete; + default: + goto s_n_llhttp__internal__n_error_91; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_93: { + state->error = 0x2; + state->reason = "Expected LF after CR"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_29: { + switch (llhttp__internal__c_test_lenient_flags_8(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_llhttp__on_status_complete; + default: + goto s_n_llhttp__internal__n_error_93; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_94: { + state->error = 0x19; + state->reason = "Missing expected CR after response line"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_status: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_status(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_test_lenient_flags_30; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_30; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_status_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_status(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_res_line_almost_done; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_res_line_almost_done; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_95: { + state->error = 0xd; + state->reason = "Invalid response status"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_mul_add_status_code_2: { + switch (llhttp__internal__c_mul_add_status_code(state, p, endp, match)) { + case 1: + goto s_n_llhttp__internal__n_error_96; + default: + goto s_n_llhttp__internal__n_res_status_code_otherwise; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_97: { + state->error = 0xd; + state->reason = "Invalid status code"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_mul_add_status_code_1: { + switch (llhttp__internal__c_mul_add_status_code(state, p, endp, match)) { + case 1: + goto s_n_llhttp__internal__n_error_98; + default: + goto s_n_llhttp__internal__n_res_status_code_digit_3; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_99: { + state->error = 0xd; + state->reason = "Invalid status code"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_mul_add_status_code: { + switch (llhttp__internal__c_mul_add_status_code(state, p, endp, match)) { + case 1: + goto s_n_llhttp__internal__n_error_100; + default: + goto s_n_llhttp__internal__n_res_status_code_digit_2; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_101: { + state->error = 0xd; + state->reason = "Invalid status code"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_status_code: { + switch (llhttp__internal__c_update_status_code(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_res_status_code_digit_1; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_102: { + state->error = 0x9; + state->reason = "Expected space after version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_25: { + state->error = 0x15; + state->reason = "on_version_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_res_after_version; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_90: { + state->error = 0x21; + state->reason = "`on_version_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_version_6: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_version_complete_1; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_version_complete_1; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_version_5: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_89; + return s_error; + } + goto s_n_llhttp__internal__n_error_89; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_http_minor_3: { + switch (llhttp__internal__c_load_http_minor(state, p, endp)) { + case 9: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_6; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_5; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_http_minor_4: { + switch (llhttp__internal__c_load_http_minor(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_6; + case 1: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_6; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_5; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_http_minor_5: { + switch (llhttp__internal__c_load_http_minor(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_6; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_5; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_http_major_1: { + switch (llhttp__internal__c_load_http_major(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_load_http_minor_3; + case 1: + goto s_n_llhttp__internal__n_invoke_load_http_minor_4; + case 2: + goto s_n_llhttp__internal__n_invoke_load_http_minor_5; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_5; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_27: { + switch (llhttp__internal__c_test_lenient_flags_24(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_6; + default: + goto s_n_llhttp__internal__n_invoke_load_http_major_1; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_store_http_minor_1: { + switch (llhttp__internal__c_store_http_minor(state, p, endp, match)) { + default: + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_27; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_version_7: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_103; + return s_error; + } + goto s_n_llhttp__internal__n_error_103; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_version_8: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_104; + return s_error; + } + goto s_n_llhttp__internal__n_error_104; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_store_http_major_1: { + switch (llhttp__internal__c_store_http_major(state, p, endp, match)) { + default: + goto s_n_llhttp__internal__n_res_http_dot; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_version_9: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_105; + return s_error; + } + goto s_n_llhttp__internal__n_error_105; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_109: { + state->error = 0x8; + state->reason = "Expected HTTP/"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_23: { + state->error = 0x15; + state->reason = "on_method_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_req_first_space_before_url; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_1: { + state->error = 0x20; + state->reason = "`on_method_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_method: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_method(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_method_complete; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_method_complete; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_type: { + switch (llhttp__internal__c_update_type(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_method; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_store_method: { + switch (llhttp__internal__c_store_method(state, p, endp, match)) { + default: + goto s_n_llhttp__internal__n_invoke_update_type; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_106: { + state->error = 0x8; + state->reason = "Invalid word encountered"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_method_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_method(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_update_type_1; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_update_type_1; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_type_2: { + switch (llhttp__internal__c_update_type(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_span_start_llhttp__on_method_1; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_27: { + state->error = 0x15; + state->reason = "on_message_begin pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_load_type; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error: { + state->error = 0x10; + state->reason = "`on_message_begin` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__on_message_begin: { + switch (llhttp__on_message_begin(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_load_type; + case 21: + goto s_n_llhttp__internal__n_pause_27; + default: + goto s_n_llhttp__internal__n_error; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_28: { + state->error = 0x15; + state->reason = "on_reset pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_update_finish; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_110: { + state->error = 0x1f; + state->reason = "`on_reset` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__on_reset: { + switch (llhttp__on_reset(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_update_finish; + case 21: + goto s_n_llhttp__internal__n_pause_28; + default: + goto s_n_llhttp__internal__n_error_110; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_initial_message_completed: { + switch (llhttp__internal__c_load_initial_message_completed(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_llhttp__on_reset; + default: + goto s_n_llhttp__internal__n_invoke_update_finish; + } + /* UNREACHABLE */; + abort(); + } +} + +int llhttp__internal_execute(llhttp__internal_t* state, const char* p, const char* endp) { + llparse_state_t next; + + /* check lingering errors */ + if (state->error != 0) { + return state->error; + } + + /* restart spans */ + if (state->_span_pos0 != NULL) { + state->_span_pos0 = (void*) p; + } + + next = llhttp__internal__run(state, (const unsigned char*) p, (const unsigned char*) endp); + if (next == s_error) { + return state->error; + } + state->_current = (void*) (intptr_t) next; + + /* execute spans */ + if (state->_span_pos0 != NULL) { + int error; + + error = ((llhttp__internal__span_cb) state->_span_cb0)(state, state->_span_pos0, (const char*) endp); + if (error != 0) { + state->error = error; + state->error_pos = endp; + return error; + } + } + + return 0; +} + +#ifdef __GNUC__ + #pragma GCC diagnostic pop +#endif diff --git a/common/http/llhttp_support.c b/common/http/llhttp_support.c new file mode 100644 index 0000000..1ab91a5 --- /dev/null +++ b/common/http/llhttp_support.c @@ -0,0 +1,170 @@ +#include +#ifndef LLHTTP__TEST +# include "llhttp.h" +#else +# define llhttp_t llparse_t +#endif /* */ + +int llhttp_message_needs_eof(const llhttp_t* parser); +int llhttp_should_keep_alive(const llhttp_t* parser); + +int llhttp__before_headers_complete(llhttp_t* parser, const char* p, + const char* endp) { + /* Set this here so that on_headers_complete() callbacks can see it */ + if ((parser->flags & F_UPGRADE) && + (parser->flags & F_CONNECTION_UPGRADE)) { + /* For responses, "Upgrade: foo" and "Connection: upgrade" are + * mandatory only when it is a 101 Switching Protocols response, + * otherwise it is purely informational, to announce support. + */ + parser->upgrade = + (parser->type == HTTP_REQUEST || parser->status_code == 101); + } else { + parser->upgrade = (parser->method == HTTP_CONNECT); + } + return 0; +} + + +/* Return values: + * 0 - No body, `restart`, message_complete + * 1 - CONNECT request, `restart`, message_complete, and pause + * 2 - chunk_size_start + * 3 - body_identity + * 4 - body_identity_eof + * 5 - invalid transfer-encoding for request + */ +int llhttp__after_headers_complete(llhttp_t* parser, const char* p, + const char* endp) { + int hasBody; + + hasBody = parser->flags & F_CHUNKED || parser->content_length > 0; + if ( + (parser->upgrade && (parser->method == HTTP_CONNECT || + (parser->flags & F_SKIPBODY) || !hasBody)) || + /* See RFC 2616 section 4.4 - 1xx e.g. Continue */ + (parser->type == HTTP_RESPONSE && parser->status_code == 101) + ) { + /* Exit, the rest of the message is in a different protocol. */ + return 1; + } + + if (parser->type == HTTP_RESPONSE && parser->status_code == 100) { + /* No body, restart as the message is complete */ + return 0; + } + + /* See RFC 2616 section 4.4 */ + if ( + parser->flags & F_SKIPBODY || /* response to a HEAD request */ + ( + parser->type == HTTP_RESPONSE && ( + parser->status_code == 102 || /* Processing */ + parser->status_code == 103 || /* Early Hints */ + parser->status_code == 204 || /* No Content */ + parser->status_code == 304 /* Not Modified */ + ) + ) + ) { + return 0; + } else if (parser->flags & F_CHUNKED) { + /* chunked encoding - ignore Content-Length header, prepare for a chunk */ + return 2; + } else if (parser->flags & F_TRANSFER_ENCODING) { + if (parser->type == HTTP_REQUEST && + (parser->lenient_flags & LENIENT_CHUNKED_LENGTH) == 0 && + (parser->lenient_flags & LENIENT_TRANSFER_ENCODING) == 0) { + /* RFC 7230 3.3.3 */ + + /* If a Transfer-Encoding header field + * is present in a request and the chunked transfer coding is not + * the final encoding, the message body length cannot be determined + * reliably; the server MUST respond with the 400 (Bad Request) + * status code and then close the connection. + */ + return 5; + } else { + /* RFC 7230 3.3.3 */ + + /* If a Transfer-Encoding header field is present in a response and + * the chunked transfer coding is not the final encoding, the + * message body length is determined by reading the connection until + * it is closed by the server. + */ + return 4; + } + } else { + if (!(parser->flags & F_CONTENT_LENGTH)) { + if (!llhttp_message_needs_eof(parser)) { + /* Assume content-length 0 - read the next */ + return 0; + } else { + /* Read body until EOF */ + return 4; + } + } else if (parser->content_length == 0) { + /* Content-Length header given but zero: Content-Length: 0\r\n */ + return 0; + } else { + /* Content-Length header given and non-zero */ + return 3; + } + } +} + + +int llhttp__after_message_complete(llhttp_t* parser, const char* p, + const char* endp) { + int should_keep_alive; + + should_keep_alive = llhttp_should_keep_alive(parser); + parser->finish = HTTP_FINISH_SAFE; + parser->flags = 0; + + /* NOTE: this is ignored in loose parsing mode */ + return should_keep_alive; +} + + +int llhttp_message_needs_eof(const llhttp_t* parser) { + if (parser->type == HTTP_REQUEST) { + return 0; + } + + /* See RFC 2616 section 4.4 */ + if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */ + parser->status_code == 204 || /* No Content */ + parser->status_code == 304 || /* Not Modified */ + (parser->flags & F_SKIPBODY)) { /* response to a HEAD request */ + return 0; + } + + /* RFC 7230 3.3.3, see `llhttp__after_headers_complete` */ + if ((parser->flags & F_TRANSFER_ENCODING) && + (parser->flags & F_CHUNKED) == 0) { + return 1; + } + + if (parser->flags & (F_CHUNKED | F_CONTENT_LENGTH)) { + return 0; + } + + return 1; +} + + +int llhttp_should_keep_alive(const llhttp_t* parser) { + if (parser->http_major > 0 && parser->http_minor > 0) { + /* HTTP/1.1 */ + if (parser->flags & F_CONNECTION_CLOSE) { + return 0; + } + } else { + /* HTTP/1.0 or earlier */ + if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) { + return 0; + } + } + + return !llhttp_message_needs_eof(parser); +} diff --git a/common/http/llhttp_url.c b/common/http/llhttp_url.c new file mode 100644 index 0000000..c0ac411 --- /dev/null +++ b/common/http/llhttp_url.c @@ -0,0 +1,640 @@ +#include +#include +#include "llhttp_url.h" + +#ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wconversion" + #pragma GCC diagnostic ignored "-Wsign-conversion" +#endif + +#ifndef BIT_AT +# define BIT_AT(a, i) \ + (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \ + (1 << ((unsigned int) (i) & 7)))) +#endif + +#if LLHTTP_STRICT_MODE +# define T(v) 0 +#else +# define T(v) v +#endif + + +static const uint8_t normal_url_char[32] = { + /* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, + /* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0, + /* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, + /* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, + /* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128, + /* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, + /* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, }; + +#undef T + +enum state + { s_dead = 1 /* important that this is > 0 */ + + , s_start_req_or_res + , s_res_or_resp_H + , s_start_res + , s_res_H + , s_res_HT + , s_res_HTT + , s_res_HTTP + , s_res_http_major + , s_res_http_dot + , s_res_http_minor + , s_res_http_end + , s_res_first_status_code + , s_res_status_code + , s_res_status_start + , s_res_status + , s_res_line_almost_done + + , s_start_req + + , s_req_method + , s_req_spaces_before_url + , s_req_schema + , s_req_schema_slash + , s_req_schema_slash_slash + , s_req_server_start + , s_req_server + , s_req_server_with_at + , s_req_path + , s_req_query_string_start + , s_req_query_string + , s_req_fragment_start + , s_req_fragment + , s_req_http_start + , s_req_http_H + , s_req_http_HT + , s_req_http_HTT + , s_req_http_HTTP + , s_req_http_I + , s_req_http_IC + , s_req_http_major + , s_req_http_dot + , s_req_http_minor + , s_req_http_end + , s_req_line_almost_done + + , s_header_field_start + , s_header_field + , s_header_value_discard_ws + , s_header_value_discard_ws_almost_done + , s_header_value_discard_lws + , s_header_value_start + , s_header_value + , s_header_value_lws + + , s_header_almost_done + + , s_chunk_size_start + , s_chunk_size + , s_chunk_parameters + , s_chunk_size_almost_done + + , s_headers_almost_done + , s_headers_done + + /* Important: 's_headers_done' must be the last 'header' state. All + * states beyond this must be 'body' states. It is used for overflow + * checking. See the PARSING_HEADER() macro. + */ + + , s_chunk_data + , s_chunk_data_almost_done + , s_chunk_data_done + + , s_body_identity + , s_body_identity_eof + + , s_message_done + }; + +enum http_host_state +{ + s_http_host_dead = 1 + , s_http_userinfo_start + , s_http_userinfo + , s_http_host_start + , s_http_host_v6_start + , s_http_host + , s_http_host_v6 + , s_http_host_v6_end + , s_http_host_v6_zone_start + , s_http_host_v6_zone + , s_http_host_port_start + , s_http_host_port +}; + +/* Macros for character classes; depends on strict-mode */ +#define CR '\r' +#define LF '\n' +#define LOWER(c) (unsigned char)(c | 0x20) +#define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z') +#define IS_NUM(c) ((c) >= '0' && (c) <= '9') +#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) +#define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f')) +#define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \ + (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \ + (c) == ')') +#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \ + (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \ + (c) == '$' || (c) == ',') + +#define STRICT_TOKEN(c) ((c == ' ') ? 0 : tokens[(unsigned char)c]) + +#if LLHTTP_STRICT_MODE +#define TOKEN(c) STRICT_TOKEN(c) +#define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c)) +#define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-') +#else +#define TOKEN(c) tokens[(unsigned char)c] +#define IS_URL_CHAR(c) \ + (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80)) +#define IS_HOST_CHAR(c) \ + (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_') +#endif + +/* Our URL parser. + * + * This is designed to be shared by http_parser_execute() for URL validation, + * hence it has a state transition + byte-for-byte interface. In addition, it + * is meant to be embedded in http_parser_parse_url(), which does the dirty + * work of turning state transitions URL components for its API. + * + * This function should only be invoked with non-space characters. It is + * assumed that the caller cares about (and can detect) the transition between + * URL and non-URL states by looking for these. + */ +static enum state +parse_url_char(enum state s, const char ch) +{ + if (ch == ' ' || ch == '\r' || ch == '\n') { + return s_dead; + } + +#if LLHTTP_STRICT_MODE + if (ch == '\t' || ch == '\f') { + return s_dead; + } +#endif + + switch (s) { + case s_req_spaces_before_url: + /* Proxied requests are followed by scheme of an absolute URI (alpha). + * All methods except CONNECT are followed by '/' or '*'. + */ + + if (ch == '/' || ch == '*') { + return s_req_path; + } + + if (IS_ALPHA(ch)) { + return s_req_schema; + } + + break; + + case s_req_schema: + if (IS_ALPHA(ch)) { + return s; + } + + if (ch == ':') { + return s_req_schema_slash; + } + + break; + + case s_req_schema_slash: + if (ch == '/') { + return s_req_schema_slash_slash; + } + + break; + + case s_req_schema_slash_slash: + if (ch == '/') { + return s_req_server_start; + } + + break; + + case s_req_server_with_at: + if (ch == '@') { + return s_dead; + } + + /* fall through */ + case s_req_server_start: + case s_req_server: + if (ch == '/') { + return s_req_path; + } + + if (ch == '?') { + return s_req_query_string_start; + } + + if (ch == '@') { + return s_req_server_with_at; + } + + if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') { + return s_req_server; + } + + break; + + case s_req_path: + if (IS_URL_CHAR(ch)) { + return s; + } + + switch (ch) { + case '?': + return s_req_query_string_start; + + case '#': + return s_req_fragment_start; + } + + break; + + case s_req_query_string_start: + case s_req_query_string: + if (IS_URL_CHAR(ch)) { + return s_req_query_string; + } + + switch (ch) { + case '?': + /* allow extra '?' in query string */ + return s_req_query_string; + + case '#': + return s_req_fragment_start; + } + + break; + + case s_req_fragment_start: + if (IS_URL_CHAR(ch)) { + return s_req_fragment; + } + + switch (ch) { + case '?': + return s_req_fragment; + + case '#': + return s; + } + + break; + + case s_req_fragment: + if (IS_URL_CHAR(ch)) { + return s; + } + + switch (ch) { + case '?': + case '#': + return s; + } + + break; + + default: + break; + } + + /* We should never fall out of the switch above unless there's an error */ + return s_dead; +} + +static enum http_host_state +http_parse_host_char(enum http_host_state s, const char ch) { + switch(s) { + case s_http_userinfo: + case s_http_userinfo_start: + if (ch == '@') { + return s_http_host_start; + } + + if (IS_USERINFO_CHAR(ch)) { + return s_http_userinfo; + } + break; + + case s_http_host_start: + if (ch == '[') { + return s_http_host_v6_start; + } + + if (IS_HOST_CHAR(ch)) { + return s_http_host; + } + + break; + + case s_http_host: + if (IS_HOST_CHAR(ch)) { + return s_http_host; + } + + /* fall through */ + case s_http_host_v6_end: + if (ch == ':') { + return s_http_host_port_start; + } + + break; + + case s_http_host_v6: + if (ch == ']') { + return s_http_host_v6_end; + } + + /* fall through */ + case s_http_host_v6_start: + if (IS_HEX(ch) || ch == ':' || ch == '.') { + return s_http_host_v6; + } + + if (s == s_http_host_v6 && ch == '%') { + return s_http_host_v6_zone_start; + } + break; + + case s_http_host_v6_zone: + if (ch == ']') { + return s_http_host_v6_end; + } + + /* fall through */ + case s_http_host_v6_zone_start: + /* RFC 6874 Zone ID consists of 1*( unreserved / pct-encoded) */ + if (IS_ALPHANUM(ch) || ch == '%' || ch == '.' || ch == '-' || ch == '_' || + ch == '~') { + return s_http_host_v6_zone; + } + break; + + case s_http_host_port: + case s_http_host_port_start: + if (IS_NUM(ch)) { + return s_http_host_port; + } + + break; + + default: + break; + } + return s_http_host_dead; +} + +static int +http_parse_host(const char * buf, struct http_parser_url *u, int found_at) { + enum http_host_state s; + + const char *p; + size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len; + + assert(u->field_set & (1 << UF_HOST)); + + u->field_data[UF_HOST].len = 0; + + s = found_at ? s_http_userinfo_start : s_http_host_start; + + for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) { + enum http_host_state new_s = http_parse_host_char(s, *p); + + if (new_s == s_http_host_dead) { + return 1; + } + + switch(new_s) { + case s_http_host: + if (s != s_http_host) { + u->field_data[UF_HOST].off = (uint16_t)(p - buf); + } + u->field_data[UF_HOST].len++; + break; + + case s_http_host_v6: + if (s != s_http_host_v6) { + u->field_data[UF_HOST].off = (uint16_t)(p - buf); + } + u->field_data[UF_HOST].len++; + break; + + case s_http_host_v6_zone_start: + case s_http_host_v6_zone: + u->field_data[UF_HOST].len++; + break; + + case s_http_host_port: + if (s != s_http_host_port) { + u->field_data[UF_PORT].off = (uint16_t)(p - buf); + u->field_data[UF_PORT].len = 0; + u->field_set |= (1 << UF_PORT); + } + u->field_data[UF_PORT].len++; + break; + + case s_http_userinfo: + if (s != s_http_userinfo) { + u->field_data[UF_USERINFO].off = (uint16_t)(p - buf); + u->field_data[UF_USERINFO].len = 0; + u->field_set |= (1 << UF_USERINFO); + } + u->field_data[UF_USERINFO].len++; + break; + + default: + break; + } + s = new_s; + } + + /* Make sure we don't end somewhere unexpected */ + switch (s) { + case s_http_host_start: + case s_http_host_v6_start: + case s_http_host_v6: + case s_http_host_v6_zone_start: + case s_http_host_v6_zone: + case s_http_host_port_start: + case s_http_userinfo: + case s_http_userinfo_start: + return 1; + default: + break; + } + + return 0; +} + +void +http_parser_url_init(struct http_parser_url *u) { + memset(u, 0, sizeof(*u)); +} + +int +http_parser_parse_url(const char *buf, size_t buflen, int is_connect, +struct http_parser_url *u) +{ + enum state s; + const char *p; + enum http_parser_url_fields uf, old_uf; + int found_at = 0; + + if (buflen == 0) { + return 1; + } + + u->port = u->field_set = 0; + s = is_connect ? s_req_server_start : s_req_spaces_before_url; + old_uf = UF_MAX; + + for (p = buf; p < buf + buflen; p++) { + s = parse_url_char(s, *p); + + /* Figure out the next field that we're operating on */ + switch (s) { +case s_dead: + return 1; + + /* Skip delimeters */ +case s_req_schema_slash: +case s_req_schema_slash_slash: +case s_req_server_start: +case s_req_query_string_start: +case s_req_fragment_start: + continue; + +case s_req_schema: + uf = UF_SCHEMA; + break; + +case s_req_server_with_at: + found_at = 1; + + /* fall through */ +case s_req_server: + uf = UF_HOST; + break; + +case s_req_path: + uf = UF_PATH; + break; + +case s_req_query_string: + uf = UF_QUERY; + break; + +case s_req_fragment: + uf = UF_FRAGMENT; + break; + +default: + assert(!"Unexpected state"); + return 1; + } + + /* Nothing's changed; soldier on */ + if (uf == old_uf) { + u->field_data[uf].len++; + continue; + } + + u->field_data[uf].off = (uint16_t)(p - buf); + u->field_data[uf].len = 1; + + u->field_set |= (1 << uf); + old_uf = uf; + } + + /* host must be present if there is a schema */ + /* parsing http:///toto will fail */ + if ((u->field_set & (1 << UF_SCHEMA)) && + (u->field_set & (1 << UF_HOST)) == 0) { + return 1; + } + + if (u->field_set & (1 << UF_HOST)) { + if (http_parse_host(buf, u, found_at) != 0) { + return 1; + } + } + + /* CONNECT requests can only contain "hostname:port" */ + if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) { + return 1; + } + + if (u->field_set & (1 << UF_PORT)) { + uint16_t off; + uint16_t len; + const char* p; + const char* end; + unsigned long v; + + off = u->field_data[UF_PORT].off; + len = u->field_data[UF_PORT].len; + end = buf + off + len; + + /* NOTE: The characters are already validated and are in the [0-9] range */ + assert((size_t)(off + len) <= buflen && "Port number overflow"); + v = 0; + for (p = buf + off; p < end; p++) { + v *= 10; + v += *p - '0'; + + /* Ports have a max value of 2^16 */ + if (v > 0xffff) { + return 1; + } + } + + u->port = (uint16_t) v; + } + + return 0; +} + +#ifdef __GNUC__ + #pragma GCC diagnostic pop +#endif diff --git a/common/http/llhttp_url.h b/common/http/llhttp_url.h new file mode 100644 index 0000000..146633f --- /dev/null +++ b/common/http/llhttp_url.h @@ -0,0 +1,60 @@ +#ifndef INCLUDE_LLHTTP_URL_H_ +#define INCLUDE_LLHTTP_URL_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +// copy code from http_parser + +/* Compile with -DLLHTTP_STRICT_MODE=0 to make less checks, but run + * faster + */ +#ifndef LLHTTP_STRICT_MODE +# define LLHTTP_STRICT_MODE 1 +#endif + +enum http_parser_url_fields + { UF_SCHEMA = 0 + , UF_HOST = 1 + , UF_PORT = 2 + , UF_PATH = 3 + , UF_QUERY = 4 + , UF_FRAGMENT = 5 + , UF_USERINFO = 6 + , UF_MAX = 7 + }; + + +/* Result structure for http_parser_parse_url(). + * + * Callers should index into field_data[] with UF_* values iff field_set + * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and + * because we probably have padding left over), we convert any port to + * a uint16_t. + */ +struct http_parser_url { + uint16_t field_set; /* Bitmask of (1 << UF_*) values */ + uint16_t port; /* Converted UF_PORT string */ + + struct { + uint16_t off; /* Offset into buffer in which field starts */ + uint16_t len; /* Length of run in buffer */ + } field_data[UF_MAX]; +}; + +/* Initialize all http_parser_url members to 0 */ +void http_parser_url_init(struct http_parser_url *u); + +/* Parse a URL; return nonzero on failure */ +int http_parser_parse_url(const char *buf, size_t buflen, + int is_connect, struct http_parser_url *u); + +#ifdef __cplusplus +} +#endif + +#endif // INCLUDE_LLHTTP_URL_H_ \ No newline at end of file diff --git a/common/kcp/Readme.txt b/common/kcp/Readme.txt new file mode 100644 index 0000000..01d8c7c --- /dev/null +++ b/common/kcp/Readme.txt @@ -0,0 +1,4 @@ +Modifications +-------------------- +1. kcp.c ignore warning: "-Wconversion", "-Wsign-conversion" +2. kcp.c ikcp_input(): "if (conv != kcp->conv) return -1;" -> "conv = kcp->conv;" \ No newline at end of file diff --git a/common/kcp/ikcp.c b/common/kcp/ikcp.c new file mode 100644 index 0000000..7d54f0e --- /dev/null +++ b/common/kcp/ikcp.c @@ -0,0 +1,1307 @@ +//===================================================================== +// +// KCP - A Better ARQ Protocol Implementation +// skywind3000 (at) gmail.com, 2010-2011 +// +// Features: +// + Average RTT reduce 30% - 40% vs traditional ARQ like tcp. +// + Maximum RTT reduce three times vs tcp. +// + Lightweight, distributed as a single source file. +// +//===================================================================== +#include "ikcp.h" + +#include +#include +#include +#include +#include + +#ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wconversion" + #pragma GCC diagnostic ignored "-Wsign-conversion" +#endif + +//===================================================================== +// KCP BASIC +//===================================================================== +const IUINT32 IKCP_RTO_NDL = 30; // no delay min rto +const IUINT32 IKCP_RTO_MIN = 100; // normal min rto +const IUINT32 IKCP_RTO_DEF = 200; +const IUINT32 IKCP_RTO_MAX = 60000; +const IUINT32 IKCP_CMD_PUSH = 81; // cmd: push data +const IUINT32 IKCP_CMD_ACK = 82; // cmd: ack +const IUINT32 IKCP_CMD_WASK = 83; // cmd: window probe (ask) +const IUINT32 IKCP_CMD_WINS = 84; // cmd: window size (tell) +const IUINT32 IKCP_ASK_SEND = 1; // need to send IKCP_CMD_WASK +const IUINT32 IKCP_ASK_TELL = 2; // need to send IKCP_CMD_WINS +const IUINT32 IKCP_WND_SND = 32; +const IUINT32 IKCP_WND_RCV = 128; // must >= max fragment size +const IUINT32 IKCP_MTU_DEF = 1400; +const IUINT32 IKCP_ACK_FAST = 3; +const IUINT32 IKCP_INTERVAL = 100; +const IUINT32 IKCP_OVERHEAD = 24; +const IUINT32 IKCP_DEADLINK = 20; +const IUINT32 IKCP_THRESH_INIT = 2; +const IUINT32 IKCP_THRESH_MIN = 2; +const IUINT32 IKCP_PROBE_INIT = 7000; // 7 secs to probe window size +const IUINT32 IKCP_PROBE_LIMIT = 120000; // up to 120 secs to probe window +const IUINT32 IKCP_FASTACK_LIMIT = 5; // max times to trigger fastack + + +//--------------------------------------------------------------------- +// encode / decode +//--------------------------------------------------------------------- + +/* encode 8 bits unsigned int */ +static inline char *ikcp_encode8u(char *p, unsigned char c) +{ + *(unsigned char*)p++ = c; + return p; +} + +/* decode 8 bits unsigned int */ +static inline const char *ikcp_decode8u(const char *p, unsigned char *c) +{ + *c = *(unsigned char*)p++; + return p; +} + +/* encode 16 bits unsigned int (lsb) */ +static inline char *ikcp_encode16u(char *p, unsigned short w) +{ +#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN + *(unsigned char*)(p + 0) = (w & 255); + *(unsigned char*)(p + 1) = (w >> 8); +#else + *(unsigned short*)(p) = w; +#endif + p += 2; + return p; +} + +/* decode 16 bits unsigned int (lsb) */ +static inline const char *ikcp_decode16u(const char *p, unsigned short *w) +{ +#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN + *w = *(const unsigned char*)(p + 1); + *w = *(const unsigned char*)(p + 0) + (*w << 8); +#else + *w = *(const unsigned short*)p; +#endif + p += 2; + return p; +} + +/* encode 32 bits unsigned int (lsb) */ +static inline char *ikcp_encode32u(char *p, IUINT32 l) +{ +#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN + *(unsigned char*)(p + 0) = (unsigned char)((l >> 0) & 0xff); + *(unsigned char*)(p + 1) = (unsigned char)((l >> 8) & 0xff); + *(unsigned char*)(p + 2) = (unsigned char)((l >> 16) & 0xff); + *(unsigned char*)(p + 3) = (unsigned char)((l >> 24) & 0xff); +#else + *(IUINT32*)p = l; +#endif + p += 4; + return p; +} + +/* decode 32 bits unsigned int (lsb) */ +static inline const char *ikcp_decode32u(const char *p, IUINT32 *l) +{ +#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN + *l = *(const unsigned char*)(p + 3); + *l = *(const unsigned char*)(p + 2) + (*l << 8); + *l = *(const unsigned char*)(p + 1) + (*l << 8); + *l = *(const unsigned char*)(p + 0) + (*l << 8); +#else + *l = *(const IUINT32*)p; +#endif + p += 4; + return p; +} + +static inline IUINT32 _imin_(IUINT32 a, IUINT32 b) { + return a <= b ? a : b; +} + +static inline IUINT32 _imax_(IUINT32 a, IUINT32 b) { + return a >= b ? a : b; +} + +static inline IUINT32 _ibound_(IUINT32 lower, IUINT32 middle, IUINT32 upper) +{ + return _imin_(_imax_(lower, middle), upper); +} + +static inline long _itimediff(IUINT32 later, IUINT32 earlier) +{ + return ((IINT32)(later - earlier)); +} + +//--------------------------------------------------------------------- +// manage segment +//--------------------------------------------------------------------- +typedef struct IKCPSEG IKCPSEG; + +static void* (*ikcp_malloc_hook)(size_t) = NULL; +static void (*ikcp_free_hook)(void *) = NULL; + +// internal malloc +static void* ikcp_malloc(size_t size) { + if (ikcp_malloc_hook) + return ikcp_malloc_hook(size); + return malloc(size); +} + +// internal free +static void ikcp_free(void *ptr) { + if (ikcp_free_hook) { + ikcp_free_hook(ptr); + } else { + free(ptr); + } +} + +// redefine allocator +void ikcp_allocator(void* (*new_malloc)(size_t), void (*new_free)(void*)) +{ + ikcp_malloc_hook = new_malloc; + ikcp_free_hook = new_free; +} + +// allocate a new kcp segment +static IKCPSEG* ikcp_segment_new(ikcpcb *kcp, int size) +{ + return (IKCPSEG*)ikcp_malloc(sizeof(IKCPSEG) + size); +} + +// delete a segment +static void ikcp_segment_delete(ikcpcb *kcp, IKCPSEG *seg) +{ + ikcp_free(seg); +} + +// write log +void ikcp_log(ikcpcb *kcp, int mask, const char *fmt, ...) +{ + char buffer[1024]; + va_list argptr; + if ((mask & kcp->logmask) == 0 || kcp->writelog == 0) return; + va_start(argptr, fmt); + vsprintf(buffer, fmt, argptr); + va_end(argptr); + kcp->writelog(buffer, kcp, kcp->user); +} + +// check log mask +static int ikcp_canlog(const ikcpcb *kcp, int mask) +{ + if ((mask & kcp->logmask) == 0 || kcp->writelog == NULL) return 0; + return 1; +} + +// output segment +static int ikcp_output(ikcpcb *kcp, const void *data, int size) +{ + assert(kcp); + assert(kcp->output); + if (ikcp_canlog(kcp, IKCP_LOG_OUTPUT)) { + ikcp_log(kcp, IKCP_LOG_OUTPUT, "[RO] %ld bytes", (long)size); + } + if (size == 0) return 0; + return kcp->output((const char*)data, size, kcp, kcp->user); +} + +// output queue +void ikcp_qprint(const char *name, const struct IQUEUEHEAD *head) +{ +#if 0 + const struct IQUEUEHEAD *p; + printf("<%s>: [", name); + for (p = head->next; p != head; p = p->next) { + const IKCPSEG *seg = iqueue_entry(p, const IKCPSEG, node); + printf("(%lu %d)", (unsigned long)seg->sn, (int)(seg->ts % 10000)); + if (p->next != head) printf(","); + } + printf("]\n"); +#endif +} + + +//--------------------------------------------------------------------- +// create a new kcpcb +//--------------------------------------------------------------------- +ikcpcb* ikcp_create(IUINT32 conv, void *user) +{ + ikcpcb *kcp = (ikcpcb*)ikcp_malloc(sizeof(struct IKCPCB)); + if (kcp == NULL) return NULL; + kcp->conv = conv; + kcp->user = user; + kcp->snd_una = 0; + kcp->snd_nxt = 0; + kcp->rcv_nxt = 0; + kcp->ts_recent = 0; + kcp->ts_lastack = 0; + kcp->ts_probe = 0; + kcp->probe_wait = 0; + kcp->snd_wnd = IKCP_WND_SND; + kcp->rcv_wnd = IKCP_WND_RCV; + kcp->rmt_wnd = IKCP_WND_RCV; + kcp->cwnd = 0; + kcp->incr = 0; + kcp->probe = 0; + kcp->mtu = IKCP_MTU_DEF; + kcp->mss = kcp->mtu - IKCP_OVERHEAD; + kcp->stream = 0; + + kcp->buffer = (char*)ikcp_malloc((kcp->mtu + IKCP_OVERHEAD) * 3); + if (kcp->buffer == NULL) { + ikcp_free(kcp); + return NULL; + } + + iqueue_init(&kcp->snd_queue); + iqueue_init(&kcp->rcv_queue); + iqueue_init(&kcp->snd_buf); + iqueue_init(&kcp->rcv_buf); + kcp->nrcv_buf = 0; + kcp->nsnd_buf = 0; + kcp->nrcv_que = 0; + kcp->nsnd_que = 0; + kcp->state = 0; + kcp->acklist = NULL; + kcp->ackblock = 0; + kcp->ackcount = 0; + kcp->rx_srtt = 0; + kcp->rx_rttval = 0; + kcp->rx_rto = IKCP_RTO_DEF; + kcp->rx_minrto = IKCP_RTO_MIN; + kcp->current = 0; + kcp->interval = IKCP_INTERVAL; + kcp->ts_flush = IKCP_INTERVAL; + kcp->nodelay = 0; + kcp->updated = 0; + kcp->logmask = 0; + kcp->ssthresh = IKCP_THRESH_INIT; + kcp->fastresend = 0; + kcp->fastlimit = IKCP_FASTACK_LIMIT; + kcp->nocwnd = 0; + kcp->xmit = 0; + kcp->dead_link = IKCP_DEADLINK; + kcp->output = NULL; + kcp->writelog = NULL; + + return kcp; +} + + +//--------------------------------------------------------------------- +// release a new kcpcb +//--------------------------------------------------------------------- +void ikcp_release(ikcpcb *kcp) +{ + assert(kcp); + if (kcp) { + IKCPSEG *seg; + while (!iqueue_is_empty(&kcp->snd_buf)) { + seg = iqueue_entry(kcp->snd_buf.next, IKCPSEG, node); + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + } + while (!iqueue_is_empty(&kcp->rcv_buf)) { + seg = iqueue_entry(kcp->rcv_buf.next, IKCPSEG, node); + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + } + while (!iqueue_is_empty(&kcp->snd_queue)) { + seg = iqueue_entry(kcp->snd_queue.next, IKCPSEG, node); + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + } + while (!iqueue_is_empty(&kcp->rcv_queue)) { + seg = iqueue_entry(kcp->rcv_queue.next, IKCPSEG, node); + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + } + if (kcp->buffer) { + ikcp_free(kcp->buffer); + } + if (kcp->acklist) { + ikcp_free(kcp->acklist); + } + + kcp->nrcv_buf = 0; + kcp->nsnd_buf = 0; + kcp->nrcv_que = 0; + kcp->nsnd_que = 0; + kcp->ackcount = 0; + kcp->buffer = NULL; + kcp->acklist = NULL; + ikcp_free(kcp); + } +} + + +//--------------------------------------------------------------------- +// set output callback, which will be invoked by kcp +//--------------------------------------------------------------------- +void ikcp_setoutput(ikcpcb *kcp, int (*output)(const char *buf, int len, + ikcpcb *kcp, void *user)) +{ + kcp->output = output; +} + + +//--------------------------------------------------------------------- +// user/upper level recv: returns size, returns below zero for EAGAIN +//--------------------------------------------------------------------- +int ikcp_recv(ikcpcb *kcp, char *buffer, int len) +{ + struct IQUEUEHEAD *p; + int ispeek = (len < 0)? 1 : 0; + int peeksize; + int recover = 0; + IKCPSEG *seg; + assert(kcp); + + if (iqueue_is_empty(&kcp->rcv_queue)) + return -1; + + if (len < 0) len = -len; + + peeksize = ikcp_peeksize(kcp); + + if (peeksize < 0) + return -2; + + if (peeksize > len) + return -3; + + if (kcp->nrcv_que >= kcp->rcv_wnd) + recover = 1; + + // merge fragment + for (len = 0, p = kcp->rcv_queue.next; p != &kcp->rcv_queue; ) { + int fragment; + seg = iqueue_entry(p, IKCPSEG, node); + p = p->next; + + if (buffer) { + memcpy(buffer, seg->data, seg->len); + buffer += seg->len; + } + + len += seg->len; + fragment = seg->frg; + + if (ikcp_canlog(kcp, IKCP_LOG_RECV)) { + ikcp_log(kcp, IKCP_LOG_RECV, "recv sn=%lu", (unsigned long)seg->sn); + } + + if (ispeek == 0) { + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + kcp->nrcv_que--; + } + + if (fragment == 0) + break; + } + + assert(len == peeksize); + + // move available data from rcv_buf -> rcv_queue + while (! iqueue_is_empty(&kcp->rcv_buf)) { + seg = iqueue_entry(kcp->rcv_buf.next, IKCPSEG, node); + if (seg->sn == kcp->rcv_nxt && kcp->nrcv_que < kcp->rcv_wnd) { + iqueue_del(&seg->node); + kcp->nrcv_buf--; + iqueue_add_tail(&seg->node, &kcp->rcv_queue); + kcp->nrcv_que++; + kcp->rcv_nxt++; + } else { + break; + } + } + + // fast recover + if (kcp->nrcv_que < kcp->rcv_wnd && recover) { + // ready to send back IKCP_CMD_WINS in ikcp_flush + // tell remote my window size + kcp->probe |= IKCP_ASK_TELL; + } + + return len; +} + + +//--------------------------------------------------------------------- +// peek data size +//--------------------------------------------------------------------- +int ikcp_peeksize(const ikcpcb *kcp) +{ + struct IQUEUEHEAD *p; + IKCPSEG *seg; + int length = 0; + + assert(kcp); + + if (iqueue_is_empty(&kcp->rcv_queue)) return -1; + + seg = iqueue_entry(kcp->rcv_queue.next, IKCPSEG, node); + if (seg->frg == 0) return seg->len; + + if (kcp->nrcv_que < seg->frg + 1) return -1; + + for (p = kcp->rcv_queue.next; p != &kcp->rcv_queue; p = p->next) { + seg = iqueue_entry(p, IKCPSEG, node); + length += seg->len; + if (seg->frg == 0) break; + } + + return length; +} + + +//--------------------------------------------------------------------- +// user/upper level send, returns below zero for error +//--------------------------------------------------------------------- +int ikcp_send(ikcpcb *kcp, const char *buffer, int len) +{ + IKCPSEG *seg; + int count, i; + + assert(kcp->mss > 0); + if (len < 0) return -1; + + // append to previous segment in streaming mode (if possible) + if (kcp->stream != 0) { + if (!iqueue_is_empty(&kcp->snd_queue)) { + IKCPSEG *old = iqueue_entry(kcp->snd_queue.prev, IKCPSEG, node); + if (old->len < kcp->mss) { + int capacity = kcp->mss - old->len; + int extend = (len < capacity)? len : capacity; + seg = ikcp_segment_new(kcp, old->len + extend); + assert(seg); + if (seg == NULL) { + return -2; + } + iqueue_add_tail(&seg->node, &kcp->snd_queue); + memcpy(seg->data, old->data, old->len); + if (buffer) { + memcpy(seg->data + old->len, buffer, extend); + buffer += extend; + } + seg->len = old->len + extend; + seg->frg = 0; + len -= extend; + iqueue_del_init(&old->node); + ikcp_segment_delete(kcp, old); + } + } + if (len <= 0) { + return 0; + } + } + + if (len <= (int)kcp->mss) count = 1; + else count = (len + kcp->mss - 1) / kcp->mss; + + if (count >= (int)IKCP_WND_RCV) return -2; + + if (count == 0) count = 1; + + // fragment + for (i = 0; i < count; i++) { + int size = len > (int)kcp->mss ? (int)kcp->mss : len; + seg = ikcp_segment_new(kcp, size); + assert(seg); + if (seg == NULL) { + return -2; + } + if (buffer && len > 0) { + memcpy(seg->data, buffer, size); + } + seg->len = size; + seg->frg = (kcp->stream == 0)? (count - i - 1) : 0; + iqueue_init(&seg->node); + iqueue_add_tail(&seg->node, &kcp->snd_queue); + kcp->nsnd_que++; + if (buffer) { + buffer += size; + } + len -= size; + } + + return 0; +} + + +//--------------------------------------------------------------------- +// parse ack +//--------------------------------------------------------------------- +static void ikcp_update_ack(ikcpcb *kcp, IINT32 rtt) +{ + IINT32 rto = 0; + if (kcp->rx_srtt == 0) { + kcp->rx_srtt = rtt; + kcp->rx_rttval = rtt / 2; + } else { + long delta = rtt - kcp->rx_srtt; + if (delta < 0) delta = -delta; + kcp->rx_rttval = (3 * kcp->rx_rttval + delta) / 4; + kcp->rx_srtt = (7 * kcp->rx_srtt + rtt) / 8; + if (kcp->rx_srtt < 1) kcp->rx_srtt = 1; + } + rto = kcp->rx_srtt + _imax_(kcp->interval, 4 * kcp->rx_rttval); + kcp->rx_rto = _ibound_(kcp->rx_minrto, rto, IKCP_RTO_MAX); +} + +static void ikcp_shrink_buf(ikcpcb *kcp) +{ + struct IQUEUEHEAD *p = kcp->snd_buf.next; + if (p != &kcp->snd_buf) { + IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); + kcp->snd_una = seg->sn; + } else { + kcp->snd_una = kcp->snd_nxt; + } +} + +static void ikcp_parse_ack(ikcpcb *kcp, IUINT32 sn) +{ + struct IQUEUEHEAD *p, *next; + + if (_itimediff(sn, kcp->snd_una) < 0 || _itimediff(sn, kcp->snd_nxt) >= 0) + return; + + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = next) { + IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); + next = p->next; + if (sn == seg->sn) { + iqueue_del(p); + ikcp_segment_delete(kcp, seg); + kcp->nsnd_buf--; + break; + } + if (_itimediff(sn, seg->sn) < 0) { + break; + } + } +} + +static void ikcp_parse_una(ikcpcb *kcp, IUINT32 una) +{ + struct IQUEUEHEAD *p, *next; + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = next) { + IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); + next = p->next; + if (_itimediff(una, seg->sn) > 0) { + iqueue_del(p); + ikcp_segment_delete(kcp, seg); + kcp->nsnd_buf--; + } else { + break; + } + } +} + +static void ikcp_parse_fastack(ikcpcb *kcp, IUINT32 sn, IUINT32 ts) +{ + struct IQUEUEHEAD *p, *next; + + if (_itimediff(sn, kcp->snd_una) < 0 || _itimediff(sn, kcp->snd_nxt) >= 0) + return; + + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = next) { + IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); + next = p->next; + if (_itimediff(sn, seg->sn) < 0) { + break; + } + else if (sn != seg->sn) { + #ifndef IKCP_FASTACK_CONSERVE + seg->fastack++; + #else + if (_itimediff(ts, seg->ts) >= 0) + seg->fastack++; + #endif + } + } +} + + +//--------------------------------------------------------------------- +// ack append +//--------------------------------------------------------------------- +static void ikcp_ack_push(ikcpcb *kcp, IUINT32 sn, IUINT32 ts) +{ + IUINT32 newsize = kcp->ackcount + 1; + IUINT32 *ptr; + + if (newsize > kcp->ackblock) { + IUINT32 *acklist; + IUINT32 newblock; + + for (newblock = 8; newblock < newsize; newblock <<= 1); + acklist = (IUINT32*)ikcp_malloc(newblock * sizeof(IUINT32) * 2); + + if (acklist == NULL) { + assert(acklist != NULL); + abort(); + } + + if (kcp->acklist != NULL) { + IUINT32 x; + for (x = 0; x < kcp->ackcount; x++) { + acklist[x * 2 + 0] = kcp->acklist[x * 2 + 0]; + acklist[x * 2 + 1] = kcp->acklist[x * 2 + 1]; + } + ikcp_free(kcp->acklist); + } + + kcp->acklist = acklist; + kcp->ackblock = newblock; + } + + ptr = &kcp->acklist[kcp->ackcount * 2]; + ptr[0] = sn; + ptr[1] = ts; + kcp->ackcount++; +} + +static void ikcp_ack_get(const ikcpcb *kcp, int p, IUINT32 *sn, IUINT32 *ts) +{ + if (sn) sn[0] = kcp->acklist[p * 2 + 0]; + if (ts) ts[0] = kcp->acklist[p * 2 + 1]; +} + + +//--------------------------------------------------------------------- +// parse data +//--------------------------------------------------------------------- +void ikcp_parse_data(ikcpcb *kcp, IKCPSEG *newseg) +{ + struct IQUEUEHEAD *p, *prev; + IUINT32 sn = newseg->sn; + int repeat = 0; + + if (_itimediff(sn, kcp->rcv_nxt + kcp->rcv_wnd) >= 0 || + _itimediff(sn, kcp->rcv_nxt) < 0) { + ikcp_segment_delete(kcp, newseg); + return; + } + + for (p = kcp->rcv_buf.prev; p != &kcp->rcv_buf; p = prev) { + IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); + prev = p->prev; + if (seg->sn == sn) { + repeat = 1; + break; + } + if (_itimediff(sn, seg->sn) > 0) { + break; + } + } + + if (repeat == 0) { + iqueue_init(&newseg->node); + iqueue_add(&newseg->node, p); + kcp->nrcv_buf++; + } else { + ikcp_segment_delete(kcp, newseg); + } + +#if 0 + ikcp_qprint("rcvbuf", &kcp->rcv_buf); + printf("rcv_nxt=%lu\n", kcp->rcv_nxt); +#endif + + // move available data from rcv_buf -> rcv_queue + while (! iqueue_is_empty(&kcp->rcv_buf)) { + IKCPSEG *seg = iqueue_entry(kcp->rcv_buf.next, IKCPSEG, node); + if (seg->sn == kcp->rcv_nxt && kcp->nrcv_que < kcp->rcv_wnd) { + iqueue_del(&seg->node); + kcp->nrcv_buf--; + iqueue_add_tail(&seg->node, &kcp->rcv_queue); + kcp->nrcv_que++; + kcp->rcv_nxt++; + } else { + break; + } + } + +#if 0 + ikcp_qprint("queue", &kcp->rcv_queue); + printf("rcv_nxt=%lu\n", kcp->rcv_nxt); +#endif + +#if 1 +// printf("snd(buf=%d, queue=%d)\n", kcp->nsnd_buf, kcp->nsnd_que); +// printf("rcv(buf=%d, queue=%d)\n", kcp->nrcv_buf, kcp->nrcv_que); +#endif +} + + +//--------------------------------------------------------------------- +// input data +//--------------------------------------------------------------------- +int ikcp_input(ikcpcb *kcp, const char *data, long size) +{ + IUINT32 prev_una = kcp->snd_una; + IUINT32 maxack = 0, latest_ts = 0; + int flag = 0; + + if (ikcp_canlog(kcp, IKCP_LOG_INPUT)) { + ikcp_log(kcp, IKCP_LOG_INPUT, "[RI] %d bytes", (int)size); + } + + if (data == NULL || (int)size < (int)IKCP_OVERHEAD) return -1; + + while (1) { + IUINT32 ts, sn, len, una, conv; + IUINT16 wnd; + IUINT8 cmd, frg; + IKCPSEG *seg; + + if (size < (int)IKCP_OVERHEAD) break; + + data = ikcp_decode32u(data, &conv); + // !!! modify !!! + // if (conv != kcp->conv) return -1; + conv = kcp->conv; + + data = ikcp_decode8u(data, &cmd); + data = ikcp_decode8u(data, &frg); + data = ikcp_decode16u(data, &wnd); + data = ikcp_decode32u(data, &ts); + data = ikcp_decode32u(data, &sn); + data = ikcp_decode32u(data, &una); + data = ikcp_decode32u(data, &len); + + size -= IKCP_OVERHEAD; + + if ((long)size < (long)len || (int)len < 0) return -2; + + if (cmd != IKCP_CMD_PUSH && cmd != IKCP_CMD_ACK && + cmd != IKCP_CMD_WASK && cmd != IKCP_CMD_WINS) + return -3; + + kcp->rmt_wnd = wnd; + ikcp_parse_una(kcp, una); + ikcp_shrink_buf(kcp); + + if (cmd == IKCP_CMD_ACK) { + if (_itimediff(kcp->current, ts) >= 0) { + ikcp_update_ack(kcp, _itimediff(kcp->current, ts)); + } + ikcp_parse_ack(kcp, sn); + ikcp_shrink_buf(kcp); + if (flag == 0) { + flag = 1; + maxack = sn; + latest_ts = ts; + } else { + if (_itimediff(sn, maxack) > 0) { + #ifndef IKCP_FASTACK_CONSERVE + maxack = sn; + latest_ts = ts; + #else + if (_itimediff(ts, latest_ts) > 0) { + maxack = sn; + latest_ts = ts; + } + #endif + } + } + if (ikcp_canlog(kcp, IKCP_LOG_IN_ACK)) { + ikcp_log(kcp, IKCP_LOG_IN_ACK, + "input ack: sn=%lu rtt=%ld rto=%ld", (unsigned long)sn, + (long)_itimediff(kcp->current, ts), + (long)kcp->rx_rto); + } + } + else if (cmd == IKCP_CMD_PUSH) { + if (ikcp_canlog(kcp, IKCP_LOG_IN_DATA)) { + ikcp_log(kcp, IKCP_LOG_IN_DATA, + "input psh: sn=%lu ts=%lu", (unsigned long)sn, (unsigned long)ts); + } + if (_itimediff(sn, kcp->rcv_nxt + kcp->rcv_wnd) < 0) { + ikcp_ack_push(kcp, sn, ts); + if (_itimediff(sn, kcp->rcv_nxt) >= 0) { + seg = ikcp_segment_new(kcp, len); + seg->conv = conv; + seg->cmd = cmd; + seg->frg = frg; + seg->wnd = wnd; + seg->ts = ts; + seg->sn = sn; + seg->una = una; + seg->len = len; + + if (len > 0) { + memcpy(seg->data, data, len); + } + + ikcp_parse_data(kcp, seg); + } + } + } + else if (cmd == IKCP_CMD_WASK) { + // ready to send back IKCP_CMD_WINS in ikcp_flush + // tell remote my window size + kcp->probe |= IKCP_ASK_TELL; + if (ikcp_canlog(kcp, IKCP_LOG_IN_PROBE)) { + ikcp_log(kcp, IKCP_LOG_IN_PROBE, "input probe"); + } + } + else if (cmd == IKCP_CMD_WINS) { + // do nothing + if (ikcp_canlog(kcp, IKCP_LOG_IN_WINS)) { + ikcp_log(kcp, IKCP_LOG_IN_WINS, + "input wins: %lu", (unsigned long)(wnd)); + } + } + else { + return -3; + } + + data += len; + size -= len; + } + + if (flag != 0) { + ikcp_parse_fastack(kcp, maxack, latest_ts); + } + + if (_itimediff(kcp->snd_una, prev_una) > 0) { + if (kcp->cwnd < kcp->rmt_wnd) { + IUINT32 mss = kcp->mss; + if (kcp->cwnd < kcp->ssthresh) { + kcp->cwnd++; + kcp->incr += mss; + } else { + if (kcp->incr < mss) kcp->incr = mss; + kcp->incr += (mss * mss) / kcp->incr + (mss / 16); + if ((kcp->cwnd + 1) * mss <= kcp->incr) { + #if 1 + kcp->cwnd = (kcp->incr + mss - 1) / ((mss > 0)? mss : 1); + #else + kcp->cwnd++; + #endif + } + } + if (kcp->cwnd > kcp->rmt_wnd) { + kcp->cwnd = kcp->rmt_wnd; + kcp->incr = kcp->rmt_wnd * mss; + } + } + } + + return 0; +} + + +//--------------------------------------------------------------------- +// ikcp_encode_seg +//--------------------------------------------------------------------- +static char *ikcp_encode_seg(char *ptr, const IKCPSEG *seg) +{ + ptr = ikcp_encode32u(ptr, seg->conv); + ptr = ikcp_encode8u(ptr, (IUINT8)seg->cmd); + ptr = ikcp_encode8u(ptr, (IUINT8)seg->frg); + ptr = ikcp_encode16u(ptr, (IUINT16)seg->wnd); + ptr = ikcp_encode32u(ptr, seg->ts); + ptr = ikcp_encode32u(ptr, seg->sn); + ptr = ikcp_encode32u(ptr, seg->una); + ptr = ikcp_encode32u(ptr, seg->len); + return ptr; +} + +static int ikcp_wnd_unused(const ikcpcb *kcp) +{ + if (kcp->nrcv_que < kcp->rcv_wnd) { + return kcp->rcv_wnd - kcp->nrcv_que; + } + return 0; +} + + +//--------------------------------------------------------------------- +// ikcp_flush +//--------------------------------------------------------------------- +void ikcp_flush(ikcpcb *kcp) +{ + IUINT32 current = kcp->current; + char *buffer = kcp->buffer; + char *ptr = buffer; + int count, size, i; + IUINT32 resent, cwnd; + IUINT32 rtomin; + struct IQUEUEHEAD *p; + int change = 0; + int lost = 0; + IKCPSEG seg; + + // 'ikcp_update' haven't been called. + if (kcp->updated == 0) return; + + seg.conv = kcp->conv; + seg.cmd = IKCP_CMD_ACK; + seg.frg = 0; + seg.wnd = ikcp_wnd_unused(kcp); + seg.una = kcp->rcv_nxt; + seg.len = 0; + seg.sn = 0; + seg.ts = 0; + + // flush acknowledges + count = kcp->ackcount; + for (i = 0; i < count; i++) { + size = (int)(ptr - buffer); + if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu) { + ikcp_output(kcp, buffer, size); + ptr = buffer; + } + ikcp_ack_get(kcp, i, &seg.sn, &seg.ts); + ptr = ikcp_encode_seg(ptr, &seg); + } + + kcp->ackcount = 0; + + // probe window size (if remote window size equals zero) + if (kcp->rmt_wnd == 0) { + if (kcp->probe_wait == 0) { + kcp->probe_wait = IKCP_PROBE_INIT; + kcp->ts_probe = kcp->current + kcp->probe_wait; + } + else { + if (_itimediff(kcp->current, kcp->ts_probe) >= 0) { + if (kcp->probe_wait < IKCP_PROBE_INIT) + kcp->probe_wait = IKCP_PROBE_INIT; + kcp->probe_wait += kcp->probe_wait / 2; + if (kcp->probe_wait > IKCP_PROBE_LIMIT) + kcp->probe_wait = IKCP_PROBE_LIMIT; + kcp->ts_probe = kcp->current + kcp->probe_wait; + kcp->probe |= IKCP_ASK_SEND; + } + } + } else { + kcp->ts_probe = 0; + kcp->probe_wait = 0; + } + + // flush window probing commands + if (kcp->probe & IKCP_ASK_SEND) { + seg.cmd = IKCP_CMD_WASK; + size = (int)(ptr - buffer); + if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu) { + ikcp_output(kcp, buffer, size); + ptr = buffer; + } + ptr = ikcp_encode_seg(ptr, &seg); + } + + // flush window probing commands + if (kcp->probe & IKCP_ASK_TELL) { + seg.cmd = IKCP_CMD_WINS; + size = (int)(ptr - buffer); + if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu) { + ikcp_output(kcp, buffer, size); + ptr = buffer; + } + ptr = ikcp_encode_seg(ptr, &seg); + } + + kcp->probe = 0; + + // calculate window size + cwnd = _imin_(kcp->snd_wnd, kcp->rmt_wnd); + if (kcp->nocwnd == 0) cwnd = _imin_(kcp->cwnd, cwnd); + + // move data from snd_queue to snd_buf + while (_itimediff(kcp->snd_nxt, kcp->snd_una + cwnd) < 0) { + IKCPSEG *newseg; + if (iqueue_is_empty(&kcp->snd_queue)) break; + + newseg = iqueue_entry(kcp->snd_queue.next, IKCPSEG, node); + + iqueue_del(&newseg->node); + iqueue_add_tail(&newseg->node, &kcp->snd_buf); + kcp->nsnd_que--; + kcp->nsnd_buf++; + + newseg->conv = kcp->conv; + newseg->cmd = IKCP_CMD_PUSH; + newseg->wnd = seg.wnd; + newseg->ts = current; + newseg->sn = kcp->snd_nxt++; + newseg->una = kcp->rcv_nxt; + newseg->resendts = current; + newseg->rto = kcp->rx_rto; + newseg->fastack = 0; + newseg->xmit = 0; + } + + // calculate resent + resent = (kcp->fastresend > 0)? (IUINT32)kcp->fastresend : 0xffffffff; + rtomin = (kcp->nodelay == 0)? (kcp->rx_rto >> 3) : 0; + + // flush data segments + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = p->next) { + IKCPSEG *segment = iqueue_entry(p, IKCPSEG, node); + int needsend = 0; + if (segment->xmit == 0) { + needsend = 1; + segment->xmit++; + segment->rto = kcp->rx_rto; + segment->resendts = current + segment->rto + rtomin; + } + else if (_itimediff(current, segment->resendts) >= 0) { + needsend = 1; + segment->xmit++; + kcp->xmit++; + if (kcp->nodelay == 0) { + segment->rto += _imax_(segment->rto, (IUINT32)kcp->rx_rto); + } else { + IINT32 step = (kcp->nodelay < 2)? + ((IINT32)(segment->rto)) : kcp->rx_rto; + segment->rto += step / 2; + } + segment->resendts = current + segment->rto; + lost = 1; + } + else if (segment->fastack >= resent) { + if ((int)segment->xmit <= kcp->fastlimit || + kcp->fastlimit <= 0) { + needsend = 1; + segment->xmit++; + segment->fastack = 0; + segment->resendts = current + segment->rto; + change++; + } + } + + if (needsend) { + int need; + segment->ts = current; + segment->wnd = seg.wnd; + segment->una = kcp->rcv_nxt; + + size = (int)(ptr - buffer); + need = IKCP_OVERHEAD + segment->len; + + if (size + need > (int)kcp->mtu) { + ikcp_output(kcp, buffer, size); + ptr = buffer; + } + + ptr = ikcp_encode_seg(ptr, segment); + + if (segment->len > 0) { + memcpy(ptr, segment->data, segment->len); + ptr += segment->len; + } + + if (segment->xmit >= kcp->dead_link) { + kcp->state = (IUINT32)-1; + } + } + } + + // flash remain segments + size = (int)(ptr - buffer); + if (size > 0) { + ikcp_output(kcp, buffer, size); + } + + // update ssthresh + if (change) { + IUINT32 inflight = kcp->snd_nxt - kcp->snd_una; + kcp->ssthresh = inflight / 2; + if (kcp->ssthresh < IKCP_THRESH_MIN) + kcp->ssthresh = IKCP_THRESH_MIN; + kcp->cwnd = kcp->ssthresh + resent; + kcp->incr = kcp->cwnd * kcp->mss; + } + + if (lost) { + kcp->ssthresh = cwnd / 2; + if (kcp->ssthresh < IKCP_THRESH_MIN) + kcp->ssthresh = IKCP_THRESH_MIN; + kcp->cwnd = 1; + kcp->incr = kcp->mss; + } + + if (kcp->cwnd < 1) { + kcp->cwnd = 1; + kcp->incr = kcp->mss; + } +} + + +//--------------------------------------------------------------------- +// update state (call it repeatedly, every 10ms-100ms), or you can ask +// ikcp_check when to call it again (without ikcp_input/_send calling). +// 'current' - current timestamp in millisec. +//--------------------------------------------------------------------- +void ikcp_update(ikcpcb *kcp, IUINT32 current) +{ + IINT32 slap; + + kcp->current = current; + + if (kcp->updated == 0) { + kcp->updated = 1; + kcp->ts_flush = kcp->current; + } + + slap = _itimediff(kcp->current, kcp->ts_flush); + + if (slap >= 10000 || slap < -10000) { + kcp->ts_flush = kcp->current; + slap = 0; + } + + if (slap >= 0) { + kcp->ts_flush += kcp->interval; + if (_itimediff(kcp->current, kcp->ts_flush) >= 0) { + kcp->ts_flush = kcp->current + kcp->interval; + } + ikcp_flush(kcp); + } +} + + +//--------------------------------------------------------------------- +// Determine when should you invoke ikcp_update: +// returns when you should invoke ikcp_update in millisec, if there +// is no ikcp_input/_send calling. you can call ikcp_update in that +// time, instead of call update repeatly. +// Important to reduce unnacessary ikcp_update invoking. use it to +// schedule ikcp_update (eg. implementing an epoll-like mechanism, +// or optimize ikcp_update when handling massive kcp connections) +//--------------------------------------------------------------------- +IUINT32 ikcp_check(const ikcpcb *kcp, IUINT32 current) +{ + IUINT32 ts_flush = kcp->ts_flush; + IINT32 tm_flush = 0x7fffffff; + IINT32 tm_packet = 0x7fffffff; + IUINT32 minimal = 0; + struct IQUEUEHEAD *p; + + if (kcp->updated == 0) { + return current; + } + + if (_itimediff(current, ts_flush) >= 10000 || + _itimediff(current, ts_flush) < -10000) { + ts_flush = current; + } + + if (_itimediff(current, ts_flush) >= 0) { + return current; + } + + tm_flush = _itimediff(ts_flush, current); + + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = p->next) { + const IKCPSEG *seg = iqueue_entry(p, const IKCPSEG, node); + IINT32 diff = _itimediff(seg->resendts, current); + if (diff <= 0) { + return current; + } + if (diff < tm_packet) tm_packet = diff; + } + + minimal = (IUINT32)(tm_packet < tm_flush ? tm_packet : tm_flush); + if (minimal >= kcp->interval) minimal = kcp->interval; + + return current + minimal; +} + + + +int ikcp_setmtu(ikcpcb *kcp, int mtu) +{ + char *buffer; + if (mtu < 50 || mtu < (int)IKCP_OVERHEAD) + return -1; + buffer = (char*)ikcp_malloc((mtu + IKCP_OVERHEAD) * 3); + if (buffer == NULL) + return -2; + kcp->mtu = mtu; + kcp->mss = kcp->mtu - IKCP_OVERHEAD; + ikcp_free(kcp->buffer); + kcp->buffer = buffer; + return 0; +} + +int ikcp_interval(ikcpcb *kcp, int interval) +{ + if (interval > 5000) interval = 5000; + else if (interval < 10) interval = 10; + kcp->interval = interval; + return 0; +} + +int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc) +{ + if (nodelay >= 0) { + kcp->nodelay = nodelay; + if (nodelay) { + kcp->rx_minrto = IKCP_RTO_NDL; + } + else { + kcp->rx_minrto = IKCP_RTO_MIN; + } + } + if (interval >= 0) { + if (interval > 5000) interval = 5000; + else if (interval < 10) interval = 10; + kcp->interval = interval; + } + if (resend >= 0) { + kcp->fastresend = resend; + } + if (nc >= 0) { + kcp->nocwnd = nc; + } + return 0; +} + + +int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd) +{ + if (kcp) { + if (sndwnd > 0) { + kcp->snd_wnd = sndwnd; + } + if (rcvwnd > 0) { // must >= max fragment size + kcp->rcv_wnd = _imax_(rcvwnd, IKCP_WND_RCV); + } + } + return 0; +} + +int ikcp_waitsnd(const ikcpcb *kcp) +{ + return kcp->nsnd_buf + kcp->nsnd_que; +} + + +// read conv +IUINT32 ikcp_getconv(const void *ptr) +{ + IUINT32 conv; + ikcp_decode32u((const char*)ptr, &conv); + return conv; +} + +#ifdef __GNUC__ + #pragma GCC diagnostic pop +#endif diff --git a/common/kcp/ikcp.h b/common/kcp/ikcp.h new file mode 100644 index 0000000..b8c337f --- /dev/null +++ b/common/kcp/ikcp.h @@ -0,0 +1,416 @@ +//===================================================================== +// +// KCP - A Better ARQ Protocol Implementation +// skywind3000 (at) gmail.com, 2010-2011 +// +// Features: +// + Average RTT reduce 30% - 40% vs traditional ARQ like tcp. +// + Maximum RTT reduce three times vs tcp. +// + Lightweight, distributed as a single source file. +// +//===================================================================== +#ifndef __IKCP_H__ +#define __IKCP_H__ + +#include +#include +#include + + +//===================================================================== +// 32BIT INTEGER DEFINITION +//===================================================================== +#ifndef __INTEGER_32_BITS__ +#define __INTEGER_32_BITS__ +#if defined(_WIN64) || defined(WIN64) || defined(__amd64__) || \ + defined(__x86_64) || defined(__x86_64__) || defined(_M_IA64) || \ + defined(_M_AMD64) + typedef unsigned int ISTDUINT32; + typedef int ISTDINT32; +#elif defined(_WIN32) || defined(WIN32) || defined(__i386__) || \ + defined(__i386) || defined(_M_X86) + typedef unsigned long ISTDUINT32; + typedef long ISTDINT32; +#elif defined(__MACOS__) + typedef UInt32 ISTDUINT32; + typedef SInt32 ISTDINT32; +#elif defined(__APPLE__) && defined(__MACH__) + #include + typedef u_int32_t ISTDUINT32; + typedef int32_t ISTDINT32; +#elif defined(__BEOS__) + #include + typedef u_int32_t ISTDUINT32; + typedef int32_t ISTDINT32; +#elif (defined(_MSC_VER) || defined(__BORLANDC__)) && (!defined(__MSDOS__)) + typedef unsigned __int32 ISTDUINT32; + typedef __int32 ISTDINT32; +#elif defined(__GNUC__) + #include + typedef uint32_t ISTDUINT32; + typedef int32_t ISTDINT32; +#else + typedef unsigned long ISTDUINT32; + typedef long ISTDINT32; +#endif +#endif + + +//===================================================================== +// Integer Definition +//===================================================================== +#ifndef __IINT8_DEFINED +#define __IINT8_DEFINED +typedef char IINT8; +#endif + +#ifndef __IUINT8_DEFINED +#define __IUINT8_DEFINED +typedef unsigned char IUINT8; +#endif + +#ifndef __IUINT16_DEFINED +#define __IUINT16_DEFINED +typedef unsigned short IUINT16; +#endif + +#ifndef __IINT16_DEFINED +#define __IINT16_DEFINED +typedef short IINT16; +#endif + +#ifndef __IINT32_DEFINED +#define __IINT32_DEFINED +typedef ISTDINT32 IINT32; +#endif + +#ifndef __IUINT32_DEFINED +#define __IUINT32_DEFINED +typedef ISTDUINT32 IUINT32; +#endif + +#ifndef __IINT64_DEFINED +#define __IINT64_DEFINED +#if defined(_MSC_VER) || defined(__BORLANDC__) +typedef __int64 IINT64; +#else +typedef long long IINT64; +#endif +#endif + +#ifndef __IUINT64_DEFINED +#define __IUINT64_DEFINED +#if defined(_MSC_VER) || defined(__BORLANDC__) +typedef unsigned __int64 IUINT64; +#else +typedef unsigned long long IUINT64; +#endif +#endif + +#ifndef INLINE +#if defined(__GNUC__) + +#if (__GNUC__ > 3) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1)) +#define INLINE __inline__ __attribute__((always_inline)) +#else +#define INLINE __inline__ +#endif + +#elif (defined(_MSC_VER) || defined(__BORLANDC__) || defined(__WATCOMC__)) +#define INLINE __inline +#else +#define INLINE +#endif +#endif + +#if (!defined(__cplusplus)) && (!defined(inline)) +#define inline INLINE +#endif + + +//===================================================================== +// QUEUE DEFINITION +//===================================================================== +#ifndef __IQUEUE_DEF__ +#define __IQUEUE_DEF__ + +struct IQUEUEHEAD { + struct IQUEUEHEAD *next, *prev; +}; + +typedef struct IQUEUEHEAD iqueue_head; + + +//--------------------------------------------------------------------- +// queue init +//--------------------------------------------------------------------- +#define IQUEUE_HEAD_INIT(name) { &(name), &(name) } +#define IQUEUE_HEAD(name) \ + struct IQUEUEHEAD name = IQUEUE_HEAD_INIT(name) + +#define IQUEUE_INIT(ptr) ( \ + (ptr)->next = (ptr), (ptr)->prev = (ptr)) + +#define IOFFSETOF(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) + +#define ICONTAINEROF(ptr, type, member) ( \ + (type*)( ((char*)((type*)ptr)) - IOFFSETOF(type, member)) ) + +#define IQUEUE_ENTRY(ptr, type, member) ICONTAINEROF(ptr, type, member) + + +//--------------------------------------------------------------------- +// queue operation +//--------------------------------------------------------------------- +#define IQUEUE_ADD(node, head) ( \ + (node)->prev = (head), (node)->next = (head)->next, \ + (head)->next->prev = (node), (head)->next = (node)) + +#define IQUEUE_ADD_TAIL(node, head) ( \ + (node)->prev = (head)->prev, (node)->next = (head), \ + (head)->prev->next = (node), (head)->prev = (node)) + +#define IQUEUE_DEL_BETWEEN(p, n) ((n)->prev = (p), (p)->next = (n)) + +#define IQUEUE_DEL(entry) (\ + (entry)->next->prev = (entry)->prev, \ + (entry)->prev->next = (entry)->next, \ + (entry)->next = 0, (entry)->prev = 0) + +#define IQUEUE_DEL_INIT(entry) do { \ + IQUEUE_DEL(entry); IQUEUE_INIT(entry); } while (0) + +#define IQUEUE_IS_EMPTY(entry) ((entry) == (entry)->next) + +#define iqueue_init IQUEUE_INIT +#define iqueue_entry IQUEUE_ENTRY +#define iqueue_add IQUEUE_ADD +#define iqueue_add_tail IQUEUE_ADD_TAIL +#define iqueue_del IQUEUE_DEL +#define iqueue_del_init IQUEUE_DEL_INIT +#define iqueue_is_empty IQUEUE_IS_EMPTY + +#define IQUEUE_FOREACH(iterator, head, TYPE, MEMBER) \ + for ((iterator) = iqueue_entry((head)->next, TYPE, MEMBER); \ + &((iterator)->MEMBER) != (head); \ + (iterator) = iqueue_entry((iterator)->MEMBER.next, TYPE, MEMBER)) + +#define iqueue_foreach(iterator, head, TYPE, MEMBER) \ + IQUEUE_FOREACH(iterator, head, TYPE, MEMBER) + +#define iqueue_foreach_entry(pos, head) \ + for( (pos) = (head)->next; (pos) != (head) ; (pos) = (pos)->next ) + + +#define __iqueue_splice(list, head) do { \ + iqueue_head *first = (list)->next, *last = (list)->prev; \ + iqueue_head *at = (head)->next; \ + (first)->prev = (head), (head)->next = (first); \ + (last)->next = (at), (at)->prev = (last); } while (0) + +#define iqueue_splice(list, head) do { \ + if (!iqueue_is_empty(list)) __iqueue_splice(list, head); } while (0) + +#define iqueue_splice_init(list, head) do { \ + iqueue_splice(list, head); iqueue_init(list); } while (0) + + +#ifdef _MSC_VER +#pragma warning(disable:4311) +#pragma warning(disable:4312) +#pragma warning(disable:4996) +#endif + +#endif + + +//--------------------------------------------------------------------- +// BYTE ORDER & ALIGNMENT +//--------------------------------------------------------------------- +#ifndef IWORDS_BIG_ENDIAN + #ifdef _BIG_ENDIAN_ + #if _BIG_ENDIAN_ + #define IWORDS_BIG_ENDIAN 1 + #endif + #endif + #ifndef IWORDS_BIG_ENDIAN + #if defined(__hppa__) || \ + defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \ + (defined(__MIPS__) && defined(__MIPSEB__)) || \ + defined(__ppc__) || defined(__POWERPC__) || defined(_M_PPC) || \ + defined(__sparc__) || defined(__powerpc__) || \ + defined(__mc68000__) || defined(__s390x__) || defined(__s390__) + #define IWORDS_BIG_ENDIAN 1 + #endif + #endif + #ifndef IWORDS_BIG_ENDIAN + #define IWORDS_BIG_ENDIAN 0 + #endif +#endif + +#ifndef IWORDS_MUST_ALIGN + #if defined(__i386__) || defined(__i386) || defined(_i386_) + #define IWORDS_MUST_ALIGN 0 + #elif defined(_M_IX86) || defined(_X86_) || defined(__x86_64__) + #define IWORDS_MUST_ALIGN 0 + #elif defined(__amd64) || defined(__amd64__) + #define IWORDS_MUST_ALIGN 0 + #else + #define IWORDS_MUST_ALIGN 1 + #endif +#endif + + +//===================================================================== +// SEGMENT +//===================================================================== +struct IKCPSEG +{ + struct IQUEUEHEAD node; + IUINT32 conv; + IUINT32 cmd; + IUINT32 frg; + IUINT32 wnd; + IUINT32 ts; + IUINT32 sn; + IUINT32 una; + IUINT32 len; + IUINT32 resendts; + IUINT32 rto; + IUINT32 fastack; + IUINT32 xmit; + char data[1]; +}; + + +//--------------------------------------------------------------------- +// IKCPCB +//--------------------------------------------------------------------- +struct IKCPCB +{ + IUINT32 conv, mtu, mss, state; + IUINT32 snd_una, snd_nxt, rcv_nxt; + IUINT32 ts_recent, ts_lastack, ssthresh; + IINT32 rx_rttval, rx_srtt, rx_rto, rx_minrto; + IUINT32 snd_wnd, rcv_wnd, rmt_wnd, cwnd, probe; + IUINT32 current, interval, ts_flush, xmit; + IUINT32 nrcv_buf, nsnd_buf; + IUINT32 nrcv_que, nsnd_que; + IUINT32 nodelay, updated; + IUINT32 ts_probe, probe_wait; + IUINT32 dead_link, incr; + struct IQUEUEHEAD snd_queue; + struct IQUEUEHEAD rcv_queue; + struct IQUEUEHEAD snd_buf; + struct IQUEUEHEAD rcv_buf; + IUINT32 *acklist; + IUINT32 ackcount; + IUINT32 ackblock; + void *user; + char *buffer; + int fastresend; + int fastlimit; + int nocwnd, stream; + int logmask; + int (*output)(const char *buf, int len, struct IKCPCB *kcp, void *user); + void (*writelog)(const char *log, struct IKCPCB *kcp, void *user); +}; + + +typedef struct IKCPCB ikcpcb; + +#define IKCP_LOG_OUTPUT 1 +#define IKCP_LOG_INPUT 2 +#define IKCP_LOG_SEND 4 +#define IKCP_LOG_RECV 8 +#define IKCP_LOG_IN_DATA 16 +#define IKCP_LOG_IN_ACK 32 +#define IKCP_LOG_IN_PROBE 64 +#define IKCP_LOG_IN_WINS 128 +#define IKCP_LOG_OUT_DATA 256 +#define IKCP_LOG_OUT_ACK 512 +#define IKCP_LOG_OUT_PROBE 1024 +#define IKCP_LOG_OUT_WINS 2048 + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------- +// interface +//--------------------------------------------------------------------- + +// create a new kcp control object, 'conv' must equal in two endpoint +// from the same connection. 'user' will be passed to the output callback +// output callback can be setup like this: 'kcp->output = my_udp_output' +ikcpcb* ikcp_create(IUINT32 conv, void *user); + +// release kcp control object +void ikcp_release(ikcpcb *kcp); + +// set output callback, which will be invoked by kcp +void ikcp_setoutput(ikcpcb *kcp, int (*output)(const char *buf, int len, + ikcpcb *kcp, void *user)); + +// user/upper level recv: returns size, returns below zero for EAGAIN +int ikcp_recv(ikcpcb *kcp, char *buffer, int len); + +// user/upper level send, returns below zero for error +int ikcp_send(ikcpcb *kcp, const char *buffer, int len); + +// update state (call it repeatedly, every 10ms-100ms), or you can ask +// ikcp_check when to call it again (without ikcp_input/_send calling). +// 'current' - current timestamp in millisec. +void ikcp_update(ikcpcb *kcp, IUINT32 current); + +// Determine when should you invoke ikcp_update: +// returns when you should invoke ikcp_update in millisec, if there +// is no ikcp_input/_send calling. you can call ikcp_update in that +// time, instead of call update repeatly. +// Important to reduce unnacessary ikcp_update invoking. use it to +// schedule ikcp_update (eg. implementing an epoll-like mechanism, +// or optimize ikcp_update when handling massive kcp connections) +IUINT32 ikcp_check(const ikcpcb *kcp, IUINT32 current); + +// when you received a low level packet (eg. UDP packet), call it +int ikcp_input(ikcpcb *kcp, const char *data, long size); + +// flush pending data +void ikcp_flush(ikcpcb *kcp); + +// check the size of next message in the recv queue +int ikcp_peeksize(const ikcpcb *kcp); + +// change MTU size, default is 1400 +int ikcp_setmtu(ikcpcb *kcp, int mtu); + +// set maximum window size: sndwnd=32, rcvwnd=32 by default +int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd); + +// get how many packet is waiting to be sent +int ikcp_waitsnd(const ikcpcb *kcp); + +// fastest: ikcp_nodelay(kcp, 1, 20, 2, 1) +// nodelay: 0:disable(default), 1:enable +// interval: internal update timer interval in millisec, default is 100ms +// resend: 0:disable fast resend(default), 1:enable fast resend +// nc: 0:normal congestion control(default), 1:disable congestion control +int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc); + + +void ikcp_log(ikcpcb *kcp, int mask, const char *fmt, ...); + +// setup allocator +void ikcp_allocator(void* (*new_malloc)(size_t), void (*new_free)(void*)); + +// read conv +IUINT32 ikcp_getconv(const void *ptr); + + +#ifdef __cplusplus +} +#endif + +#endif + + diff --git a/hpsocket/GlobalDef.h b/hpsocket/GlobalDef.h new file mode 100644 index 0000000..abca8ed --- /dev/null +++ b/hpsocket/GlobalDef.h @@ -0,0 +1,278 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +typedef int BOOL; +typedef float FLOAT; +typedef FLOAT *PFLOAT, *LPFLOAT; +typedef double DOUBLE; +typedef DOUBLE *PDOUBLE, *LPDOUBLE; +typedef short SHORT, INT16; +typedef SHORT *PSHORT, *LPSHORT; +typedef unsigned short USHORT, UINT16; +typedef USHORT *PUSHORT, *LPUSHORT; +typedef unsigned short WORD; +typedef WORD *PWORD, *LPWORD; +typedef unsigned int DWORD; +typedef DWORD *PDWORD, *LPDWORD; +typedef long LONG, LID; +typedef LONG *PLONG, *LPLONG; +typedef unsigned long ULONG, ULID; +typedef ULONG *PULONG, *LPULONG; +typedef long long LONGLONG, LLONG, INT64; +typedef LONGLONG *PLONGLONG, *LPLONGLONG, *PLLONG, *LPLLONG; +typedef unsigned long long ULONGLONG, ULLONG, UINT64; +typedef ULONGLONG *PULONGLONG, *LPULONGLONG, *PULLONG, *LPULLONG; +typedef int INT, IID, INT32; +typedef INT *PINT, *LPINT; +typedef unsigned int UINT, UIID, UINT32; +typedef UINT *PUINT, *LPUINT; +typedef void VOID; +typedef VOID *PVOID, *LPVOID; +typedef char CHAR, INT8; +typedef CHAR *PCHAR, *LPCHAR, *PSTR, *LPSTR; +typedef const char *PCSTR, *LPCSTR; +typedef unsigned char BYTE, UINT8; +typedef BYTE *PBYTE, *LPBYTE; +typedef const BYTE *PCBYTE, *LPCBYTE; +typedef wchar_t WCHAR; +typedef WCHAR *PWSTR, *LPWSTR; +typedef const WCHAR *PCWSTR, *LPCWSTR; +typedef LONG INT_PTR, LONG_PTR, LPARAM; +typedef ULONG UINT_PTR, ULONG_PTR, DWORD_PTR, WPARAM; + +typedef IID FD, HANDLE, SOCKET; +typedef INT LRESULT, HRESULT; + +typedef LLONG __time64_t; +typedef INT __time32_t; + +typedef LID NTHR_ID; + +#ifdef __ANDROID__ + typedef LID THR_ID; +#else + typedef ULID THR_ID; +#endif + +typedef size_t SIZE_T; +typedef ssize_t SSIZE_T; + +#ifdef _UNICODE + typedef WCHAR TCHAR; +#else + typedef CHAR TCHAR; +#endif + +typedef TCHAR *PTSTR, *LPTSTR; +typedef const TCHAR *PCTSTR, *LPCTSTR; + +#define MAXUINT8 ((UINT8)~((UINT8)0)) +#define MAXINT8 ((INT8)(MAXUINT8 >> 1)) +#define MININT8 ((INT8)~MAXINT8) + +#define MAXUINT16 ((UINT16)~((UINT16)0)) +#define MAXINT16 ((INT16)(MAXUINT16 >> 1)) +#define MININT16 ((INT16)~MAXINT16) + +#define MAXUINT32 ((UINT32)~((UINT32)0)) +#define MAXINT32 ((INT32)(MAXUINT32 >> 1)) +#define MININT32 ((INT32)~MAXINT32) + +#define MAXUINT64 ((UINT64)~((UINT64)0)) +#define MAXINT64 ((INT64)(MAXUINT64 >> 1)) +#define MININT64 ((INT64)~MAXINT64) + +#define MAXULONG ((ULONG)~((ULONG)0)) +#define MAXLONG ((LONG)(MAXULONG >> 1)) +#define MINLONG ((LONG)~MAXLONG) + +#define MAXULONGLONG ((ULONGLONG)~((ULONGLONG)0)) +#define MINLONGLONG ((LONGLONG)~MAXLONGLONG) + +#define MAXSIZE_T ((SIZE_T)~((SIZE_T)0)) +#define MAXSSIZE_T ((SSIZE_T)(MAXSIZE_T >> 1)) +#define MINSSIZE_T ((SSIZE_T)~MAXSSIZE_T) + +#define MAXUINT ((UINT)~((UINT)0)) +#define MAXINT ((INT)(MAXUINT >> 1)) +#define MININT ((INT)~MAXINT) + +#define MAXDWORD32 ((DWORD32)~((DWORD32)0)) +#define MAXDWORD64 ((DWORD64)~((DWORD64)0)) + +#define MINBYTE 0x00 +#define MAXBYTE 0xFF +#define MINCHAR 0x80 +#define MAXCHAR 0x7F +#define MINSHORT 0x8000 +#define MAXSHORT 0x7FFF +#define MINUSHORT 0x0000 +#define MAXUSHORT 0xFFFF +#define MINWORD 0x0000 +#define MAXWORD 0xFFFF +#define MINDWORD 0x00000000 +#define MAXDWORD 0xFFFFFFFF + +#ifdef _UNICODE + #define __T(x) L ## x +#else + #define __T(x) x + #define T2A(p) (p) + #define A2T(p) (p) + #define A2CT(p) (p) + #define T2CA(p) (p) + #define CT2A(p) (p) + #define CA2T(p) (p) + #define CA2CT(p) (p) +#endif + +#define _T(x) __T(x) +#define _TEXT(x) __T(x) + +#define _In_ +#define _Out_ +#define _Inout_ +#define _In_opt_ +#define _Out_opt_ +#define _Inout_opt_ +#define USES_CONVERSION + +#define INFINITE -1 +#define NO_ERROR 0 +#define HAS_ERROR -1 +#define TIMEOUT 0 +#define RS_OK NO_ERROR +#define RS_FAIL HAS_ERROR +#define RS_TIMEOUT TIMEOUT +#define INVALID_FD -1 +#define INVALID_HANDLE_VALUE INVALID_FD +#define INVALID_PVOID ((PVOID)-1) +#define _MAX_PATH 256 +#define MAX_PATH _MAX_PATH +#define TRUE 1 +#define FALSE 0 +#define CONST const + +#define MAKEWORD(a, b) ((WORD)(((BYTE)(((DWORD_PTR)(a)) & 0xff)) | ((WORD)((BYTE)(((DWORD_PTR)(b)) & 0xff))) << 8)) +#define MAKELONG(a, b) ((LONG)(((WORD)(((DWORD_PTR)(a)) & 0xffff)) | ((DWORD)((WORD)(((DWORD_PTR)(b)) & 0xffff))) << 16)) +#define LOWORD(l) ((WORD)(((DWORD_PTR)(l)) & 0xffff)) +#define HIWORD(l) ((WORD)((((DWORD_PTR)(l)) >> 16) & 0xffff)) +#define LOBYTE(w) ((BYTE)(((DWORD_PTR)(w)) & 0xff)) +#define HIBYTE(w) ((BYTE)((((DWORD_PTR)(w)) >> 8) & 0xff)) + +#if !defined(MAX) + #define MAX(a,b) (((a) >= (b)) ? (a) : (b)) +#endif + +#if !defined(MIN) + #define MIN(a,b) (((a) <= (b)) ? (a) : (b)) +#endif + +#if !defined(_max) + #define _max(a,b) MAX(a,b) +#endif + +#if !defined(_min) + #define _min(a,b) MIN(a,b) +#endif + +#if defined(NDEBUG) + #if !defined(_NDEBUG) + #define _NDEBUG + #endif + #if defined(DEBUG) + #undef DEBUG + #endif + #if defined(_DEBUG) + #undef _DEBUG + #endif + #if defined(DEBUG_TRACE) + #undef DEBUG_TRACE + #endif +#else + #if defined(_NDEBUG) + #undef _NDEBUG + #endif + #if !defined(DEBUG) + #define DEBUG + #endif + #if !defined(_DEBUG) + #define _DEBUG + #endif +#endif + +#if defined(__arm64__) && !defined(__aarch64__) + #define __aarch64__ __arm64__ +#elif defined(__aarch64__) && !defined(__arm64__) + #define __arm64__ __aarch64__ +#endif + +#ifdef __cplusplus + #define EXTERN_C extern "C" + #define EXTERN_C_BEGIN EXTERN_C { + #define EXTERN_C_END } +#else + #define EXTERN_C extern + #define EXTERN_C_BEGIN + #define EXTERN_C_END +#endif + +#if !defined(__stdcall) + #define __stdcall __attribute__ ((__stdcall__)) +#endif + +#if !defined(__cdecl) + #define __cdecl __attribute__ ((__cdecl__)) +#endif + +#if !defined(_GNU_SOURCE) + #define _GNU_SOURCE +#endif + +#if !defined(CALLBACK) + #define CALLBACK +#endif + +#if !defined(WINAPI) + #define WINAPI +#endif + +#if !defined(FORCEINLINE) + #ifdef __GNUC__ + #define FORCEINLINE __attribute__ ((always_inline)) + #else + #define FORCEINLINE inline + #endif +#endif + +#if !defined(STATIC_FORCEINLINE) + #define STATIC_FORCEINLINE static FORCEINLINE +#endif + +#if !defined(EXTERN_FORCEINLINE) + #define EXTERN_FORCEINLINE extern FORCEINLINE +#endif diff --git a/hpsocket/GlobalErrno.h b/hpsocket/GlobalErrno.h new file mode 100644 index 0000000..4e1eed9 --- /dev/null +++ b/hpsocket/GlobalErrno.h @@ -0,0 +1,252 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "GlobalDef.h" + +#include +#include + +#define ERROR_INVALID_STATE EPERM +#define ERROR_INVALID_PARAMETER EINVAL +#define ERROR_BROKEN_PIPE EPIPE +#define ERROR_AGAIN EAGAIN +#define ERROR_WOULDBLOCK EAGAIN +#define ERROR_READ_FAULT EFAULT +#define ERROR_WRITE_FAULT EFAULT +#define ERROR_HANDLES_CLOSED EBADFD +#define ERROR_IO_PENDING EINPROGRESS +#define ERROR_INTR EINTR +#define ERROR_EMPTY ENODATA +#define ERROR_NO_DATA ENODATA +#define ERROR_FILE_TOO_LARGE EFBIG +#define ERROR_INVALID_OPERATION EPERM +#define ERROR_CANCELLED ECANCELED +#define ERROR_UNKNOWN ENOMSG +#define ERROR_OBJECT_NOT_FOUND EBADSLT +#define ERROR_NOT_FOUND EBADSLT +#define ERROR_INVALID_INDEX ENOANO +#define ERROR_OPERATION_ABORTED ECANCELED +#define ERROR_CONNABORTED ECONNABORTED +#define ERROR_ADDRNOTAVAIL EADDRNOTAVAIL +#define ERROR_INCORRECT_ADDRESS EADDRNOTAVAIL +#define ERROR_PFNOSUPPORT EPFNOSUPPORT +#define ERROR_AFNOSUPPORT EAFNOSUPPORT +#define ERROR_TIMEOUT ETIMEDOUT +#define ERROR_TIMEDOUT ETIMEDOUT +#define ERROR_PROTO EPROTO +#define ERROR_CONNECTION_COUNT_LIMIT ENOSR +#define ERROR_VERIFY_CHECK EBADRQC +#define ERROR_CREATE_FAILED EMFILE +#define ERROR_INVALID_DATA EBADMSG +#define ERROR_BAD_LENGTH EMSGSIZE +#define ERROR_CALL_NOT_IMPLEMENTED EPERM +#define ERROR_INCORRECT_SIZE EMSGSIZE +#define ERROR_CONNRESET ECONNRESET +#define ERROR_CONNREFUSED ECONNREFUSED +#define ERROR_HOSTUNREACH EHOSTUNREACH +#define ERROR_INVALID_NAME ENOENT +#define ERROR_BAD_FILE_TYPE EBADF +#define ERROR_FILE_NOT_FOUND ENOENT +#define ERROR_FUNCTION_FAILED EFAULT +#define ERROR_INVALID_PASSWORD EACCES +#define ERROR_INVALID_ACCESS EACCES +#define ERROR_NOT_READY EPERM +#define ERROR_NOT_SUPPORTED EPERM +#define ERROR_BAD_FORMAT EBADMSG +#define ERROR_BUFFER_OVERFLOW E2BIG +#define ERROR_OUT_OF_RANGE ERANGE +#define ERROR_DESTINATION_ELEMENT_FULL EXFULL +#define ERROR_ALREADY_INITIALIZED EALREADY +#define ERROR_CANT_WAIT EIO + +#define EXIT_CODE_OK EX_OK +#define EXIT_CODE_CONFIG EX_CONFIG +#define EXIT_CODE_SOFTWARE EX_SOFTWARE + +/* +* Socket error codes. +*/ +#ifndef WSABASEERR + +/* +* All Sockets error constants are biased by WSABASEERR from +* the "normal" +*/ +#define WSABASEERR 10000 + +/* +* Sockets definitions of regular Microsoft C error constants +*/ +#define WSAEINTR (WSABASEERR+4) +#define WSAEBADF (WSABASEERR+9) +#define WSAEACCES (WSABASEERR+13) +#define WSAEFAULT (WSABASEERR+14) +#define WSAEINVAL (WSABASEERR+22) +#define WSAEMFILE (WSABASEERR+24) + +/* + * Sockets definitions of regular Berkeley error constants + */ +#define WSAEWOULDBLOCK (WSABASEERR+35) +#define WSAEINPROGRESS (WSABASEERR+36) +#define WSAEALREADY (WSABASEERR+37) +#define WSAENOTSOCK (WSABASEERR+38) +#define WSAEDESTADDRREQ (WSABASEERR+39) +#define WSAEMSGSIZE (WSABASEERR+40) +#define WSAEPROTOTYPE (WSABASEERR+41) +#define WSAENOPROTOOPT (WSABASEERR+42) +#define WSAEPROTONOSUPPORT (WSABASEERR+43) +#define WSAESOCKTNOSUPPORT (WSABASEERR+44) +#define WSAEOPNOTSUPP (WSABASEERR+45) +#define WSAEPFNOSUPPORT (WSABASEERR+46) +#define WSAEAFNOSUPPORT (WSABASEERR+47) +#define WSAEADDRINUSE (WSABASEERR+48) +#define WSAEADDRNOTAVAIL (WSABASEERR+49) +#define WSAENETDOWN (WSABASEERR+50) +#define WSAENETUNREACH (WSABASEERR+51) +#define WSAENETRESET (WSABASEERR+52) +#define WSAECONNABORTED (WSABASEERR+53) +#define WSAECONNRESET (WSABASEERR+54) +#define WSAENOBUFS (WSABASEERR+55) +#define WSAEISCONN (WSABASEERR+56) +#define WSAENOTCONN (WSABASEERR+57) +#define WSAESHUTDOWN (WSABASEERR+58) +#define WSAETOOMANYREFS (WSABASEERR+59) +#define WSAETIMEDOUT (WSABASEERR+60) +#define WSAECONNREFUSED (WSABASEERR+61) +#define WSAELOOP (WSABASEERR+62) +#define WSAENAMETOOLONG (WSABASEERR+63) +#define WSAEHOSTDOWN (WSABASEERR+64) +#define WSAEHOSTUNREACH (WSABASEERR+65) +#define WSAENOTEMPTY (WSABASEERR+66) +#define WSAEPROCLIM (WSABASEERR+67) +#define WSAEUSERS (WSABASEERR+68) +#define WSAEDQUOT (WSABASEERR+69) +#define WSAESTALE (WSABASEERR+70) +#define WSAEREMOTE (WSABASEERR+71) + +/* + * Extended Sockets error constant definitions + */ +#define WSASYSNOTREADY (WSABASEERR+91) +#define WSAVERNOTSUPPORTED (WSABASEERR+92) +#define WSANOTINITIALISED (WSABASEERR+93) +#define WSAEDISCON (WSABASEERR+101) +#define WSAENOMORE (WSABASEERR+102) +#define WSAECANCELLED (WSABASEERR+103) +#define WSAEINVALIDPROCTABLE (WSABASEERR+104) +#define WSAEINVALIDPROVIDER (WSABASEERR+105) +#define WSAEPROVIDERFAILEDINIT (WSABASEERR+106) +#define WSASYSCALLFAILURE (WSABASEERR+107) +#define WSASERVICE_NOT_FOUND (WSABASEERR+108) +#define WSATYPE_NOT_FOUND (WSABASEERR+109) +#define WSA_E_NO_MORE (WSABASEERR+110) +#define WSA_E_CANCELLED (WSABASEERR+111) +#define WSAEREFUSED (WSABASEERR+112) + +/* + * Error return codes from gethostbyname() and gethostbyaddr() + * (when using the resolver). Note that these errors are + * retrieved via WSAGetLastError() and must therefore follow + * the rules for avoiding clashes with error numbers from + * specific implementations or language run-time systems. + * For this reason the codes are based at WSABASEERR+1001. + * Note also that [WSA]NO_ADDRESS is defined only for + * compatibility purposes. + */ + +/* Authoritative Answer: Host not found */ +#define WSAHOST_NOT_FOUND (WSABASEERR+1001) + +/* Non-Authoritative: Host not found, or SERVERFAIL */ +#define WSATRY_AGAIN (WSABASEERR+1002) + +/* Non-recoverable errors, FORMERR, REFUSED, NOTIMP */ +#define WSANO_RECOVERY (WSABASEERR+1003) + +/* Valid name, no data record of requested type */ +#define WSANO_DATA (WSABASEERR+1004) + +/* + * Define QOS related error return codes + * + */ +#define WSA_QOS_RECEIVERS (WSABASEERR + 1005) + /* at least one Reserve has arrived */ +#define WSA_QOS_SENDERS (WSABASEERR + 1006) + /* at least one Path has arrived */ +#define WSA_QOS_NO_SENDERS (WSABASEERR + 1007) + /* there are no senders */ +#define WSA_QOS_NO_RECEIVERS (WSABASEERR + 1008) + /* there are no receivers */ +#define WSA_QOS_REQUEST_CONFIRMED (WSABASEERR + 1009) + /* Reserve has been confirmed */ +#define WSA_QOS_ADMISSION_FAILURE (WSABASEERR + 1010) + /* error due to lack of resources */ +#define WSA_QOS_POLICY_FAILURE (WSABASEERR + 1011) + /* rejected for administrative reasons - bad credentials */ +#define WSA_QOS_BAD_STYLE (WSABASEERR + 1012) + /* unknown or conflicting style */ +#define WSA_QOS_BAD_OBJECT (WSABASEERR + 1013) + /* problem with some part of the filterspec or providerspecific + * buffer in general */ +#define WSA_QOS_TRAFFIC_CTRL_ERROR (WSABASEERR + 1014) + /* problem with some part of the flowspec */ +#define WSA_QOS_GENERIC_ERROR (WSABASEERR + 1015) + /* general error */ +#define WSA_QOS_ESERVICETYPE (WSABASEERR + 1016) + /* invalid service type in flowspec */ +#define WSA_QOS_EFLOWSPEC (WSABASEERR + 1017) + /* invalid flowspec */ +#define WSA_QOS_EPROVSPECBUF (WSABASEERR + 1018) + /* invalid provider specific buffer */ +#define WSA_QOS_EFILTERSTYLE (WSABASEERR + 1019) + /* invalid filter style */ +#define WSA_QOS_EFILTERTYPE (WSABASEERR + 1020) + /* invalid filter type */ +#define WSA_QOS_EFILTERCOUNT (WSABASEERR + 1021) + /* incorrect number of filters */ +#define WSA_QOS_EOBJLENGTH (WSABASEERR + 1022) + /* invalid object length */ +#define WSA_QOS_EFLOWCOUNT (WSABASEERR + 1023) + /* incorrect number of flows */ +#define WSA_QOS_EUNKOWNPSOBJ (WSABASEERR + 1024) + /* unknown object in provider specific buffer */ +#define WSA_QOS_EPOLICYOBJ (WSABASEERR + 1025) + /* invalid policy object in provider specific buffer */ +#define WSA_QOS_EFLOWDESC (WSABASEERR + 1026) + /* invalid flow descriptor in the list */ +#define WSA_QOS_EPSFLOWSPEC (WSABASEERR + 1027) + /* inconsistent flow spec in provider specific buffer */ +#define WSA_QOS_EPSFILTERSPEC (WSABASEERR + 1028) + /* invalid filter spec in provider specific buffer */ +#define WSA_QOS_ESDMODEOBJ (WSABASEERR + 1029) + /* invalid shape discard mode object in provider specific buffer */ +#define WSA_QOS_ESHAPERATEOBJ (WSABASEERR + 1030) + /* invalid shaping rate object in provider specific buffer */ +#define WSA_QOS_RESERVED_PETYPE (WSABASEERR + 1031) + /* reserved policy element in provider specific buffer */ + +#endif /* ifdef WSABASEERR */ diff --git a/hpsocket/HPSocket-SSL.h b/hpsocket/HPSocket-SSL.h new file mode 100644 index 0000000..966a1a1 --- /dev/null +++ b/hpsocket/HPSocket-SSL.h @@ -0,0 +1,338 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "HPSocket.h" + +#ifdef _SSL_SUPPORT + +/*****************************************************************************************************************************************************/ +/******************************************************************** SSL Exports ********************************************************************/ +/*****************************************************************************************************************************************************/ + +/**************************************************/ +/************** HPSocket-SSL 导出函数 **************/ + +// 创建 SSL ITcpServer 对象 +HPSOCKET_API ITcpServer* HP_Create_SSLServer(ITcpServerListener* pListener); +// 创建 SSL ITcpAgent 对象 +HPSOCKET_API ITcpAgent* HP_Create_SSLAgent(ITcpAgentListener* pListener); +// 创建 SSL ITcpClient 对象 +HPSOCKET_API ITcpClient* HP_Create_SSLClient(ITcpClientListener* pListener); +// 创建 SSL ITcpPullServer 对象 +HPSOCKET_API ITcpPullServer* HP_Create_SSLPullServer(ITcpServerListener* pListener); +// 创建 SSL ITcpPullAgent 对象 +HPSOCKET_API ITcpPullAgent* HP_Create_SSLPullAgent(ITcpAgentListener* pListener); +// 创建 SSL ITcpPullClient 对象 +HPSOCKET_API ITcpPullClient* HP_Create_SSLPullClient(ITcpClientListener* pListener); +// 创建 SSL ITcpPackServer 对象 +HPSOCKET_API ITcpPackServer* HP_Create_SSLPackServer(ITcpServerListener* pListener); +// 创建 SSL ITcpPackAgent 对象 +HPSOCKET_API ITcpPackAgent* HP_Create_SSLPackAgent(ITcpAgentListener* pListener); +// 创建 SSL ITcpPackClient 对象 +HPSOCKET_API ITcpPackClient* HP_Create_SSLPackClient(ITcpClientListener* pListener); + +// 销毁 SSL ITcpServer 对象 +HPSOCKET_API void HP_Destroy_SSLServer(ITcpServer* pServer); +// 销毁 SSL ITcpAgent 对象 +HPSOCKET_API void HP_Destroy_SSLAgent(ITcpAgent* pAgent); +// 销毁 SSL ITcpClient 对象 +HPSOCKET_API void HP_Destroy_SSLClient(ITcpClient* pClient); +// 销毁 SSL ITcpPullServer 对象 +HPSOCKET_API void HP_Destroy_SSLPullServer(ITcpPullServer* pServer); +// 销毁 SSL ITcpPullAgent 对象 +HPSOCKET_API void HP_Destroy_SSLPullAgent(ITcpPullAgent* pAgent); +// 销毁 SSL ITcpPullClient 对象 +HPSOCKET_API void HP_Destroy_SSLPullClient(ITcpPullClient* pClient); +// 销毁 SSL ITcpPackServer 对象 +HPSOCKET_API void HP_Destroy_SSLPackServer(ITcpPackServer* pServer); +// 销毁 SSL ITcpPackAgent 对象 +HPSOCKET_API void HP_Destroy_SSLPackAgent(ITcpPackAgent* pAgent); +// 销毁 SSL ITcpPackClient 对象 +HPSOCKET_API void HP_Destroy_SSLPackClient(ITcpPackClient* pClient); + +// SSL ITcpServer 对象创建器 +struct SSLServer_Creator +{ + static ITcpServer* Create(ITcpServerListener* pListener) + { + return HP_Create_SSLServer(pListener); + } + + static void Destroy(ITcpServer* pServer) + { + HP_Destroy_SSLServer(pServer); + } +}; + +// SSL ITcpAgent 对象创建器 +struct SSLAgent_Creator +{ + static ITcpAgent* Create(ITcpAgentListener* pListener) + { + return HP_Create_SSLAgent(pListener); + } + + static void Destroy(ITcpAgent* pAgent) + { + HP_Destroy_SSLAgent(pAgent); + } +}; + +// SSL ITcpClient 对象创建器 +struct SSLClient_Creator +{ + static ITcpClient* Create(ITcpClientListener* pListener) + { + return HP_Create_SSLClient(pListener); + } + + static void Destroy(ITcpClient* pClient) + { + HP_Destroy_SSLClient(pClient); + } +}; + +// SSL ITcpPullServer 对象创建器 +struct SSLPullServer_Creator +{ + static ITcpPullServer* Create(ITcpServerListener* pListener) + { + return HP_Create_SSLPullServer(pListener); + } + + static void Destroy(ITcpPullServer* pServer) + { + HP_Destroy_SSLPullServer(pServer); + } +}; + +// SSL ITcpPullAgent 对象创建器 +struct SSLPullAgent_Creator +{ + static ITcpPullAgent* Create(ITcpAgentListener* pListener) + { + return HP_Create_SSLPullAgent(pListener); + } + + static void Destroy(ITcpPullAgent* pAgent) + { + HP_Destroy_SSLPullAgent(pAgent); + } +}; + +// SSL ITcpPullClient 对象创建器 +struct SSLPullClient_Creator +{ + static ITcpPullClient* Create(ITcpClientListener* pListener) + { + return HP_Create_SSLPullClient(pListener); + } + + static void Destroy(ITcpPullClient* pClient) + { + HP_Destroy_SSLPullClient(pClient); + } +}; + +// SSL ITcpPackServer 对象创建器 +struct SSLPackServer_Creator +{ + static ITcpPackServer* Create(ITcpServerListener* pListener) + { + return HP_Create_SSLPackServer(pListener); + } + + static void Destroy(ITcpPackServer* pServer) + { + HP_Destroy_SSLPackServer(pServer); + } +}; + +// SSL ITcpPackAgent 对象创建器 +struct SSLPackAgent_Creator +{ + static ITcpPackAgent* Create(ITcpAgentListener* pListener) + { + return HP_Create_SSLPackAgent(pListener); + } + + static void Destroy(ITcpPackAgent* pAgent) + { + HP_Destroy_SSLPackAgent(pAgent); + } +}; + +// SSL ITcpPackClient 对象创建器 +struct SSLPackClient_Creator +{ + static ITcpPackClient* Create(ITcpClientListener* pListener) + { + return HP_Create_SSLPackClient(pListener); + } + + static void Destroy(ITcpPackClient* pClient) + { + HP_Destroy_SSLPackClient(pClient); + } +}; + +// SSL ITcpServer 对象智能指针 +typedef CHPObjectPtr CSSLServerPtr; +// SSL ITcpAgent 对象智能指针 +typedef CHPObjectPtr CSSLAgentPtr; +// SSL ITcpClient 对象智能指针 +typedef CHPObjectPtr CSSLClientPtr; +// SSL ITcpPullServer 对象智能指针 +typedef CHPObjectPtr CSSLPullServerPtr; +// SSL ITcpPullAgent 对象智能指针 +typedef CHPObjectPtr CSSLPullAgentPtr; +// SSL ITcpPullClient 对象智能指针 +typedef CHPObjectPtr CSSLPullClientPtr; +// SSL ITcpPackServer 对象智能指针 +typedef CHPObjectPtr CSSLPackServerPtr; +// SSL ITcpPackAgent 对象智能指针 +typedef CHPObjectPtr CSSLPackAgentPtr; +// SSL ITcpPackClient 对象智能指针 +typedef CHPObjectPtr CSSLPackClientPtr; + +/*****************************************************************************************************************************************************/ +/*************************************************************** Global Function Exports *************************************************************/ +/*****************************************************************************************************************************************************/ + +/* +* 名称:SNI 默认回调函数 +* 描述:SSL Server 的 SetupSSLContext 方法中如果不指定 SNI 回调函数则使用此 SNI 默认回调函数 +* +* 参数: lpszServerName -- 请求域名 +* pContext -- SSL Context 对象 +* +* 返回值:SNI 主机证书对应的索引 +*/ +HPSOCKET_API int __HP_CALL HP_SSL_DefaultServerNameCallback(LPCTSTR lpszServerName, PVOID pContext); + +/* +* 名称:清理线程局部环境 SSL 资源 +* 描述:任何一个操作 SSL 的线程,通信结束时都需要清理线程局部环境 SSL 资源 +* 1、主线程和 HP-Socket 工作线程在通信结束时会自动清理线程局部环境 SSL 资源。因此,一般情况下不必手工调用本方法 +* 2、特殊情况下,当自定义线程参与 HP-Socket 通信操作并检查到 SSL 内存泄漏时,需在每次通信结束时自定义线程调用本方法 +* +* 参数: dwThreadID -- 线程 ID(0:当前线程) +* +* 返回值:无 +*/ +HPSOCKET_API void HP_SSL_RemoveThreadLocalState(THR_ID dwThreadID); + +/*****************************************************************************************************************************************************/ +/******************************************************************** HTTPS Exports ******************************************************************/ +/*****************************************************************************************************************************************************/ + +#ifdef _HTTP_SUPPORT + +// 创建 IHttpsServer 对象 +HPSOCKET_API IHttpServer* HP_Create_HttpsServer(IHttpServerListener* pListener); +// 创建 IHttpsAgent 对象 +HPSOCKET_API IHttpAgent* HP_Create_HttpsAgent(IHttpAgentListener* pListener); +// 创建 IHttpsClient 对象 +HPSOCKET_API IHttpClient* HP_Create_HttpsClient(IHttpClientListener* pListener); +// 创建 IHttpsSyncClient 对象 +HPSOCKET_API IHttpSyncClient* HP_Create_HttpsSyncClient(IHttpClientListener* pListener = nullptr); + +// 销毁 IHttpsServer 对象 +HPSOCKET_API void HP_Destroy_HttpsServer(IHttpServer* pServer); +// 销毁 IHttpsAgent 对象 +HPSOCKET_API void HP_Destroy_HttpsAgent(IHttpAgent* pAgent); +// 销毁 IHttpsClient 对象 +HPSOCKET_API void HP_Destroy_HttpsClient(IHttpClient* pClient); +// 销毁 IHttpsSyncClient 对象 +HPSOCKET_API void HP_Destroy_HttpsSyncClient(IHttpSyncClient* pClient); + +// IHttpsServer 对象创建器 +struct HttpsServer_Creator +{ + static IHttpServer* Create(IHttpServerListener* pListener) + { + return HP_Create_HttpsServer(pListener); + } + + static void Destroy(IHttpServer* pServer) + { + HP_Destroy_HttpsServer(pServer); + } +}; + +// IHttpsAgent 对象创建器 +struct HttpsAgent_Creator +{ + static IHttpAgent* Create(IHttpAgentListener* pListener) + { + return HP_Create_HttpsAgent(pListener); + } + + static void Destroy(IHttpAgent* pAgent) + { + HP_Destroy_HttpsAgent(pAgent); + } +}; + +// IHttpsClient 对象创建器 +struct HttpsClient_Creator +{ + static IHttpClient* Create(IHttpClientListener* pListener) + { + return HP_Create_HttpsClient(pListener); + } + + static void Destroy(IHttpClient* pClient) + { + HP_Destroy_HttpsClient(pClient); + } +}; + +// IHttpsSyncClient 对象创建器 +struct HttpsSyncClient_Creator +{ + static IHttpSyncClient* Create(IHttpClientListener* pListener = nullptr) + { + return HP_Create_HttpsSyncClient(pListener); + } + + static void Destroy(IHttpSyncClient* pClient) + { + HP_Destroy_HttpsSyncClient(pClient); + } +}; + +// IHttpsServer 对象智能指针 +typedef CHPObjectPtr CHttpsServerPtr; +// IHttpsAgent 对象智能指针 +typedef CHPObjectPtr CHttpsAgentPtr; +// IHttpsClient 对象智能指针 +typedef CHPObjectPtr CHttpsClientPtr; +// IHttpsSyncClient 对象智能指针 +typedef CHPObjectPtr CHttpsSyncClientPtr; + +#endif + +#endif \ No newline at end of file diff --git a/hpsocket/HPSocket.h b/hpsocket/HPSocket.h new file mode 100644 index 0000000..4f96948 --- /dev/null +++ b/hpsocket/HPSocket.h @@ -0,0 +1,818 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/****************************************************************************** +Module: HPSocket + +Usage: + 方法一: + -------------------------------------------------------------------------------------- + 0. 应用程序包含 HPTypeDef.h / SocketInterface.h / HPSocket.h 头文件 + 1. 调用 HP_Create_Xxx() 函数创建 HPSocket 对象 + 2. 使用完毕后调用 HP_Destroy_Xxx() 函数销毁 HPSocket 对象 + + 方法二: + -------------------------------------------------------------------------------------- + 0. 应用程序包含 SocketInterface.h 和 HPSocket.h 头文件 + 1. 创建 CXxxPtr 智能指针,通过智能指针使用 HPSocket 对象 + +Release: + <-- 动态链接库 --> + 1. x86/libhpsocket.so - (32位/MBCS/Release) + 2. x86/libhpsocket_d.so - (32位/MBCS/DeBug) + 3. x64/libhpsocket.so - (64位/MBCS/Release) + 4. x64/libhpsocket_d.so - (64位/MBCS/DeBug) + + <-- 静态链接库 --> + 1. x86/static/libhpsocket.a - (32位/MBCS/Release) + 2. x86/static/libhpsocket_d.a - (32位/MBCS/DeBug) + 3. x64/static/libhpsocket.a - (64位/MBCS/Release) + 4. x64/static/libhpsocket_d.a - (64位/MBCS/DeBug) + +******************************************************************************/ + +#pragma once + +#include "SocketInterface.h" + +/*****************************************************************************************************************************************************/ +/****************************************************************** TCP/UDP Exports ******************************************************************/ +/*****************************************************************************************************************************************************/ + +/**************************************************/ +/************** HPSocket 对象智能指针 **************/ + +template class CHPObjectPtr +{ +public: + CHPObjectPtr& Reset(T* pObj = nullptr) + { + if(pObj != m_pObj) + { + if(m_pObj) + _Creator::Destroy(m_pObj); + + m_pObj = pObj; + } + + return *this; + } + + CHPObjectPtr& Attach(T* pObj) + { + return Reset(pObj); + } + + T* Detach() + { + T* pObj = m_pObj; + m_pObj = nullptr; + + return pObj; + } + + BOOL IsValid () const {return m_pObj != nullptr ;} + T* Get () const {return m_pObj ;} + T* operator -> () const {return m_pObj ;} + operator T* () const {return m_pObj ;} + + CHPObjectPtr& operator = (T* pObj) {return Reset(pObj) ;} + +public: + CHPObjectPtr(_Listener* pListener = nullptr) + { + m_pObj = _Creator::Create(pListener); + } + + CHPObjectPtr(BOOL bCreate, _Listener* pListener = nullptr) + { + m_pObj = bCreate ? _Creator::Create(pListener) : nullptr; + } + + virtual ~CHPObjectPtr() + { + Reset(); + } + +private: + CHPObjectPtr(const CHPObjectPtr&) = delete; + CHPObjectPtr& operator = (const CHPObjectPtr&) = delete; + +protected: + T* m_pObj; +}; + +/**************************************************/ +/**************** HPSocket 导出函数 ****************/ + +// 创建 ITcpServer 对象 +HPSOCKET_API ITcpServer* HP_Create_TcpServer(ITcpServerListener* pListener); +// 创建 ITcpAgent 对象 +HPSOCKET_API ITcpAgent* HP_Create_TcpAgent(ITcpAgentListener* pListener); +// 创建 ITcpClient 对象 +HPSOCKET_API ITcpClient* HP_Create_TcpClient(ITcpClientListener* pListener); +// 创建 ITcpPullServer 对象 +HPSOCKET_API ITcpPullServer* HP_Create_TcpPullServer(ITcpServerListener* pListener); +// 创建 ITcpPullAgent 对象 +HPSOCKET_API ITcpPullAgent* HP_Create_TcpPullAgent(ITcpAgentListener* pListener); +// 创建 ITcpPullClient 对象 +HPSOCKET_API ITcpPullClient* HP_Create_TcpPullClient(ITcpClientListener* pListener); +// 创建 ITcpPackServer 对象 +HPSOCKET_API ITcpPackServer* HP_Create_TcpPackServer(ITcpServerListener* pListener); +// 创建 ITcpPackAgent 对象 +HPSOCKET_API ITcpPackAgent* HP_Create_TcpPackAgent(ITcpAgentListener* pListener); +// 创建 ITcpPackClient 对象 +HPSOCKET_API ITcpPackClient* HP_Create_TcpPackClient(ITcpClientListener* pListener); + +// 销毁 ITcpServer 对象 +HPSOCKET_API void HP_Destroy_TcpServer(ITcpServer* pServer); +// 销毁 ITcpAgent 对象 +HPSOCKET_API void HP_Destroy_TcpAgent(ITcpAgent* pAgent); +// 销毁 ITcpClient 对象 +HPSOCKET_API void HP_Destroy_TcpClient(ITcpClient* pClient); +// 销毁 ITcpPullServer 对象 +HPSOCKET_API void HP_Destroy_TcpPullServer(ITcpPullServer* pServer); +// 销毁 ITcpPullAgent 对象 +HPSOCKET_API void HP_Destroy_TcpPullAgent(ITcpPullAgent* pAgent); +// 销毁 ITcpPullClient 对象 +HPSOCKET_API void HP_Destroy_TcpPullClient(ITcpPullClient* pClient); +// 销毁 ITcpPackServer 对象 +HPSOCKET_API void HP_Destroy_TcpPackServer(ITcpPackServer* pServer); +// 销毁 ITcpPackAgent 对象 +HPSOCKET_API void HP_Destroy_TcpPackAgent(ITcpPackAgent* pAgent); +// 销毁 ITcpPackClient 对象 +HPSOCKET_API void HP_Destroy_TcpPackClient(ITcpPackClient* pClient); + +#ifdef _UDP_SUPPORT + +// 创建 IUdpServer 对象 +HPSOCKET_API IUdpServer* HP_Create_UdpServer(IUdpServerListener* pListener); +// 创建 IUdpClient 对象 +HPSOCKET_API IUdpClient* HP_Create_UdpClient(IUdpClientListener* pListener); +// 创建 IUdpCast 对象 +HPSOCKET_API IUdpCast* HP_Create_UdpCast(IUdpCastListener* pListener); +// 创建 IUdpNode 对象 +HPSOCKET_API IUdpNode* HP_Create_UdpNode(IUdpNodeListener* pListener); +// 创建 IUdpArqServer 对象 +HPSOCKET_API IUdpArqServer* HP_Create_UdpArqServer(IUdpServerListener* pListener); +// 创建 IUdpArqClient 对象 +HPSOCKET_API IUdpArqClient* HP_Create_UdpArqClient(IUdpClientListener* pListener); + +// 销毁 IUdpServer 对象 +HPSOCKET_API void HP_Destroy_UdpServer(IUdpServer* pServer); +// 销毁 IUdpClient 对象 +HPSOCKET_API void HP_Destroy_UdpClient(IUdpClient* pClient); +// 销毁 IUdpCast 对象 +HPSOCKET_API void HP_Destroy_UdpCast(IUdpCast* pCast); +// 销毁 IUdpNode 对象 +HPSOCKET_API void HP_Destroy_UdpNode(IUdpNode* pNode); +// 销毁 IUdpArqServer 对象 +HPSOCKET_API void HP_Destroy_UdpArqServer(IUdpArqServer* pServer); +// 销毁 IUdpArqClient 对象 +HPSOCKET_API void HP_Destroy_UdpArqClient(IUdpArqClient* pClient); + +#endif + +// ITcpServer 对象创建器 +struct TcpServer_Creator +{ + static ITcpServer* Create(ITcpServerListener* pListener) + { + return HP_Create_TcpServer(pListener); + } + + static void Destroy(ITcpServer* pServer) + { + HP_Destroy_TcpServer(pServer); + } +}; + +// ITcpAgent 对象创建器 +struct TcpAgent_Creator +{ + static ITcpAgent* Create(ITcpAgentListener* pListener) + { + return HP_Create_TcpAgent(pListener); + } + + static void Destroy(ITcpAgent* pAgent) + { + HP_Destroy_TcpAgent(pAgent); + } +}; + +// ITcpClient 对象创建器 +struct TcpClient_Creator +{ + static ITcpClient* Create(ITcpClientListener* pListener) + { + return HP_Create_TcpClient(pListener); + } + + static void Destroy(ITcpClient* pClient) + { + HP_Destroy_TcpClient(pClient); + } +}; + +// ITcpPullServer 对象创建器 +struct TcpPullServer_Creator +{ + static ITcpPullServer* Create(ITcpServerListener* pListener) + { + return HP_Create_TcpPullServer(pListener); + } + + static void Destroy(ITcpPullServer* pServer) + { + HP_Destroy_TcpPullServer(pServer); + } +}; + +// ITcpPullAgent 对象创建器 +struct TcpPullAgent_Creator +{ + static ITcpPullAgent* Create(ITcpAgentListener* pListener) + { + return HP_Create_TcpPullAgent(pListener); + } + + static void Destroy(ITcpPullAgent* pAgent) + { + HP_Destroy_TcpPullAgent(pAgent); + } +}; + +// ITcpPullClient 对象创建器 +struct TcpPullClient_Creator +{ + static ITcpPullClient* Create(ITcpClientListener* pListener) + { + return HP_Create_TcpPullClient(pListener); + } + + static void Destroy(ITcpPullClient* pClient) + { + HP_Destroy_TcpPullClient(pClient); + } +}; + +// ITcpPackServer 对象创建器 +struct TcpPackServer_Creator +{ + static ITcpPackServer* Create(ITcpServerListener* pListener) + { + return HP_Create_TcpPackServer(pListener); + } + + static void Destroy(ITcpPackServer* pServer) + { + HP_Destroy_TcpPackServer(pServer); + } +}; + +// ITcpPackAgent 对象创建器 +struct TcpPackAgent_Creator +{ + static ITcpPackAgent* Create(ITcpAgentListener* pListener) + { + return HP_Create_TcpPackAgent(pListener); + } + + static void Destroy(ITcpPackAgent* pAgent) + { + HP_Destroy_TcpPackAgent(pAgent); + } +}; + +// ITcpPackClient 对象创建器 +struct TcpPackClient_Creator +{ + static ITcpPackClient* Create(ITcpClientListener* pListener) + { + return HP_Create_TcpPackClient(pListener); + } + + static void Destroy(ITcpPackClient* pClient) + { + HP_Destroy_TcpPackClient(pClient); + } +}; + +// ITcpServer 对象智能指针 +typedef CHPObjectPtr CTcpServerPtr; +// ITcpAgent 对象智能指针 +typedef CHPObjectPtr CTcpAgentPtr; +// ITcpClient 对象智能指针 +typedef CHPObjectPtr CTcpClientPtr; +// ITcpPullServer 对象智能指针 +typedef CHPObjectPtr CTcpPullServerPtr; +// ITcpPullAgent 对象智能指针 +typedef CHPObjectPtr CTcpPullAgentPtr; +// ITcpPullClient 对象智能指针 +typedef CHPObjectPtr CTcpPullClientPtr; +// ITcpPackServer 对象智能指针 +typedef CHPObjectPtr CTcpPackServerPtr; +// ITcpPackAgent 对象智能指针 +typedef CHPObjectPtr CTcpPackAgentPtr; +// ITcpPackClient 对象智能指针 +typedef CHPObjectPtr CTcpPackClientPtr; + +#ifdef _UDP_SUPPORT + +// IUdpServer 对象创建器 +struct UdpServer_Creator +{ + static IUdpServer* Create(IUdpServerListener* pListener) + { + return HP_Create_UdpServer(pListener); + } + + static void Destroy(IUdpServer* pServer) + { + HP_Destroy_UdpServer(pServer); + } +}; + +// IUdpClient 对象创建器 +struct UdpClient_Creator +{ + static IUdpClient* Create(IUdpClientListener* pListener) + { + return HP_Create_UdpClient(pListener); + } + + static void Destroy(IUdpClient* pClient) + { + HP_Destroy_UdpClient(pClient); + } +}; + +// IUdpCast 对象创建器 +struct UdpCast_Creator +{ + static IUdpCast* Create(IUdpCastListener* pListener) + { + return HP_Create_UdpCast(pListener); + } + + static void Destroy(IUdpCast* pCast) + { + HP_Destroy_UdpCast(pCast); + } +}; + +// IUdpNode 对象创建器 +struct UdpNode_Creator +{ + static IUdpNode* Create(IUdpNodeListener* pListener) + { + return HP_Create_UdpNode(pListener); + } + + static void Destroy(IUdpNode* pNode) + { + HP_Destroy_UdpNode(pNode); + } +}; + +// IUdpArqServer 对象创建器 +struct UdpArqServer_Creator +{ + static IUdpArqServer* Create(IUdpServerListener* pListener) + { + return HP_Create_UdpArqServer(pListener); + } + + static void Destroy(IUdpArqServer* pServer) + { + HP_Destroy_UdpArqServer(pServer); + } +}; + +// IUdpArqClient 对象创建器 +struct UdpArqClient_Creator +{ + static IUdpArqClient* Create(IUdpClientListener* pListener) + { + return HP_Create_UdpArqClient(pListener); + } + + static void Destroy(IUdpArqClient* pClient) + { + HP_Destroy_UdpArqClient(pClient); + } +}; + +// IUdpServer 对象智能指针 +typedef CHPObjectPtr CUdpServerPtr; +// IUdpClient 对象智能指针 +typedef CHPObjectPtr CUdpClientPtr; +// IUdpCast 对象智能指针 +typedef CHPObjectPtr CUdpCastPtr; +// IUdpNode 对象智能指针 +typedef CHPObjectPtr CUdpNodePtr; +// IUdpArqServer 对象智能指针 +typedef CHPObjectPtr CUdpArqServerPtr; +// IUdpArqClient 对象智能指针 +typedef CHPObjectPtr CUdpArqClientPtr; + +#endif + +/*****************************************************************************************************************************************************/ +/*************************************************************** Global Function Exports *************************************************************/ +/*****************************************************************************************************************************************************/ + +// 获取 HPSocket 版本号(4 个字节分别为:主版本号,子版本号,修正版本号,构建编号) +HPSOCKET_API DWORD HP_GetHPSocketVersion(); + +// 获取错误描述文本 +HPSOCKET_API LPCTSTR HP_GetSocketErrorDesc(EnSocketError enCode); +// 调用系统的 errno 方法获取系统错误代码 +HPSOCKET_API DWORD SYS_GetLastError(); +// 调用系统的 strerror() 方法获取系统错误代码描述 +HPSOCKET_API LPCSTR SYS_GetLastErrorStr(); +// 调用系统的 setsockopt() +HPSOCKET_API int SYS_SetSocketOption(SOCKET sock, int level, int name, LPVOID val, int len); +// 调用系统的 getsockopt() +HPSOCKET_API int SYS_GetSocketOption(SOCKET sock, int level, int name, LPVOID val, int* len); +// 调用系统的 ioctlsocket() +HPSOCKET_API int SYS_IoctlSocket(SOCKET sock, long cmd, ULONG* arg); + +// 调用系统的 fcntl() 设置 F_SETFL 属性 +HPSOCKET_API BOOL SYS_fcntl_SETFL(FD fd, INT fl, BOOL bSet = TRUE); + +// 设置 FD 选项:O_NONBLOCK +HPSOCKET_API int SYS_SSO_NoBlock(SOCKET sock, BOOL bNoBlock = TRUE); +// 设置 socket 选项:IPPROTO_TCP -> TCP_NODELAY +HPSOCKET_API int SYS_SSO_NoDelay(SOCKET sock, BOOL bNoDelay = TRUE); +// 设置 socket 选项:SOL_SOCKET -> SO_DONTLINGER +HPSOCKET_API int SYS_SSO_DontLinger(SOCKET sock, BOOL bDont = TRUE); +// 设置 socket 选项:SOL_SOCKET -> SO_LINGER +HPSOCKET_API int SYS_SSO_Linger(SOCKET sock, USHORT l_onoff, USHORT l_linger); +// 设置 socket 选项:SOL_SOCKET -> SO_RCVBUF +HPSOCKET_API int SYS_SSO_RecvBuffSize(SOCKET sock, int size); +// 设置 socket 选项:SOL_SOCKET -> SO_SNDBUF +HPSOCKET_API int SYS_SSO_SendBuffSize(SOCKET sock, int size); +// 设置 socket 选项:SOL_SOCKET -> SO_RCVTIMEO +HPSOCKET_API int SYS_SSO_RecvTimeOut(SOCKET sock, int ms); +// 设置 socket 选项:SOL_SOCKET -> SO_SNDTIMEO +HPSOCKET_API int SYS_SSO_SendTimeOut(SOCKET sock, int ms); +// 设置 socket 选项:SOL_SOCKET -> SO_REUSEADDR / SO_REUSEPORT +HPSOCKET_API int SYS_SSO_ReuseAddress(SOCKET sock, EnReuseAddressPolicy opt); + +// 获取 SOCKET 本地地址信息 +HPSOCKET_API BOOL SYS_GetSocketLocalAddress(SOCKET socket, TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort); +// 获取 SOCKET 远程地址信息 +HPSOCKET_API BOOL SYS_GetSocketRemoteAddress(SOCKET socket, TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort); + +/* 枚举主机 IP 地址 */ +HPSOCKET_API BOOL SYS_EnumHostIPAddresses(LPCTSTR lpszHost, EnIPAddrType enType, LPTIPAddr** lpppIPAddr, int& iIPAddrCount); +/* 释放 LPTIPAddr* */ +HPSOCKET_API BOOL SYS_FreeHostIPAddresses(LPTIPAddr* lppIPAddr); +/* 检查字符串是否符合 IP 地址格式 */ +HPSOCKET_API BOOL SYS_IsIPAddress(LPCTSTR lpszAddress, EnIPAddrType* penType = nullptr); +/* 通过主机名获取 IP 地址 */ +HPSOCKET_API BOOL SYS_GetIPAddress(LPCTSTR lpszHost, TCHAR lpszIP[], int& iIPLenth, EnIPAddrType& enType); + +/* 64 位网络字节序转主机字节序 */ +HPSOCKET_API ULONGLONG SYS_NToH64(ULONGLONG value); +/* 64 位主机字节序转网络字节序 */ +HPSOCKET_API ULONGLONG SYS_HToN64(ULONGLONG value); +/* 短整型高低字节交换 */ +HPSOCKET_API USHORT SYS_SwapEndian16(USHORT value); +/* 长整型高低字节交换 */ +HPSOCKET_API DWORD SYS_SwapEndian32(DWORD value); +/* 检查是否小端字节序 */ +HPSOCKET_API BOOL SYS_IsLittleEndian(); + +/* 分配内存 */ +HPSOCKET_API LPBYTE SYS_Malloc(int size); +/* 重新分配内存 */ +HPSOCKET_API LPBYTE SYS_Realloc(LPBYTE p, int size); +/* 释放内存 */ +HPSOCKET_API VOID SYS_Free(LPBYTE p); +/* 分配内存块 */ +HPSOCKET_API LPVOID SYS_Calloc(int number, int size); + +// 计算 Base64 编码后长度 +HPSOCKET_API DWORD SYS_GuessBase64EncodeBound(DWORD dwSrcLen); +// 计算 Base64 解码后长度 +HPSOCKET_API DWORD SYS_GuessBase64DecodeBound(const BYTE* lpszSrc, DWORD dwSrcLen); +// Base64 编码(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +HPSOCKET_API int SYS_Base64Encode(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen); +// Base64 解码(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +HPSOCKET_API int SYS_Base64Decode(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen); + +// 计算 URL 编码后长度 +HPSOCKET_API DWORD SYS_GuessUrlEncodeBound(const BYTE* lpszSrc, DWORD dwSrcLen); +// 计算 URL 解码后长度 +HPSOCKET_API DWORD SYS_GuessUrlDecodeBound(const BYTE* lpszSrc, DWORD dwSrcLen); +// URL 编码(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +HPSOCKET_API int SYS_UrlEncode(BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen); +// URL 解码(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +HPSOCKET_API int SYS_UrlDecode(BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen); + +#ifdef _ZLIB_SUPPORT + +// 普通压缩(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +// (默认参数:iLevel -> -1,iMethod -> 8,iWindowBits -> 15,iMemLevel -> 8,iStrategy -> 0) +HPSOCKET_API int SYS_Compress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen); +// 高级压缩(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +//(默认参数:iLevel -> -1,iMethod -> 8,iWindowBits -> 15,iMemLevel -> 8,iStrategy -> 0) +HPSOCKET_API int SYS_CompressEx(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen, int iLevel = -1, int iMethod = 8, int iWindowBits = 15, int iMemLevel = 8, int iStrategy = 0); +// 普通解压(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +//(默认参数:iWindowBits -> 15) +HPSOCKET_API int SYS_Uncompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen); +// 高级解压(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +//(默认参数:iWindowBits -> 15) +HPSOCKET_API int SYS_UncompressEx(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen, int iWindowBits = 15); +// 推测压缩结果长度 +HPSOCKET_API DWORD SYS_GuessCompressBound(DWORD dwSrcLen, BOOL bGZip = FALSE); + +// Gzip 压缩(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +HPSOCKET_API int SYS_GZipCompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen); +// Gzip 解压(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +HPSOCKET_API int SYS_GZipUncompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen); +// 推测 Gzip 解压结果长度(如果返回 0 或不合理值则说明输入内容并非有效的 Gzip 格式) +HPSOCKET_API DWORD SYS_GZipGuessUncompressBound(const BYTE* lpszSrc, DWORD dwSrcLen); + +#endif + +#ifdef _BROTLI_SUPPORT + +// Brotli 压缩(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +// (默认参数:iQuality -> 11,iWindow -> 22,iMode -> 0) +HPSOCKET_API int SYS_BrotliCompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen); +// Brotli 高级压缩(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +//(默认参数:iQuality -> 11,iWindow -> 22,iMode -> 0) +HPSOCKET_API int SYS_BrotliCompressEx(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen, int iQuality = 11, int iWindow = 22, int iMode = 0); +// Brotli 解压(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +HPSOCKET_API int SYS_BrotliUncompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD& dwDestLen); +// Brotli 推测压缩结果长度 +HPSOCKET_API DWORD SYS_BrotliGuessCompressBound(DWORD dwSrcLen); + +#endif + +#ifdef _ICONV_SUPPORT + +// Charset A -> Charset B +HPSOCKET_API BOOL SYS_CharsetConvert(LPCSTR lpszFromCharset, LPCSTR lpszToCharset, LPCSTR lpszInBuf, int iInBufLen, LPSTR lpszOutBuf, int& iOutBufLen); + +// GBK -> UNICODE +HPSOCKET_API BOOL SYS_GbkToUnicodeEx(const char szSrc[], int iSrcLength, WCHAR szDest[], int& iDestLength); +// UNICODE -> GBK +HPSOCKET_API BOOL SYS_UnicodeToGbkEx(const WCHAR szSrc[], int iSrcLength, char szDest[], int& iDestLength); +// UTF8 -> UNICODE +HPSOCKET_API BOOL SYS_Utf8ToUnicodeEx(const char szSrc[], int iSrcLength, WCHAR szDest[], int& iDestLength); +// UNICODE -> UTF8 +HPSOCKET_API BOOL SYS_UnicodeToUtf8Ex(const WCHAR szSrc[], int iSrcLength, char szDest[], int& iDestLength); +// GBK -> UTF8 +HPSOCKET_API BOOL SYS_GbkToUtf8Ex(const char szSrc[], int iSrcLength, char szDest[], int& iDestLength); +// UTF8 -> GBK +HPSOCKET_API BOOL SYS_Utf8ToGbkEx(const char szSrc[], int iSrcLength, char szDest[], int& iDestLength); + +// GBK -> UNICODE +HPSOCKET_API BOOL SYS_GbkToUnicode(const char szSrc[], WCHAR szDest[], int& iDestLength); +// UNICODE -> GBK +HPSOCKET_API BOOL SYS_UnicodeToGbk(const WCHAR szSrc[], char szDest[], int& iDestLength); +// UTF8 -> UNICODE +HPSOCKET_API BOOL SYS_Utf8ToUnicode(const char szSrc[], WCHAR szDest[], int& iDestLength); +// UNICODE -> UTF8 +HPSOCKET_API BOOL SYS_UnicodeToUtf8(const WCHAR szSrc[], char szDest[], int& iDestLength); +// GBK -> UTF8 +HPSOCKET_API BOOL SYS_GbkToUtf8(const char szSrc[], char szDest[], int& iDestLength); +// UTF8 -> GBK +HPSOCKET_API BOOL SYS_Utf8ToGbk(const char szSrc[], char szDest[], int& iDestLength); + +#endif + +/*****************************************************************************************************************************************************/ +/******************************************************************** HTTP Exports *******************************************************************/ +/*****************************************************************************************************************************************************/ + +#ifdef _HTTP_SUPPORT + +// 创建 IHttpServer 对象 +HPSOCKET_API IHttpServer* HP_Create_HttpServer(IHttpServerListener* pListener); +// 创建 IHttpAgent 对象 +HPSOCKET_API IHttpAgent* HP_Create_HttpAgent(IHttpAgentListener* pListener); +// 创建 IHttpClient 对象 +HPSOCKET_API IHttpClient* HP_Create_HttpClient(IHttpClientListener* pListener); +// 创建 IHttpSyncClient 对象 +HPSOCKET_API IHttpSyncClient* HP_Create_HttpSyncClient(IHttpClientListener* pListener = nullptr); + +// 销毁 IHttpServer 对象 +HPSOCKET_API void HP_Destroy_HttpServer(IHttpServer* pServer); +// 销毁 IHttpAgent 对象 +HPSOCKET_API void HP_Destroy_HttpAgent(IHttpAgent* pAgent); +// 销毁 IHttpClient 对象 +HPSOCKET_API void HP_Destroy_HttpClient(IHttpClient* pClient); +// 销毁 IHttpSyncClient 对象 +HPSOCKET_API void HP_Destroy_HttpSyncClient(IHttpSyncClient* pClient); + +// IHttpServer 对象创建器 +struct HttpServer_Creator +{ + static IHttpServer* Create(IHttpServerListener* pListener) + { + return HP_Create_HttpServer(pListener); + } + + static void Destroy(IHttpServer* pServer) + { + HP_Destroy_HttpServer(pServer); + } +}; + +// IHttpAgent 对象创建器 +struct HttpAgent_Creator +{ + static IHttpAgent* Create(IHttpAgentListener* pListener) + { + return HP_Create_HttpAgent(pListener); + } + + static void Destroy(IHttpAgent* pAgent) + { + HP_Destroy_HttpAgent(pAgent); + } +}; + +// IHttpClient 对象创建器 +struct HttpClient_Creator +{ + static IHttpClient* Create(IHttpClientListener* pListener) + { + return HP_Create_HttpClient(pListener); + } + + static void Destroy(IHttpClient* pClient) + { + HP_Destroy_HttpClient(pClient); + } +}; + +// IHttpSyncClient 对象创建器 +struct HttpSyncClient_Creator +{ + static IHttpSyncClient* Create(IHttpClientListener* pListener = nullptr) + { + return HP_Create_HttpSyncClient(pListener); + } + + static void Destroy(IHttpSyncClient* pClient) + { + HP_Destroy_HttpSyncClient(pClient); + } +}; + +// IHttpServer 对象智能指针 +typedef CHPObjectPtr CHttpServerPtr; +// IHttpAgent 对象智能指针 +typedef CHPObjectPtr CHttpAgentPtr; +// IHttpClient 对象智能指针 +typedef CHPObjectPtr CHttpClientPtr; +// IHttpSyncClient 对象智能指针 +typedef CHPObjectPtr CHttpSyncClientPtr; + +/**************************************************************************/ +/*************************** HTTP Cookie 管理方法 **************************/ + +/* 从文件加载 Cookie */ +HPSOCKET_API BOOL HP_HttpCookie_MGR_LoadFromFile(LPCSTR lpszFile, BOOL bKeepExists = TRUE); +/* 保存 Cookie 到文件 */ +HPSOCKET_API BOOL HP_HttpCookie_MGR_SaveToFile(LPCSTR lpszFile, BOOL bKeepExists = TRUE); +/* 清理 Cookie */ +HPSOCKET_API BOOL HP_HttpCookie_MGR_ClearCookies(LPCSTR lpszDomain = nullptr, LPCSTR lpszPath = nullptr); +/* 清理过期 Cookie */ +HPSOCKET_API BOOL HP_HttpCookie_MGR_RemoveExpiredCookies(LPCSTR lpszDomain = nullptr, LPCSTR lpszPath = nullptr); +/* 设置 Cookie */ +HPSOCKET_API BOOL HP_HttpCookie_MGR_SetCookie(LPCSTR lpszName, LPCSTR lpszValue, LPCSTR lpszDomain, LPCSTR lpszPath, int iMaxAge = -1, BOOL bHttpOnly = FALSE, BOOL bSecure = FALSE, int enSameSite = 0, BOOL bOnlyUpdateValueIfExists = TRUE); +/* 删除 Cookie */ +HPSOCKET_API BOOL HP_HttpCookie_MGR_DeleteCookie(LPCSTR lpszDomain, LPCSTR lpszPath, LPCSTR lpszName); +/* 设置是否允许第三方 Cookie */ +HPSOCKET_API void HP_HttpCookie_MGR_SetEnableThirdPartyCookie(BOOL bEnableThirdPartyCookie = TRUE); +/* 检查是否允许第三方 Cookie */ +HPSOCKET_API BOOL HP_HttpCookie_MGR_IsEnableThirdPartyCookie(); + +/* Cookie expires 字符串转换为整数 */ +HPSOCKET_API BOOL HP_HttpCookie_HLP_ParseExpires(LPCSTR lpszExpires, __time64_t& tmExpires); +/* 整数转换为 Cookie expires 字符串 */ +HPSOCKET_API BOOL HP_HttpCookie_HLP_MakeExpiresStr(char lpszBuff[], int& iBuffLen, __time64_t tmExpires); +/* 生成 Cookie 字符串 */ +HPSOCKET_API BOOL HP_HttpCookie_HLP_ToString(char lpszBuff[], int& iBuffLen, LPCSTR lpszName, LPCSTR lpszValue, LPCSTR lpszDomain, LPCSTR lpszPath, int iMaxAge /*= -1*/, BOOL bHttpOnly /*= FALSE*/, BOOL bSecure /*= FALSE*/, int enSameSite /*= 0*/); +/* 获取当前 UTC 时间 */ +HPSOCKET_API __time64_t HP_HttpCookie_HLP_CurrentUTCTime(); +/* Max-Age -> expires */ +HPSOCKET_API __time64_t HP_HttpCookie_HLP_MaxAgeToExpires(int iMaxAge); +/* expires -> Max-Age */ +HPSOCKET_API int HP_HttpCookie_HLP_ExpiresToMaxAge(__time64_t tmExpires); + +/*****************************************************************************************************************************************************/ +/************************************************************* HTTP Global Function Exports **********************************************************/ +/*****************************************************************************************************************************************************/ + +#endif + +/*****************************************************************************************************************************************************/ +/**************************************************************** Thread Pool Exports ****************************************************************/ +/*****************************************************************************************************************************************************/ + +// 创建 IHPThreadPool 对象 +HPSOCKET_API IHPThreadPool* HP_Create_ThreadPool(IHPThreadPoolListener* pListener = nullptr); +// 销毁 IHPThreadPool 对象 +HPSOCKET_API void HP_Destroy_ThreadPool(IHPThreadPool* pThreadPool); + +/* +* 名称:创建 TSocketTask 对象 +* 描述:创建任务对象,该对象最终需由 HP_Destroy_SocketTaskObj() 销毁 +* +* 参数: fnTaskProc -- 任务处理函数 +* pSender -- 发起对象 +* dwConnID -- 连接 ID +* pBuffer -- 数据缓冲区 +* iBuffLen -- 数据缓冲区长度 +* enBuffType -- 数据缓冲区类型(默认:TBT_COPY) +* TBT_COPY :(深拷贝)pBuffer 复制到 TSocketTask 对象。此后 TSocketTask 对象与 pBuffer 不再有任何关联 +* -> 适用于 pBuffer 不大或 pBuffer 生命周期不受控的场景 +* TBT_REFER :(浅拷贝)pBuffer 不复制到 TSocketTask 对象,需确保 TSocketTask 对象生命周期内 pBuffer 必须有效 +* -> 适用于 pBuffer 较大或 pBuffer 可重用,并且 pBuffer 生命周期受控的场景 +* TBT_ATTACH :(附属)执行浅拷贝,但 TSocketTask 对象会获得 pBuffer 的所有权,并负责释放 pBuffer,避免多次缓冲区拷贝 +* -> 注意:pBuffer 必须由 SYS_Malloc()/SYS_Calloc() 函数分配才能使用本类型,否则可能会发生内存访问错误 +* wParam -- 自定义参数 +* lParam -- 自定义参数 +* 返回值: LPTSocketTask +*/ +HPSOCKET_API LPTSocketTask HP_Create_SocketTaskObj(Fn_SocketTaskProc fnTaskProc, PVOID pSender, CONNID dwConnID, LPCBYTE pBuffer, INT iBuffLen, EnTaskBufferType enBuffType = TBT_COPY, WPARAM wParam = 0, LPARAM lParam = 0); + +// 销毁 TSocketTask 对象 +HPSOCKET_API void HP_Destroy_SocketTaskObj(LPTSocketTask pTask); + +// IHPThreadPool 对象创建器 +struct HPThreadPool_Creator +{ + static IHPThreadPool* Create(IHPThreadPoolListener* pListener = nullptr) + { + return HP_Create_ThreadPool(pListener); + } + + static void Destroy(IHPThreadPool* pThreadPool) + { + HP_Destroy_ThreadPool(pThreadPool); + } +}; + +// IHPThreadPool 对象智能指针 +typedef CHPObjectPtr CHPThreadPoolPtr; + +/*****************************************************************************************************************************************************/ +/********************************************************* Compressor / Decompressor Exports *********************************************************/ +/*****************************************************************************************************************************************************/ + +/* 销毁压缩器对象 */ +HPSOCKET_API void HP_Destroy_Compressor(IHPCompressor* pCompressor); +/* 销毁解压器对象 */ +HPSOCKET_API void HP_Destroy_Decompressor(IHPDecompressor* pDecompressor); + +#ifdef _ZLIB_SUPPORT + +/* 创建 ZLib 压缩器对象 */ +HPSOCKET_API IHPCompressor* HP_Create_ZLibCompressor(Fn_CompressDataCallback fnCallback, int iWindowBits = 15, int iLevel = -1, int iMethod = 8, int iMemLevel = 8, int iStrategy = 0, DWORD dwBuffSize = 16 * 1024); +/* 创建 GZip 压缩器对象 */ +HPSOCKET_API IHPCompressor* HP_Create_GZipCompressor(Fn_CompressDataCallback fnCallback, int iLevel = -1, int iMethod = 8, int iMemLevel = 8, int iStrategy = 0, DWORD dwBuffSize = 16 * 1024); +/* 创建 ZLib 解压器对象 */ +HPSOCKET_API IHPDecompressor* HP_Create_ZLibDecompressor(Fn_DecompressDataCallback fnCallback, int iWindowBits = 15, DWORD dwBuffSize = 16 * 1024); +/* 创建 GZip 解压器对象 */ +HPSOCKET_API IHPDecompressor* HP_Create_GZipDecompressor(Fn_DecompressDataCallback fnCallback, DWORD dwBuffSize = 16 * 1024); + +#endif + +#ifdef _BROTLI_SUPPORT + +/* 创建 Brotli 压缩器对象 */ +HPSOCKET_API IHPCompressor* HP_Create_BrotliCompressor(Fn_CompressDataCallback fnCallback, int iQuality = 11, int iWindow = 22, int iMode = 0, DWORD dwBuffSize = 16 * 1024); +/* 创建 Brotli 解压器对象 */ +HPSOCKET_API IHPDecompressor* HP_Create_BrotliDecompressor(Fn_DecompressDataCallback fnCallback, DWORD dwBuffSize = 16 * 1024); + +#endif diff --git a/hpsocket/HPSocket4C-SSL.h b/hpsocket/HPSocket4C-SSL.h new file mode 100644 index 0000000..2dd9e24 --- /dev/null +++ b/hpsocket/HPSocket4C-SSL.h @@ -0,0 +1,411 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "HPSocket4C.h" + +#ifdef _SSL_SUPPORT + +/************************************************************************ +名称:定义 SSL Socket 对象指针类型别名 +描述:把 SSL Socket 对象指针定义为更直观的别名 +************************************************************************/ + +typedef HP_Object HP_SSLServer; +typedef HP_Object HP_SSLAgent; +typedef HP_Object HP_SSLClient; +typedef HP_Object HP_SSLPullServer; +typedef HP_Object HP_SSLPullAgent; +typedef HP_Object HP_SSLPullClient; +typedef HP_Object HP_SSLPackServer; +typedef HP_Object HP_SSLPackAgent; +typedef HP_Object HP_SSLPackClient; + +typedef HP_Object HP_HttpsServer; +typedef HP_Object HP_HttpsAgent; +typedef HP_Object HP_HttpsClient; +typedef HP_Object HP_HttpsSyncClient; + +/*****************************************************************************************************************************************************/ +/******************************************************************** SSL Exports ********************************************************************/ +/*****************************************************************************************************************************************************/ + +/********************************************************/ +/************** HPSocket4C-SSL 对象创建函数 **************/ + +// 创建 HP_SSLServer 对象 +HPSOCKET_API HP_SSLServer __HP_CALL Create_HP_SSLServer(HP_TcpServerListener pListener); +// 创建 HP_SSLAgent 对象 +HPSOCKET_API HP_SSLAgent __HP_CALL Create_HP_SSLAgent(HP_TcpAgentListener pListener); +// 创建 HP_SSLClient 对象 +HPSOCKET_API HP_SSLClient __HP_CALL Create_HP_SSLClient(HP_TcpClientListener pListener); +// 创建 HP_SSLPullServer 对象 +HPSOCKET_API HP_SSLPullServer __HP_CALL Create_HP_SSLPullServer(HP_TcpPullServerListener pListener); +// 创建 HP_SSLPullAgent 对象 +HPSOCKET_API HP_SSLPullAgent __HP_CALL Create_HP_SSLPullAgent(HP_TcpPullAgentListener pListener); +// 创建 HP_SSLPullClient 对象 +HPSOCKET_API HP_SSLPullClient __HP_CALL Create_HP_SSLPullClient(HP_TcpPullClientListener pListener); +// 创建 HP_SSLPackServer 对象 +HPSOCKET_API HP_SSLPackServer __HP_CALL Create_HP_SSLPackServer(HP_TcpServerListener pListener); +// 创建 HP_SSLPackAgent 对象 +HPSOCKET_API HP_SSLPackAgent __HP_CALL Create_HP_SSLPackAgent(HP_TcpAgentListener pListener); +// 创建 HP_SSLPackClient 对象 +HPSOCKET_API HP_SSLPackClient __HP_CALL Create_HP_SSLPackClient(HP_TcpClientListener pListener); + +// 销毁 HP_SSLServer 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_SSLServer(HP_SSLServer pServer); +// 销毁 HP_SSLAgent 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_SSLAgent(HP_SSLAgent pAgent); +// 销毁 HP_SSLClient 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_SSLClient(HP_SSLClient pClient); +// 销毁 HP_SSLPullServer 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_SSLPullServer(HP_SSLPullServer pServer); +// 销毁 HP_SSLPullAgent 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_SSLPullAgent(HP_SSLPullAgent pAgent); +// 销毁 HP_SSLPullClient 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_SSLPullClient(HP_SSLPullClient pClient); +// 销毁 HP_SSLPackServer 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_SSLPackServer(HP_SSLPackServer pServer); +// 销毁 HP_SSLPackAgent 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_SSLPackAgent(HP_SSLPackAgent pAgent); +// 销毁 HP_SSLPackClient 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_SSLPackClient(HP_SSLPackClient pClient); + +/*****************************************************************************************************************************************************/ +/*************************************************************** Global Function Exports *************************************************************/ +/*****************************************************************************************************************************************************/ + +/***************************************************************************************/ +/************************************ SSL 初始化方法 ************************************/ + +/* +* 名称:SNI 默认回调函数 +* 描述:HP_SSLServer_SetupSSLContext 方法中如果不指定 SNI 回调函数则使用此 SNI 默认回调函数 +* +* 参数: lpszServerName -- 请求域名 +* pContext -- SSL Context 对象 +* +* 返回值:SNI 主机证书对应的索引 +*/ +HPSOCKET_API int __HP_CALL HP_SSL_DefaultServerNameCallback(LPCTSTR lpszServerName, PVOID pContext); + +/* +* 名称:清理线程局部环境 SSL 资源 +* 描述:任何一个操作 SSL 的线程,通信结束时都需要清理线程局部环境 SSL 资源 +* 1、主线程和 HP-Socket 工作线程在通信结束时会自动清理线程局部环境 SSL 资源。因此,一般情况下不必手工调用本方法 +* 2、特殊情况下,当自定义线程参与 HP-Socket 通信操作并检查到 SSL 内存泄漏时,需在每次通信结束时自定义线程调用本方法 +* +* 参数: dwThreadID -- 线程 ID(0:当前线程) +* +* 返回值:无 +*/ +HPSOCKET_API void __HP_CALL HP_SSL_RemoveThreadLocalState(THR_ID dwThreadID); + +/* +* 名称:初始化通信组件 SSL 环境参数 +* 描述:SSL 环境参数必须在 SSL 通信组件启动前完成初始化,否则启动失败 +* +* 参数: iVerifyMode -- SSL 验证模式(参考 En_HP_SSLVerifyMode) +* lpszPemCertFile -- 证书文件 +* lpszPemKeyFile -- 私钥文件 +* lpszKeyPassword -- 私钥密码(没有密码则为空) +* lpszCAPemCertFileOrPath -- CA 证书文件或目录(单向验证或客户端可选) +* fnServerNameCallback -- SNI 回调函数指针(可选,如果为 nullptr 则使用 SNI 默认回调函数) +* +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 +*/ +HPSOCKET_API BOOL __HP_CALL HP_SSLServer_SetupSSLContext(HP_SSLServer pServer, int iVerifyMode /* SSL_VM_NONE */, LPCTSTR lpszPemCertFile /* nullptr */, LPCTSTR lpszPemKeyFile /* nullptr */, LPCTSTR lpszKeyPassword /* nullptr */, LPCTSTR lpszCAPemCertFileOrPath /* nullptr */, HP_Fn_SNI_ServerNameCallback fnServerNameCallback /* nullptr */); + +/* +* 名称:初始化通信组件 SSL 环境参数(通过内存加载证书) +* 描述:SSL 环境参数必须在 SSL 通信组件启动前完成初始化,否则启动失败 +* +* 参数: iVerifyMode -- SSL 验证模式(参考 En_HP_SSLVerifyMode) +* lpszPemCert -- 证书内容 +* lpszPemKey -- 私钥内容 +* lpszKeyPassword -- 私钥密码(没有密码则为空) +* lpszCAPemCert -- CA 证书内容(单向验证或客户端可选) +* fnServerNameCallback -- SNI 回调函数指针(可选,如果为 nullptr 则使用 SNI 默认回调函数) +* +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 +*/ +HPSOCKET_API BOOL __HP_CALL HP_SSLServer_SetupSSLContextByMemory(HP_SSLServer pServer, int iVerifyMode /* SSL_VM_NONE */, LPCSTR lpszPemCert /* nullptr */, LPCSTR lpszPemKey /* nullptr */, LPCSTR lpszKeyPassword /* nullptr */, LPCSTR lpszCAPemCert /* nullptr */, HP_Fn_SNI_ServerNameCallback fnServerNameCallback /* nullptr */); + +/* +* 名称:增加 SNI 主机证书 +* 描述:SSL 服务端在 SetupSSLContext() 成功后可以调用本方法增加多个 SNI 主机证书 +* +* 参数: iVerifyMode -- SSL 验证模式(参考 En_HP_SSLVerifyMode) +* lpszPemCertFile -- 证书文件 +* lpszPemKeyFile -- 私钥文件 +* lpszKeyPassword -- 私钥密码(没有密码则为空) +* lpszCAPemCertFileOrPath -- CA 证书文件或目录(单向验证可选) +* +* 返回值: 正数 -- 成功,并返回 SNI 主机证书对应的索引,该索引用于在 SNI 回调函数中定位 SNI 主机 +* 负数 -- 失败,可通过 SYS_GetLastError() 获取失败原因 +*/ +HPSOCKET_API int __HP_CALL HP_SSLServer_AddSSLContext(HP_SSLServer pServer, int iVerifyMode, LPCTSTR lpszPemCertFile, LPCTSTR lpszPemKeyFile, LPCTSTR lpszKeyPassword /* nullptr */, LPCTSTR lpszCAPemCertFileOrPath /* nullptr */); + +/* +* 名称:增加 SNI 主机证书(通过内存加载证书) +* 描述:SSL 服务端在 SetupSSLContext() 成功后可以调用本方法增加多个 SNI 主机证书 +* +* 参数: iVerifyMode -- SSL 验证模式(参考 En_HP_SSLVerifyMode) +* lpszPemCert -- 证书内容 +* lpszPemKey -- 私钥内容 +* lpszKeyPassword -- 私钥密码(没有密码则为空) +* lpszCAPemCert -- CA 证书内容(单向验证可选) +* +* 返回值: 正数 -- 成功,并返回 SNI 主机证书对应的索引,该索引用于在 SNI 回调函数中定位 SNI 主机 +* 负数 -- 失败,可通过 SYS_GetLastError() 获取失败原因 +*/ +HPSOCKET_API int __HP_CALL HP_SSLServer_AddSSLContextByMemory(HP_SSLServer pServer, int iVerifyMode, LPCSTR lpszPemCert, LPCSTR lpszPemKey, LPCSTR lpszKeyPassword /* nullptr */, LPCSTR lpszCAPemCert /* nullptr */); + +/* +* 名称:绑定 SNI 主机域名 +* 描述:SSL 服务端在 AddSSLContext() 成功后可以调用本方法绑定主机域名到 SNI 主机证书 +* +* 参数: lpszServerName -- 主机域名 +* iContextIndex -- SNI 主机证书对应的索引 +* +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 +*/ +HPSOCKET_API BOOL __HP_CALL HP_SSLServer_BindSSLServerName(HP_SSLServer pServer, LPCTSTR lpszServerName, int iContextIndex); + +/* +* 名称:清理通信组件 SSL 运行环境 +* 描述:清理通信组件 SSL 运行环境,回收 SSL 相关内存 +* 1、通信组件析构时会自动调用本方法 +* 2、当要重新设置通信组件 SSL 环境参数时,需要先调用本方法清理原先的环境参数 +* +* 参数: 无 +* +* 返回值:无 +*/ +HPSOCKET_API void __HP_CALL HP_SSLServer_CleanupSSLContext(HP_SSLServer pServer); + +/* +* 名称:初始化通信组件 SSL 环境参数 +* 描述:SSL 环境参数必须在 SSL 通信组件启动前完成初始化,否则启动失败 +* +* 参数: iVerifyMode -- SSL 验证模式(参考 En_HP_SSLVerifyMode) +* lpszPemCertFile -- 证书文件(客户端可选) +* lpszPemKeyFile -- 私钥文件(客户端可选) +* lpszKeyPassword -- 私钥密码(没有密码则为空) +* lpszCAPemCertFileOrPath -- CA 证书文件或目录(单向验证或客户端可选) +* +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 +*/ +HPSOCKET_API BOOL __HP_CALL HP_SSLAgent_SetupSSLContext(HP_SSLAgent pAgent, int iVerifyMode /* SSL_VM_NONE */, LPCTSTR lpszPemCertFile /* nullptr */, LPCTSTR lpszPemKeyFile /* nullptr */, LPCTSTR lpszKeyPassword /* nullptr */, LPCTSTR lpszCAPemCertFileOrPath /* nullptr */); + +/* +* 名称:初始化通信组件 SSL 环境参数(通过内存加载证书) +* 描述:SSL 环境参数必须在 SSL 通信组件启动前完成初始化,否则启动失败 +* +* 参数: iVerifyMode -- SSL 验证模式(参考 En_HP_SSLVerifyMode) +* lpszPemCert -- 证书内容 +* lpszPemKey -- 私钥内容 +* lpszKeyPassword -- 私钥密码(没有密码则为空) +* lpszCAPemCert -- CA 证书内容(单向验证或客户端可选) +* +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 +*/ +HPSOCKET_API BOOL __HP_CALL HP_SSLAgent_SetupSSLContextByMemory(HP_SSLAgent pAgent, int iVerifyMode /* SSL_VM_NONE */, LPCSTR lpszPemCert /* nullptr */, LPCSTR lpszPemKey /* nullptr */, LPCSTR lpszKeyPassword /* nullptr */, LPCSTR lpszCAPemCert /* nullptr */); + +/* +* 名称:清理通信组件 SSL 运行环境 +* 描述:清理通信组件 SSL 运行环境,回收 SSL 相关内存 +* 1、通信组件析构时会自动调用本方法 +* 2、当要重新设置通信组件 SSL 环境参数时,需要先调用本方法清理原先的环境参数 +* +* 参数: 无 +* +* 返回值:无 +*/ +HPSOCKET_API void __HP_CALL HP_SSLAgent_CleanupSSLContext(HP_SSLAgent pAgent); + +/* +* 名称:初始化通信组件 SSL 环境参数 +* 描述:SSL 环境参数必须在 SSL 通信组件启动前完成初始化,否则启动失败 +* +* 参数: iVerifyMode -- SSL 验证模式(参考 En_HP_SSLVerifyMode) +* lpszPemCertFile -- 证书文件(客户端可选) +* lpszPemKeyFile -- 私钥文件(客户端可选) +* lpszKeyPassword -- 私钥密码(没有密码则为空) +* lpszCAPemCertFileOrPath -- CA 证书文件或目录(单向验证或客户端可选) +* +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 +*/ +HPSOCKET_API BOOL __HP_CALL HP_SSLClient_SetupSSLContext(HP_SSLClient pClient, int iVerifyMode /* SSL_VM_NONE */, LPCTSTR lpszPemCertFile /* nullptr */, LPCTSTR lpszPemKeyFile /* nullptr */, LPCTSTR lpszKeyPassword /* nullptr */, LPCTSTR lpszCAPemCertFileOrPath /* nullptr */); + +/* +* 名称:初始化通信组件 SSL 环境参数(通过内存加载证书) +* 描述:SSL 环境参数必须在 SSL 通信组件启动前完成初始化,否则启动失败 +* +* 参数: iVerifyMode -- SSL 验证模式(参考 En_HP_SSLVerifyMode) +* lpszPemCert -- 证书内容 +* lpszPemKey -- 私钥内容 +* lpszKeyPassword -- 私钥密码(没有密码则为空) +* lpszCAPemCert -- CA 证书内容(单向验证或客户端可选) +* +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 +*/ +HPSOCKET_API BOOL __HP_CALL HP_SSLClient_SetupSSLContextByMemory(HP_SSLClient pClient, int iVerifyMode /* SSL_VM_NONE */, LPCSTR lpszPemCert /* nullptr */, LPCSTR lpszPemKey /* nullptr */, LPCSTR lpszKeyPassword /* nullptr */, LPCSTR lpszCAPemCert /* nullptr */); + +/* +* 名称:清理通信组件 SSL 运行环境 +* 描述:清理通信组件 SSL 运行环境,回收 SSL 相关内存 +* 1、通信组件析构时会自动调用本方法 +* 2、当要重新设置通信组件 SSL 环境参数时,需要先调用本方法清理原先的环境参数 +* +* 参数: 无 +* +* 返回值:无 +*/ +HPSOCKET_API void __HP_CALL HP_SSLClient_CleanupSSLContext(HP_SSLClient pClient); + +/***************************************************************************************/ +/************************************* SSL 操作方法 ************************************/ + +/* +* 名称:启动 SSL 握手 +* 描述:当通信组件设置为非自动握手时,需要调用本方法启动 SSL 握手 +* +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 +*/ +HPSOCKET_API BOOL __HP_CALL HP_SSLServer_StartSSLHandShake(HP_SSLServer pServer, HP_CONNID dwConnID); + +/* 设置通信组件握手方式(默认:TRUE,自动握手) */ +HPSOCKET_API void __HP_CALL HP_SSLServer_SetSSLAutoHandShake(HP_SSLServer pServer, BOOL bAutoHandShake); +/* 获取通信组件握手方式 */ +HPSOCKET_API BOOL __HP_CALL HP_SSLServer_IsSSLAutoHandShake(HP_SSLServer pServer); + +/* 设置 SSL 加密算法列表 */ +HPSOCKET_API void __HP_CALL HP_SSLServer_SetSSLCipherList(HP_SSLServer pServer, LPCTSTR lpszCipherList); +/* 获取 SSL 加密算法列表 */ +HPSOCKET_API LPCTSTR __HP_CALL HP_SSLServer_GetSSLCipherList(HP_SSLServer pServer); + +/* +* 名称:获取 SSL Session 信息 +* 描述:获取指定类型的 SSL Session 信息(输出类型参考:En_HP_SSLSessionInfo) +* +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 +*/ +HPSOCKET_API BOOL __HP_CALL HP_SSLServer_GetSSLSessionInfo(HP_SSLServer pServer, HP_CONNID dwConnID, En_HP_SSLSessionInfo enInfo, LPVOID* lppInfo); + +/* +* 名称:启动 SSL 握手 +* 描述:当通信组件设置为非自动握手时,需要调用本方法启动 SSL 握手 +* +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 +*/ +HPSOCKET_API BOOL __HP_CALL HP_SSLAgent_StartSSLHandShake(HP_SSLAgent pAgent, HP_CONNID dwConnID); + +/* 设置通信组件握手方式(默认:TRUE,自动握手) */ +HPSOCKET_API void __HP_CALL HP_SSLAgent_SetSSLAutoHandShake(HP_SSLAgent pAgent, BOOL bAutoHandShake); +/* 获取通信组件握手方式 */ +HPSOCKET_API BOOL __HP_CALL HP_SSLAgent_IsSSLAutoHandShake(HP_SSLAgent pAgent); + +/* 设置 SSL 加密算法列表 */ +HPSOCKET_API void __HP_CALL HP_SSLAgent_SetSSLCipherList(HP_SSLAgent pAgent, LPCTSTR lpszCipherList); +/* 获取 SSL 加密算法列表 */ +HPSOCKET_API LPCTSTR __HP_CALL HP_SSLAgent_GetSSLCipherList(HP_SSLAgent pAgent); + +/* +* 名称:获取 SSL Session 信息 +* 描述:获取指定类型的 SSL Session 信息(输出类型参考:En_HP_SSLSessionInfo) +* +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 +*/ +HPSOCKET_API BOOL __HP_CALL HP_SSLAgent_GetSSLSessionInfo(HP_SSLAgent pAgent, HP_CONNID dwConnID, En_HP_SSLSessionInfo enInfo, LPVOID* lppInfo); + +/* +* 名称:启动 SSL 握手 +* 描述:当通信组件设置为非自动握手时,需要调用本方法启动 SSL 握手 +* +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 +*/ +HPSOCKET_API BOOL __HP_CALL HP_SSLClient_StartSSLHandShake(HP_SSLClient pClient); + +/* 设置通信组件握手方式(默认:TRUE,自动握手) */ +HPSOCKET_API void __HP_CALL HP_SSLClient_SetSSLAutoHandShake(HP_SSLClient pClient, BOOL bAutoHandShake); +/* 获取通信组件握手方式 */ +HPSOCKET_API BOOL __HP_CALL HP_SSLClient_IsSSLAutoHandShake(HP_SSLClient pClient); + +/* 设置 SSL 加密算法列表 */ +HPSOCKET_API void __HP_CALL HP_SSLClient_SetSSLCipherList(HP_SSLClient pClient, LPCTSTR lpszCipherList); +/* 获取 SSL 加密算法列表 */ +HPSOCKET_API LPCTSTR __HP_CALL HP_SSLClient_GetSSLCipherList(HP_SSLClient pClient); + +/* +* 名称:获取 SSL Session 信息 +* 描述:获取指定类型的 SSL Session 信息(输出类型参考:En_HP_SSLSessionInfo) +* +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 +*/ +HPSOCKET_API BOOL __HP_CALL HP_SSLClient_GetSSLSessionInfo(HP_SSLClient pClient, En_HP_SSLSessionInfo enInfo, LPVOID* lppInfo); + +/*****************************************************************************************************************************************************/ +/******************************************************************** HTTPS Exports ******************************************************************/ +/*****************************************************************************************************************************************************/ + +#ifdef _HTTP_SUPPORT + +/****************************************************/ +/**************** HTTPS 对象创建函数 *****************/ + +// 创建 HP_HttpsServer 对象 +HPSOCKET_API HP_HttpsServer __HP_CALL Create_HP_HttpsServer(HP_HttpServerListener pListener); +// 创建 HP_HttpsAgent 对象 +HPSOCKET_API HP_HttpsAgent __HP_CALL Create_HP_HttpsAgent(HP_HttpAgentListener pListener); +// 创建 HP_HttpsClient 对象 +HPSOCKET_API HP_HttpsClient __HP_CALL Create_HP_HttpsClient(HP_HttpClientListener pListener); +// 创建 HP_HttpsSyncClient 对象(pListener 参数可传入 nullptr) +HPSOCKET_API HP_HttpsSyncClient __HP_CALL Create_HP_HttpsSyncClient(HP_HttpClientListener pListener /*= nullptr*/); + +// 销毁 HP_HttpsServer 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_HttpsServer(HP_HttpsServer pServer); +// 销毁 HP_HttpsAgent 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_HttpsAgent(HP_HttpsAgent pAgent); +// 销毁 HP_HttpsClient 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_HttpsClient(HP_HttpsClient pClient); +// 销毁 HP_HttpsSyncClient 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_HttpsSyncClient(HP_HttpsSyncClient pClient); + +#endif + +#endif \ No newline at end of file diff --git a/hpsocket/HPSocket4C.h b/hpsocket/HPSocket4C.h new file mode 100644 index 0000000..22f612a --- /dev/null +++ b/hpsocket/HPSocket4C.h @@ -0,0 +1,2833 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/****************************************************************************** +Module: HPSocket for C + +Desc: 导出纯 C 函数,让其它语言(如:C / C# / Delphi 等)能方便地使用 HPSocket + +Usage: + 方法一: + -------------------------------------------------------------------------------------- + 0. (C/C++ 程序)包含 HPTypeDef.h / HPSocket4C.h 头文件 + 1. 调用 ::Create_HP_XxxListener() 函数创建监听器对象 + 2. 调用 ::Create_HP_Xxx(pListener) 函数创建 HPSocket 对象 + 3. 调用 ::HP_Set_FN_Xxx_OnYyy(pListener, ...) 函数设置监听器的回调函数 + 4. 调用相关导出函数操作 HPSocket 对象 + 5. ...... ...... + 6. 调用 ::Destroy_HP_Xxx(pSocket) 函数销毁 HPSocket 对象 + 7. 调用 ::Destroy_HP_XxxListener(pListener) 函数销毁监听器对象 + + 方法二: + -------------------------------------------------------------------------------------- + 1. 应用程序把需要用到的导出函数封装到特定语言的包装类中 + 2. 通过包装类封装后,以面向对象的方式使用 HPSocket + +Release: + <-- 动态链接库 --> + 1. x86/libhpsocket4c.so - (32位/MBCS/Release) + 2. x86/libhpsocket4c_d.so - (32位/MBCS/DeBug) + 3. x64/libhpsocket4c.so - (64位/MBCS/Release) + 4. x64/libhpsocket4c_d.so - (64位/MBCS/DeBug) + + <-- 静态链接库 --> + 1. x86/static/libhpsocket4c.a - (32位/MBCS/Release) + 2. x86/static/libhpsocket4c_d.a - (32位/MBCS/DeBug) + 3. x64/static/libhpsocket4c.a - (64位/MBCS/Release) + 4. x64/static/libhpsocket4c_d.a - (64位/MBCS/DeBug) + +******************************************************************************/ + +#pragma once + +#include "HPTypeDef.h" + +/************************************************************************ +名称:定义 Socket 对象指针类型别名 +描述:把 Socket 对象指针定义为更直观的别名 +************************************************************************/ + +typedef PVOID HP_Object; + +typedef HP_Object HP_Server; +typedef HP_Object HP_Agent; +typedef HP_Object HP_Client; +typedef HP_Object HP_Node; +typedef HP_Object HP_TcpServer; +typedef HP_Object HP_TcpAgent; +typedef HP_Object HP_TcpClient; +typedef HP_Object HP_PullSocket; +typedef HP_Object HP_PullClient; +typedef HP_Object HP_TcpPullServer; +typedef HP_Object HP_TcpPullAgent; +typedef HP_Object HP_TcpPullClient; +typedef HP_Object HP_PackSocket; +typedef HP_Object HP_PackClient; +typedef HP_Object HP_TcpPackServer; +typedef HP_Object HP_TcpPackAgent; +typedef HP_Object HP_TcpPackClient; +typedef HP_Object HP_ThreadPool; +typedef HP_Object HP_Compressor; +typedef HP_Object HP_Decompressor; + +typedef HP_Object HP_Listener; +typedef HP_Object HP_ServerListener; +typedef HP_Object HP_AgentListener; +typedef HP_Object HP_ClientListener; +typedef HP_Object HP_TcpServerListener; +typedef HP_Object HP_TcpAgentListener; +typedef HP_Object HP_TcpClientListener; +typedef HP_Object HP_PullSocketListener; +typedef HP_Object HP_PullClientListener; +typedef HP_Object HP_TcpPullServerListener; +typedef HP_Object HP_TcpPullAgentListener; +typedef HP_Object HP_TcpPullClientListener; +typedef HP_Object HP_PackSocketListener; +typedef HP_Object HP_PackClientListener; +typedef HP_Object HP_TcpPackServerListener; +typedef HP_Object HP_TcpPackAgentListener; +typedef HP_Object HP_TcpPackClientListener; +typedef HP_Object HP_ThreadPoolListener; + +#ifdef _UDP_SUPPORT + +typedef HP_Object HP_UdpServer; +typedef HP_Object HP_UdpClient; +typedef HP_Object HP_UdpCast; +typedef HP_Object HP_UdpNode; +typedef HP_Object HP_UdpArqServer; +typedef HP_Object HP_UdpArqClient; + +typedef HP_Object HP_UdpServerListener; +typedef HP_Object HP_UdpClientListener; +typedef HP_Object HP_UdpCastListener; +typedef HP_Object HP_UdpNodeListener; +typedef HP_Object HP_UdpArqServerListener; +typedef HP_Object HP_UdpArqClientListener; + +#endif + +#ifdef _HTTP_SUPPORT + +typedef HP_Object HP_Http; +typedef HP_Object HP_HttpServer; +typedef HP_Object HP_HttpAgent; +typedef HP_Object HP_HttpClient; +typedef HP_Object HP_HttpSyncClient; + +typedef HP_Object HP_HttpServerListener; +typedef HP_Object HP_HttpAgentListener; +typedef HP_Object HP_HttpClientListener; + +#endif + +/*****************************************************************************************************************************************************/ +/****************************************************************** TCP/UDP Exports ******************************************************************/ +/*****************************************************************************************************************************************************/ + +/****************************************************/ +/***************** TCP/UDP 回调函数 ******************/ + +/* Server 回调函数 */ +typedef En_HP_HandleResult (__HP_CALL *HP_FN_Server_OnPrepareListen) (HP_Server pSender, UINT_PTR soListen); +// 如果为 TCP 连接,pClient为 SOCKET 句柄;如果为 UDP 连接,pClient为 SOCKADDR 指针; +typedef En_HP_HandleResult (__HP_CALL *HP_FN_Server_OnAccept) (HP_Server pSender, HP_CONNID dwConnID, UINT_PTR pClient); +typedef En_HP_HandleResult (__HP_CALL *HP_FN_Server_OnHandShake) (HP_Server pSender, HP_CONNID dwConnID); +typedef En_HP_HandleResult (__HP_CALL *HP_FN_Server_OnSend) (HP_Server pSender, HP_CONNID dwConnID, const BYTE* pData, int iLength); +typedef En_HP_HandleResult (__HP_CALL *HP_FN_Server_OnReceive) (HP_Server pSender, HP_CONNID dwConnID, const BYTE* pData, int iLength); +typedef En_HP_HandleResult (__HP_CALL *HP_FN_Server_OnPullReceive) (HP_Server pSender, HP_CONNID dwConnID, int iLength); +typedef En_HP_HandleResult (__HP_CALL *HP_FN_Server_OnClose) (HP_Server pSender, HP_CONNID dwConnID, En_HP_SocketOperation enOperation, int iErrorCode); +typedef En_HP_HandleResult (__HP_CALL *HP_FN_Server_OnShutdown) (HP_Server pSender); + +/* Agent 回调函数 */ +typedef En_HP_HandleResult (__HP_CALL *HP_FN_Agent_OnPrepareConnect) (HP_Agent pSender, HP_CONNID dwConnID, UINT_PTR socket); +typedef En_HP_HandleResult (__HP_CALL *HP_FN_Agent_OnConnect) (HP_Agent pSender, HP_CONNID dwConnID); +typedef En_HP_HandleResult (__HP_CALL *HP_FN_Agent_OnHandShake) (HP_Agent pSender, HP_CONNID dwConnID); +typedef En_HP_HandleResult (__HP_CALL *HP_FN_Agent_OnSend) (HP_Agent pSender, HP_CONNID dwConnID, const BYTE* pData, int iLength); +typedef En_HP_HandleResult (__HP_CALL *HP_FN_Agent_OnReceive) (HP_Agent pSender, HP_CONNID dwConnID, const BYTE* pData, int iLength); +typedef En_HP_HandleResult (__HP_CALL *HP_FN_Agent_OnPullReceive) (HP_Agent pSender, HP_CONNID dwConnID, int iLength); +typedef En_HP_HandleResult (__HP_CALL *HP_FN_Agent_OnClose) (HP_Agent pSender, HP_CONNID dwConnID, En_HP_SocketOperation enOperation, int iErrorCode); +typedef En_HP_HandleResult (__HP_CALL *HP_FN_Agent_OnShutdown) (HP_Agent pSender); + +/* Client 回调函数 */ +typedef En_HP_HandleResult (__HP_CALL *HP_FN_Client_OnPrepareConnect) (HP_Client pSender, HP_CONNID dwConnID, UINT_PTR socket); +typedef En_HP_HandleResult (__HP_CALL *HP_FN_Client_OnConnect) (HP_Client pSender, HP_CONNID dwConnID); +typedef En_HP_HandleResult (__HP_CALL *HP_FN_Client_OnHandShake) (HP_Client pSender, HP_CONNID dwConnID); +typedef En_HP_HandleResult (__HP_CALL *HP_FN_Client_OnSend) (HP_Client pSender, HP_CONNID dwConnID, const BYTE* pData, int iLength); +typedef En_HP_HandleResult (__HP_CALL *HP_FN_Client_OnReceive) (HP_Client pSender, HP_CONNID dwConnID, const BYTE* pData, int iLength); +typedef En_HP_HandleResult (__HP_CALL *HP_FN_Client_OnPullReceive) (HP_Client pSender, HP_CONNID dwConnID, int iLength); +typedef En_HP_HandleResult (__HP_CALL *HP_FN_Client_OnClose) (HP_Client pSender, HP_CONNID dwConnID, En_HP_SocketOperation enOperation, int iErrorCode); + +#ifdef _UDP_SUPPORT + +/* UdpNode 回调函数 */ +typedef En_HP_HandleResult (__HP_CALL *HP_FN_UdpNode_OnPrepareListen) (HP_UdpNode pSender, UINT_PTR soListen); +typedef En_HP_HandleResult (__HP_CALL *HP_FN_UdpNode_OnSend) (HP_UdpNode pSender, LPCTSTR lpszRemoteAddress, USHORT usRemotePort, const BYTE* pData, int iLength); +typedef En_HP_HandleResult (__HP_CALL *HP_FN_UdpNode_OnReceive) (HP_UdpNode pSender, LPCTSTR lpszRemoteAddress, USHORT usRemotePort, const BYTE* pData, int iLength); +typedef En_HP_HandleResult (__HP_CALL *HP_FN_UdpNode_OnError) (HP_UdpNode pSender, En_HP_SocketOperation enOperation, int iErrorCode, LPCTSTR lpszRemoteAddress, USHORT usRemotePort, const BYTE* pData, int iLength); +typedef En_HP_HandleResult (__HP_CALL *HP_FN_UdpNode_OnShutdown) (HP_UdpNode pSender); + +#endif + +/****************************************************/ +/*************** TCP/UDP 对象创建函数 ****************/ + +// 创建 HP_TcpServer 对象 +HPSOCKET_API HP_TcpServer __HP_CALL Create_HP_TcpServer(HP_TcpServerListener pListener); +// 创建 HP_TcpAgent 对象 +HPSOCKET_API HP_TcpAgent __HP_CALL Create_HP_TcpAgent(HP_TcpAgentListener pListener); +// 创建 HP_TcpClient 对象 +HPSOCKET_API HP_TcpClient __HP_CALL Create_HP_TcpClient(HP_TcpClientListener pListener); +// 创建 HP_TcpPullServer 对象 +HPSOCKET_API HP_TcpPullServer __HP_CALL Create_HP_TcpPullServer(HP_TcpPullServerListener pListener); +// 创建 HP_TcpPullAgent 对象 +HPSOCKET_API HP_TcpPullAgent __HP_CALL Create_HP_TcpPullAgent(HP_TcpPullAgentListener pListener); +// 创建 HP_TcpPullClient 对象 +HPSOCKET_API HP_TcpPullClient __HP_CALL Create_HP_TcpPullClient(HP_TcpPullClientListener pListener); +// 创建 HP_TcpPackServer 对象 +HPSOCKET_API HP_TcpPackServer __HP_CALL Create_HP_TcpPackServer(HP_TcpServerListener pListener); +// 创建 HP_TcpPackAgent 对象 +HPSOCKET_API HP_TcpPackAgent __HP_CALL Create_HP_TcpPackAgent(HP_TcpAgentListener pListener); +// 创建 HP_TcpPackClient 对象 +HPSOCKET_API HP_TcpPackClient __HP_CALL Create_HP_TcpPackClient(HP_TcpClientListener pListener); + +// 销毁 HP_TcpServer 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_TcpServer(HP_TcpServer pServer); +// 销毁 HP_TcpAgent 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_TcpAgent(HP_TcpAgent pAgent); +// 销毁 HP_TcpClient 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_TcpClient(HP_TcpClient pClient); +// 销毁 HP_TcpPullServer 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_TcpPullServer(HP_TcpPullServer pServer); +// 销毁 HP_TcpPullAgent 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_TcpPullAgent(HP_TcpPullAgent pAgent); +// 销毁 HP_TcpPullClient 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_TcpPullClient(HP_TcpPullClient pClient); +// 销毁 HP_TcpPackServer 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_TcpPackServer(HP_TcpPackServer pServer); +// 销毁 HP_TcpPackAgent 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_TcpPackAgent(HP_TcpPackAgent pAgent); +// 销毁 HP_TcpPackClient 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_TcpPackClient(HP_TcpPackClient pClient); + +// 创建 HP_TcpServerListener 对象 +HPSOCKET_API HP_TcpServerListener __HP_CALL Create_HP_TcpServerListener(); +// 创建 HP_TcpAgentListener 对象 +HPSOCKET_API HP_TcpAgentListener __HP_CALL Create_HP_TcpAgentListener(); +// 创建 HP_TcpClientListener 对象 +HPSOCKET_API HP_TcpClientListener __HP_CALL Create_HP_TcpClientListener(); +// 创建 HP_TcpPullServerListener 对象 +HPSOCKET_API HP_TcpPullServerListener __HP_CALL Create_HP_TcpPullServerListener(); +// 创建 HP_TcpPullAgentListener 对象 +HPSOCKET_API HP_TcpPullAgentListener __HP_CALL Create_HP_TcpPullAgentListener(); +// 创建 HP_TcpPullClientListener 对象 +HPSOCKET_API HP_TcpPullClientListener __HP_CALL Create_HP_TcpPullClientListener(); +// 创建 HP_TcpPackServerListener 对象 +HPSOCKET_API HP_TcpPackServerListener __HP_CALL Create_HP_TcpPackServerListener(); +// 创建 HP_TcpPackAgentListener 对象 +HPSOCKET_API HP_TcpPackAgentListener __HP_CALL Create_HP_TcpPackAgentListener(); +// 创建 HP_TcpPackClientListener 对象 +HPSOCKET_API HP_TcpPackClientListener __HP_CALL Create_HP_TcpPackClientListener(); + +// 销毁 HP_TcpServerListener 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_TcpServerListener(HP_TcpServerListener pListener); +// 销毁 HP_TcpAgentListener 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_TcpAgentListener(HP_TcpAgentListener pListener); +// 销毁 HP_TcpClientListener 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_TcpClientListener(HP_TcpClientListener pListener); +// 销毁 HP_TcpPullServerListener 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_TcpPullServerListener(HP_TcpPullServerListener pListener); +// 销毁 HP_TcpPullAgentListener 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_TcpPullAgentListener(HP_TcpPullAgentListener pListener); +// 销毁 HP_TcpPullClientListener 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_TcpPullClientListener(HP_TcpPullClientListener pListener); +// 销毁 HP_TcpPackServerListener 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_TcpPackServerListener(HP_TcpPackServerListener pListener); +// 销毁 HP_TcpPackAgentListener 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_TcpPackAgentListener(HP_TcpPackAgentListener pListener); +// 销毁 HP_TcpPackClientListener 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_TcpPackClientListener(HP_TcpPackClientListener pListener); + +#ifdef _UDP_SUPPORT + +// 创建 HP_UdpServer 对象 +HPSOCKET_API HP_UdpServer __HP_CALL Create_HP_UdpServer(HP_UdpServerListener pListener); +// 创建 HP_UdpClient 对象 +HPSOCKET_API HP_UdpClient __HP_CALL Create_HP_UdpClient(HP_UdpClientListener pListener); +// 创建 HP_UdpCast 对象 +HPSOCKET_API HP_UdpCast __HP_CALL Create_HP_UdpCast(HP_UdpCastListener pListener); +// 创建 HP_UdpNode 对象 +HPSOCKET_API HP_UdpNode __HP_CALL Create_HP_UdpNode(HP_UdpNodeListener pListener); +// 创建 HP_UdpArqServer 对象 +HPSOCKET_API HP_UdpArqServer __HP_CALL Create_HP_UdpArqServer(HP_UdpServerListener pListener); +// 创建 HP_UdpArqClient 对象 +HPSOCKET_API HP_UdpArqClient __HP_CALL Create_HP_UdpArqClient(HP_UdpClientListener pListener); + +// 销毁 HP_UdpServer 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_UdpServer(HP_UdpServer pServer); +// 销毁 HP_UdpClient 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_UdpClient(HP_UdpClient pClient); +// 销毁 HP_UdpCast 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_UdpCast(HP_UdpCast pCast); +// 销毁 HP_UdpNode 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_UdpNode(HP_UdpNode pNode); +// 销毁 HP_UdpArqServer 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_UdpArqServer(HP_UdpArqServer pServer); +// 销毁 HP_UdpArqClient 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_UdpArqClient(HP_UdpArqClient pClient); + +// 创建 HP_UdpServerListener 对象 +HPSOCKET_API HP_UdpServerListener __HP_CALL Create_HP_UdpServerListener(); +// 创建 HP_UdpClientListener 对象 +HPSOCKET_API HP_UdpClientListener __HP_CALL Create_HP_UdpClientListener(); +// 创建 HP_UdpCastListener 对象 +HPSOCKET_API HP_UdpCastListener __HP_CALL Create_HP_UdpCastListener(); +// 创建 HP_UdpNodeListener 对象 +HPSOCKET_API HP_UdpNodeListener __HP_CALL Create_HP_UdpNodeListener(); +// 创建 HP_UdpArqServerListener 对象 +HPSOCKET_API HP_UdpArqServerListener __HP_CALL Create_HP_UdpArqServerListener(); +// 创建 HP_UdpArqClientListener 对象 +HPSOCKET_API HP_UdpArqClientListener __HP_CALL Create_HP_UdpArqClientListener(); + +// 销毁 HP_UdpServerListener 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_UdpServerListener(HP_UdpServerListener pListener); +// 销毁 HP_UdpClientListener 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_UdpClientListener(HP_UdpClientListener pListener); +// 销毁 HP_UdpCastListener 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_UdpCastListener(HP_UdpCastListener pListener); +// 销毁 HP_UdpNodeListener 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_UdpNodeListener(HP_UdpNodeListener pListener); +// 销毁 HP_UdpArqServerListener 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_UdpArqServerListener(HP_UdpArqServerListener pListener); +// 销毁 HP_UdpArqClientListener 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_UdpArqClientListener(HP_UdpArqClientListener pListener); + +#endif + +/**********************************************************************************/ +/***************************** Server 回调函数设置方法 *****************************/ + +HPSOCKET_API void __HP_CALL HP_Set_FN_Server_OnPrepareListen(HP_ServerListener pListener , HP_FN_Server_OnPrepareListen fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_Server_OnAccept(HP_ServerListener pListener , HP_FN_Server_OnAccept fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_Server_OnHandShake(HP_ServerListener pListener , HP_FN_Server_OnHandShake fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_Server_OnSend(HP_ServerListener pListener , HP_FN_Server_OnSend fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_Server_OnReceive(HP_ServerListener pListener , HP_FN_Server_OnReceive fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_Server_OnPullReceive(HP_ServerListener pListener , HP_FN_Server_OnPullReceive fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_Server_OnClose(HP_ServerListener pListener , HP_FN_Server_OnClose fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_Server_OnShutdown(HP_ServerListener pListener , HP_FN_Server_OnShutdown fn); + +/**********************************************************************************/ +/****************************** Agent 回调函数设置方法 *****************************/ + +HPSOCKET_API void __HP_CALL HP_Set_FN_Agent_OnPrepareConnect(HP_AgentListener pListener , HP_FN_Agent_OnPrepareConnect fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_Agent_OnConnect(HP_AgentListener pListener , HP_FN_Agent_OnConnect fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_Agent_OnHandShake(HP_AgentListener pListener , HP_FN_Agent_OnHandShake fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_Agent_OnSend(HP_AgentListener pListener , HP_FN_Agent_OnSend fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_Agent_OnReceive(HP_AgentListener pListener , HP_FN_Agent_OnReceive fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_Agent_OnPullReceive(HP_AgentListener pListener , HP_FN_Agent_OnPullReceive fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_Agent_OnClose(HP_AgentListener pListener , HP_FN_Agent_OnClose fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_Agent_OnShutdown(HP_AgentListener pListener , HP_FN_Agent_OnShutdown fn); + +/**********************************************************************************/ +/***************************** Client 回调函数设置方法 *****************************/ + +HPSOCKET_API void __HP_CALL HP_Set_FN_Client_OnPrepareConnect(HP_ClientListener pListener , HP_FN_Client_OnPrepareConnect fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_Client_OnConnect(HP_ClientListener pListener , HP_FN_Client_OnConnect fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_Client_OnHandShake(HP_ClientListener pListener , HP_FN_Client_OnHandShake fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_Client_OnSend(HP_ClientListener pListener , HP_FN_Client_OnSend fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_Client_OnReceive(HP_ClientListener pListener , HP_FN_Client_OnReceive fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_Client_OnPullReceive(HP_ClientListener pListener , HP_FN_Client_OnPullReceive fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_Client_OnClose(HP_ClientListener pListener , HP_FN_Client_OnClose fn); + +#ifdef _UDP_SUPPORT + +/**********************************************************************************/ +/***************************** UdpNode 回调函数设置方法 *****************************/ + +HPSOCKET_API void __HP_CALL HP_Set_FN_UdpNode_OnPrepareListen(HP_UdpNodeListener pListener , HP_FN_UdpNode_OnPrepareListen fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_UdpNode_OnSend(HP_UdpNodeListener pListener , HP_FN_UdpNode_OnSend fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_UdpNode_OnReceive(HP_UdpNodeListener pListener , HP_FN_UdpNode_OnReceive fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_UdpNode_OnError(HP_UdpNodeListener pListener , HP_FN_UdpNode_OnError fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_UdpNode_OnShutdown(HP_UdpNodeListener pListener , HP_FN_UdpNode_OnShutdown fn); + +#endif + +/**************************************************************************/ +/***************************** Server 操作方法 *****************************/ + +/* +* 名称:启动通信组件 +* 描述:启动服务端通信组件,启动完成后可开始接收客户端连接并收发数据 +* +* 参数: lpszBindAddress -- 监听地址 +* usPort -- 监听端口 +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 HP_Server_GetLastError() 获取错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Server_Start(HP_Server pServer, LPCTSTR lpszBindAddress, USHORT usPort); + +/* +* 名称:关闭通信组件 +* 描述:关闭服务端通信组件,关闭完成后断开所有客户端连接并释放所有资源 +* +* 参数: +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 HP_Server_GetLastError() 获取错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Server_Stop(HP_Server pServer); + +/* +* 名称:等待 +* 描述:等待通信组件停止运行 +* +* 参数: dwMilliseconds -- 超时时间(毫秒,默认:-1,永不超时) +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Server_Wait(HP_Server pServer, DWORD dwMilliseconds); + +/* +* 名称:发送数据 +* 描述:向指定连接发送数据 +* +* 参数: dwConnID -- 连接 ID +* pBuffer -- 发送缓冲区 +* iLength -- 发送缓冲区长度 +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取系统错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Server_Send(HP_Server pServer, HP_CONNID dwConnID, const BYTE* pBuffer, int iLength); + +/* +* 名称:发送数据 +* 描述:向指定连接发送数据 +* +* 参数: dwConnID -- 连接 ID +* pBuffer -- 发送缓冲区 +* iLength -- 发送缓冲区长度 +* iOffset -- 发送缓冲区指针偏移量 +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取系统错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Server_SendPart(HP_Server pServer, HP_CONNID dwConnID, const BYTE* pBuffer, int iLength, int iOffset); + +/* +* 名称:发送多组数据 +* 描述:向指定连接发送多组数据 +* TCP - 顺序发送所有数据包 +* UDP - 把所有数据包组合成一个数据包发送(数据包的总长度不能大于设置的 UDP 包最大长度) +* +* 参数: dwConnID -- 连接 ID +* pBuffers -- 发送缓冲区数组 +* iCount -- 发送缓冲区数目 +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取系统错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Server_SendPackets(HP_Server pServer, HP_CONNID dwConnID, const WSABUF pBuffers[], int iCount); + +/* +* 名称:暂停/恢复接收 +* 描述:暂停/恢复某个连接的数据接收工作 +* +* 参数: dwConnID -- 连接 ID +* bPause -- TRUE - 暂停, FALSE - 恢复 +* 返回值: TRUE -- 成功 +* FALSE -- 失败 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Server_PauseReceive(HP_Server pServer, HP_CONNID dwConnID, BOOL bPause); + +/* +* 名称:断开连接 +* 描述:断开与某个客户端的连接 +* +* 参数: dwConnID -- 连接 ID +* bForce -- 是否强制断开连接 +* 返回值: TRUE -- 成功 +* FALSE -- 失败 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Server_Disconnect(HP_Server pServer, HP_CONNID dwConnID, BOOL bForce); + +/* +* 名称:断开超时连接 +* 描述:断开超过指定时长的连接 +* +* 参数: dwPeriod -- 时长(毫秒) +* bForce -- 是否强制断开连接 +* 返回值: TRUE -- 成功 +* FALSE -- 失败 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Server_DisconnectLongConnections(HP_Server pServer, DWORD dwPeriod, BOOL bForce); + +/* +* 名称:断开静默连接 +* 描述:断开超过指定时长的静默连接 +* +* 参数: dwPeriod -- 时长(毫秒) +* bForce -- 是否强制断开连接 +* 返回值: TRUE -- 成功 +* FALSE -- 失败 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Server_DisconnectSilenceConnections(HP_Server pServer, DWORD dwPeriod, BOOL bForce); + +/******************************************************************************/ +/***************************** Server 属性访问方法 *****************************/ + +/* +* 名称:设置连接的附加数据 +* 描述:是否为连接绑定附加数据或者绑定什么样的数据,均由应用程序只身决定 +* +* 参数: dwConnID -- 连接 ID +* pv -- 数据 +* 返回值: TRUE -- 成功 +* FALSE -- 失败(无效的连接 ID) +*/ +HPSOCKET_API BOOL __HP_CALL HP_Server_SetConnectionExtra(HP_Server pServer, HP_CONNID dwConnID, PVOID pExtra); + +/* +* 名称:获取连接的附加数据 +* 描述:是否为连接绑定附加数据或者绑定什么样的数据,均由应用程序只身决定 +* +* 参数: dwConnID -- 连接 ID +* ppv -- 数据指针 +* 返回值: TRUE -- 成功 +* FALSE -- 失败(无效的连接 ID) +*/ +HPSOCKET_API BOOL __HP_CALL HP_Server_GetConnectionExtra(HP_Server pServer, HP_CONNID dwConnID, PVOID* ppExtra); + +/* 检测是否为安全连接(SSL/HTTPS) */ +HPSOCKET_API BOOL __HP_CALL HP_Server_IsSecure(HP_Server pServer); +/* 检查通信组件是否已启动 */ +HPSOCKET_API BOOL __HP_CALL HP_Server_HasStarted(HP_Server pServer); +/* 查看通信组件当前状态 */ +HPSOCKET_API En_HP_ServiceState __HP_CALL HP_Server_GetState(HP_Server pServer); +/* 获取最近一次失败操作的错误代码 */ +HPSOCKET_API En_HP_SocketError __HP_CALL HP_Server_GetLastError(HP_Server pServer); +/* 获取最近一次失败操作的错误描述 */ +HPSOCKET_API LPCTSTR __HP_CALL HP_Server_GetLastErrorDesc(HP_Server pServer); +/* 获取连接中未发出数据的长度 */ +HPSOCKET_API BOOL __HP_CALL HP_Server_GetPendingDataLength(HP_Server pServer, HP_CONNID dwConnID, int* piPending); +/* 获取连接的数据接收状态 */ +HPSOCKET_API BOOL __HP_CALL HP_Server_IsPauseReceive(HP_Server pServer, HP_CONNID dwConnID, BOOL* pbPaused); +/* 检测是否有效连接 */ +HPSOCKET_API BOOL __HP_CALL HP_Server_IsConnected(HP_Server pServer, HP_CONNID dwConnID); +/* 获取客户端连接数 */ +HPSOCKET_API DWORD __HP_CALL HP_Server_GetConnectionCount(HP_Server pServer); +/* 获取所有连接的 HP_CONNID */ +HPSOCKET_API BOOL __HP_CALL HP_Server_GetAllConnectionIDs(HP_Server pServer, HP_CONNID pIDs[], DWORD* pdwCount); +/* 获取某个客户端连接时长(毫秒) */ +HPSOCKET_API BOOL __HP_CALL HP_Server_GetConnectPeriod(HP_Server pServer, HP_CONNID dwConnID, DWORD* pdwPeriod); +/* 获取某个连接静默时间(毫秒) */ +HPSOCKET_API BOOL __HP_CALL HP_Server_GetSilencePeriod(HP_Server pServer, HP_CONNID dwConnID, DWORD* pdwPeriod); +/* 获取监听 Socket 的地址信息 */ +HPSOCKET_API BOOL __HP_CALL HP_Server_GetListenAddress(HP_Server pServer, TCHAR lpszAddress[], int* piAddressLen, USHORT* pusPort); +/* 获取某个连接的本地地址信息 */ +HPSOCKET_API BOOL __HP_CALL HP_Server_GetLocalAddress(HP_Server pServer, HP_CONNID dwConnID, TCHAR lpszAddress[], int* piAddressLen, USHORT* pusPort); +/* 获取某个连接的远程地址信息 */ +HPSOCKET_API BOOL __HP_CALL HP_Server_GetRemoteAddress(HP_Server pServer, HP_CONNID dwConnID, TCHAR lpszAddress[], int* piAddressLen, USHORT* pusPort); + +/* 设置地址重用选项 */ +HPSOCKET_API void __HP_CALL HP_Server_SetReuseAddressPolicy(HP_Server pServer, En_HP_ReuseAddressPolicy enReusePolicy); +/* 设置数据发送策略(对 Linux 平台组件无效) */ +HPSOCKET_API void __HP_CALL HP_Server_SetSendPolicy(HP_Server pServer, En_HP_SendPolicy enSendPolicy); +/* 设置 OnSend 事件同步策略(对 Linux 平台组件无效) */ +HPSOCKET_API void __HP_CALL HP_Server_SetOnSendSyncPolicy(HP_Server pServer, En_HP_OnSendSyncPolicy enSyncPolicy); +//* 设置 Socket 缓存对象锁定时间(毫秒,在锁定期间该 Socket 缓存对象不能被获取使用) */ +HPSOCKET_API void __HP_CALL HP_Server_SetFreeSocketObjLockTime(HP_Server pServer, DWORD dwFreeSocketObjLockTime); +/* 设置 Socket 缓存池大小(通常设置为平均并发连接数量的 1/3 - 1/2) */ +HPSOCKET_API void __HP_CALL HP_Server_SetFreeSocketObjPool(HP_Server pServer, DWORD dwFreeSocketObjPool); +/* 设置内存块缓存池大小(通常设置为 Socket 缓存池大小的 2 - 3 倍) */ +HPSOCKET_API void __HP_CALL HP_Server_SetFreeBufferObjPool(HP_Server pServer, DWORD dwFreeBufferObjPool); +/* 设置 Socket 缓存池回收阀值(通常设置为 Socket 缓存池大小的 3 倍) */ +HPSOCKET_API void __HP_CALL HP_Server_SetFreeSocketObjHold(HP_Server pServer, DWORD dwFreeSocketObjHold); +/* 设置内存块缓存池回收阀值(通常设置为内存块缓存池大小的 3 倍) */ +HPSOCKET_API void __HP_CALL HP_Server_SetFreeBufferObjHold(HP_Server pServer, DWORD dwFreeBufferObjHold); +/* 设置最大连接数(组件会根据设置值预分配内存,因此需要根据实际情况设置,不宜过大)*/ +HPSOCKET_API void __HP_CALL HP_Server_SetMaxConnectionCount(HP_Server pServer, DWORD dwMaxConnectionCount); +/* 设置工作线程数量(通常设置为 2 * CPU + 2) */ +HPSOCKET_API void __HP_CALL HP_Server_SetWorkerThreadCount(HP_Server pServer, DWORD dwWorkerThreadCount); +/* 设置是否标记静默时间(设置为 TRUE 时 DisconnectSilenceConnections() 和 GetSilencePeriod() 才有效,默认:TRUE) */ +HPSOCKET_API void __HP_CALL HP_Server_SetMarkSilence(HP_Server pServer, BOOL bMarkSilence); + +/* 获取地址重用选项 */ +HPSOCKET_API En_HP_ReuseAddressPolicy __HP_CALL HP_Server_GetReuseAddressPolicy(HP_Server pServer); +/* 获取数据发送策略(对 Linux 平台组件无效) */ +HPSOCKET_API En_HP_SendPolicy __HP_CALL HP_Server_GetSendPolicy(HP_Server pServer); +/* 获取 OnSend 事件同步策略(对 Linux 平台组件无效) */ +HPSOCKET_API En_HP_OnSendSyncPolicy __HP_CALL HP_Server_GetOnSendSyncPolicy(HP_Server pServer); +//* 获取 Socket 缓存对象锁定时间 */ +HPSOCKET_API DWORD __HP_CALL HP_Server_GetFreeSocketObjLockTime(HP_Server pServer); +/* 获取 Socket 缓存池大小 */ +HPSOCKET_API DWORD __HP_CALL HP_Server_GetFreeSocketObjPool(HP_Server pServer); +/* 获取内存块缓存池大小 */ +HPSOCKET_API DWORD __HP_CALL HP_Server_GetFreeBufferObjPool(HP_Server pServer); +/* 获取 Socket 缓存池回收阀值 */ +HPSOCKET_API DWORD __HP_CALL HP_Server_GetFreeSocketObjHold(HP_Server pServer); +/* 获取内存块缓存池回收阀值 */ +HPSOCKET_API DWORD __HP_CALL HP_Server_GetFreeBufferObjHold(HP_Server pServer); +/* 获取最大连接数 */ +HPSOCKET_API DWORD __HP_CALL HP_Server_GetMaxConnectionCount(HP_Server pServer); +/* 获取工作线程数量 */ +HPSOCKET_API DWORD __HP_CALL HP_Server_GetWorkerThreadCount(HP_Server pServer); +/* 检测是否标记静默时间 */ +HPSOCKET_API BOOL __HP_CALL HP_Server_IsMarkSilence(HP_Server pServer); + +/**********************************************************************************/ +/******************************* TCP Server 操作方法 *******************************/ + +/* +* 名称:发送小文件 +* 描述:向指定连接发送 4096 KB 以下的小文件 +* +* 参数: dwConnID -- 连接 ID +* lpszFileName -- 文件路径 +* pHead -- 头部附加数据 +* pTail -- 尾部附加数据 +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取系统错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_TcpServer_SendSmallFile(HP_Server pServer, HP_CONNID dwConnID, LPCTSTR lpszFileName, const LPWSABUF pHead, const LPWSABUF pTail); + +/**********************************************************************************/ +/***************************** TCP Server 属性访问方法 *****************************/ + +/* 设置监听 Socket 的等候队列大小(根据并发连接数量调整设置) */ +HPSOCKET_API void __HP_CALL HP_TcpServer_SetSocketListenQueue(HP_TcpServer pServer, DWORD dwSocketListenQueue); +/* 设置 EPOLL 等待事件的最大数量 */ +HPSOCKET_API void __HP_CALL HP_TcpServer_SetAcceptSocketCount(HP_TcpServer pServer, DWORD dwAcceptSocketCount); +/* 设置通信数据缓冲区大小(根据平均通信数据包大小调整设置,通常设置为 1024 的倍数) */ +HPSOCKET_API void __HP_CALL HP_TcpServer_SetSocketBufferSize(HP_TcpServer pServer, DWORD dwSocketBufferSize); +/* 设置正常心跳包间隔(毫秒,0 则不发送心跳包,默认:60 * 1000) */ +HPSOCKET_API void __HP_CALL HP_TcpServer_SetKeepAliveTime(HP_TcpServer pServer, DWORD dwKeepAliveTime); +/* 设置异常心跳包间隔(毫秒,0 不发送心跳包,,默认:20 * 1000,如果超过若干次 [默认:WinXP 5 次, Win7 10 次] 检测不到心跳确认包则认为已断线) */ +HPSOCKET_API void __HP_CALL HP_TcpServer_SetKeepAliveInterval(HP_TcpServer pServer, DWORD dwKeepAliveInterval); +/* 设置是否开启 nodelay 模式(默认:FALSE,不开启) */ +HPSOCKET_API void __HP_CALL HP_TcpServer_SetNoDelay(HP_TcpServer pServer, BOOL bNoDelay); + +/* 获取 EPOLL 等待事件的最大数量 */ +HPSOCKET_API DWORD __HP_CALL HP_TcpServer_GetAcceptSocketCount(HP_TcpServer pServer); +/* 获取通信数据缓冲区大小 */ +HPSOCKET_API DWORD __HP_CALL HP_TcpServer_GetSocketBufferSize(HP_TcpServer pServer); +/* 获取监听 Socket 的等候队列大小 */ +HPSOCKET_API DWORD __HP_CALL HP_TcpServer_GetSocketListenQueue(HP_TcpServer pServer); +/* 获取正常心跳包间隔 */ +HPSOCKET_API DWORD __HP_CALL HP_TcpServer_GetKeepAliveTime(HP_TcpServer pServer); +/* 获取异常心跳包间隔 */ +HPSOCKET_API DWORD __HP_CALL HP_TcpServer_GetKeepAliveInterval(HP_TcpServer pServer); +/* 检查是否开启 nodelay 模式 */ +HPSOCKET_API BOOL __HP_CALL HP_TcpServer_IsNoDelay(HP_TcpServer pServer); + +#ifdef _UDP_SUPPORT + +/**********************************************************************************/ +/***************************** UDP Server 属性访问方法 *****************************/ + +/* 设置数据报文最大长度(建议在局域网环境下不超过 1432 字节,在广域网环境下不超过 548 字节) */ +HPSOCKET_API void __HP_CALL HP_UdpServer_SetMaxDatagramSize(HP_UdpServer pServer, DWORD dwMaxDatagramSize); +/* 获取数据报文最大长度 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpServer_GetMaxDatagramSize(HP_UdpServer pServer); + +/* 设置 Receive 预投递数量(根据负载调整设置,Receive 预投递数量越大则丢包概率越小) */ +HPSOCKET_API void __HP_CALL HP_UdpServer_SetPostReceiveCount(HP_UdpServer pServer, DWORD dwPostReceiveCount); +/* 获取 Receive 预投递数量 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpServer_GetPostReceiveCount(HP_UdpServer pServer); + +/* 设置监测包尝试次数(0 则不发送监测跳包,如果超过最大尝试次数则认为已断线) */ +HPSOCKET_API void __HP_CALL HP_UdpServer_SetDetectAttempts(HP_UdpServer pServer, DWORD dwDetectAttempts); +/* 设置监测包发送间隔(毫秒,0 不发送监测包) */ +HPSOCKET_API void __HP_CALL HP_UdpServer_SetDetectInterval(HP_UdpServer pServer, DWORD dwDetectInterval); +/* 获取心跳检查次数 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpServer_GetDetectAttempts(HP_UdpServer pServer); +/* 获取心跳检查间隔 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpServer_GetDetectInterval(HP_UdpServer pServer); + +/**********************************************************************************/ +/*************************** UDP ARQ Server 属性访问方法 ***************************/ + +/* 设置是否开启 nodelay 模式(默认:FALSE,不开启) */ +HPSOCKET_API void __HP_CALL HP_UdpArqServer_SetNoDelay(HP_UdpArqServer pServer, BOOL bNoDelay); +/* 设置是否关闭拥塞控制(默认:FALSE,不关闭) */ +HPSOCKET_API void __HP_CALL HP_UdpArqServer_SetTurnoffCongestCtrl(HP_UdpArqServer pServer, BOOL bTurnOff); +/* 设置数据刷新间隔(毫秒,默认:60) */ +HPSOCKET_API void __HP_CALL HP_UdpArqServer_SetFlushInterval(HP_UdpArqServer pServer, DWORD dwFlushInterval); +/* 设置快速重传 ACK 跨越次数(默认:0,关闭快速重传) */ +HPSOCKET_API void __HP_CALL HP_UdpArqServer_SetResendByAcks(HP_UdpArqServer pServer, DWORD dwResendByAcks); +/* 设置发送窗口大小(数据包数量,默认:128) */ +HPSOCKET_API void __HP_CALL HP_UdpArqServer_SetSendWndSize(HP_UdpArqServer pServer, DWORD dwSendWndSize); +/* 设置接收窗口大小(数据包数量,默认:512) */ +HPSOCKET_API void __HP_CALL HP_UdpArqServer_SetRecvWndSize(HP_UdpArqServer pServer, DWORD dwRecvWndSize); +/* 设置最小重传超时时间(毫秒,默认:30) */ +HPSOCKET_API void __HP_CALL HP_UdpArqServer_SetMinRto(HP_UdpArqServer pServer, DWORD dwMinRto); +/* 设置快速握手次数限制(默认:5,如果为 0 则不限制) */ +HPSOCKET_API void __HP_CALL HP_UdpArqServer_SetFastLimit(HP_UdpArqServer pServer, DWORD dwFastLimit); +/* 设置最大传输单元(默认:0,与 SetMaxDatagramSize() 一致) */ +HPSOCKET_API void __HP_CALL HP_UdpArqServer_SetMaxTransUnit(HP_UdpArqServer pServer, DWORD dwMaxTransUnit); +/* 设置最大数据包大小(默认:4096) */ +HPSOCKET_API void __HP_CALL HP_UdpArqServer_SetMaxMessageSize(HP_UdpArqServer pServer, DWORD dwMaxMessageSize); +/* 设置握手超时时间(毫秒,默认:5000) */ +HPSOCKET_API void __HP_CALL HP_UdpArqServer_SetHandShakeTimeout(HP_UdpArqServer pServer, DWORD dwHandShakeTimeout); + +/* 检测是否开启 nodelay 模式 */ +HPSOCKET_API BOOL __HP_CALL HP_UdpArqServer_IsNoDelay(HP_UdpArqServer pServer); +/* 检测是否关闭拥塞控制 */ +HPSOCKET_API BOOL __HP_CALL HP_UdpArqServer_IsTurnoffCongestCtrl(HP_UdpArqServer pServer); +/* 获取数据刷新间隔 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpArqServer_GetFlushInterval(HP_UdpArqServer pServer); +/* 获取快速重传 ACK 跨越次数 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpArqServer_GetResendByAcks(HP_UdpArqServer pServer); +/* 获取发送窗口大小 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpArqServer_GetSendWndSize(HP_UdpArqServer pServer); +/* 获取接收窗口大小 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpArqServer_GetRecvWndSize(HP_UdpArqServer pServer); +/* 获取最小重传超时时间 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpArqServer_GetMinRto(HP_UdpArqServer pServer); +/* 获取快速握手次数限制 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpArqServer_GetFastLimit(HP_UdpArqServer pServer); +/* 获取最大传输单元 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpArqServer_GetMaxTransUnit(HP_UdpArqServer pServer); +/* 获取最大数据包大小 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpArqServer_GetMaxMessageSize(HP_UdpArqServer pServer); +/* 获取握手超时时间 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpArqServer_GetHandShakeTimeout(HP_UdpArqServer pServer); + +/* 获取等待发送包数量 */ +HPSOCKET_API BOOL __HP_CALL HP_UdpArqServer_GetWaitingSendMessageCount(HP_UdpArqServer pServer, HP_CONNID dwConnID, int* piCount); + +#endif + +/**************************************************************************/ +/***************************** Agent 操作方法 *****************************/ + +/* +* 名称:启动通信组件 +* 描述:启动通信代理组件,启动完成后可开始连接远程服务器 +* +* 参数: lpszBindAddress -- 绑定地址(默认:nullptr,绑定任意地址) +* bAsyncConnect -- 是否采用异步 Connect +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 HP_Agent_GetLastError() 获取错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Agent_Start(HP_Agent pAgent, LPCTSTR lpszBindAddress, BOOL bAsyncConnect); + +/* +* 名称:关闭通信组件 +* 描述:关闭通信组件,关闭完成后断开所有连接并释放所有资源 +* +* 参数: +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 HP_Agent_GetLastError() 获取错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Agent_Stop(HP_Agent pAgent); + +/* +* 名称:等待 +* 描述:等待通信组件停止运行 +* +* 参数: dwMilliseconds -- 超时时间(毫秒,默认:-1,永不超时) +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Agent_Wait(HP_Agent pAgent, DWORD dwMilliseconds); + +/* +* 名称:连接服务器 +* 描述:连接服务器,连接成功后 IAgentListener 会接收到 OnConnect() / OnHandShake() 事件 +* +* 参数: lpszRemoteAddress -- 服务端地址 +* usPort -- 服务端端口 +* pdwConnID -- 连接 ID(默认:nullptr,不获取连接 ID) +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过函数 SYS_GetLastError() 获取系统错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Agent_Connect(HP_Agent pAgent, LPCTSTR lpszRemoteAddress, USHORT usPort, HP_CONNID* pdwConnID); + +/* +* 名称:连接服务器 +* 描述:连接服务器,连接成功后 IAgentListener 会接收到 OnConnect() / OnHandShake() 事件 +* +* 参数: lpszRemoteAddress -- 服务端地址 +* usPort -- 服务端端口 +* pdwConnID -- 连接 ID(默认:nullptr,不获取连接 ID) +* pExtra -- 连接附加数据(默认:nullptr) +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过函数 SYS_GetLastError() 获取系统错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Agent_ConnectWithExtra(HP_Agent pAgent, LPCTSTR lpszRemoteAddress, USHORT usPort, HP_CONNID* pdwConnID, PVOID pExtra); + +/* +* 名称:连接服务器 +* 描述:连接服务器,连接成功后 IAgentListener 会接收到 OnConnect() / OnHandShake() 事件 +* +* 参数: lpszRemoteAddress -- 服务端地址 +* usPort -- 服务端端口 +* pdwConnID -- 连接 ID(默认:nullptr,不获取连接 ID) +* usLocalPort -- 本地端口(默认:0) +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过函数 SYS_GetLastError() 获取 Windows 错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Agent_ConnectWithLocalPort(HP_Agent pAgent, LPCTSTR lpszRemoteAddress, USHORT usPort, HP_CONNID* pdwConnID, USHORT usLocalPort); + +/* +* 名称:连接服务器 +* 描述:连接服务器,连接成功后 IAgentListener 会接收到 OnConnect() / OnHandShake() 事件 +* +* 参数: lpszRemoteAddress -- 服务端地址 +* usPort -- 服务端端口 +* pdwConnID -- 连接 ID(默认:nullptr,不获取连接 ID) +* lpszLocalAddress -- 本地地址(默认:nullptr,使用 Start() 方法中绑定的地址) +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过函数 SYS_GetLastError() 获取 Windows 错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Agent_ConnectWithLocalAddress(HP_Agent pAgent, LPCTSTR lpszRemoteAddress, USHORT usPort, HP_CONNID* pdwConnID, LPCTSTR lpszLocalAddress); + +/* +* 名称:连接服务器 +* 描述:连接服务器,连接成功后 IAgentListener 会接收到 OnConnect() / OnHandShake() 事件 +* +* 参数: lpszRemoteAddress -- 服务端地址 +* usPort -- 服务端端口 +* pdwConnID -- 连接 ID(默认:nullptr,不获取连接 ID) +* pExtra -- 连接附加数据(默认:nullptr) +* usLocalPort -- 本地端口(默认:0) +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过函数 SYS_GetLastError() 获取 Windows 错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Agent_ConnectWithExtraAndLocalPort(HP_Agent pAgent, LPCTSTR lpszRemoteAddress, USHORT usPort, HP_CONNID* pdwConnID, PVOID pExtra, USHORT usLocalPort); + +/* +* 名称:连接服务器 +* 描述:连接服务器,连接成功后 IAgentListener 会接收到 OnConnect() / OnHandShake() 事件 +* +* 参数: lpszRemoteAddress -- 服务端地址 +* usPort -- 服务端端口 +* pdwConnID -- 连接 ID(默认:nullptr,不获取连接 ID) +* pExtra -- 连接附加数据(默认:nullptr) +* usLocalPort -- 本地端口(默认:0) +* lpszLocalAddress -- 本地地址(默认:nullptr,使用 Start() 方法中绑定的地址) +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过函数 SYS_GetLastError() 获取 Windows 错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Agent_ConnectWithExtraAndLocalAddressPort(HP_Agent pAgent, LPCTSTR lpszRemoteAddress, USHORT usPort, HP_CONNID* pdwConnID, PVOID pExtra, USHORT usLocalPort, LPCTSTR lpszLocalAddress); + +/* +* 名称:发送数据 +* 描述:向指定连接发送数据 +* +* 参数: dwConnID -- 连接 ID +* pBuffer -- 发送缓冲区 +* iLength -- 发送缓冲区长度 +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取系统错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Agent_Send(HP_Agent pAgent, HP_CONNID dwConnID, const BYTE* pBuffer, int iLength); + +/* +* 名称:发送数据 +* 描述:向指定连接发送数据 +* +* 参数: dwConnID -- 连接 ID +* pBuffer -- 发送缓冲区 +* iLength -- 发送缓冲区长度 +* iOffset -- 发送缓冲区指针偏移量 +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取系统错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Agent_SendPart(HP_Agent pAgent, HP_CONNID dwConnID, const BYTE* pBuffer, int iLength, int iOffset); + +/* +* 名称:发送多组数据 +* 描述:向指定连接发送多组数据 +* TCP - 顺序发送所有数据包 +* UDP - 把所有数据包组合成一个数据包发送(数据包的总长度不能大于设置的 UDP 包最大长度) +* +* 参数: dwConnID -- 连接 ID +* pBuffers -- 发送缓冲区数组 +* iCount -- 发送缓冲区数目 +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取系统错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Agent_SendPackets(HP_Agent pAgent, HP_CONNID dwConnID, const WSABUF pBuffers[], int iCount); + +/* +* 名称:暂停/恢复接收 +* 描述:暂停/恢复某个连接的数据接收工作 +* +* 参数: dwConnID -- 连接 ID +* bPause -- TRUE - 暂停, FALSE - 恢复 +* 返回值: TRUE -- 成功 +* FALSE -- 失败 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Agent_PauseReceive(HP_Agent pAgent, HP_CONNID dwConnID, BOOL bPause); + +/* +* 名称:断开连接 +* 描述:断开某个连接 +* +* 参数: dwConnID -- 连接 ID +* bForce -- 是否强制断开连接 +* 返回值: TRUE -- 成功 +* FALSE -- 失败 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Agent_Disconnect(HP_Agent pAgent, HP_CONNID dwConnID, BOOL bForce); + +/* +* 名称:断开超时连接 +* 描述:断开超过指定时长的连接 +* +* 参数: dwPeriod -- 时长(毫秒) +* bForce -- 是否强制断开连接 +* 返回值: TRUE -- 成功 +* FALSE -- 失败 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Agent_DisconnectLongConnections(HP_Agent pAgent, DWORD dwPeriod, BOOL bForce); + +/* +* 名称:断开静默连接 +* 描述:断开超过指定时长的静默连接 +* +* 参数: dwPeriod -- 时长(毫秒) +* bForce -- 是否强制断开连接 +* 返回值: TRUE -- 成功 +* FALSE -- 失败 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Agent_DisconnectSilenceConnections(HP_Agent pAgent, DWORD dwPeriod, BOOL bForce); + +/******************************************************************************/ +/***************************** Agent 属性访问方法 *****************************/ + +/* +* 名称:设置连接的附加数据 +* 描述:是否为连接绑定附加数据或者绑定什么样的数据,均由应用程序只身决定 +* +* 参数: dwConnID -- 连接 ID +* pv -- 数据 +* 返回值: TRUE -- 成功 +* FALSE -- 失败(无效的连接 ID) +*/ +HPSOCKET_API BOOL __HP_CALL HP_Agent_SetConnectionExtra(HP_Agent pAgent, HP_CONNID dwConnID, PVOID pExtra); + +/* +* 名称:获取连接的附加数据 +* 描述:是否为连接绑定附加数据或者绑定什么样的数据,均由应用程序只身决定 +* +* 参数: dwConnID -- 连接 ID +* ppv -- 数据指针 +* 返回值: TRUE -- 成功 +* FALSE -- 失败(无效的连接 ID) +*/ +HPSOCKET_API BOOL __HP_CALL HP_Agent_GetConnectionExtra(HP_Agent pAgent, HP_CONNID dwConnID, PVOID* ppExtra); + +/* 检测是否为安全连接(SSL/HTTPS) */ +HPSOCKET_API BOOL __HP_CALL HP_Agent_IsSecure(HP_Agent pAgent); +/* 检查通信组件是否已启动 */ +HPSOCKET_API BOOL __HP_CALL HP_Agent_HasStarted(HP_Agent pAgent); +/* 查看通信组件当前状态 */ +HPSOCKET_API En_HP_ServiceState __HP_CALL HP_Agent_GetState(HP_Agent pAgent); +/* 获取连接数 */ +HPSOCKET_API DWORD __HP_CALL HP_Agent_GetConnectionCount(HP_Agent pAgent); +/* 获取所有连接的 HP_CONNID */ +HPSOCKET_API BOOL __HP_CALL HP_Agent_GetAllConnectionIDs(HP_Agent pAgent, HP_CONNID pIDs[], DWORD* pdwCount); +/* 获取某个连接时长(毫秒) */ +HPSOCKET_API BOOL __HP_CALL HP_Agent_GetConnectPeriod(HP_Agent pAgent, HP_CONNID dwConnID, DWORD* pdwPeriod); +/* 获取某个连接静默时间(毫秒) */ +HPSOCKET_API BOOL __HP_CALL HP_Agent_GetSilencePeriod(HP_Agent pAgent, HP_CONNID dwConnID, DWORD* pdwPeriod); +/* 获取某个连接的本地地址信息 */ +HPSOCKET_API BOOL __HP_CALL HP_Agent_GetLocalAddress(HP_Agent pAgent, HP_CONNID dwConnID, TCHAR lpszAddress[], int* piAddressLen, USHORT* pusPort); +/* 获取某个连接的远程地址信息 */ +HPSOCKET_API BOOL __HP_CALL HP_Agent_GetRemoteAddress(HP_Agent pAgent, HP_CONNID dwConnID, TCHAR lpszAddress[], int* piAddressLen, USHORT* pusPort); +/* 获取某个连接的远程主机信息 */ +HPSOCKET_API BOOL __HP_CALL HP_Agent_GetRemoteHost(HP_Agent pAgent, HP_CONNID dwConnID, TCHAR lpszHost[], int* piHostLen, USHORT* pusPort); +/* 获取最近一次失败操作的错误代码 */ +HPSOCKET_API En_HP_SocketError __HP_CALL HP_Agent_GetLastError(HP_Agent pAgent); +/* 获取最近一次失败操作的错误描述 */ +HPSOCKET_API LPCTSTR __HP_CALL HP_Agent_GetLastErrorDesc(HP_Agent pAgent); +/* 获取连接中未发出数据的长度 */ +HPSOCKET_API BOOL __HP_CALL HP_Agent_GetPendingDataLength(HP_Agent pAgent, HP_CONNID dwConnID, int* piPending); +/* 获取连接的数据接收状态 */ +HPSOCKET_API BOOL __HP_CALL HP_Agent_IsPauseReceive(HP_Agent pAgent, HP_CONNID dwConnID, BOOL* pbPaused); +/* 检测是否有效连接 */ +HPSOCKET_API BOOL __HP_CALL HP_Agent_IsConnected(HP_Agent pAgent, HP_CONNID dwConnID); + +/* 设置地址重用选项 */ +HPSOCKET_API void __HP_CALL HP_Agent_SetReuseAddressPolicy(HP_Agent pAgent, En_HP_ReuseAddressPolicy enReusePolicy); +/* 设置数据发送策略(对 Linux 平台组件无效) */ +HPSOCKET_API void __HP_CALL HP_Agent_SetSendPolicy(HP_Agent pAgent, En_HP_SendPolicy enSendPolicy); +/* 设置 OnSend 事件同步策略(对 Linux 平台组件无效) */ +HPSOCKET_API void __HP_CALL HP_Agent_SetOnSendSyncPolicy(HP_Agent pAgent, En_HP_OnSendSyncPolicy enSyncPolicy); +/* 设置 Socket 缓存对象锁定时间(毫秒,在锁定期间该 Socket 缓存对象不能被获取使用) */ +HPSOCKET_API void __HP_CALL HP_Agent_SetFreeSocketObjLockTime(HP_Agent pAgent, DWORD dwFreeSocketObjLockTime); +/* 设置 Socket 缓存池大小(通常设置为平均并发连接数量的 1/3 - 1/2) */ +HPSOCKET_API void __HP_CALL HP_Agent_SetFreeSocketObjPool(HP_Agent pAgent, DWORD dwFreeSocketObjPool); +/* 设置内存块缓存池大小(通常设置为 Socket 缓存池大小的 2 - 3 倍) */ +HPSOCKET_API void __HP_CALL HP_Agent_SetFreeBufferObjPool(HP_Agent pAgent, DWORD dwFreeBufferObjPool); +/* 设置 Socket 缓存池回收阀值(通常设置为 Socket 缓存池大小的 3 倍) */ +HPSOCKET_API void __HP_CALL HP_Agent_SetFreeSocketObjHold(HP_Agent pAgent, DWORD dwFreeSocketObjHold); +/* 设置内存块缓存池回收阀值(通常设置为内存块缓存池大小的 3 倍) */ +HPSOCKET_API void __HP_CALL HP_Agent_SetFreeBufferObjHold(HP_Agent pAgent, DWORD dwFreeBufferObjHold); +/* 设置最大连接数(组件会根据设置值预分配内存,因此需要根据实际情况设置,不宜过大)*/ +HPSOCKET_API void __HP_CALL HP_Agent_SetMaxConnectionCount(HP_Agent pAgent, DWORD dwMaxConnectionCount); +/* 设置工作线程数量(通常设置为 2 * CPU + 2) */ +HPSOCKET_API void __HP_CALL HP_Agent_SetWorkerThreadCount(HP_Agent pAgent, DWORD dwWorkerThreadCount); +/* 设置是否标记静默时间(设置为 TRUE 时 DisconnectSilenceConnections() 和 GetSilencePeriod() 才有效,默认:TRUE) */ +HPSOCKET_API void __HP_CALL HP_Agent_SetMarkSilence(HP_Agent pAgent, BOOL bMarkSilence); + +/* 获取地址重用选项 */ +HPSOCKET_API En_HP_ReuseAddressPolicy __HP_CALL HP_Agent_GetReuseAddressPolicy(HP_Agent pAgent); +/* 获取数据发送策略(对 Linux 平台组件无效) */ +HPSOCKET_API En_HP_SendPolicy __HP_CALL HP_Agent_GetSendPolicy(HP_Agent pAgent); +/* 获取 OnSend 事件同步策略(对 Linux 平台组件无效) */ +HPSOCKET_API En_HP_OnSendSyncPolicy __HP_CALL HP_Agent_GetOnSendSyncPolicy(HP_Agent pAgent); +/* 获取 Socket 缓存对象锁定时间 */ +HPSOCKET_API DWORD __HP_CALL HP_Agent_GetFreeSocketObjLockTime(HP_Agent pAgent); +/* 获取 Socket 缓存池大小 */ +HPSOCKET_API DWORD __HP_CALL HP_Agent_GetFreeSocketObjPool(HP_Agent pAgent); +/* 获取内存块缓存池大小 */ +HPSOCKET_API DWORD __HP_CALL HP_Agent_GetFreeBufferObjPool(HP_Agent pAgent); +/* 获取 Socket 缓存池回收阀值 */ +HPSOCKET_API DWORD __HP_CALL HP_Agent_GetFreeSocketObjHold(HP_Agent pAgent); +/* 获取内存块缓存池回收阀值 */ +HPSOCKET_API DWORD __HP_CALL HP_Agent_GetFreeBufferObjHold(HP_Agent pAgent); +/* 获取最大连接数 */ +HPSOCKET_API DWORD __HP_CALL HP_Agent_GetMaxConnectionCount(HP_Agent pAgent); +/* 获取工作线程数量 */ +HPSOCKET_API DWORD __HP_CALL HP_Agent_GetWorkerThreadCount(HP_Agent pAgent); +/* 检测是否标记静默时间 */ +HPSOCKET_API BOOL __HP_CALL HP_Agent_IsMarkSilence(HP_Agent pAgent); + +/**********************************************************************************/ +/******************************* TCP Agent 操作方法 *******************************/ + +/* +* 名称:发送小文件 +* 描述:向指定连接发送 4096 KB 以下的小文件 +* +* 参数: dwConnID -- 连接 ID +* lpszFileName -- 文件路径 +* pHead -- 头部附加数据 +* pTail -- 尾部附加数据 +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取系统错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_TcpAgent_SendSmallFile(HP_Agent pAgent, HP_CONNID dwConnID, LPCTSTR lpszFileName, const LPWSABUF pHead, const LPWSABUF pTail); + +/**********************************************************************************/ +/***************************** TCP Agent 属性访问方法 *****************************/ + +/* 设置同步连接超时时间(毫秒) */ +HPSOCKET_API void __HP_CALL HP_TcpAgent_SetSyncConnectTimeout(HP_TcpAgent pAgent, DWORD dwSyncConnectTimeout); +/* 设置通信数据缓冲区大小(根据平均通信数据包大小调整设置,通常设置为 1024 的倍数) */ +HPSOCKET_API void __HP_CALL HP_TcpAgent_SetSocketBufferSize(HP_TcpAgent pAgent, DWORD dwSocketBufferSize); +/* 设置正常心跳包间隔(毫秒,0 则不发送心跳包,默认:60 * 1000) */ +HPSOCKET_API void __HP_CALL HP_TcpAgent_SetKeepAliveTime(HP_TcpAgent pAgent, DWORD dwKeepAliveTime); +/* 设置异常心跳包间隔(毫秒,0 不发送心跳包,,默认:20 * 1000,如果超过若干次 [默认:WinXP 5 次, Win7 10 次] 检测不到心跳确认包则认为已断线) */ +HPSOCKET_API void __HP_CALL HP_TcpAgent_SetKeepAliveInterval(HP_TcpAgent pAgent, DWORD dwKeepAliveInterval); +/* 设置是否开启 nodelay 模式(默认:FALSE,不开启) */ +HPSOCKET_API void __HP_CALL HP_TcpAgent_SetNoDelay(HP_TcpAgent pAgent, BOOL bNoDelay); + +/* 获取同步连接超时时间 */ +HPSOCKET_API DWORD __HP_CALL HP_TcpAgent_GetSyncConnectTimeout(HP_TcpAgent pAgent); +/* 获取通信数据缓冲区大小 */ +HPSOCKET_API DWORD __HP_CALL HP_TcpAgent_GetSocketBufferSize(HP_TcpAgent pAgent); +/* 获取正常心跳包间隔 */ +HPSOCKET_API DWORD __HP_CALL HP_TcpAgent_GetKeepAliveTime(HP_TcpAgent pAgent); +/* 获取异常心跳包间隔 */ +HPSOCKET_API DWORD __HP_CALL HP_TcpAgent_GetKeepAliveInterval(HP_TcpAgent pAgent); +/* 检查是否开启 nodelay 模式 */ +HPSOCKET_API BOOL __HP_CALL HP_TcpAgent_IsNoDelay(HP_TcpAgent pAgent); + +/******************************************************************************/ +/***************************** Client 组件操作方法 *****************************/ + +/* +* 名称:启动通信组件 +* 描述:启动客户端通信组件并连接服务端,启动完成后可开始收发数据 +* +* 参数: lpszRemoteAddress -- 服务端地址 +* usPort -- 服务端端口 +* bAsyncConnect -- 是否采用异步 Connect +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 HP_Client_GetLastError() 获取错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Client_Start(HP_Client pClient, LPCTSTR lpszRemoteAddress, USHORT usPort, BOOL bAsyncConnect); + +/* +* 名称:启动通信组件(并指定绑定地址) +* 描述:启动客户端通信组件并连接服务端,启动完成后可开始收发数据 +* +* 参数: lpszRemoteAddress -- 服务端地址 +* usPort -- 服务端端口 +* bAsyncConnect -- 是否采用异步 Connect +* lpszBindAddress -- 绑定地址(默认:nullptr,TcpClient/UdpClient -> 不执行绑定操作,UdpCast 绑定 -> 任意地址) +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 HP_Client_GetLastError() 获取错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Client_StartWithBindAddress(HP_Client pClient, LPCTSTR lpszRemoteAddress, USHORT usPort, BOOL bAsyncConnect, LPCTSTR lpszBindAddress); + +/* +* 名称:启动通信组件(并指定绑定地址) +* 描述:启动客户端通信组件并连接服务端,启动完成后可开始收发数据 +* +* 参数: lpszRemoteAddress -- 服务端地址 +* usPort -- 服务端端口 +* bAsyncConnect -- 是否采用异步 Connect +* lpszBindAddress -- 绑定地址(默认:nullptr,TcpClient/UdpClient -> 不执行绑定操作,UdpCast 绑定 -> 任意地址) +* usLocalPort -- 本地端口(默认:0) +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 HP_Client_GetLastError() 获取错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Client_StartWithBindAddressAndLocalPort(HP_Client pClient, LPCTSTR lpszRemoteAddress, USHORT usPort, BOOL bAsyncConnect, LPCTSTR lpszBindAddress, USHORT usLocalPort); + +/* +* 名称:关闭通信组件 +* 描述:关闭客户端通信组件,关闭完成后断开与服务端的连接并释放所有资源 +* +* 参数: +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 HP_Client_GetLastError() 获取错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Client_Stop(HP_Client pClient); + +/* +* 名称:发送数据 +* 描述:向服务端发送数据 +* +* 参数: pBuffer -- 发送缓冲区 +* iLength -- 发送缓冲区长度 +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取系统错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Client_Send(HP_Client pClient, const BYTE* pBuffer, int iLength); + +/* +* 名称:发送数据 +* 描述:向服务端发送数据 +* +* 参数: pBuffer -- 发送缓冲区 +* iLength -- 发送缓冲区长度 +* iOffset -- 发送缓冲区指针偏移量 +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取系统错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Client_SendPart(HP_Client pClient, const BYTE* pBuffer, int iLength, int iOffset); + +/* +* 名称:发送多组数据 +* 描述:向服务端发送多组数据 +* TCP - 顺序发送所有数据包 +* UDP - 把所有数据包组合成一个数据包发送(数据包的总长度不能大于设置的 UDP 包最大长度) +* +* 参数: pBuffers -- 发送缓冲区数组 +* iCount -- 发送缓冲区数目 +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取系统错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Client_SendPackets(HP_Client pClient, const WSABUF pBuffers[], int iCount); + +/* +* 名称:暂停/恢复接收 +* 描述:暂停/恢复某个连接的数据接收工作 +* +* bPause -- TRUE - 暂停, FALSE - 恢复 +* 返回值: TRUE -- 成功 +* FALSE -- 失败 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Client_PauseReceive(HP_Client pClient, BOOL bPause); + +/* +* 名称:等待 +* 描述:等待通信组件停止运行 +* +* 参数: dwMilliseconds -- 超时时间(毫秒,默认:-1,永不超时) +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Client_Wait(HP_Client pClient, DWORD dwMilliseconds); + +/******************************************************************************/ +/***************************** Client 属性访问方法 *****************************/ + +/* 设置连接的附加数据 */ +HPSOCKET_API void __HP_CALL HP_Client_SetExtra(HP_Client pClient, PVOID pExtra); +/* 获取连接的附加数据 */ +HPSOCKET_API PVOID __HP_CALL HP_Client_GetExtra(HP_Client pClient); + +/* 检测是否为安全连接(SSL/HTTPS) */ +HPSOCKET_API BOOL __HP_CALL HP_Client_IsSecure(HP_Client pClient); +/* 检查通信组件是否已启动 */ +HPSOCKET_API BOOL __HP_CALL HP_Client_HasStarted(HP_Client pClient); +/* 查看通信组件当前状态 */ +HPSOCKET_API En_HP_ServiceState __HP_CALL HP_Client_GetState(HP_Client pClient); +/* 获取最近一次失败操作的错误代码 */ +HPSOCKET_API En_HP_SocketError __HP_CALL HP_Client_GetLastError(HP_Client pClient); +/* 获取最近一次失败操作的错误描述 */ +HPSOCKET_API LPCTSTR __HP_CALL HP_Client_GetLastErrorDesc(HP_Client pClient); +/* 获取该组件对象的连接 ID */ +HPSOCKET_API HP_CONNID __HP_CALL HP_Client_GetConnectionID(HP_Client pClient); +/* 获取 Client Socket 的地址信息 */ +HPSOCKET_API BOOL __HP_CALL HP_Client_GetLocalAddress(HP_Client pClient, TCHAR lpszAddress[], int* piAddressLen, USHORT* pusPort); +/* 获取连接的远程主机信息 */ +HPSOCKET_API BOOL __HP_CALL HP_Client_GetRemoteHost(HP_Client pClient, TCHAR lpszHost[], int* piHostLen, USHORT* pusPort); +/* 获取连接中未发出数据的长度 */ +HPSOCKET_API BOOL __HP_CALL HP_Client_GetPendingDataLength(HP_Client pClient, int* piPending); +/* 获取连接的数据接收状态 */ +HPSOCKET_API BOOL __HP_CALL HP_Client_IsPauseReceive(HP_Client pClient, BOOL* pbPaused); +/* 检测是否有效连接 */ +HPSOCKET_API BOOL __HP_CALL HP_Client_IsConnected(HP_Client pClient); + +/* 设置地址重用选项 */ +HPSOCKET_API void __HP_CALL HP_Client_SetReuseAddressPolicy(HP_Client pClient, En_HP_ReuseAddressPolicy enReusePolicy); +/* 设置内存块缓存池大小(通常设置为 -> PUSH 模型:5 - 10;PULL 模型:10 - 20 ) */ +HPSOCKET_API void __HP_CALL HP_Client_SetFreeBufferPoolSize(HP_Client pClient, DWORD dwFreeBufferPoolSize); +/* 设置内存块缓存池回收阀值(通常设置为内存块缓存池大小的 3 倍) */ +HPSOCKET_API void __HP_CALL HP_Client_SetFreeBufferPoolHold(HP_Client pClient, DWORD dwFreeBufferPoolHold); + +/* 获取地址重用选项 */ +HPSOCKET_API En_HP_ReuseAddressPolicy __HP_CALL HP_Client_GetReuseAddressPolicy(HP_Client pClient); +/* 获取内存块缓存池大小 */ +HPSOCKET_API DWORD __HP_CALL HP_Client_GetFreeBufferPoolSize(HP_Client pClient); +/* 获取内存块缓存池回收阀值 */ +HPSOCKET_API DWORD __HP_CALL HP_Client_GetFreeBufferPoolHold(HP_Client pClient); + +/**********************************************************************************/ +/******************************* TCP Client 操作方法 *******************************/ + +/* +* 名称:发送小文件 +* 描述:向服务端发送 4096 KB 以下的小文件 +* +* 参数: lpszFileName -- 文件路径 +* pHead -- 头部附加数据 +* pTail -- 尾部附加数据 +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取系统错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_TcpClient_SendSmallFile(HP_Client pClient, LPCTSTR lpszFileName, const LPWSABUF pHead, const LPWSABUF pTail); + +/**********************************************************************************/ +/***************************** TCP Client 属性访问方法 *****************************/ + +/* 设置同步连接超时时间(毫秒) */ +HPSOCKET_API void __HP_CALL HP_TcpClient_SetSyncConnectTimeout(HP_TcpClient pClient, DWORD dwSyncConnectTimeout); +/* 设置通信数据缓冲区大小(根据平均通信数据包大小调整设置,通常设置为:(N * 1024) - sizeof(TBufferObj)) */ +HPSOCKET_API void __HP_CALL HP_TcpClient_SetSocketBufferSize(HP_TcpClient pClient, DWORD dwSocketBufferSize); +/* 设置正常心跳包间隔(毫秒,0 则不发送心跳包,默认:60 * 1000) */ +HPSOCKET_API void __HP_CALL HP_TcpClient_SetKeepAliveTime(HP_TcpClient pClient, DWORD dwKeepAliveTime); +/* 设置异常心跳包间隔(毫秒,0 不发送心跳包,,默认:20 * 1000,如果超过若干次 [默认:WinXP 5 次, Win7 10 次] 检测不到心跳确认包则认为已断线) */ +HPSOCKET_API void __HP_CALL HP_TcpClient_SetKeepAliveInterval(HP_TcpClient pClient, DWORD dwKeepAliveInterval); +/* 设置是否开启 nodelay 模式(默认:FALSE,不开启) */ +HPSOCKET_API void __HP_CALL HP_TcpClient_SetNoDelay(HP_TcpClient pClient, BOOL bNoDelay); + +/* 获取同步连接超时时间 */ +HPSOCKET_API DWORD __HP_CALL HP_TcpClient_GetSyncConnectTimeout(HP_TcpClient pClient); +/* 获取通信数据缓冲区大小 */ +HPSOCKET_API DWORD __HP_CALL HP_TcpClient_GetSocketBufferSize(HP_TcpClient pClient); +/* 获取正常心跳包间隔 */ +HPSOCKET_API DWORD __HP_CALL HP_TcpClient_GetKeepAliveTime(HP_TcpClient pClient); +/* 获取异常心跳包间隔 */ +HPSOCKET_API DWORD __HP_CALL HP_TcpClient_GetKeepAliveInterval(HP_TcpClient pClient); +/* 检查是否开启 nodelay 模式 */ +HPSOCKET_API BOOL __HP_CALL HP_TcpClient_IsNoDelay(HP_TcpClient pClient); + +#ifdef _UDP_SUPPORT + +/**********************************************************************************/ +/***************************** UDP Client 属性访问方法 *****************************/ + +/* 设置数据报文最大长度(建议在局域网环境下不超过 1432 字节,在广域网环境下不超过 548 字节) */ +HPSOCKET_API void __HP_CALL HP_UdpClient_SetMaxDatagramSize(HP_UdpClient pClient, DWORD dwMaxDatagramSize); +/* 获取数据报文最大长度 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpClient_GetMaxDatagramSize(HP_UdpClient pClient); + +/* 设置监测包尝试次数(0 则不发送监测跳包,如果超过最大尝试次数则认为已断线) */ +HPSOCKET_API void __HP_CALL HP_UdpClient_SetDetectAttempts(HP_UdpClient pClient, DWORD dwDetectAttempts); +/* 设置监测包发送间隔(毫秒,0 不发送监测包) */ +HPSOCKET_API void __HP_CALL HP_UdpClient_SetDetectInterval(HP_UdpClient pClient, DWORD dwDetectInterval); +/* 获取心跳检查次数 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpClient_GetDetectAttempts(HP_UdpClient pClient); +/* 获取心跳检查间隔 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpClient_GetDetectInterval(HP_UdpClient pClient); + +/**********************************************************************************/ +/*************************** UDP ARQ Client 属性访问方法 ***************************/ + +/* 设置是否开启 nodelay 模式(默认:FALSE,不开启) */ +HPSOCKET_API void __HP_CALL HP_UdpArqClient_SetNoDelay(HP_UdpArqClient pClient, BOOL bNoDelay); +/* 设置是否关闭拥塞控制(默认:FALSE,不关闭) */ +HPSOCKET_API void __HP_CALL HP_UdpArqClient_SetTurnoffCongestCtrl(HP_UdpArqClient pClient, BOOL bTurnOff); +/* 设置数据刷新间隔(毫秒,默认:60) */ +HPSOCKET_API void __HP_CALL HP_UdpArqClient_SetFlushInterval(HP_UdpArqClient pClient, DWORD dwFlushInterval); +/* 设置快速重传 ACK 跨越次数(默认:0,关闭快速重传) */ +HPSOCKET_API void __HP_CALL HP_UdpArqClient_SetResendByAcks(HP_UdpArqClient pClient, DWORD dwResendByAcks); +/* 设置发送窗口大小(数据包数量,默认:128) */ +HPSOCKET_API void __HP_CALL HP_UdpArqClient_SetSendWndSize(HP_UdpArqClient pClient, DWORD dwSendWndSize); +/* 设置接收窗口大小(数据包数量,默认:512) */ +HPSOCKET_API void __HP_CALL HP_UdpArqClient_SetRecvWndSize(HP_UdpArqClient pClient, DWORD dwRecvWndSize); +/* 设置最小重传超时时间(毫秒,默认:30) */ +HPSOCKET_API void __HP_CALL HP_UdpArqClient_SetMinRto(HP_UdpArqClient pClient, DWORD dwMinRto); +/* 设置快速握手次数限制(默认:5,如果为 0 则不限制) */ +HPSOCKET_API void __HP_CALL HP_UdpArqClient_SetFastLimit(HP_UdpArqClient pClient, DWORD dwFastLimit); +/* 设置最大传输单元(默认:0,与 SetMaxDatagramSize() 一致) */ +HPSOCKET_API void __HP_CALL HP_UdpArqClient_SetMaxTransUnit(HP_UdpArqClient pClient, DWORD dwMaxTransUnit); +/* 设置最大数据包大小(默认:4096) */ +HPSOCKET_API void __HP_CALL HP_UdpArqClient_SetMaxMessageSize(HP_UdpArqClient pClient, DWORD dwMaxMessageSize); +/* 设置握手超时时间(毫秒,默认:5000) */ +HPSOCKET_API void __HP_CALL HP_UdpArqClient_SetHandShakeTimeout(HP_UdpArqClient pClient, DWORD dwHandShakeTimeout); + +/* 检测是否开启 nodelay 模式 */ +HPSOCKET_API BOOL __HP_CALL HP_UdpArqClient_IsNoDelay(HP_UdpArqClient pClient); +/* 检测是否关闭拥塞控制 */ +HPSOCKET_API BOOL __HP_CALL HP_UdpArqClient_IsTurnoffCongestCtrl(HP_UdpArqClient pClient); +/* 获取数据刷新间隔 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpArqClient_GetFlushInterval(HP_UdpArqClient pClient); +/* 获取快速重传 ACK 跨越次数 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpArqClient_GetResendByAcks(HP_UdpArqClient pClient); +/* 获取发送窗口大小 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpArqClient_GetSendWndSize(HP_UdpArqClient pClient); +/* 获取接收窗口大小 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpArqClient_GetRecvWndSize(HP_UdpArqClient pClient); +/* 获取最小重传超时时间 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpArqClient_GetMinRto(HP_UdpArqClient pClient); +/* 获取快速握手次数限制 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpArqClient_GetFastLimit(HP_UdpArqClient pClient); +/* 获取最大传输单元 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpArqClient_GetMaxTransUnit(HP_UdpArqClient pClient); +/* 获取最大数据包大小 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpArqClient_GetMaxMessageSize(HP_UdpArqClient pClient); +/* 获取握手超时时间 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpArqClient_GetHandShakeTimeout(HP_UdpArqClient pClient); + +/* 获取等待发送包数量 */ +HPSOCKET_API BOOL __HP_CALL HP_UdpArqClient_GetWaitingSendMessageCount(HP_UdpArqClient pClient, int* piCount); + +/**********************************************************************************/ +/****************************** UDP Cast 属性访问方法 ******************************/ + +/* 设置数据报文最大长度(建议在局域网环境下不超过 1432 字节,在广域网环境下不超过 548 字节) */ +HPSOCKET_API void __HP_CALL HP_UdpCast_SetMaxDatagramSize(HP_UdpCast pCast, DWORD dwMaxDatagramSize); +/* 获取数据报文最大长度 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpCast_GetMaxDatagramSize(HP_UdpCast pCast); +/* 获取当前数据报的远程地址信息(通常在 OnReceive 事件中调用) */ +HPSOCKET_API BOOL __HP_CALL HP_UdpCast_GetRemoteAddress(HP_UdpCast pCast, TCHAR lpszAddress[], int* piAddressLen, USHORT* pusPort); +/* 设置传播模式(组播或广播) */ +HPSOCKET_API void __HP_CALL HP_UdpCast_SetCastMode(HP_UdpCast pCast, En_HP_CastMode enCastMode); +/* 获取传播模式 */ +HPSOCKET_API En_HP_CastMode __HP_CALL HP_UdpCast_GetCastMode(HP_UdpCast pCast); +/* 设置组播报文的 TTL(0 - 255) */ +HPSOCKET_API void __HP_CALL HP_UdpCast_SetMultiCastTtl(HP_UdpCast pCast, int iMCTtl); +/* 获取组播报文的 TTL */ +HPSOCKET_API int __HP_CALL HP_UdpCast_GetMultiCastTtl(HP_UdpCast pCast); +/* 设置是否启用组播环路(TRUE or FALSE) */ +HPSOCKET_API void __HP_CALL HP_UdpCast_SetMultiCastLoop(HP_UdpCast pCast, BOOL bMCLoop); +/* 检测是否启用组播环路 */ +HPSOCKET_API BOOL __HP_CALL HP_UdpCast_IsMultiCastLoop(HP_UdpCast pCast); + +/**********************************************************************************/ +/****************************** UDP Node 组件操作方法 ******************************/ + +/* +* 名称:启动通信组件 +* 描述:启动 UDP 节点通信组件,启动完成后可开始收发数据 +* +* 参数: lpszBindAddress -- 绑定地址(默认:nullptr,绑定任意地址) +* usPort -- 本地端口(默认:0) +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 GetLastError() 获取错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_Start(HP_UdpNode pNode, LPCTSTR lpszBindAddress, USHORT usPort); + +/* +* 名称:启动通信组件 +* 描述:启动 UDP 节点通信组件,启动完成后可开始收发数据 +* +* 参数: lpszBindAddress -- 绑定地址(默认:nullptr,绑定任意地址) +* usPort -- 本地端口(默认:0) +* enCastMode -- 传播模式(默认:CM_UNICAST) +* lpszCastAddress -- 传播地址(默认:nullptr,当 enCaseMode 为 CM_MULTICAST 或 CM_BROADCAST 时有效) +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 GetLastError() 获取错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_StartWithCast(HP_UdpNode pNode, LPCTSTR lpszBindAddress, USHORT usPort, En_HP_CastMode enCastMode, LPCTSTR lpszCastAddress); + +/* +* 名称:关闭通信组件 +* 描述:关闭 UDP 节点通信组件,关闭完成后释放所有资源 +* +* 参数: +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 GetLastError() 获取错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_Stop(HP_UdpNode pNode); + +/* +* 名称:等待 +* 描述:等待通信组件停止运行 +* +* 参数: dwMilliseconds -- 超时时间(毫秒,默认:-1,永不超时) +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_Wait(HP_UdpNode pNode, DWORD dwMilliseconds); + +/* +* 名称:发送数据 +* 描述:向指定地址发送数据 +* +* 参数: lpszRemoteAddress -- 远程地址 +* usRemotePort -- 远程端口 +* pBuffer -- 发送缓冲区 +* iLength -- 发送缓冲区长度 +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_Send(HP_UdpNode pNode, LPCTSTR lpszRemoteAddress, USHORT usRemotePort, const BYTE* pBuffer, int iLength); + +/* +* 名称:发送数据 +* 描述:向指定地址发送数据 +* +* 参数: lpszRemoteAddress -- 远程地址 +* usRemotePort -- 远程端口 +* pBuffer -- 发送缓冲区 +* iLength -- 发送缓冲区长度 +* iOffset -- 发送缓冲区指针偏移量 +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_SendPart(HP_UdpNode pNode, LPCTSTR lpszRemoteAddress, USHORT usRemotePort, const BYTE* pBuffer, int iLength, int iOffset); + +/* +* 名称:发送多组数据 +* 描述:向指定地址发送多组数据,把所有数据包组合成一个数据包发送(数据包的总长度不能大于设置的 UDP 包最大长度) +* +* 参数: lpszRemoteAddress -- 远程地址 +* usRemotePort -- 远程端口 +* pBuffers -- 发送缓冲区数组 +* iCount -- 发送缓冲区数目 +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_SendPackets(HP_UdpNode pNode, LPCTSTR lpszRemoteAddress, USHORT usRemotePort, const WSABUF pBuffers[], int iCount); + +/* +* 名称:发送数据 +* 描述:向传播地址发送数据 +* +* 参数: pBuffer -- 发送缓冲区 +* iLength -- 发送缓冲区长度 +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_SendCast(HP_UdpNode pNode, const BYTE* pBuffer, int iLength); + +/* +* 名称:发送数据 +* 描述:向传播地址发送数据 +* +* 参数: pBuffer -- 发送缓冲区 +* iLength -- 发送缓冲区长度 +* iOffset -- 发送缓冲区指针偏移量 +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_SendCastPart(HP_UdpNode pNode, const BYTE* pBuffer, int iLength, int iOffset); + +/* +* 名称:发送多组数据 +* 描述:向传播地址发送多组数据,把所有数据包组合成一个数据包发送(数据包的总长度不能大于设置的 UDP 包最大长度) +* +* 参数: pBuffers -- 发送缓冲区数组 +* iCount -- 发送缓冲区数目 +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_SendCastPackets(HP_UdpNode pNode, const WSABUF pBuffers[], int iCount); + +/**********************************************************************************/ +/****************************** UDP Node 属性访问方法 ******************************/ + +/* 设置附加数据 */ +HPSOCKET_API void __HP_CALL HP_UdpNode_SetExtra(HP_UdpNode pNode, PVOID pExtra); + +/* 获取附加数据 */ +HPSOCKET_API PVOID __HP_CALL HP_UdpNode_GetExtra(HP_UdpNode pNode); + +/* 检查通信组件是否已启动 */ +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_HasStarted(HP_UdpNode pNode); +/* 查看通信组件当前状态 */ +HPSOCKET_API En_HP_ServiceState __HP_CALL HP_UdpNode_GetState(HP_UdpNode pNode); +/* 获取最近一次失败操作的错误代码 */ +HPSOCKET_API En_HP_SocketError __HP_CALL HP_UdpNode_GetLastError(HP_UdpNode pNode); +/* 获取最近一次失败操作的错误描述 */ +HPSOCKET_API LPCTSTR __HP_CALL HP_UdpNode_GetLastErrorDesc(HP_UdpNode pNode); +/* 获取本节点地址 */ +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_GetLocalAddress(HP_UdpNode pNode, TCHAR lpszAddress[], int* piAddressLen, USHORT* pusPort); +/* 获取本节点传播地址 */ +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_GetCastAddress(HP_UdpNode pNode, TCHAR lpszAddress[], int* piAddressLen, USHORT* pusPort); +/* 获取传播模式 */ +HPSOCKET_API En_HP_CastMode __HP_CALL HP_UdpNode_GetCastMode(HP_UdpNode pNode); +/* 获取未发出数据的长度 */ +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_GetPendingDataLength(HP_UdpNode pNode, int* piPending); + +/* 设置数据报文最大长度(建议在局域网环境下不超过 1432 字节,在广域网环境下不超过 548 字节) */ +HPSOCKET_API void __HP_CALL HP_UdpNode_SetMaxDatagramSize(HP_UdpNode pNode, DWORD dwMaxDatagramSize); +/* 获取数据报文最大长度 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpNode_GetMaxDatagramSize(HP_UdpNode pNode); + +/* 设置组播报文的 TTL(0 - 255) */ +HPSOCKET_API void __HP_CALL HP_UdpNode_SetMultiCastTtl(HP_UdpNode pNode, int iMCTtl); +/* 获取组播报文的 TTL */ +HPSOCKET_API int __HP_CALL HP_UdpNode_GetMultiCastTtl(HP_UdpNode pNode); + +/* 设置是否启用组播环路(TRUE or FALSE) */ +HPSOCKET_API void __HP_CALL HP_UdpNode_SetMultiCastLoop(HP_UdpNode pNode, BOOL bMCLoop); +/* 检测是否启用组播环路 */ +HPSOCKET_API BOOL __HP_CALL HP_UdpNode_IsMultiCastLoop(HP_UdpNode pNode); + +/* 设置地址重用选项 */ +HPSOCKET_API void __HP_CALL HP_UdpNode_SetReuseAddressPolicy(HP_UdpNode pNode, En_HP_ReuseAddressPolicy enReusePolicy); +/* 设置工作线程数量(通常设置为 2 * CPU + 2) */ +HPSOCKET_API void __HP_CALL HP_UdpNode_SetWorkerThreadCount(HP_UdpNode pNode, DWORD dwWorkerThreadCount); +/* 设置 Receive 预投递数量(根据负载调整设置,Receive 预投递数量越大则丢包概率越小) */ +HPSOCKET_API void __HP_CALL HP_UdpNode_SetPostReceiveCount(HP_UdpNode pNode, DWORD dwPostReceiveCount); +/* 设置内存块缓存池大小 */ +HPSOCKET_API void __HP_CALL HP_UdpNode_SetFreeBufferPoolSize(HP_UdpNode pNode, DWORD dwFreeBufferPoolSize); +/* 设置内存块缓存池回收阀值 */ +HPSOCKET_API void __HP_CALL HP_UdpNode_SetFreeBufferPoolHold(HP_UdpNode pNode, DWORD dwFreeBufferPoolHold); + +/* 获取地址重用选项 */ +HPSOCKET_API En_HP_ReuseAddressPolicy __HP_CALL HP_UdpNode_GetReuseAddressPolicy(HP_UdpNode pNode); +/* 获取工作线程数量 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpNode_GetWorkerThreadCount(HP_UdpNode pNode); +/* 获取 Receive 预投递数量 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpNode_GetPostReceiveCount(HP_UdpNode pNode); +/* 获取内存块缓存池大小 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpNode_GetFreeBufferPoolSize(HP_UdpNode pNode); +/* 获取内存块缓存池回收阀值 */ +HPSOCKET_API DWORD __HP_CALL HP_UdpNode_GetFreeBufferPoolHold(HP_UdpNode pNode); + +#endif + +/***************************************************************************************/ +/***************************** TCP Pull Server 组件操作方法 *****************************/ + +/* +* 名称:抓取数据 +* 描述:用户通过该方法从 Socket 组件中抓取数据 +* +* 参数: dwConnID -- 连接 ID +* pData -- 抓取缓冲区 +* iLength -- 抓取数据长度 +* 返回值: En_HP_FetchResult +*/ +HPSOCKET_API En_HP_FetchResult __HP_CALL HP_TcpPullServer_Fetch(HP_TcpPullServer pServer, HP_CONNID dwConnID, BYTE* pData, int iLength); + +/* +* 名称:窥探数据(不会移除缓冲区数据) +* 描述:用户通过该方法从 Socket 组件中窥探数据 +* +* 参数: dwConnID -- 连接 ID +* pData -- 窥探缓冲区 +* iLength -- 窥探数据长度 +* 返回值: En_HP_FetchResult +*/ +HPSOCKET_API En_HP_FetchResult __HP_CALL HP_TcpPullServer_Peek(HP_TcpPullServer pServer, HP_CONNID dwConnID, BYTE* pData, int iLength); + +/***************************************************************************************/ +/***************************** TCP Pull Server 属性访问方法 *****************************/ + +/***************************************************************************************/ +/***************************** TCP Pull Agent 组件操作方法 *****************************/ + +/* +* 名称:抓取数据 +* 描述:用户通过该方法从 Socket 组件中抓取数据 +* +* 参数: dwConnID -- 连接 ID +* pData -- 抓取缓冲区 +* iLength -- 抓取数据长度 +* 返回值: En_HP_FetchResult +*/ +HPSOCKET_API En_HP_FetchResult __HP_CALL HP_TcpPullAgent_Fetch(HP_TcpPullAgent pAgent, HP_CONNID dwConnID, BYTE* pData, int iLength); + +/* +* 名称:窥探数据(不会移除缓冲区数据) +* 描述:用户通过该方法从 Socket 组件中窥探数据 +* +* 参数: dwConnID -- 连接 ID +* pData -- 窥探缓冲区 +* iLength -- 窥探数据长度 +* 返回值: En_HP_FetchResult +*/ +HPSOCKET_API En_HP_FetchResult __HP_CALL HP_TcpPullAgent_Peek(HP_TcpPullAgent pAgent, HP_CONNID dwConnID, BYTE* pData, int iLength); + +/***************************************************************************************/ +/***************************** TCP Pull Agent 属性访问方法 *****************************/ + +/***************************************************************************************/ +/***************************** TCP Pull Client 组件操作方法 *****************************/ + +/* +* 名称:抓取数据 +* 描述:用户通过该方法从 Socket 组件中抓取数据 +* +* 参数: dwConnID -- 连接 ID +* pData -- 抓取缓冲区 +* iLength -- 抓取数据长度 +* 返回值: En_HP_FetchResult +*/ +HPSOCKET_API En_HP_FetchResult __HP_CALL HP_TcpPullClient_Fetch(HP_TcpPullClient pClient, BYTE* pData, int iLength); + +/* +* 名称:窥探数据(不会移除缓冲区数据) +* 描述:用户通过该方法从 Socket 组件中窥探数据 +* +* 参数: dwConnID -- 连接 ID +* pData -- 窥探缓冲区 +* iLength -- 窥探数据长度 +* 返回值: En_HP_FetchResult +*/ +HPSOCKET_API En_HP_FetchResult __HP_CALL HP_TcpPullClient_Peek(HP_TcpPullClient pClient, BYTE* pData, int iLength); + +/***************************************************************************************/ +/***************************** TCP Pull Client 属性访问方法 *****************************/ + +/***************************************************************************************/ +/***************************** TCP Pack Server 组件操作方法 *****************************/ + +/***************************************************************************************/ +/***************************** TCP Pack Server 属性访问方法 *****************************/ + +/* 设置数据包最大长度(有效数据包最大长度不能超过 4194303/0x3FFFFF 字节,默认:262144/0x40000) */ +HPSOCKET_API void __HP_CALL HP_TcpPackServer_SetMaxPackSize(HP_TcpPackServer pServer, DWORD dwMaxPackSize); +/* 设置包头标识(有效包头标识取值范围 0 ~ 1023/0x3FF,当包头标识为 0 时不校验包头,默认:0) */ +HPSOCKET_API void __HP_CALL HP_TcpPackServer_SetPackHeaderFlag(HP_TcpPackServer pServer, USHORT usPackHeaderFlag); + +/* 获取数据包最大长度 */ +HPSOCKET_API DWORD __HP_CALL HP_TcpPackServer_GetMaxPackSize(HP_TcpPackServer pServer); +/* 获取包头标识 */ +HPSOCKET_API USHORT __HP_CALL HP_TcpPackServer_GetPackHeaderFlag(HP_TcpPackServer pServer); + +/***************************************************************************************/ +/***************************** TCP Pack Agent 组件操作方法 *****************************/ + +/***************************************************************************************/ +/***************************** TCP Pack Agent 属性访问方法 *****************************/ + +/* 设置数据包最大长度(有效数据包最大长度不能超过 4194303/0x3FFFFF 字节,默认:262144/0x40000) */ +HPSOCKET_API void __HP_CALL HP_TcpPackAgent_SetMaxPackSize(HP_TcpPackAgent pAgent, DWORD dwMaxPackSize); +/* 设置包头标识(有效包头标识取值范围 0 ~ 1023/0x3FF,当包头标识为 0 时不校验包头,默认:0) */ +HPSOCKET_API void __HP_CALL HP_TcpPackAgent_SetPackHeaderFlag(HP_TcpPackAgent pAgent, USHORT usPackHeaderFlag); + +/* 获取数据包最大长度 */ +HPSOCKET_API DWORD __HP_CALL HP_TcpPackAgent_GetMaxPackSize(HP_TcpPackAgent pAgent); +/* 获取包头标识 */ +HPSOCKET_API USHORT __HP_CALL HP_TcpPackAgent_GetPackHeaderFlag(HP_TcpPackAgent pAgent); + +/***************************************************************************************/ +/***************************** TCP Pack Client 组件操作方法 *****************************/ + +/***************************************************************************************/ +/***************************** TCP Pack Client 属性访问方法 *****************************/ + +/* 设置数据包最大长度(有效数据包最大长度不能超过 4194303/0x3FFFFF 字节,默认:262144/0x40000) */ +HPSOCKET_API void __HP_CALL HP_TcpPackClient_SetMaxPackSize(HP_TcpPackClient pClient, DWORD dwMaxPackSize); +/* 设置包头标识(有效包头标识取值范围 0 ~ 1023/0x3FF,当包头标识为 0 时不校验包头,默认:0) */ +HPSOCKET_API void __HP_CALL HP_TcpPackClient_SetPackHeaderFlag(HP_TcpPackClient pClient, USHORT usPackHeaderFlag); + +/* 获取数据包最大长度 */ +HPSOCKET_API DWORD __HP_CALL HP_TcpPackClient_GetMaxPackSize(HP_TcpPackClient pClient); +/* 获取包头标识 */ +HPSOCKET_API USHORT __HP_CALL HP_TcpPackClient_GetPackHeaderFlag(HP_TcpPackClient pClient); + +/*****************************************************************************************************************************************************/ +/*************************************************************** Global Function Exports *************************************************************/ +/*****************************************************************************************************************************************************/ + +// 获取 HPSocket 版本号(4 个字节分别为:主版本号,子版本号,修正版本号,构建编号) +HPSOCKET_API DWORD __HP_CALL HP_GetHPSocketVersion(); + +/* 获取错误描述文本 */ +HPSOCKET_API LPCTSTR __HP_CALL HP_GetSocketErrorDesc(En_HP_SocketError enCode); +// 调用系统的 errno 方法获取系统错误代码 +HPSOCKET_API DWORD __HP_CALL SYS_GetLastError(); +// 调用系统的 strerror() 方法获取系统错误代码描述 +HPSOCKET_API LPCSTR __HP_CALL SYS_GetLastErrorStr(); +// 调用系统的 setsockopt() +HPSOCKET_API int __HP_CALL SYS_SetSocketOption(SOCKET sock, int level, int name, LPVOID val, int len); +// 调用系统的 getsockopt() +HPSOCKET_API int __HP_CALL SYS_GetSocketOption(SOCKET sock, int level, int name, LPVOID val, int* len); +// 调用系统的 ioctlsocket() +HPSOCKET_API int __HP_CALL SYS_IoctlSocket(SOCKET sock, long cmd, ULONG* arg); + +// 调用系统的 fcntl() 设置 F_SETFL 属性 +HPSOCKET_API BOOL __HP_CALL SYS_fcntl_SETFL(FD fd, INT fl, BOOL bSet); + +// 设置 FD 选项:O_NONBLOCK +HPSOCKET_API int __HP_CALL SYS_SSO_NoBlock(SOCKET sock, BOOL bNoBlock); +// 设置 socket 选项:IPPROTO_TCP -> TCP_NODELAY +HPSOCKET_API int __HP_CALL SYS_SSO_NoDelay(SOCKET sock, BOOL bNoDelay); +// 设置 socket 选项:SOL_SOCKET -> SO_DONTLINGER +HPSOCKET_API int __HP_CALL SYS_SSO_DontLinger(SOCKET sock, BOOL bDont); +// 设置 socket 选项:SOL_SOCKET -> SO_LINGER +HPSOCKET_API int __HP_CALL SYS_SSO_Linger(SOCKET sock, USHORT l_onoff, USHORT l_linger); +// 设置 socket 选项:SOL_SOCKET -> SO_RCVBUF +HPSOCKET_API int __HP_CALL SYS_SSO_RecvBuffSize(SOCKET sock, int size); +// 设置 socket 选项:SOL_SOCKET -> SO_SNDBUF +HPSOCKET_API int __HP_CALL SYS_SSO_SendBuffSize(SOCKET sock, int size); +// 设置 socket 选项:SOL_SOCKET -> SO_RCVTIMEO +HPSOCKET_API int __HP_CALL SYS_SSO_RecvTimeOut(SOCKET sock, int ms); +// 设置 socket 选项:SOL_SOCKET -> SO_SNDTIMEO +HPSOCKET_API int __HP_CALL SYS_SSO_SendTimeOut(SOCKET sock, int ms); +// 设置 socket 选项:SOL_SOCKET -> SO_REUSEADDR / SO_REUSEPORT +HPSOCKET_API int __HP_CALL SYS_SSO_ReuseAddress(SOCKET sock, En_HP_ReuseAddressPolicy opt); + +// 获取 SOCKET 本地地址信息 +HPSOCKET_API BOOL __HP_CALL SYS_GetSocketLocalAddress(SOCKET socket, TCHAR lpszAddress[], int* piAddressLen, USHORT* pusPort); +// 获取 SOCKET 远程地址信息 +HPSOCKET_API BOOL __HP_CALL SYS_GetSocketRemoteAddress(SOCKET socket, TCHAR lpszAddress[], int* piAddressLen, USHORT* pusPort); + +/* 枚举主机 IP 地址 */ +HPSOCKET_API BOOL __HP_CALL SYS_EnumHostIPAddresses(LPCTSTR lpszHost, En_HP_IPAddrType enType, HP_LPTIPAddr** lpppIPAddr, int* piIPAddrCount); +/* 释放 HP_LPTIPAddr* */ +HPSOCKET_API BOOL __HP_CALL SYS_FreeHostIPAddresses(HP_LPTIPAddr* lppIPAddr); +/* 检查字符串是否符合 IP 地址格式 */ +HPSOCKET_API BOOL __HP_CALL SYS_IsIPAddress(LPCTSTR lpszAddress, En_HP_IPAddrType* penType); +/* 通过主机名获取 IP 地址 */ +HPSOCKET_API BOOL __HP_CALL SYS_GetIPAddress(LPCTSTR lpszHost, TCHAR lpszIP[], int* piIPLenth, En_HP_IPAddrType* penType); + +/* 64 位网络字节序转主机字节序 */ +HPSOCKET_API ULONGLONG __HP_CALL SYS_NToH64(ULONGLONG value); +/* 64 位主机字节序转网络字节序 */ +HPSOCKET_API ULONGLONG __HP_CALL SYS_HToN64(ULONGLONG value); +/* 短整型高低字节交换 */ +HPSOCKET_API USHORT __HP_CALL SYS_SwapEndian16(USHORT value); +/* 长整型高低字节交换 */ +HPSOCKET_API DWORD __HP_CALL SYS_SwapEndian32(DWORD value); +/* 检查是否小端字节序 */ +HPSOCKET_API BOOL __HP_CALL SYS_IsLittleEndian(); + +/* 分配内存 */ +HPSOCKET_API LPBYTE __HP_CALL SYS_Malloc(int size); +/* 重新分配内存 */ +HPSOCKET_API LPBYTE __HP_CALL SYS_Realloc(LPBYTE p, int size); +/* 释放内存 */ +HPSOCKET_API VOID __HP_CALL SYS_Free(LPBYTE p); +/* 分配内存块 */ +HPSOCKET_API LPVOID __HP_CALL SYS_Calloc(int number, int size); + +// 计算 Base64 编码后长度 +HPSOCKET_API DWORD __HP_CALL SYS_GuessBase64EncodeBound(DWORD dwSrcLen); +// 计算 Base64 解码后长度 +HPSOCKET_API DWORD __HP_CALL SYS_GuessBase64DecodeBound(const BYTE* lpszSrc, DWORD dwSrcLen); +// Base64 编码(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +HPSOCKET_API int __HP_CALL SYS_Base64Encode(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD* pdwDestLen); +// Base64 解码(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +HPSOCKET_API int __HP_CALL SYS_Base64Decode(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD* pdwDestLen); + +// 计算 URL 编码后长度 +HPSOCKET_API DWORD __HP_CALL SYS_GuessUrlEncodeBound(const BYTE* lpszSrc, DWORD dwSrcLen); +// 计算 URL 解码后长度 +HPSOCKET_API DWORD __HP_CALL SYS_GuessUrlDecodeBound(const BYTE* lpszSrc, DWORD dwSrcLen); +// URL 编码(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +HPSOCKET_API int __HP_CALL SYS_UrlEncode(BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD* pdwDestLen); +// URL 解码(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +HPSOCKET_API int __HP_CALL SYS_UrlDecode(BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD* pdwDestLen); + +#ifdef _ZLIB_SUPPORT + +// 普通压缩(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +//(默认参数:iLevel -> -1,iMethod -> 8,iWindowBits -> 15,iMemLevel -> 8,iStrategy -> 0) +HPSOCKET_API int __HP_CALL SYS_Compress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD* pdwDestLen); +// 高级压缩(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +HPSOCKET_API int __HP_CALL SYS_CompressEx(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD* pdwDestLen, int iLevel /*= -1*/, int iMethod /*= 8*/, int iWindowBits /*= 15*/, int iMemLevel /*= 8*/, int iStrategy /*= 0*/); +// 普通解压(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +// (默认参数:iWindowBits -> 15) +HPSOCKET_API int __HP_CALL SYS_Uncompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD* pdwDestLen); +// 高级解压(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +HPSOCKET_API int __HP_CALL SYS_UncompressEx(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD* pdwDestLen, int iWindowBits /*= 15*/); +// 推测压缩结果长度 +HPSOCKET_API DWORD __HP_CALL SYS_GuessCompressBound(DWORD dwSrcLen, BOOL bGZip); + +// Gzip 压缩(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +HPSOCKET_API int __HP_CALL SYS_GZipCompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD* pdwDestLen); +// Gzip 解压(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +HPSOCKET_API int __HP_CALL SYS_GZipUncompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD* pdwDestLen); +// 推测 Gzip 解压结果长度(如果返回 0 或不合理值则说明输入内容并非有效的 Gzip 格式) +HPSOCKET_API DWORD __HP_CALL SYS_GZipGuessUncompressBound(const BYTE* lpszSrc, DWORD dwSrcLen); + +#endif + +#ifdef _BROTLI_SUPPORT + +// Brotli 压缩(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +//(默认参数:iQuality -> 11,iWindow -> 22,iMode -> 0) +HPSOCKET_API int __HP_CALL SYS_BrotliCompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD* pdwDestLen); +// Brotli 高级压缩(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +HPSOCKET_API int __HP_CALL SYS_BrotliCompressEx(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD* pdwDestLen, int iQuality /*= 11*/, int iWindow /*= 22*/, int iMode /*= 0*/); +// Brotli 解压(返回值:0 -> 成功,-3 -> 输入数据不正确,-5 -> 输出缓冲区不足) +HPSOCKET_API int __HP_CALL SYS_BrotliUncompress(const BYTE* lpszSrc, DWORD dwSrcLen, BYTE* lpszDest, DWORD* pdwDestLen); +// Brotli 推测压缩结果长度 +HPSOCKET_API DWORD __HP_CALL SYS_BrotliGuessCompressBound(DWORD dwSrcLen); + +#endif + +#ifdef _ICONV_SUPPORT + +// Charset A -> Charset B +HPSOCKET_API BOOL __HP_CALL SYS_CharsetConvert(LPCSTR lpszFromCharset, LPCSTR lpszToCharset, LPCSTR lpszInBuf, int iInBufLen, LPSTR lpszOutBuf, int* piOutBufLen); + +// GBK -> UNICODE +HPSOCKET_API BOOL __HP_CALL SYS_GbkToUnicodeEx(const char szSrc[], int iSrcLength, WCHAR szDest[], int* piDestLength); +// UNICODE -> GBK +HPSOCKET_API BOOL __HP_CALL SYS_UnicodeToGbkEx(const WCHAR szSrc[], int iSrcLength, char szDest[], int* piDestLength); +// UTF8 -> UNICODE +HPSOCKET_API BOOL __HP_CALL SYS_Utf8ToUnicodeEx(const char szSrc[], int iSrcLength, WCHAR szDest[], int* piDestLength); +// UNICODE -> UTF8 +HPSOCKET_API BOOL __HP_CALL SYS_UnicodeToUtf8Ex(const WCHAR szSrc[], int iSrcLength, char szDest[], int* piDestLength); +// GBK -> UTF8 +HPSOCKET_API BOOL __HP_CALL SYS_GbkToUtf8Ex(const char szSrc[], int iSrcLength, char szDest[], int* piDestLength); +// UTF8 -> GBK +HPSOCKET_API BOOL __HP_CALL SYS_Utf8ToGbkEx(const char szSrc[], int iSrcLength, char szDest[], int* piDestLength); + +// GBK -> UNICODE +HPSOCKET_API BOOL __HP_CALL SYS_GbkToUnicode(const char szSrc[], WCHAR szDest[], int* piDestLength); +// UNICODE -> GBK +HPSOCKET_API BOOL __HP_CALL SYS_UnicodeToGbk(const WCHAR szSrc[], char szDest[], int* piDestLength); +// UTF8 -> UNICODE +HPSOCKET_API BOOL __HP_CALL SYS_Utf8ToUnicode(const char szSrc[], WCHAR szDest[], int* piDestLength); +// UNICODE -> UTF8 +HPSOCKET_API BOOL __HP_CALL SYS_UnicodeToUtf8(const WCHAR szSrc[], char szDest[], int* piDestLength); +// GBK -> UTF8 +HPSOCKET_API BOOL __HP_CALL SYS_GbkToUtf8(const char szSrc[], char szDest[], int* piDestLength); +// UTF8 -> GBK +HPSOCKET_API BOOL __HP_CALL SYS_Utf8ToGbk(const char szSrc[], char szDest[], int* piDestLength); + +#endif + +/*****************************************************************************************************************************************************/ +/******************************************************************** HTTP Exports *******************************************************************/ +/*****************************************************************************************************************************************************/ + +#ifdef _HTTP_SUPPORT + +/****************************************************/ +/******************* HTTP 回调函数 *******************/ + +/* HTTP 回调函数 */ +typedef En_HP_HttpParseResult (__HP_CALL *HP_FN_Http_OnMessageBegin) (HP_Http pSender, HP_CONNID dwConnID); +typedef En_HP_HttpParseResult (__HP_CALL *HP_FN_Http_OnRequestLine) (HP_Http pSender, HP_CONNID dwConnID, LPCSTR lpszMethod, LPCSTR lpszUrl); +typedef En_HP_HttpParseResult (__HP_CALL *HP_FN_Http_OnStatusLine) (HP_Http pSender, HP_CONNID dwConnID, USHORT usStatusCode, LPCSTR lpszDesc); +typedef En_HP_HttpParseResult (__HP_CALL *HP_FN_Http_OnHeader) (HP_Http pSender, HP_CONNID dwConnID, LPCSTR lpszName, LPCSTR lpszValue); +typedef En_HP_HttpParseResult (__HP_CALL *HP_FN_Http_OnHeadersComplete) (HP_Http pSender, HP_CONNID dwConnID); +typedef En_HP_HttpParseResult (__HP_CALL *HP_FN_Http_OnBody) (HP_Http pSender, HP_CONNID dwConnID, const BYTE* pData, int iLength); +typedef En_HP_HttpParseResult (__HP_CALL *HP_FN_Http_OnChunkHeader) (HP_Http pSender, HP_CONNID dwConnID, int iLength); +typedef En_HP_HttpParseResult (__HP_CALL *HP_FN_Http_OnChunkComplete) (HP_Http pSender, HP_CONNID dwConnID); +typedef En_HP_HttpParseResult (__HP_CALL *HP_FN_Http_OnMessageComplete) (HP_Http pSender, HP_CONNID dwConnID); +typedef En_HP_HttpParseResult (__HP_CALL *HP_FN_Http_OnUpgrade) (HP_Http pSender, HP_CONNID dwConnID, En_HP_HttpUpgradeType enUpgradeType); +typedef En_HP_HttpParseResult (__HP_CALL *HP_FN_Http_OnParseError) (HP_Http pSender, HP_CONNID dwConnID, int iErrorCode, LPCSTR lpszErrorDesc); + +typedef En_HP_HandleResult (__HP_CALL *HP_FN_Http_OnWSMessageHeader) (HP_Http pSender, HP_CONNID dwConnID, BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], ULONGLONG ullBodyLen); +typedef En_HP_HandleResult (__HP_CALL *HP_FN_Http_OnWSMessageBody) (HP_Http pSender, HP_CONNID dwConnID, const BYTE* pData, int iLength); +typedef En_HP_HandleResult (__HP_CALL *HP_FN_Http_OnWSMessageComplete)(HP_Http pSender, HP_CONNID dwConnID); + +/* HTTP Server 回调函数 */ +typedef HP_FN_Http_OnMessageBegin HP_FN_HttpServer_OnMessageBegin; +typedef HP_FN_Http_OnRequestLine HP_FN_HttpServer_OnRequestLine; +typedef HP_FN_Http_OnHeader HP_FN_HttpServer_OnHeader; +typedef HP_FN_Http_OnHeadersComplete HP_FN_HttpServer_OnHeadersComplete; +typedef HP_FN_Http_OnBody HP_FN_HttpServer_OnBody; +typedef HP_FN_Http_OnChunkHeader HP_FN_HttpServer_OnChunkHeader; +typedef HP_FN_Http_OnChunkComplete HP_FN_HttpServer_OnChunkComplete; +typedef HP_FN_Http_OnMessageComplete HP_FN_HttpServer_OnMessageComplete; +typedef HP_FN_Http_OnUpgrade HP_FN_HttpServer_OnUpgrade; +typedef HP_FN_Http_OnParseError HP_FN_HttpServer_OnParseError; + +typedef HP_FN_Http_OnWSMessageHeader HP_FN_HttpServer_OnWSMessageHeader; +typedef HP_FN_Http_OnWSMessageBody HP_FN_HttpServer_OnWSMessageBody; +typedef HP_FN_Http_OnWSMessageComplete HP_FN_HttpServer_OnWSMessageComplete; + +typedef HP_FN_Server_OnPrepareListen HP_FN_HttpServer_OnPrepareListen; +typedef HP_FN_Server_OnAccept HP_FN_HttpServer_OnAccept; +typedef HP_FN_Server_OnHandShake HP_FN_HttpServer_OnHandShake; +typedef HP_FN_Server_OnReceive HP_FN_HttpServer_OnReceive; +typedef HP_FN_Server_OnSend HP_FN_HttpServer_OnSend; +typedef HP_FN_Server_OnClose HP_FN_HttpServer_OnClose; +typedef HP_FN_Server_OnShutdown HP_FN_HttpServer_OnShutdown; + +/* HTTP Agent 回调函数 */ +typedef HP_FN_Http_OnMessageBegin HP_FN_HttpAgent_OnMessageBegin; +typedef HP_FN_Http_OnStatusLine HP_FN_HttpAgent_OnStatusLine; +typedef HP_FN_Http_OnHeader HP_FN_HttpAgent_OnHeader; +typedef HP_FN_Http_OnHeadersComplete HP_FN_HttpAgent_OnHeadersComplete; +typedef HP_FN_Http_OnBody HP_FN_HttpAgent_OnBody; +typedef HP_FN_Http_OnChunkHeader HP_FN_HttpAgent_OnChunkHeader; +typedef HP_FN_Http_OnChunkComplete HP_FN_HttpAgent_OnChunkComplete; +typedef HP_FN_Http_OnMessageComplete HP_FN_HttpAgent_OnMessageComplete; +typedef HP_FN_Http_OnUpgrade HP_FN_HttpAgent_OnUpgrade; +typedef HP_FN_Http_OnParseError HP_FN_HttpAgent_OnParseError; + +typedef HP_FN_Http_OnWSMessageHeader HP_FN_HttpAgent_OnWSMessageHeader; +typedef HP_FN_Http_OnWSMessageBody HP_FN_HttpAgent_OnWSMessageBody; +typedef HP_FN_Http_OnWSMessageComplete HP_FN_HttpAgent_OnWSMessageComplete; + +typedef HP_FN_Agent_OnPrepareConnect HP_FN_HttpAgent_OnPrepareConnect; +typedef HP_FN_Agent_OnConnect HP_FN_HttpAgent_OnConnect; +typedef HP_FN_Agent_OnHandShake HP_FN_HttpAgent_OnHandShake; +typedef HP_FN_Agent_OnReceive HP_FN_HttpAgent_OnReceive; +typedef HP_FN_Agent_OnSend HP_FN_HttpAgent_OnSend; +typedef HP_FN_Agent_OnClose HP_FN_HttpAgent_OnClose; +typedef HP_FN_Agent_OnShutdown HP_FN_HttpAgent_OnShutdown; + +/* HTTP Client 回调函数 */ +typedef HP_FN_Http_OnMessageBegin HP_FN_HttpClient_OnMessageBegin; +typedef HP_FN_Http_OnStatusLine HP_FN_HttpClient_OnStatusLine; +typedef HP_FN_Http_OnHeader HP_FN_HttpClient_OnHeader; +typedef HP_FN_Http_OnHeadersComplete HP_FN_HttpClient_OnHeadersComplete; +typedef HP_FN_Http_OnBody HP_FN_HttpClient_OnBody; +typedef HP_FN_Http_OnChunkHeader HP_FN_HttpClient_OnChunkHeader; +typedef HP_FN_Http_OnChunkComplete HP_FN_HttpClient_OnChunkComplete; +typedef HP_FN_Http_OnMessageComplete HP_FN_HttpClient_OnMessageComplete; +typedef HP_FN_Http_OnUpgrade HP_FN_HttpClient_OnUpgrade; +typedef HP_FN_Http_OnParseError HP_FN_HttpClient_OnParseError; + +typedef HP_FN_Http_OnWSMessageHeader HP_FN_HttpClient_OnWSMessageHeader; +typedef HP_FN_Http_OnWSMessageBody HP_FN_HttpClient_OnWSMessageBody; +typedef HP_FN_Http_OnWSMessageComplete HP_FN_HttpClient_OnWSMessageComplete; + +typedef HP_FN_Client_OnPrepareConnect HP_FN_HttpClient_OnPrepareConnect; +typedef HP_FN_Client_OnConnect HP_FN_HttpClient_OnConnect; +typedef HP_FN_Client_OnHandShake HP_FN_HttpClient_OnHandShake; +typedef HP_FN_Client_OnReceive HP_FN_HttpClient_OnReceive; +typedef HP_FN_Client_OnSend HP_FN_HttpClient_OnSend; +typedef HP_FN_Client_OnClose HP_FN_HttpClient_OnClose; + +/****************************************************/ +/***************** HTTP 对象创建函数 *****************/ + +// 创建 HP_HttpServer 对象 +HPSOCKET_API HP_HttpServer __HP_CALL Create_HP_HttpServer(HP_HttpServerListener pListener); +// 创建 HP_HttpAgent 对象 +HPSOCKET_API HP_HttpAgent __HP_CALL Create_HP_HttpAgent(HP_HttpAgentListener pListener); +// 创建 HP_HttpClient 对象 +HPSOCKET_API HP_HttpClient __HP_CALL Create_HP_HttpClient(HP_HttpClientListener pListener); +// 创建 HP_HttpSyncClient 对象(pListener 参数可传入 nullptr) +HPSOCKET_API HP_HttpSyncClient __HP_CALL Create_HP_HttpSyncClient(HP_HttpClientListener pListener /*= nullptr*/); + +// 销毁 HP_HttpServer 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_HttpServer(HP_HttpServer pServer); +// 销毁 HP_HttpAgent 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_HttpAgent(HP_HttpAgent pAgent); +// 销毁 HP_HttpClient 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_HttpClient(HP_HttpClient pClient); +// 销毁 HP_HttpSyncClient 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_HttpSyncClient(HP_HttpSyncClient pClient); + +// 创建 HP_HttpServerListener 对象 +HPSOCKET_API HP_HttpServerListener __HP_CALL Create_HP_HttpServerListener(); +// 创建 HP_HttpAgentListener 对象 +HPSOCKET_API HP_HttpAgentListener __HP_CALL Create_HP_HttpAgentListener(); +// 创建 HP_HttpClientListener 对象 +HPSOCKET_API HP_HttpClientListener __HP_CALL Create_HP_HttpClientListener(); + +// 销毁 HP_HttpServerListener 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_HttpServerListener(HP_HttpServerListener pListener); +// 销毁 HP_HttpAgentListener 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_HttpAgentListener(HP_HttpAgentListener pListener); +// 销毁 HP_HttpClientListener 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_HttpClientListener(HP_HttpClientListener pListener); + +/**********************************************************************************/ +/*************************** HTTP Server 回调函数设置方法 **************************/ + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnMessageBegin(HP_HttpServerListener pListener , HP_FN_HttpServer_OnMessageBegin fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnRequestLine(HP_HttpServerListener pListener , HP_FN_HttpServer_OnRequestLine fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnHeader(HP_HttpServerListener pListener , HP_FN_HttpServer_OnHeader fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnHeadersComplete(HP_HttpServerListener pListener , HP_FN_HttpServer_OnHeadersComplete fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnBody(HP_HttpServerListener pListener , HP_FN_HttpServer_OnBody fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnChunkHeader(HP_HttpServerListener pListener , HP_FN_HttpServer_OnChunkHeader fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnChunkComplete(HP_HttpServerListener pListener , HP_FN_HttpServer_OnChunkComplete fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnMessageComplete(HP_HttpServerListener pListener , HP_FN_HttpServer_OnMessageComplete fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnUpgrade(HP_HttpServerListener pListener , HP_FN_HttpServer_OnUpgrade fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnParseError(HP_HttpServerListener pListener , HP_FN_HttpServer_OnParseError fn); + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnWSMessageHeader(HP_HttpServerListener pListener , HP_FN_HttpServer_OnWSMessageHeader fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnWSMessageBody(HP_HttpServerListener pListener , HP_FN_HttpServer_OnWSMessageBody fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnWSMessageComplete(HP_HttpServerListener pListener, HP_FN_HttpServer_OnWSMessageComplete fn); + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnPrepareListen(HP_HttpServerListener pListener , HP_FN_HttpServer_OnPrepareListen fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnAccept(HP_HttpServerListener pListener , HP_FN_HttpServer_OnAccept fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnHandShake(HP_HttpServerListener pListener , HP_FN_HttpServer_OnHandShake fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnReceive(HP_HttpServerListener pListener , HP_FN_HttpServer_OnReceive fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnSend(HP_HttpServerListener pListener , HP_FN_HttpServer_OnSend fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnClose(HP_HttpServerListener pListener , HP_FN_HttpServer_OnClose fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpServer_OnShutdown(HP_HttpServerListener pListener , HP_FN_HttpServer_OnShutdown fn); + +/**********************************************************************************/ +/**************************** HTTP Agent 回调函数设置方法 **************************/ + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnMessageBegin(HP_HttpAgentListener pListener , HP_FN_HttpAgent_OnMessageBegin fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnStatusLine(HP_HttpAgentListener pListener , HP_FN_HttpAgent_OnStatusLine fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnHeader(HP_HttpAgentListener pListener , HP_FN_HttpAgent_OnHeader fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnHeadersComplete(HP_HttpAgentListener pListener , HP_FN_HttpAgent_OnHeadersComplete fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnBody(HP_HttpAgentListener pListener , HP_FN_HttpAgent_OnBody fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnChunkHeader(HP_HttpAgentListener pListener , HP_FN_HttpAgent_OnChunkHeader fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnChunkComplete(HP_HttpAgentListener pListener , HP_FN_HttpAgent_OnChunkComplete fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnMessageComplete(HP_HttpAgentListener pListener , HP_FN_HttpAgent_OnMessageComplete fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnUpgrade(HP_HttpAgentListener pListener , HP_FN_HttpAgent_OnUpgrade fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnParseError(HP_HttpAgentListener pListener , HP_FN_HttpAgent_OnParseError fn); + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnWSMessageHeader(HP_HttpAgentListener pListener , HP_FN_HttpAgent_OnWSMessageHeader fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnWSMessageBody(HP_HttpAgentListener pListener , HP_FN_HttpAgent_OnWSMessageBody fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnWSMessageComplete(HP_HttpAgentListener pListener , HP_FN_HttpAgent_OnWSMessageComplete fn); + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnPrepareConnect(HP_HttpAgentListener pListener , HP_FN_HttpAgent_OnPrepareConnect fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnConnect(HP_HttpAgentListener pListener , HP_FN_HttpAgent_OnConnect fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnHandShake(HP_HttpAgentListener pListener , HP_FN_HttpAgent_OnHandShake fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnReceive(HP_HttpAgentListener pListener , HP_FN_HttpAgent_OnReceive fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnSend(HP_HttpAgentListener pListener , HP_FN_HttpAgent_OnSend fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnClose(HP_HttpAgentListener pListener , HP_FN_HttpAgent_OnClose fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpAgent_OnShutdown(HP_HttpAgentListener pListener , HP_FN_HttpAgent_OnShutdown fn); + +/**********************************************************************************/ +/*************************** HTTP Client 回调函数设置方法 **************************/ + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnMessageBegin(HP_HttpClientListener pListener , HP_FN_HttpClient_OnMessageBegin fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnStatusLine(HP_HttpClientListener pListener , HP_FN_HttpClient_OnStatusLine fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnHeader(HP_HttpClientListener pListener , HP_FN_HttpClient_OnHeader fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnHeadersComplete(HP_HttpClientListener pListener , HP_FN_HttpClient_OnHeadersComplete fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnBody(HP_HttpClientListener pListener , HP_FN_HttpClient_OnBody fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnChunkHeader(HP_HttpClientListener pListener , HP_FN_HttpClient_OnChunkHeader fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnChunkComplete(HP_HttpClientListener pListener , HP_FN_HttpClient_OnChunkComplete fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnMessageComplete(HP_HttpClientListener pListener , HP_FN_HttpClient_OnMessageComplete fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnUpgrade(HP_HttpClientListener pListener , HP_FN_HttpClient_OnUpgrade fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnParseError(HP_HttpClientListener pListener , HP_FN_HttpClient_OnParseError fn); + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnWSMessageHeader(HP_HttpClientListener pListener , HP_FN_HttpClient_OnWSMessageHeader fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnWSMessageBody(HP_HttpClientListener pListener , HP_FN_HttpClient_OnWSMessageBody fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnWSMessageComplete(HP_HttpClientListener pListener, HP_FN_HttpClient_OnWSMessageComplete fn); + +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnPrepareConnect(HP_HttpClientListener pListener , HP_FN_HttpClient_OnPrepareConnect fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnConnect(HP_HttpClientListener pListener , HP_FN_HttpClient_OnConnect fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnHandShake(HP_HttpClientListener pListener , HP_FN_HttpClient_OnHandShake fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnReceive(HP_HttpClientListener pListener , HP_FN_HttpClient_OnReceive fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnSend(HP_HttpClientListener pListener , HP_FN_HttpClient_OnSend fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_HttpClient_OnClose(HP_HttpClientListener pListener , HP_FN_HttpClient_OnClose fn); + +/**************************************************************************/ +/*************************** HTTP Server 操作方法 **************************/ + +/* +* 名称:回复请求 +* 描述:向客户端回复 HTTP 请求 +* +* 参数: dwConnID -- 连接 ID +* usStatusCode -- HTTP 状态码 +* lpszDesc -- HTTP 状态描述 +* lpHeaders -- 回复请求头 +* iHeaderCount -- 回复请求头数量 +* pData -- 回复请求体 +* iLength -- 回复请求体长度 +* 返回值: TRUE -- 成功 +* FALSE -- 失败 +*/ +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_SendResponse(HP_HttpServer pServer, HP_CONNID dwConnID, USHORT usStatusCode, LPCSTR lpszDesc, const HP_THeader lpHeaders[], int iHeaderCount, const BYTE* pData, int iLength); + +/* +* 名称:发送本地文件 +* 描述:向指定连接发送 4096 KB 以下的小文件 +* +* 参数: dwConnID -- 连接 ID +* lpszFileName -- 文件路径 +* usStatusCode -- HTTP 状态码 +* lpszDesc -- HTTP 状态描述 +* lpHeaders -- 回复请求头 +* iHeaderCount -- 回复请求头数量 +* 返回值: TRUE -- 成功 +* FALSE -- 失败 +*/ +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_SendLocalFile(HP_HttpServer pServer, HP_CONNID dwConnID, LPCSTR lpszFileName, USHORT usStatusCode, LPCSTR lpszDesc, const HP_THeader lpHeaders[], int iHeaderCount); + +/* +* 名称:发送 Chunked 数据分片 +* 描述:向对端发送 Chunked 数据分片 +* +* 参数: dwConnID -- 连接 ID +* pData -- Chunked 数据分片 +* iLength -- 数据分片长度(为 0 表示结束分片) +* lpszExtensions -- 扩展属性(默认:nullptr) +* 返回值: TRUE -- 成功 +* FALSE -- 失败 +*/ +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_SendChunkData(HP_HttpServer pServer, HP_CONNID dwConnID, const BYTE* pData /*= nullptr*/, int iLength /*= 0*/, LPCSTR lpszExtensions /*= nullptr*/); + +/* +* 名称:发送 WebSocket 消息 +* 描述:向对端端发送 WebSocket 消息 +* +* 参数: dwConnID -- 连接 ID +* bFinal -- 是否结束帧 +* iReserved -- RSV1/RSV2/RSV3 各 1 位 +* iOperationCode -- 操作码:0x0 - 0xF +* pData -- 消息体数据缓冲区 +* iLength -- 消息体数据长度 +* ullBodyLen -- 消息总长度 +* ullBodyLen = 0 -> 消息总长度为 iLength +* ullBodyLen = iLength -> 消息总长度为 ullBodyLen +* ullBodyLen > iLength -> 消息总长度为 ullBodyLen,后续消息体长度为 ullBOdyLen - iLength,后续消息体通过底层方法 Send() / SendPackets() 发送 +* ullBodyLen < iLength -> 错误参数,发送失败 +* 返回值: TRUE -- 成功 +* FALSE -- 失败 +*/ +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_SendWSMessage(HP_HttpServer pServer, HP_CONNID dwConnID, BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE* pData, int iLength, ULONGLONG ullBodyLen); + +/* +* 名称:释放连接 +* 描述:把连接放入释放队列,等待某个时间(通过 SetReleaseDelay() 设置)关闭连接 +* +* 参数: dwConnID -- 连接 ID +* 返回值: TRUE -- 成功 +* FALSE -- 失败 +*/ +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_Release(HP_HttpServer pServer, HP_CONNID dwConnID); + +/* +* 名称:启动 HTTP 通信 +* 描述:当通信组件设置为非自动启动 HTTP 通信时,需要调用本方法启动 HTTP 通信 +* +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 +*/ +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_StartHttp(HP_HttpServer pServer, HP_CONNID dwConnID); + +/******************************************************************************/ +/*************************** HTTP Server 属性访问方法 **************************/ + +/* 设置连接释放延时(默认:3000 毫秒) */ +HPSOCKET_API void __HP_CALL HP_HttpServer_SetReleaseDelay(HP_HttpServer pServer, DWORD dwReleaseDelay); +/* 获取连接释放延时 */ +HPSOCKET_API DWORD __HP_CALL HP_HttpServer_GetReleaseDelay(HP_HttpServer pServer); +/* 获取请求行 URL 域掩码(URL 域参考:EnHttpUrlField) */ +HPSOCKET_API USHORT __HP_CALL HP_HttpServer_GetUrlFieldSet(HP_HttpServer pServer, HP_CONNID dwConnID); +/* 获取某个 URL 域值 */ +HPSOCKET_API LPCSTR __HP_CALL HP_HttpServer_GetUrlField(HP_HttpServer pServer, HP_CONNID dwConnID, En_HP_HttpUrlField enField); +/* 获取请求方法 */ +HPSOCKET_API LPCSTR __HP_CALL HP_HttpServer_GetMethod(HP_HttpServer pServer, HP_CONNID dwConnID); + +/* 设置本地协议版本 */ +HPSOCKET_API void __HP_CALL HP_HttpServer_SetLocalVersion(HP_HttpServer pServer, En_HP_HttpVersion usVersion); +/* 获取本地协议版本 */ +HPSOCKET_API En_HP_HttpVersion __HP_CALL HP_HttpServer_GetLocalVersion(HP_HttpServer pServer); + +/* 检查是否升级协议 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_IsUpgrade(HP_HttpServer pServer, HP_CONNID dwConnID); +/* 检查是否有 Keep-Alive 标识 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_IsKeepAlive(HP_HttpServer pServer, HP_CONNID dwConnID); +/* 获取协议版本 */ +HPSOCKET_API USHORT __HP_CALL HP_HttpServer_GetVersion(HP_HttpServer pServer, HP_CONNID dwConnID); +/* 获取主机 */ +HPSOCKET_API LPCSTR __HP_CALL HP_HttpServer_GetHost(HP_HttpServer pServer, HP_CONNID dwConnID); +/* 获取内容长度 */ +HPSOCKET_API ULONGLONG __HP_CALL HP_HttpServer_GetContentLength(HP_HttpServer pServer, HP_CONNID dwConnID); +/* 获取内容类型 */ +HPSOCKET_API LPCSTR __HP_CALL HP_HttpServer_GetContentType(HP_HttpServer pServer, HP_CONNID dwConnID); +/* 获取内容编码 */ +HPSOCKET_API LPCSTR __HP_CALL HP_HttpServer_GetContentEncoding(HP_HttpServer pServer, HP_CONNID dwConnID); +/* 获取传输编码 */ +HPSOCKET_API LPCSTR __HP_CALL HP_HttpServer_GetTransferEncoding(HP_HttpServer pServer, HP_CONNID dwConnID); +/* 获取协议升级类型 */ +HPSOCKET_API En_HP_HttpUpgradeType __HP_CALL HP_HttpServer_GetUpgradeType(HP_HttpServer pServer, HP_CONNID dwConnID); +/* 获取解析错误代码 */ +HPSOCKET_API USHORT __HP_CALL HP_HttpServer_GetParseErrorCode(HP_HttpServer pServer, HP_CONNID dwConnID, LPCSTR* lpszErrorDesc); + +/* 获取某个请求头(单值) */ +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_GetHeader(HP_HttpServer pServer, HP_CONNID dwConnID, LPCSTR lpszName, LPCSTR* lpszValue); +/* 获取某个请求头(多值) */ +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_GetHeaders(HP_HttpServer pServer, HP_CONNID dwConnID, LPCSTR lpszName, LPCSTR lpszValue[], DWORD* pdwCount); +/* 获取所有请求头 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_GetAllHeaders(HP_HttpServer pServer, HP_CONNID dwConnID, HP_THeader lpHeaders[], DWORD* pdwCount); +/* 获取所有请求头名称 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_GetAllHeaderNames(HP_HttpServer pServer, HP_CONNID dwConnID, LPCSTR lpszName[], DWORD* pdwCount); + +/* 获取 Cookie */ +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_GetCookie(HP_HttpServer pServer, HP_CONNID dwConnID, LPCSTR lpszName, LPCSTR* lpszValue); +/* 获取所有 Cookie */ +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_GetAllCookies(HP_HttpServer pServer, HP_CONNID dwConnID, HP_TCookie lpCookies[], DWORD* pdwCount); + +/* 获取当前 WebSocket 消息状态,传入 nullptr 则不获取相应字段 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_GetWSMessageState(HP_HttpServer pServer, HP_CONNID dwConnID, BOOL* lpbFinal, BYTE* lpiReserved, BYTE* lpiOperationCode, LPCBYTE* lpszMask, ULONGLONG* lpullBodyLen, ULONGLONG* lpullBodyRemain); + +/* 设置 HTTP 启动方式(默认:TRUE,自动启动) */ +HPSOCKET_API void __HP_CALL HP_HttpServer_SetHttpAutoStart(HP_HttpServer pServer, BOOL bAutoStart); +/* 获取 HTTP 启动方式 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpServer_IsHttpAutoStart(HP_HttpServer pServer); + +/**************************************************************************/ +/*************************** HTTP Agent 操作方法 ***************************/ + +/* +* 名称:发送请求 +* 描述:向服务端发送 HTTP 请求 +* +* 参数: dwConnID -- 连接 ID +* lpszMethod -- 请求方法 +* lpszPath -- 请求路径 +* lpHeaders -- 请求头 +* iHeaderCount -- 请求头数量 +* pBody -- 请求体 +* iLength -- 请求体长度 +* 返回值: TRUE -- 成功 +* FALSE -- 失败 +*/ +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_SendRequest(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszMethod, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount, const BYTE* pData, int iLength); + +/* +* 名称:发送本地文件 +* 描述:向指定连接发送 4096 KB 以下的小文件 +* +* 参数: dwConnID -- 连接 ID +* lpszFileName -- 文件路径 +* lpszMethod -- 请求方法 +* lpszPath -- 请求路径 +* lpHeaders -- 请求头 +* iHeaderCount -- 请求头数量 +* 返回值: TRUE -- 成功 +* FALSE -- 失败 +*/ +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_SendLocalFile(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszFileName, LPCSTR lpszMethod, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount); + +/* +* 名称:发送 Chunked 数据分片 +* 描述:向对端发送 Chunked 数据分片 +* +* 参数: dwConnID -- 连接 ID +* pData -- Chunked 数据分片 +* iLength -- 数据分片长度(为 0 表示结束分片) +* lpszExtensions -- 扩展属性(默认:nullptr) +* 返回值: TRUE -- 成功 +* FALSE -- 失败 +*/ +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_SendChunkData(HP_HttpAgent pAgent, HP_CONNID dwConnID, const BYTE* pData /*= nullptr*/, int iLength /*= 0*/, LPCSTR lpszExtensions /*= nullptr*/); + +/* 发送 POST 请求 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_SendPost(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength); +/* 发送 PUT 请求 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_SendPut(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength); +/* 发送 PATCH 请求 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_SendPatch(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength); +/* 发送 GET 请求 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_SendGet(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount); +/* 发送 DELETE 请求 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_SendDelete(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount); +/* 发送 HEAD 请求 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_SendHead(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount); +/* 发送 TRACE 请求 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_SendTrace(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount); +/* 发送 OPTIONS 请求 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_SendOptions(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount); +/* 发送 CONNECT 请求 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_SendConnect(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszHost, const HP_THeader lpHeaders[], int iHeaderCount); + +/* +* 名称:发送 WebSocket 消息 +* 描述:向对端端发送 WebSocket 消息 +* +* 参数: dwConnID -- 连接 ID +* bFinal -- 是否结束帧 +* iReserved -- RSV1/RSV2/RSV3 各 1 位 +* iOperationCode -- 操作码:0x0 - 0xF +* lpszMask -- 掩码(nullptr 或 4 字节掩码,如果为 nullptr 则没有掩码) +* pData -- 消息体数据缓冲区 +* iLength -- 消息体数据长度 +* ullBodyLen -- 消息总长度 +* ullBodyLen = 0 -> 消息总长度为 iLength +* ullBodyLen = iLength -> 消息总长度为 ullBodyLen +* ullBodyLen > iLength -> 消息总长度为 ullBodyLen,后续消息体长度为 ullBOdyLen - iLength,后续消息体通过底层方法 Send() / SendPackets() 发送 +* ullBodyLen < iLength -> 错误参数,发送失败 +* 返回值: TRUE -- 成功 +* FALSE -- 失败 +*/ +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_SendWSMessage(HP_HttpAgent pAgent, HP_CONNID dwConnID, BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], const BYTE* pData, int iLength, ULONGLONG ullBodyLen); + +/* +* 名称:启动 HTTP 通信 +* 描述:当通信组件设置为非自动启动 HTTP 通信时,需要调用本方法启动 HTTP 通信 +* +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 +*/ +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_StartHttp(HP_HttpAgent pAgent, HP_CONNID dwConnID); + +/******************************************************************************/ +/*************************** HTTP Agent 属性访问方法 ***************************/ + +/* 获取 HTTP 状态码 */ +HPSOCKET_API USHORT __HP_CALL HP_HttpAgent_GetStatusCode(HP_HttpAgent pAgent, HP_CONNID dwConnID); + +/* 设置本地协议版本 */ +HPSOCKET_API void __HP_CALL HP_HttpAgent_SetLocalVersion(HP_HttpAgent pAgent, En_HP_HttpVersion usVersion); +/* 获取本地协议版本 */ +HPSOCKET_API En_HP_HttpVersion __HP_CALL HP_HttpAgent_GetLocalVersion(HP_HttpAgent pAgent); + +/* 检查是否升级协议 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_IsUpgrade(HP_HttpAgent pAgent, HP_CONNID dwConnID); +/* 检查是否有 Keep-Alive 标识 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_IsKeepAlive(HP_HttpAgent pAgent, HP_CONNID dwConnID); +/* 获取协议版本 */ +HPSOCKET_API USHORT __HP_CALL HP_HttpAgent_GetVersion(HP_HttpAgent pAgent, HP_CONNID dwConnID); +/* 获取内容长度 */ +HPSOCKET_API ULONGLONG __HP_CALL HP_HttpAgent_GetContentLength(HP_HttpAgent pAgent, HP_CONNID dwConnID); +/* 获取内容类型 */ +HPSOCKET_API LPCSTR __HP_CALL HP_HttpAgent_GetContentType(HP_HttpAgent pAgent, HP_CONNID dwConnID); +/* 获取内容编码 */ +HPSOCKET_API LPCSTR __HP_CALL HP_HttpAgent_GetContentEncoding(HP_HttpAgent pAgent, HP_CONNID dwConnID); +/* 获取传输编码 */ +HPSOCKET_API LPCSTR __HP_CALL HP_HttpAgent_GetTransferEncoding(HP_HttpAgent pAgent, HP_CONNID dwConnID); +/* 获取协议升级类型 */ +HPSOCKET_API En_HP_HttpUpgradeType __HP_CALL HP_HttpAgent_GetUpgradeType(HP_HttpAgent pAgent, HP_CONNID dwConnID); +/* 获取解析错误代码 */ +HPSOCKET_API USHORT __HP_CALL HP_HttpAgent_GetParseErrorCode(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR* lpszErrorDesc); + +/* 获取某个请求头(单值) */ +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_GetHeader(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszName, LPCSTR* lpszValue); +/* 获取某个请求头(多值) */ +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_GetHeaders(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszName, LPCSTR lpszValue[], DWORD* pdwCount); +/* 获取所有请求头 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_GetAllHeaders(HP_HttpAgent pAgent, HP_CONNID dwConnID, HP_THeader lpHeaders[], DWORD* pdwCount); +/* 获取所有请求头名称 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_GetAllHeaderNames(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszName[], DWORD* pdwCount); + +/* 设置是否使用 Cookie */ +HPSOCKET_API void __HP_CALL HP_HttpAgent_SetUseCookie(HP_HttpAgent pAgent, BOOL bUseCookie); +/* 检查是否使用 Cookie */ +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_IsUseCookie(HP_HttpAgent pAgent); +/* 获取 Cookie */ +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_GetCookie(HP_HttpAgent pAgent, HP_CONNID dwConnID, LPCSTR lpszName, LPCSTR* lpszValue); +/* 获取所有 Cookie */ +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_GetAllCookies(HP_HttpAgent pAgent, HP_CONNID dwConnID, HP_TCookie lpCookies[], DWORD* pdwCount); + +/* 获取当前 WebSocket 消息状态,传入 nullptr 则不获取相应字段 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_GetWSMessageState(HP_HttpAgent pAgent, HP_CONNID dwConnID, BOOL* lpbFinal, BYTE* lpiReserved, BYTE* lpiOperationCode, LPCBYTE* lpszMask, ULONGLONG* lpullBodyLen, ULONGLONG* lpullBodyRemain); + +/* 设置 HTTP 启动方式(默认:TRUE,自动启动) */ +HPSOCKET_API void __HP_CALL HP_HttpAgent_SetHttpAutoStart(HP_HttpAgent pAgent, BOOL bAutoStart); +/* 获取 HTTP 启动方式 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpAgent_IsHttpAutoStart(HP_HttpAgent pAgent); + +/**************************************************************************/ +/*************************** HTTP Client 操作方法 **************************/ + +/* +* 名称:发送请求 +* 描述:向服务端发送 HTTP 请求 +* +* 参数: lpszMethod -- 请求方法 +* lpszPath -- 请求路径 +* lpHeaders -- 请求头 +* iHeaderCount -- 请求头数量 +* pBody -- 请求体 +* iLength -- 请求体长度 +* 返回值: TRUE -- 成功 +* FALSE -- 失败 +*/ +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_SendRequest(HP_HttpClient pClient, LPCSTR lpszMethod, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength); + +/* +* 名称:发送本地文件 +* 描述:向指定连接发送 4096 KB 以下的小文件 +* +* 参数: dwConnID -- 连接 ID +* lpszFileName -- 文件路径 +* lpszMethod -- 请求方法 +* lpszPath -- 请求路径 +* lpHeaders -- 请求头 +* iHeaderCount -- 请求头数量 +* 返回值: TRUE -- 成功 +* FALSE -- 失败 +*/ +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_SendLocalFile(HP_HttpClient pClient, LPCSTR lpszFileName, LPCSTR lpszMethod, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount); + +/* +* 名称:发送 Chunked 数据分片 +* 描述:向对端发送 Chunked 数据分片 +* +* 参数: pData -- Chunked 数据分片 +* iLength -- 数据分片长度(为 0 表示结束分片) +* lpszExtensions -- 扩展属性(默认:nullptr) +* 返回值: TRUE -- 成功 +* FALSE -- 失败 +*/ +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_SendChunkData(HP_HttpClient pClient, const BYTE* pData /*= nullptr*/, int iLength /*= 0*/, LPCSTR lpszExtensions /*= nullptr*/); + +/* 发送 POST 请求 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_SendPost(HP_HttpClient pClient, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength); +/* 发送 PUT 请求 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_SendPut(HP_HttpClient pClient, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength); +/* 发送 PATCH 请求 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_SendPatch(HP_HttpClient pClient, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength); +/* 发送 GET 请求 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_SendGet(HP_HttpClient pClient, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount); +/* 发送 DELETE 请求 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_SendDelete(HP_HttpClient pClient, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount); +/* 发送 HEAD 请求 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_SendHead(HP_HttpClient pClient, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount); +/* 发送 TRACE 请求 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_SendTrace(HP_HttpClient pClient, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount); +/* 发送 OPTIONS 请求 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_SendOptions(HP_HttpClient pClient, LPCSTR lpszPath, const HP_THeader lpHeaders[], int iHeaderCount); +/* 发送 CONNECT 请求 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_SendConnect(HP_HttpClient pClient, LPCSTR lpszHost, const HP_THeader lpHeaders[], int iHeaderCount); + +/* +* 名称:发送 WebSocket 消息 +* 描述:向对端端发送 WebSocket 消息 +* +* 参数: bFinal -- 是否结束帧 +* iReserved -- RSV1/RSV2/RSV3 各 1 位 +* iOperationCode -- 操作码:0x0 - 0xF +* lpszMask -- 掩码(nullptr 或 4 字节掩码,如果为 nullptr 则没有掩码) +* pData -- 消息体数据缓冲区 +* iLength -- 消息体数据长度 +* ullBodyLen -- 消息总长度 +* ullBodyLen = 0 -> 消息总长度为 iLength +* ullBodyLen = iLength -> 消息总长度为 ullBodyLen +* ullBodyLen > iLength -> 消息总长度为 ullBodyLen,后续消息体长度为 ullBOdyLen - iLength,后续消息体通过底层方法 Send() / SendPackets() 发送 +* ullBodyLen < iLength -> 错误参数,发送失败 +* 返回值: TRUE -- 成功 +* FALSE -- 失败 +*/ +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_SendWSMessage(HP_HttpClient pClient, BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], const BYTE* pData, int iLength, ULONGLONG ullBodyLen); + +/* +* 名称:启动 HTTP 通信 +* 描述:当通信组件设置为非自动启动 HTTP 通信时,需要调用本方法启动 HTTP 通信 +* +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 +*/ +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_StartHttp(HP_HttpClient pClient); + +/******************************************************************************/ +/*************************** HTTP Client 属性访问方法 **************************/ + +/* 获取 HTTP 状态码 */ +HPSOCKET_API USHORT __HP_CALL HP_HttpClient_GetStatusCode(HP_HttpClient pClient); + +/* 设置本地协议版本 */ +HPSOCKET_API void __HP_CALL HP_HttpClient_SetLocalVersion(HP_HttpClient pClient, En_HP_HttpVersion usVersion); +/* 获取本地协议版本 */ +HPSOCKET_API En_HP_HttpVersion __HP_CALL HP_HttpClient_GetLocalVersion(HP_HttpClient pClient); + +/* 检查是否升级协议 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_IsUpgrade(HP_HttpClient pClient); +/* 检查是否有 Keep-Alive 标识 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_IsKeepAlive(HP_HttpClient pClient); +/* 获取协议版本 */ +HPSOCKET_API USHORT __HP_CALL HP_HttpClient_GetVersion(HP_HttpClient pClient); +/* 获取内容长度 */ +HPSOCKET_API ULONGLONG __HP_CALL HP_HttpClient_GetContentLength(HP_HttpClient pClient); +/* 获取内容类型 */ +HPSOCKET_API LPCSTR __HP_CALL HP_HttpClient_GetContentType(HP_HttpClient pClient); +/* 获取内容编码 */ +HPSOCKET_API LPCSTR __HP_CALL HP_HttpClient_GetContentEncoding(HP_HttpClient pClient); +/* 获取传输编码 */ +HPSOCKET_API LPCSTR __HP_CALL HP_HttpClient_GetTransferEncoding(HP_HttpClient pClient); +/* 获取协议升级类型 */ +HPSOCKET_API En_HP_HttpUpgradeType __HP_CALL HP_HttpClient_GetUpgradeType(HP_HttpClient pClient); +/* 获取解析错误代码 */ +HPSOCKET_API USHORT __HP_CALL HP_HttpClient_GetParseErrorCode(HP_HttpClient pClient, LPCSTR* lpszErrorDesc); + +/* 获取某个请求头(单值) */ +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_GetHeader(HP_HttpClient pClient, LPCSTR lpszName, LPCSTR* lpszValue); +/* 获取某个请求头(多值) */ +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_GetHeaders(HP_HttpClient pClient, LPCSTR lpszName, LPCSTR lpszValue[], DWORD* pdwCount); +/* 获取所有请求头 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_GetAllHeaders(HP_HttpClient pClient, HP_THeader lpHeaders[], DWORD* pdwCount); +/* 获取所有请求头名称 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_GetAllHeaderNames(HP_HttpClient pClient, LPCSTR lpszName[], DWORD* pdwCount); + +/* 设置是否使用 Cookie */ +HPSOCKET_API void __HP_CALL HP_HttpClient_SetUseCookie(HP_HttpClient pClient, BOOL bUseCookie); +/* 检查是否使用 Cookie */ +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_IsUseCookie(HP_HttpClient pClient); +/* 获取 Cookie */ +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_GetCookie(HP_HttpClient pClient, LPCSTR lpszName, LPCSTR* lpszValue); +/* 获取所有 Cookie */ +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_GetAllCookies(HP_HttpClient pClient, HP_TCookie lpCookies[], DWORD* pdwCount); + +/* 获取当前 WebSocket 消息状态,传入 nullptr 则不获取相应字段 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_GetWSMessageState(HP_HttpClient pClient, BOOL* lpbFinal, BYTE* lpiReserved, BYTE* lpiOperationCode, LPCBYTE* lpszMask, ULONGLONG* lpullBodyLen, ULONGLONG* lpullBodyRemain); + +/* 设置 HTTP 启动方式(默认:TRUE,自动启动) */ +HPSOCKET_API void __HP_CALL HP_HttpClient_SetHttpAutoStart(HP_HttpClient pClient, BOOL bAutoStart); +/* 获取 HTTP 启动方式 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpClient_IsHttpAutoStart(HP_HttpClient pClient); + +/**************************************************************************/ +/************************ HTTP Sync Client 操作方法 ************************/ + +/* +* 名称:发送 URL 请求 +* 描述:向服务端发送 HTTP URL 请求 +* +* 参数: lpszMethod -- 请求方法 +* lpszUrl -- 请求 URL +* lpHeaders -- 请求头 +* iHeaderCount -- 请求头数量 +* pBody -- 请求体 +* iLength -- 请求体长度 +* bForceReconnect -- 强制重新连接(默认:FALSE,当请求 URL 的主机和端口与现有连接一致时,重用现有连接) +* 返回值: TRUE -- 成功 +* FALSE -- 失败 +*/ +HPSOCKET_API BOOL __HP_CALL HP_HttpSyncClient_OpenUrl(HP_HttpSyncClient pClient, LPCSTR lpszMethod, LPCSTR lpszUrl, const HP_THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength, BOOL bForceReconnect); + +/* +* 名称:清除请求结果 +* 描述:清除上一次请求的响应头和响应体等结果信息(该方法会在每次发送请求前自动调用) +* +* 参数: +* 返回值: TRUE -- 成功 +* FALSE -- 失败 +*/ +HPSOCKET_API BOOL __HP_CALL HP_HttpSyncClient_CleanupRequestResult(HP_HttpSyncClient pClient); + +/******************************************************************************/ +/************************ HTTP Sync Client 属性访问方法 ************************/ + +/* 设置连接超时(毫秒,0:系统默认超时,默认:5000) */ +HPSOCKET_API void __HP_CALL HP_HttpSyncClient_SetConnectTimeout(HP_HttpSyncClient pClient, DWORD dwConnectTimeout); +/* 设置请求超时(毫秒,0:无限等待,默认:10000) */ +HPSOCKET_API void __HP_CALL HP_HttpSyncClient_SetRequestTimeout(HP_HttpSyncClient pClient, DWORD dwRequestTimeout); + +/* 获取连接超时 */ +HPSOCKET_API DWORD __HP_CALL HP_HttpSyncClient_GetConnectTimeout(HP_HttpSyncClient pClient); +/* 获取请求超时 */ +HPSOCKET_API DWORD __HP_CALL HP_HttpSyncClient_GetRequestTimeout(HP_HttpSyncClient pClient); + +/* 获取响应体 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpSyncClient_GetResponseBody(HP_HttpSyncClient pClient, LPCBYTE* lpszBody, int* piLength); + +/**************************************************************************/ +/*************************** HTTP Cookie 管理方法 **************************/ + +/* 从文件加载 Cookie */ +HPSOCKET_API BOOL __HP_CALL HP_HttpCookie_MGR_LoadFromFile(LPCSTR lpszFile, BOOL bKeepExists /*= TRUE*/); +/* 保存 Cookie 到文件 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpCookie_MGR_SaveToFile(LPCSTR lpszFile, BOOL bKeepExists /*= TRUE*/); +/* 清理 Cookie */ +HPSOCKET_API BOOL __HP_CALL HP_HttpCookie_MGR_ClearCookies(LPCSTR lpszDomain /*= nullptr*/, LPCSTR lpszPath /*= nullptr*/); +/* 清理过期 Cookie */ +HPSOCKET_API BOOL __HP_CALL HP_HttpCookie_MGR_RemoveExpiredCookies(LPCSTR lpszDomain /*= nullptr*/, LPCSTR lpszPath /*= nullptr*/); +/* 设置 Cookie */ +HPSOCKET_API BOOL __HP_CALL HP_HttpCookie_MGR_SetCookie(LPCSTR lpszName, LPCSTR lpszValue, LPCSTR lpszDomain, LPCSTR lpszPath, int iMaxAge /*= -1*/, BOOL bHttpOnly /*= FALSE*/, BOOL bSecure /*= FALSE*/, int enSameSite /*= 0*/, BOOL bOnlyUpdateValueIfExists /*= TRUE*/); +/* 删除 Cookie */ +HPSOCKET_API BOOL __HP_CALL HP_HttpCookie_MGR_DeleteCookie(LPCSTR lpszDomain, LPCSTR lpszPath, LPCSTR lpszName); +/* 设置是否允许第三方 Cookie */ +HPSOCKET_API void __HP_CALL HP_HttpCookie_MGR_SetEnableThirdPartyCookie(BOOL bEnableThirdPartyCookie /*= TRUE*/); +/* 检查是否允许第三方 Cookie */ +HPSOCKET_API BOOL __HP_CALL HP_HttpCookie_MGR_IsEnableThirdPartyCookie(); + +/* Cookie expires 字符串转换为整数 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpCookie_HLP_ParseExpires(LPCSTR lpszExpires, __time64_t* ptmExpires); +/* 整数转换为 Cookie expires 字符串 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpCookie_HLP_MakeExpiresStr(char lpszBuff[], int* piBuffLen, __time64_t tmExpires); +/* 生成 Cookie 字符串 */ +HPSOCKET_API BOOL __HP_CALL HP_HttpCookie_HLP_ToString(char lpszBuff[], int* piBuffLen, LPCSTR lpszName, LPCSTR lpszValue, LPCSTR lpszDomain, LPCSTR lpszPath, int iMaxAge /*= -1*/, BOOL bHttpOnly /*= FALSE*/, BOOL bSecure /*= FALSE*/, int enSameSite /*= 0*/); +/* 获取当前 UTC 时间 */ +HPSOCKET_API __time64_t __HP_CALL HP_HttpCookie_HLP_CurrentUTCTime(); +/* Max-Age -> expires */ +HPSOCKET_API __time64_t __HP_CALL HP_HttpCookie_HLP_MaxAgeToExpires(int iMaxAge); +/* expires -> Max-Age */ +HPSOCKET_API int __HP_CALL HP_HttpCookie_HLP_ExpiresToMaxAge(__time64_t tmExpires); + +/*****************************************************************************************************************************************************/ +/************************************************************* HTTP Global Function Exports **********************************************************/ +/*****************************************************************************************************************************************************/ + +#endif + +/*****************************************************************************************************************************************************/ +/**************************************************************** Thread Pool Exports ****************************************************************/ +/*****************************************************************************************************************************************************/ + +/* Thread Pool 回调函数 */ +typedef void (__HP_CALL *HP_FN_ThreadPool_OnStartup) (HP_ThreadPool pThreadPool); +typedef void (__HP_CALL *HP_FN_ThreadPool_OnShutdown) (HP_ThreadPool pThreadPool); +typedef void (__HP_CALL *HP_FN_ThreadPool_OnWorkerThreadStart) (HP_ThreadPool pThreadPool, THR_ID dwThreadID); +typedef void (__HP_CALL *HP_FN_ThreadPool_OnWorkerThreadEnd) (HP_ThreadPool pThreadPool, THR_ID dwThreadID); + +/**********************************************************************************/ +/************************** Thread Pool 回调函数设置方法 ***************************/ + +HPSOCKET_API void __HP_CALL HP_Set_FN_ThreadPool_OnStartup(HP_ThreadPoolListener pListener , HP_FN_ThreadPool_OnStartup fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_ThreadPool_OnShutdown(HP_ThreadPoolListener pListener , HP_FN_ThreadPool_OnShutdown fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_ThreadPool_OnWorkerThreadStart(HP_ThreadPoolListener pListener , HP_FN_ThreadPool_OnWorkerThreadStart fn); +HPSOCKET_API void __HP_CALL HP_Set_FN_ThreadPool_OnWorkerThreadEnd(HP_ThreadPoolListener pListener , HP_FN_ThreadPool_OnWorkerThreadEnd fn); + +/****************************************************/ +/******************* 对象创建函数 ********************/ + +// 创建 IHPThreadPoolListener 对象 +HPSOCKET_API HP_ThreadPoolListener __HP_CALL Create_HP_ThreadPoolListener(); +// 销毁 IHPThreadPoolListener 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_ThreadPoolListener(HP_ThreadPoolListener pListener); + +// 创建 IHPThreadPool 对象(pListener 参数可传入 nullptr) +HPSOCKET_API HP_ThreadPool __HP_CALL Create_HP_ThreadPool(HP_ThreadPoolListener pListener /*= nullptr*/); +// 销毁 IHPThreadPool 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_ThreadPool(HP_ThreadPool pThreadPool); + +/* +* 名称:创建 TSocketTask 对象 +* 描述:创建任务对象,该对象最终需由 HP_Destroy_SocketTaskObj() 销毁 +* +* 参数: fnTaskProc -- 任务处理函数 +* pSender -- 发起对象 +* dwConnID -- 连接 ID +* pBuffer -- 数据缓冲区 +* iBuffLen -- 数据缓冲区长度 +* enBuffType -- 数据缓冲区类型(默认:TBT_COPY) +* TBT_COPY :(深拷贝)pBuffer 复制到 TSocketTask 对象。此后 TSocketTask 对象与 pBuffer 不再有任何关联 +* -> 适用于 pBuffer 不大或 pBuffer 生命周期不受控的场景 +* TBT_REFER :(浅拷贝)pBuffer 不复制到 TSocketTask 对象,需确保 TSocketTask 对象生命周期内 pBuffer 必须有效 +* -> 适用于 pBuffer 较大或 pBuffer 可重用,并且 pBuffer 生命周期受控的场景 +* TBT_ATTACH :(附属)执行浅拷贝,但 TSocketTask 对象会获得 pBuffer 的所有权,并负责释放 pBuffer,避免多次缓冲区拷贝 +* -> 注意:pBuffer 必须由 SYS_Malloc()/SYS_Calloc() 函数分配才能使用本类型,否则可能会发生内存访问错误 +* wParam -- 自定义参数 +* lParam -- 自定义参数 +* 返回值: HP_LPTSocketTask +*/ +HPSOCKET_API HP_LPTSocketTask __HP_CALL Create_HP_SocketTaskObj(Fn_SocketTaskProc fnTaskProc, PVOID pSender, HP_CONNID dwConnID, LPCBYTE pBuffer, INT iBuffLen, En_HP_TaskBufferType enBuffType /*= TBT_COPY*/, WPARAM wParam /*= 0*/, LPARAM lParam /*= 0*/); + +// 销毁 TSocketTask 对象 +HPSOCKET_API void __HP_CALL Destroy_HP_SocketTaskObj(HP_LPTSocketTask pTask); + +/***********************************************************************/ +/***************************** 组件操作方法 *****************************/ + +/* +* 名称:启动线程池组件 +* 描述: +* +* 参数: dwThreadCount -- 线程数量,(默认:0) +* >0 -> dwThreadCount +* =0 -> (CPU核数 * 2 + 2) +* <0 -> (CPU核数 * (-dwThreadCount)) +* dwMaxQueueSize -- 任务队列最大容量(默认:0,不限制) +* enRejectedPolicy -- 任务拒绝处理策略 +* TRP_CALL_FAIL(默认) :立刻返回失败 +* TRP_WAIT_FOR :等待(直到成功、超时或线程池关闭等原因导致失败) +* TRP_CALLER_RUN :调用者线程直接执行 +* dwStackSize -- 线程堆栈空间大小(默认:0 -> 操作系统默认) +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取系统错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_ThreadPool_Start(HP_ThreadPool pThreadPool, DWORD dwThreadCount /*= 0*/, DWORD dwMaxQueueSize /*= 0*/, En_HP_RejectedPolicy enRejectedPolicy /*= TRP_CALL_FAIL*/, DWORD dwStackSize /*= 0*/); + +/* +* 名称:关闭线程池组件 +* 描述:在规定时间内关闭线程池组件,如果工作线程在最大等待时间内未能正常关闭,会尝试强制关闭,这种情况下很可能会造成系统资源泄漏 +* +* 参数: dwMaxWait -- 最大等待时间(毫秒,默认:INFINITE,一直等待) +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取系统错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_ThreadPool_Stop(HP_ThreadPool pThreadPool, DWORD dwMaxWait /*= INFINITE*/); + +/* +* 名称:提交任务 +* 描述:向线程池提交异步任务 +* +* 参数: fnTaskProc -- 任务处理函数 +* pvArg -- 任务参数 +* dwMaxWait -- 任务提交最大等待时间(仅对 TRP_WAIT_FOR 类型线程池生效,默认:INFINITE,一直等待) +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取系统错误代码 +* 其中,错误码 ERROR_DESTINATION_ELEMENT_FULL 表示任务队列已满 +*/ +HPSOCKET_API BOOL __HP_CALL HP_ThreadPool_Submit(HP_ThreadPool pThreadPool, HP_Fn_TaskProc fnTaskProc, PVOID pvArg, DWORD dwMaxWait /*= INFINITE*/); + +/* +* 名称:提交 Socket 任务 +* 描述:向线程池提交异步 Socket 任务 +* +* 参数: pTask -- 任务参数 +* dwMaxWait -- 任务提交最大等待时间(仅对 TRP_WAIT_FOR 类型线程池生效,默认:INFINITE,一直等待) +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取系统错误代码 +* 其中,错误码 ERROR_DESTINATION_ELEMENT_FULL 表示任务队列已满 +* 注意:如果提交失败,需要手工调用 Destroy_HP_SocketTaskObj() 销毁 TSocketTask 对象 +*/ +HPSOCKET_API BOOL __HP_CALL HP_ThreadPool_Submit_Task(HP_ThreadPool pThreadPool, HP_LPTSocketTask pTask, DWORD dwMaxWait /*= INFINITE*/); + +/* +* 名称:调整线程池大小 +* 描述:增加或减少线程池的工作线程数量 +* +* 参数: dwNewThreadCount -- 线程数量 +* >0 -> dwNewThreadCount +* =0 -> (CPU核数 * 2 + 2) +* <0 -> (CPU核数 * (-dwNewThreadCount)) +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取系统错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_ThreadPool_AdjustThreadCount(HP_ThreadPool pThreadPool, DWORD dwNewThreadCount); + +/* +* 名称:等待 +* 描述:等待线程池组件停止运行 +* +* 参数: dwMilliseconds -- 超时时间(毫秒,默认:-1,永不超时) +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_ThreadPool_Wait(HP_ThreadPool pThreadPool, DWORD dwMilliseconds); + +/***********************************************************************/ +/***************************** 属性访问方法 *****************************/ + +/* 检查线程池组件是否已启动 */ +HPSOCKET_API BOOL __HP_CALL HP_ThreadPool_HasStarted(HP_ThreadPool pThreadPool); +/* 查看线程池组件当前状态 */ +HPSOCKET_API En_HP_ServiceState __HP_CALL HP_ThreadPool_GetState(HP_ThreadPool pThreadPool); +/* 获取当前任务等待队列大小 */ +HPSOCKET_API DWORD __HP_CALL HP_ThreadPool_GetQueueSize(HP_ThreadPool pThreadPool); +/* 获取当前正在执行的任务数量 */ +HPSOCKET_API DWORD __HP_CALL HP_ThreadPool_GetTaskCount(HP_ThreadPool pThreadPool); +/* 获取工作线程数量 */ +HPSOCKET_API DWORD __HP_CALL HP_ThreadPool_GetThreadCount(HP_ThreadPool pThreadPool); +/* 获取任务队列最大容量 */ +HPSOCKET_API DWORD __HP_CALL HP_ThreadPool_GetMaxQueueSize(HP_ThreadPool pThreadPool); +/* 获取任务拒绝处理策略 */ +HPSOCKET_API En_HP_RejectedPolicy __HP_CALL HP_ThreadPool_GetRejectedPolicy(HP_ThreadPool pThreadPool); + +/*****************************************************************************************************************************************************/ +/********************************************************* Compressor / Decompressor Exports *********************************************************/ +/*****************************************************************************************************************************************************/ + +/****************************************************/ +/******************* 对象创建函数 ********************/ + +/* 销毁压缩器对象 */ +HPSOCKET_API void __HP_CALL Destroy_HP_Compressor(HP_Compressor pCompressor); +/* 销毁解压器对象 */ +HPSOCKET_API void __HP_CALL Destroy_HP_Decompressor(HP_Decompressor pDecompressor); + +#ifdef _ZLIB_SUPPORT + +/* 创建 ZLib 压缩器对象 */ +// (默认参数:iWindowBits = 15, iLevel = -1, iMethod = 8, iMemLevel = 8, iStrategy = 0, dwBuffSize = 16 * 1024) +HPSOCKET_API HP_Compressor __HP_CALL Create_HP_ZLibCompressor(HP_Fn_CompressDataCallback fnCallback); +/* 创建 ZLib 压缩器对象 */ +HPSOCKET_API HP_Compressor __HP_CALL Create_HP_ZLibCompressorEx(HP_Fn_CompressDataCallback fnCallback, int iWindowBits /*= 15*/, int iLevel /*= -1*/, int iMethod /*= 8*/, int iMemLevel /*= 8*/, int iStrategy /*= 0*/, DWORD dwBuffSize /*= 16 * 1024*/); +/* 创建 GZip 压缩器对象 */ +// (默认参数:iLevel = -1, iMethod = 8, iMemLevel = 8, iStrategy = 0, dwBuffSize = 16 * 1024) +HPSOCKET_API HP_Compressor __HP_CALL Create_HP_GZipCompressor(HP_Fn_CompressDataCallback fnCallback); +/* 创建 GZip 压缩器对象 */ +HPSOCKET_API HP_Compressor __HP_CALL Create_HP_GZipCompressorEx(HP_Fn_CompressDataCallback fnCallback, int iLevel /*= -1*/, int iMethod /*= 8*/, int iMemLevel /*= 8*/, int iStrategy /*= 0*/, DWORD dwBuffSize /*= 16 * 1024*/); +/* 创建 ZLib 解压器对象 */ +// (默认参数:iWindowBits = 15, dwBuffSize = 16 * 1024) +HPSOCKET_API HP_Decompressor __HP_CALL Create_HP_ZLibDecompressor(HP_Fn_DecompressDataCallback fnCallback); +/* 创建 ZLib 解压器对象 */ +HPSOCKET_API HP_Decompressor __HP_CALL Create_HP_ZLibDecompressorEx(HP_Fn_DecompressDataCallback fnCallback, int iWindowBits /*= 15*/, DWORD dwBuffSize /*= 16 * 1024*/); +/* 创建 GZip 解压器对象 */ +// (默认参数:dwBuffSize = 16 * 1024) +HPSOCKET_API HP_Decompressor __HP_CALL Create_HP_GZipDecompressor(HP_Fn_DecompressDataCallback fnCallback); +/* 创建 GZip 解压器对象 */ +HPSOCKET_API HP_Decompressor __HP_CALL Create_HP_GZipDecompressorEx(HP_Fn_DecompressDataCallback fnCallback, DWORD dwBuffSize /*= 16 * 1024*/); + +#endif + +#ifdef _BROTLI_SUPPORT + +/* 创建 Brotli 压缩器对象 */ +//(默认参数:iQuality = 11,iWindow = 22,iMode = 0, dwBuffSize = 16 * 1024) +HPSOCKET_API HP_Compressor __HP_CALL Create_HP_BrotliCompressor(HP_Fn_CompressDataCallback fnCallback); +/* 创建 Brotli 压缩器对象 */ +HPSOCKET_API HP_Compressor __HP_CALL Create_HP_BrotliCompressorEx(HP_Fn_CompressDataCallback fnCallback, int iQuality /*= 11*/, int iWindow /*= 22*/, int iMode /*= 0*/, DWORD dwBuffSize /*= 16 * 1024*/); +/* 创建 Brotli 解压器对象 */ +// (默认参数:dwBuffSize = 16 * 1024) +HPSOCKET_API HP_Decompressor __HP_CALL Create_HP_BrotliDecompressor(HP_Fn_DecompressDataCallback fnCallback); +/* 创建 Brotli 解压器对象 */ +HPSOCKET_API HP_Decompressor __HP_CALL Create_HP_BrotliDecompressorEx(HP_Fn_DecompressDataCallback fnCallback, DWORD dwBuffSize /*= 16 * 1024*/); + +#endif + +/***********************************************************************/ +/***************************** 组件操作方法 *****************************/ + +/* +* 名称:执行压缩 +* 描述:可循环调用以压缩流式或分段数据 +* +* 参数: pData -- 待压缩数据缓冲区 +* iLength -- 待压缩数据长度 +* bLast -- 是否最后一段待压缩数据 +* pContext -- 压缩回调函数 Fn_CompressDataCallback 的上下文参数 +* +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Compressor_Process(HP_Compressor pCompressor, const BYTE* pData, int iLength, BOOL bLast, PVOID pContext /*= nullptr*/); + +/* +* 名称:执行压缩 +* 描述:可循环调用以压缩流式或分段数据 +* +* 参数: pData -- 待压缩数据缓冲区 +* iLength -- 待压缩数据长度 +* bLast -- 是否最后一段待压缩数据 +* bFlush -- 是否强制刷新(强制刷新会降低压缩效率,但可对数据进行分段压缩) +* pContext -- 压缩回调函数 Fn_CompressDataCallback 的上下文参数 +* +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Compressor_ProcessEx(HP_Compressor pCompressor, const BYTE* pData, int iLength, BOOL bLast, BOOL bFlush /*= FALSE*/, PVOID pContext /*= nullptr*/); + +/* 重置压缩器 */ +HPSOCKET_API BOOL __HP_CALL HP_Compressor_Reset(HP_Compressor pCompressor); + +/* +* 名称:执行解压 +* 描述:可循环调用以解压流式或分段数据 +* +* 参数: pData -- 待解压数据缓冲区 +* iLength -- 待解压数据长度 +* pContext -- 解压回调函数 Fn_DecompressDataCallback 的上下文参数 +* +* 返回值: TRUE -- 成功 +* FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 +*/ +HPSOCKET_API BOOL __HP_CALL HP_Decompressor_Process(HP_Decompressor pDecompressor, const BYTE* pData, int iLength, PVOID pContext /*= nullptr*/); + +/* 重置解压器 */ +HPSOCKET_API BOOL __HP_CALL HP_Decompressor_Reset(HP_Decompressor pDecompressor); + +/***********************************************************************/ +/***************************** 属性访问方法 *****************************/ + +/* 检测压缩器是否可用 */ +HPSOCKET_API BOOL __HP_CALL HP_Compressor_IsValid(HP_Compressor pCompressor); + +/* 检测解压器是否可用 */ +HPSOCKET_API BOOL __HP_CALL HP_Decompressor_IsValid(HP_Decompressor pDecompressor); diff --git a/hpsocket/HPTypeDef.h b/hpsocket/HPTypeDef.h new file mode 100644 index 0000000..1f84c93 --- /dev/null +++ b/hpsocket/HPTypeDef.h @@ -0,0 +1,600 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "GlobalDef.h" + +/* HP-Socket 版本号 */ +#define HP_VERSION_MAJOR 6 // 主版本号 +#define HP_VERSION_MINOR 0 // 子版本号 +#define HP_VERSION_REVISE 3 // 修正版本号 +#define HP_VERSION_BUILD 1 // 构建编号 + +//#define _UDP_DISABLED // 禁用 UDP +//#define _SSL_DISABLED // 禁用 SSL +//#define _HTTP_DISABLED // 禁用 HTTP +//#define _ZLIB_DISABLED // 禁用 ZLIB +//#define _ICONV_DISABLED // 禁用 ICONV +//#define _BROTLI_DISABLED // 禁用 BROTLI + +/* 是否启用 UDP,如果定义了 _UDP_DISABLED 则禁用(默认:启用) */ +#if !defined(_UDP_DISABLED) + #ifndef _UDP_SUPPORT + #define _UDP_SUPPORT + #endif +#endif + +/* 是否启用 SSL,如果定义了 _SSL_DISABLED 则禁用(默认:启用) */ +#if !defined(_SSL_DISABLED) + #ifndef _SSL_SUPPORT + #define _SSL_SUPPORT + #endif +#endif + +/* 是否启用 HTTP,如果定义了 _HTTP_DISABLED 则禁用(默认:启用) */ +#if !defined(_HTTP_DISABLED) + #ifndef _HTTP_SUPPORT + #define _HTTP_SUPPORT + #endif +#endif + +/* 是否启用 ZLIB,如果定义了 _ZLIB_DISABLED 则禁用(默认:启用) */ +#if !defined(_ZLIB_DISABLED) + #ifndef _ZLIB_SUPPORT + #define _ZLIB_SUPPORT + #endif +#endif + +/* 是否启用 BROTLI,如果定义了 _BROTLI_DISABLED 则禁用(默认:启用) */ +#if !defined(_BROTLI_DISABLED) + #ifndef _BROTLI_SUPPORT + #define _BROTLI_SUPPORT + #endif +#endif + +/* 是否启用 ICONV,如果定义了 _ICONV_DISABLED 则禁用(默认:启用) */ +#if !defined(_ICONV_DISABLED) + #ifndef _ICONV_SUPPORT + #define _ICONV_SUPPORT + #endif +#endif + +#define HPSOCKET_API EXTERN_C __attribute__ ((__visibility__("default"))) + +#define __HP_CALL + +/*****************************************************************************************************************************************************/ +/**************************************************************** Base Type Definitions **************************************************************/ +/*****************************************************************************************************************************************************/ + +/************************************************************************ +名称:连接 ID 数据类型 +描述:应用程序可以把 CONNID 定义为自身需要的类型(如:ULONG / ULONGLONG) +************************************************************************/ +typedef ULID CONNID, HP_CONNID; + +/************************************************************************ +名称:通信组件服务状态 +描述:应用程序可以通过通信组件的 GetState() 方法获取组件当前服务状态 +************************************************************************/ +typedef enum EnServiceState +{ + SS_STARTING = 0, // 正在启动 + SS_STARTED = 1, // 已经启动 + SS_STOPPING = 2, // 正在停止 + SS_STOPPED = 3, // 已经停止 +} En_HP_ServiceState; + +/************************************************************************ +名称:Socket 操作类型 +描述:应用程序的 OnClose() 事件中通过该参数标识是哪种操作导致的错误 +************************************************************************/ +typedef enum EnSocketOperation +{ + SO_UNKNOWN = 0, // Unknown + SO_ACCEPT = 1, // Acccept + SO_CONNECT = 2, // Connect + SO_SEND = 3, // Send + SO_RECEIVE = 4, // Receive + SO_CLOSE = 5, // Close +} En_HP_SocketOperation; + +/************************************************************************ +名称:事件处理结果 +描述:事件的返回值,不同的返回值会影响通信组件的后续行为 +************************************************************************/ +typedef enum EnHandleResult +{ + HR_OK = 0, // 成功 + HR_IGNORE = 1, // 忽略 + HR_ERROR = 2, // 错误 +} En_HP_HandleResult; + +/************************************************************************ +名称:数据抓取结果 +描述:数据抓取操作的返回值 +************************************************************************/ +typedef enum EnFetchResult +{ + FR_OK = 0, // 成功 + FR_LENGTH_TOO_LONG = 1, // 抓取长度过大 + FR_DATA_NOT_FOUND = 2, // 找不到 ConnID 对应的数据 +} En_HP_FetchResult; + +/************************************************************************ +名称:数据发送策略 +描述:Server 组件和 Agent 组件的数据发送策略 + +* 打包发送策略(默认) :尽量把多个发送操作的数据组合在一起发送,增加传输效率 +* 安全发送策略 :尽量把多个发送操作的数据组合在一起发送,并控制传输速度,避免缓冲区溢出 +* 直接发送策略 :对每一个发送操作都直接投递,适用于负载不高但要求实时性较高的场合 +************************************************************************/ +typedef enum EnSendPolicy +{ + SP_PACK = 0, // 打包模式(默认) + SP_SAFE = 1, // 安全模式 + SP_DIRECT = 2, // 直接模式 +} En_HP_SendPolicy; + +/************************************************************************ +名称:OnSend 事件同步策略 +描述:Server 组件和 Agent 组件的 OnSend 事件同步策略 + +* 不同步(默认) :不同步 OnSend 事件,可能同时触发 OnReceive 和 OnClose 事件 +* 同步 OnClose :只同步 OnClose 事件,可能同时触发 OnReceive 事件 +* 同步 OnReceive :(只用于 TCP 组件)同步 OnReceive 和 OnClose 事件,不可能同时触发 OnReceive 或 OnClose 事件 +************************************************************************/ +typedef enum EnOnSendSyncPolicy +{ + OSSP_NONE = 0, // 不同步(默认) + OSSP_CLOSE = 1, // 同步 OnClose + OSSP_RECEIVE = 2, // 同步 OnReceive(只用于 TCP 组件) +} En_HP_OnSendSyncPolicy; + +/************************************************************************ +名称:地址重用选项 +描述:通信组件底层 socket 的地址重用选项 +************************************************************************/ +typedef enum EnReuseAddressPolicy +{ + RAP_NONE = 0, // 不重用 + RAP_ADDR_ONLY = 1, // 仅重用地址 + RAP_ADDR_AND_PORT = 2, // 重用地址和端口 +} En_HP_ReuseAddressPolicy; + +/************************************************************************ +名称:操作结果代码 +描述:组件 Start() / Stop() 方法执行失败时,可通过 GetLastError() 获取错误代码 +************************************************************************/ +typedef enum EnSocketError +{ + SE_OK = NO_ERROR, // 成功 + SE_ILLEGAL_STATE = 1, // 当前状态不允许操作 + SE_INVALID_PARAM = 2, // 非法参数 + SE_SOCKET_CREATE = 3, // 创建 SOCKET 失败 + SE_SOCKET_BIND = 4, // 绑定 SOCKET 失败 + SE_SOCKET_PREPARE = 5, // 设置 SOCKET 失败 + SE_SOCKET_LISTEN = 6, // 监听 SOCKET 失败 + SE_CP_CREATE = 7, // 创建完成端口失败 + SE_WORKER_THREAD_CREATE = 8, // 创建工作线程失败 + SE_DETECT_THREAD_CREATE = 9, // 创建监测线程失败 + SE_SOCKE_ATTACH_TO_CP = 10, // 绑定完成端口失败 + SE_CONNECT_SERVER = 11, // 连接服务器失败 + SE_NETWORK = 12, // 网络错误 + SE_DATA_PROC = 13, // 数据处理错误 + SE_DATA_SEND = 14, // 数据发送失败 + SE_GC_START = 15, // 垃圾回收启动失败 + + /***** SSL Socket 扩展操作结果代码 *****/ + SE_SSL_ENV_NOT_READY = 101, // SSL 环境未就绪 +} En_HP_SocketError; + +/************************************************************************ +名称:播送模式 +描述:UDP 组件的播送模式(组播或广播) +************************************************************************/ +typedef enum EnCastMode +{ + CM_UNICAST = -1, // 单播 + CM_MULTICAST = 0, // 组播 + CM_BROADCAST = 1, // 广播 +} En_HP_CastMode; + +/************************************************************************ +名称:IP 地址类型 +描述:IP 地址类型枚举值 +************************************************************************/ +typedef enum EnIPAddrType +{ + IPT_ALL = 0, // 所有 + IPT_IPV4 = 1, // IPv4 + IPT_IPV6 = 2, // IPv6 +} En_HP_IPAddrType; + +/************************************************************************ +名称:IP 地址条目结构体 +描述:IP 地址的地址簇/地址值结构体 +************************************************************************/ +typedef struct TIPAddr +{ + En_HP_IPAddrType type; + LPCTSTR address; +} *LPTIPAddr, HP_TIPAddr, *HP_LPTIPAddr; + +/************************************************************************ +名称:缓冲区结构体 +描述:数据缓冲区 +************************************************************************/ +typedef struct _WSABUF +{ + UINT len; + LPBYTE buf; +} WSABUF, *PWSABUF, *LPWSABUF; + +/************************************************************************ +名称:拒绝策略 +描述:调用被拒绝后的处理策略 +************************************************************************/ +typedef enum EnRejectedPolicy +{ + TRP_CALL_FAIL = 0, // 立刻返回失败 + TRP_WAIT_FOR = 1, // 等待(直到成功、超时或线程池关闭等原因导致失败) + TRP_CALLER_RUN = 2, // 调用者线程直接执行 +} En_HP_RejectedPolicy; + +/************************************************************************ +名称:任务缓冲区类型 +描述:TSockeTask 对象创建和销毁时,根据不同类型的缓冲区类型作不同的处理 +************************************************************************/ +typedef enum EnTaskBufferType +{ + TBT_COPY = 0, // 深拷贝 + TBT_REFER = 1, // 浅拷贝 + TBT_ATTACH = 2, // 附属(不负责创建,但负责销毁) +} En_HP_TaskBufferType; + +/************************************************************************ +名称:任务处理函数 +描述:任务处理入口函数 +参数:pvArg -- 自定义参数 +返回值:(无) +************************************************************************/ +typedef VOID (__HP_CALL *Fn_TaskProc)(PVOID pvArg); +typedef Fn_TaskProc HP_Fn_TaskProc; + +struct TSocketTask; + +/************************************************************************ +名称:Socket 任务处理函数 +描述:Socket 任务处理入口函数 +参数:pTask -- Socket 任务结构体指针 +返回值:(无) +************************************************************************/ +typedef VOID (__HP_CALL *Fn_SocketTaskProc)(struct TSocketTask* pTask); +typedef Fn_SocketTaskProc HP_Fn_SocketTaskProc; + +/************************************************************************ +名称:Socket 任务结构体 +描述:封装 Socket 任务相关数据结构 +************************************************************************/ +typedef struct TSocketTask +{ + HP_Fn_SocketTaskProc fn; // 任务处理函数 + PVOID sender; // 发起对象 + CONNID connID; // 连接 ID + LPCBYTE buf; // 数据缓冲区 + INT bufLen; // 数据缓冲区长度 + En_HP_TaskBufferType bufType; // 缓冲区类型 + WPARAM wparam; // 自定义参数 + LPARAM lparam; // 自定义参数 +} *LPTSocketTask, HP_TSocketTask, *HP_LPTSocketTask; + +/************************************************************************ +名称:获取 HPSocket 版本号 +描述:版本号(4 个字节分别为:主版本号,子版本号,修正版本号,构建编号) +************************************************************************/ +inline DWORD GetHPSocketVersion() +{ + return (HP_VERSION_MAJOR << 24) | (HP_VERSION_MINOR << 16) | (HP_VERSION_REVISE << 8) | HP_VERSION_BUILD; +} + +/*****************************************************************************************************************************************************/ +/**************************************************************** SSL Type Definitions ***************************************************************/ +/*****************************************************************************************************************************************************/ + +#ifdef _SSL_SUPPORT + +/************************************************************************ +名称:SSL 工作模式 +描述:标识 SSL 的工作模式,客户端模式或服务端模式 +************************************************************************/ +typedef enum EnSSLSessionMode +{ + SSL_SM_CLIENT = 0, // 客户端模式 + SSL_SM_SERVER = 1, // 服务端模式 +} En_HP_SSLSessionMode; + +/************************************************************************ +名称:SSL 验证模式 +描述:SSL 验证模式选项,SSL_VM_PEER 可以和后面两个选项组合一起 +************************************************************************/ +typedef enum EnSSLVerifyMode +{ + SSL_VM_NONE = 0x00, // SSL_VERIFY_NONE + SSL_VM_PEER = 0x01, // SSL_VERIFY_PEER + SSL_VM_FAIL_IF_NO_PEER_CERT = 0x02, // SSL_VERIFY_FAIL_IF_NO_PEER_CERT + SSL_VM_CLIENT_ONCE = 0x04, // SSL_VERIFY_CLIENT_ONCE +} En_HP_SSLVerifyMode; + +/************************************************************************ +名称:SSL Session 信息类型 +描述:用于 GetSSLSessionInfo(),标识输出的 Session 信息类型 +************************************************************************/ +typedef enum EnSSLSessionInfo +{ + SSL_SSI_MIN = 0, // + SSL_SSI_CTX = 0, // SSL CTX (输出类型:SSL_CTX*) + SSL_SSI_CTX_METHOD = 1, // SSL CTX Mehtod (输出类型:SSL_METHOD*) + SSL_SSI_CTX_CIPHERS = 2, // SSL CTX Ciphers (输出类型:STACK_OF(SSL_CIPHER)*) + SSL_SSI_CTX_CERT_STORE = 3, // SSL CTX Cert Store (输出类型:X509_STORE*) + SSL_SSI_SERVER_NAME_TYPE = 4, // Server Name Type (输出类型:int) + SSL_SSI_SERVER_NAME = 5, // Server Name (输出类型:LPCSTR) + SSL_SSI_VERSION = 6, // SSL Version (输出类型:LPCSTR) + SSL_SSI_METHOD = 7, // SSL Method (输出类型:SSL_METHOD*) + SSL_SSI_CERT = 8, // SSL Cert (输出类型:X509*) + SSL_SSI_PKEY = 9, // SSL Private Key (输出类型:EVP_PKEY*) + SSL_SSI_CURRENT_CIPHER = 10, // SSL Current Cipher (输出类型:SSL_CIPHER*) + SSL_SSI_CIPHERS = 11, // SSL Available Ciphers(输出类型:STACK_OF(SSL_CIPHER)*) + SSL_SSI_CLIENT_CIPHERS = 12, // SSL Client Ciphers (输出类型:STACK_OF(SSL_CIPHER)*) + SSL_SSI_PEER_CERT = 13, // SSL Peer Cert (输出类型:X509*) + SSL_SSI_PEER_CERT_CHAIN = 14, // SSL Peer Cert Chain (输出类型:STACK_OF(X509)*) + SSL_SSI_VERIFIED_CHAIN = 15, // SSL Verified Chain (输出类型:STACK_OF(X509)*) + SSL_SSI_MAX = 15, // +} En_HP_SSLSessionInfo; + +/************************************************************************ +名称:SNI 服务名称回调函数 +描述:根据服务器名称选择 SSL 证书 +参数: + lpszServerName -- 服务器名称(域名) + +返回值: + 0 -- 成功,使用默认 SSL 证书索引 + 正数 -- 成功,使用返回值对应的 SNI 主机证书索引 + 负数 -- 失败,中断 SSL 握手 + +************************************************************************/ +typedef int (__HP_CALL *Fn_SNI_ServerNameCallback)(LPCTSTR lpszServerName, PVOID pContext); +typedef Fn_SNI_ServerNameCallback HP_Fn_SNI_ServerNameCallback; + +#endif + +/*****************************************************************************************************************************************************/ +/**************************************************************** HTTP Type Definitions **************************************************************/ +/*****************************************************************************************************************************************************/ + +#ifdef _HTTP_SUPPORT + +/************************************************************************ +名称:HTTP 版本 +描述:低字节:主版本号,高字节:次版本号 +************************************************************************/ + +typedef enum EnHttpVersion +{ + HV_1_0 = MAKEWORD(1, 0), // HTTP/1.0 + HV_1_1 = MAKEWORD(1, 1) // HTTP/1.1 +} En_HP_HttpVersion; + +/************************************************************************ +名称:URL 域 +描述:HTTP 请求行中 URL 段位的域定义 +************************************************************************/ +typedef enum EnHttpUrlField +{ + HUF_SCHEMA = 0, // Schema + HUF_HOST = 1, // Host + HUF_PORT = 2, // Port + HUF_PATH = 3, // Path + HUF_QUERY = 4, // Query String + HUF_FRAGMENT = 5, // Fragment + HUF_USERINFO = 6, // User Info + HUF_MAX = 7, // (Field Count) +} En_HP_HttpUrlField; + +/************************************************************************ +名称:HTTP 解析结果标识 +描述:指示 HTTP 解析器是否继续执行解析操作 +************************************************************************/ +typedef enum EnHttpParseResult +{ + HPR_OK = 0, // 解析成功 + HPR_SKIP_BODY = 1, // 跳过当前请求 BODY(仅用于 OnHeadersComplete 事件) + HPR_UPGRADE = 2, // 升级协议(仅用于 OnHeadersComplete 事件) + HPR_ERROR = -1, // 解析错误,终止解析,断开连接 +} En_HP_HttpParseResult; + +/************************************************************************ +名称:HTTP 协议升级类型 +描述:标识 HTTP 升级为哪种协议 +************************************************************************/ +typedef enum EnHttpUpgradeType +{ + HUT_NONE = 0, // 没有升级 + HUT_WEB_SOCKET = 1, // WebSocket + HUT_HTTP_TUNNEL = 2, // HTTP 隧道 + HUT_UNKNOWN = -1, // 未知类型 +} En_HP_HttpUpgradeType; + +/************************************************************************ +名称:HTTP 状态码 +描述:HTTP 标准状态码 +************************************************************************/ +typedef enum EnHttpStatusCode +{ + HSC_CONTINUE = 100, + HSC_SWITCHING_PROTOCOLS = 101, + HSC_PROCESSING = 102, + HSC_EARLY_HINTS = 103, + HSC_RESPONSE_IS_STALE = 110, + HSC_REVALIDATION_FAILED = 111, + HSC_DISCONNECTED_OPERATION = 112, + HSC_HEURISTIC_EXPIRATION = 113, + HSC_MISCELLANEOUS_WARNING = 199, + + HSC_OK = 200, + HSC_CREATED = 201, + HSC_ACCEPTED = 202, + HSC_NON_AUTHORITATIVE_INFORMATION = 203, + HSC_NO_CONTENT = 204, + HSC_RESET_CONTENT = 205, + HSC_PARTIAL_CONTENT = 206, + HSC_MULTI_STATUS = 207, + HSC_ALREADY_REPORTED = 208, + HSC_TRANSFORMATION_APPLIED = 214, + HSC_IM_USED = 226, + HSC_MISCELLANEOUS_PERSISTENT_WARNING = 299, + + HSC_MULTIPLE_CHOICES = 300, + HSC_MOVED_PERMANENTLY = 301, + HSC_MOVED_TEMPORARILY = 302, + HSC_SEE_OTHER = 303, + HSC_NOT_MODIFIED = 304, + HSC_USE_PROXY = 305, + HSC_SWITCH_PROXY = 306, + HSC_TEMPORARY_REDIRECT = 307, + HSC_PERMANENT_REDIRECT = 308, + + HSC_BAD_REQUEST = 400, + HSC_UNAUTHORIZED = 401, + HSC_PAYMENT_REQUIRED = 402, + HSC_FORBIDDEN = 403, + HSC_NOT_FOUND = 404, + HSC_METHOD_NOT_ALLOWED = 405, + HSC_NOT_ACCEPTABLE = 406, + HSC_PROXY_AUTHENTICATION_REQUIRED = 407, + HSC_REQUEST_TIMEOUT = 408, + HSC_CONFLICT = 409, + HSC_GONE = 410, + HSC_LENGTH_REQUIRED = 411, + HSC_PRECONDITION_FAILED = 412, + HSC_REQUEST_ENTITY_TOO_LARGE = 413, + HSC_REQUEST_URI_TOO_LONG = 414, + HSC_UNSUPPORTED_MEDIA_TYPE = 415, + HSC_REQUESTED_RANGE_NOT_SATISFIABLE = 416, + HSC_EXPECTATION_FAILED = 417, + HSC_IM_A_TEAPOT = 418, + HSC_PAGE_EXPIRED = 419, + HSC_ENHANCE_YOUR_CALM = 420, + HSC_MISDIRECTED_REQUEST = 421, + HSC_UNPROCESSABLE_ENTITY = 422, + HSC_LOCKED = 423, + HSC_FAILED_DEPENDENCY = 424, + HSC_UNORDERED_COLLECTION = 425, + HSC_UPGRADE_REQUIRED = 426, + HSC_PRECONDITION_REQUIRED = 428, + HSC_TOO_MANY_REQUESTS = 429, + HSC_REQUEST_HEADER_FIELDS_TOO_LARGE_UNOFFICIAL = 430, + HSC_REQUEST_HEADER_FIELDS_TOO_LARGE = 431, + HSC_LOGIN_TIMEOUT = 440, + HSC_NO_RESPONSE = 444, + HSC_RETRY_WITH = 449, + HSC_BLOCKED_BY_PARENTAL_CONTROL = 450, + HSC_UNAVAILABLE_FOR_LEGAL_REASONS = 451, + HSC_CLIENT_CLOSED_LOAD_BALANCED_REQUEST = 460, + HSC_INVALID_X_FORWARDED_FOR = 463, + HSC_REQUEST_HEADER_TOO_LARGE = 494, + HSC_SSL_CERTIFICATE_ERROR = 495, + HSC_SSL_CERTIFICATE_REQUIRED = 496, + HSC_HTTP_REQUEST_SENT_TO_HTTPS_PORT = 497, + HSC_INVALID_TOKEN = 498, + HSC_CLIENT_CLOSED_REQUEST = 499, + + HSC_INTERNAL_SERVER_ERROR = 500, + HSC_NOT_IMPLEMENTED = 501, + HSC_BAD_GATEWAY = 502, + HSC_SERVICE_UNAVAILABLE = 503, + HSC_GATEWAY_TIMEOUT = 504, + HSC_HTTP_VERSION_NOT_SUPPORTED = 505, + HSC_VARIANT_ALSO_NEGOTIATES = 506, + HSC_INSUFFICIENT_STORAGE = 507, + HSC_LOOP_DETECTED = 508, + HSC_BANDWIDTH_LIMIT_EXCEEDED = 509, + HSC_NOT_EXTENDED = 510, + HSC_NETWORK_AUTHENTICATION_REQUIRED = 511, + HSC_WEB_SERVER_UNKNOWN_ERROR = 520, + HSC_WEB_SERVER_IS_DOWN = 521, + HSC_CONNECTION_TIMEOUT = 522, + HSC_ORIGIN_IS_UNREACHABLE = 523, + HSC_TIMEOUT_OCCURED = 524, + HSC_SSL_HANDSHAKE_FAILED = 525, + HSC_INVALID_SSL_CERTIFICATE = 526, + HSC_RAILGUN_ERROR = 527, + HSC_SITE_IS_OVERLOADED = 529, + HSC_SITE_IS_FROZEN = 530, + HSC_IDENTITY_PROVIDER_AUTHENTICATION_ERROR = 561, + HSC_NETWORK_READ_TIMEOUT = 598, + HSC_NETWORK_CONNECT_TIMEOUT = 599, + + HSC_UNPARSEABLE_RESPONSE_HEADERS = 600 +} En_HP_HttpStatusCode; + +/************************************************************************ +名称:Name/Value 结构体 +描述:字符串名值对结构体 +************************************************************************/ +typedef struct TNVPair +{ + LPCSTR name; + LPCSTR value; +} HP_TNVPair, +TParam, HP_TParam, *LPPARAM, *HP_LPPARAM, +THeader, HP_THeader, *LPHEADER, *HP_LPHEADER, +TCookie, HP_TCookie, *LPCOOKIE, *HP_LPCOOKIE; + +#endif + +/*****************************************************************************************************************************************************/ +/********************************************************** Compress / Decompress Definitions ********************************************************/ +/*****************************************************************************************************************************************************/ + +/************************************************************************ +名称:数据回调函数 +描述:回调处理过程中产生的数据输出 +参数: + pData -- 数据缓冲区 + iLength -- 数据长度 + pContext -- 回调上下文 + +返回值: + TRUE -- 成功 + FALSE -- 失败 + +************************************************************************/ +typedef BOOL (__HP_CALL *Fn_DataCallback)(const BYTE* pData, int iLength, PVOID pContext); +typedef Fn_DataCallback Fn_CompressDataCallback; +typedef Fn_DataCallback Fn_DecompressDataCallback; +typedef Fn_DataCallback HP_Fn_DataCallback; +typedef Fn_DataCallback HP_Fn_CompressDataCallback; +typedef Fn_DataCallback HP_Fn_DecompressDataCallback; diff --git a/hpsocket/SocketInterface.h b/hpsocket/SocketInterface.h new file mode 100644 index 0000000..c3cc68b --- /dev/null +++ b/hpsocket/SocketInterface.h @@ -0,0 +1,3114 @@ +/* + * Copyright: JessMA Open Source (ldcsaa@gmail.com) + * + * Author : Bruce Liang + * Website : https://github.com/ldcsaa + * Project : https://github.com/ldcsaa/HP-Socket + * Blog : http://www.cnblogs.com/ldcsaa + * Wiki : http://www.oschina.net/p/hp-socket + * QQ Group : 44636872, 75375912 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "HPTypeDef.h" + +/*****************************************************************************************************************************************************/ +/***************************************************************** TCP/UDP Interfaces ****************************************************************/ +/*****************************************************************************************************************************************************/ + +/************************************************************************ +名称:双接口模版类 +描述:定义双接口转换方法 +************************************************************************/ + +#if defined(__arm__) && defined(__GNUC__) && !(defined(__clang__) || defined(__llvm__)) + +#define __DUAL_VPTR_GAP__ sizeof(PVOID) + +class __IFakeDualInterface__ +{ +public: + virtual ~__IFakeDualInterface__() {} +}; + +template class DualInterface : public F, private __IFakeDualInterface__, public S + +#else + +#define __DUAL_VPTR_GAP__ 0 + +template class DualInterface : public F, public S + +#endif + +{ +public: + + /* this 转换为 F* */ + inline static F* ToF(DualInterface* pThis) + { + return (F*)(pThis); + } + + /* F* 转换为 this */ + inline static DualInterface* FromF(F* pF) + { + return (DualInterface*)(pF); + } + + /* this 转换为 S* */ + inline static S* ToS(DualInterface* pThis) + { + return (S*)(F2S(ToF(pThis))); + } + + /* S* 转换为 this */ + inline static DualInterface* FromS(S* pS) + { + return FromF(S2F(pS)); + } + + /* S* 转换为 F* */ + inline static F* S2F(S* pS) + { + return (F*)((char*)pS - (sizeof(F) + __DUAL_VPTR_GAP__)); + } + + /* F* 转换为 S* */ + inline static S* F2S(F* pF) + { + return (S*)((char*)pF + (sizeof(F) + __DUAL_VPTR_GAP__)); + } + +public: + virtual ~DualInterface() = default; +}; + +/************************************************************************ +名称:复合 Socket 组件接口 +描述:定义复合 Socket 组件的所有操作方法和属性访问方法,复合 Socket 组件同时管理多个 Socket 连接 +************************************************************************/ +class IComplexSocket +{ +public: + + /***********************************************************************/ + /***************************** 组件操作方法 *****************************/ + + /* + * 名称:关闭通信组件 + * 描述:关闭通信组件,关闭完成后断开所有连接并释放所有资源 + * + * 参数: + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 GetLastError() 获取错误代码 + */ + virtual BOOL Stop () = 0; + + /* + * 名称:发送数据 + * 描述:向指定连接发送数据 + * + * 参数: dwConnID -- 连接 ID + * pBuffer -- 发送缓冲区 + * iLength -- 发送缓冲区长度 + * iOffset -- 发送缓冲区指针偏移量 + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 + */ + virtual BOOL Send (CONNID dwConnID, const BYTE* pBuffer, int iLength, int iOffset = 0) = 0; + + /* + * 名称:发送多组数据 + * 描述:向指定连接发送多组数据 + * TCP - 顺序发送所有数据包 + * UDP - 把所有数据包组合成一个数据包发送(数据包的总长度不能大于设置的 UDP 包最大长度) + * + * 参数: dwConnID -- 连接 ID + * pBuffers -- 发送缓冲区数组 + * iCount -- 发送缓冲区数目 + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 + */ + virtual BOOL SendPackets(CONNID dwConnID, const WSABUF pBuffers[], int iCount) = 0; + + /* + * 名称:暂停/恢复接收 + * 描述:暂停/恢复某个连接的数据接收工作 + * + * 参数: dwConnID -- 连接 ID + * bPause -- TRUE - 暂停, FALSE - 恢复 + * 返回值: TRUE -- 成功 + * FALSE -- 失败 + */ + virtual BOOL PauseReceive(CONNID dwConnID, BOOL bPause = TRUE) = 0; + + /* + * 名称:断开连接 + * 描述:断开某个连接 + * + * 参数: dwConnID -- 连接 ID + * bForce -- 是否强制断开连接 + * 返回值: TRUE -- 成功 + * FALSE -- 失败 + */ + virtual BOOL Disconnect(CONNID dwConnID, BOOL bForce = TRUE) = 0; + + /* + * 名称:断开超时连接 + * 描述:断开超过指定时长的连接 + * + * 参数: dwPeriod -- 时长(毫秒) + * bForce -- 是否强制断开连接 + * 返回值: TRUE -- 成功 + * FALSE -- 失败 + */ + virtual BOOL DisconnectLongConnections(DWORD dwPeriod, BOOL bForce = TRUE) = 0; + + /* + * 名称:断开静默连接 + * 描述:断开超过指定时长的静默连接 + * + * 参数: dwPeriod -- 时长(毫秒) + * bForce -- 是否强制断开连接 + * 返回值: TRUE -- 成功 + * FALSE -- 失败 + */ + virtual BOOL DisconnectSilenceConnections(DWORD dwPeriod, BOOL bForce = TRUE) = 0; + + /* + * 名称:等待 + * 描述:等待通信组件停止运行 + * + * 参数: dwMilliseconds -- 超时时间(毫秒,默认:-1,永不超时) + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 + */ + virtual BOOL Wait(DWORD dwMilliseconds = INFINITE) = 0; + +public: + + /***********************************************************************/ + /***************************** 属性访问方法 *****************************/ + + /* + * 名称:设置连接的附加数据 + * 描述:是否为连接绑定附加数据或者绑定什么样的数据,均由应用程序自身决定 + * + * 参数: dwConnID -- 连接 ID + * pv -- 数据 + * 返回值: TRUE -- 成功 + * FALSE -- 失败(无效的连接 ID) + */ + virtual BOOL SetConnectionExtra (CONNID dwConnID, PVOID pExtra) = 0; + + /* + * 名称:获取连接的附加数据 + * 描述:是否为连接绑定附加数据或者绑定什么样的数据,均由应用程序自身决定 + * + * 参数: dwConnID -- 连接 ID + * ppv -- 数据指针 + * 返回值: TRUE -- 成功 + * FALSE -- 失败(无效的连接 ID) + */ + virtual BOOL GetConnectionExtra (CONNID dwConnID, PVOID* ppExtra) = 0; + + /* 检测是否为安全连接(SSL/HTTPS) */ + virtual BOOL IsSecure () = 0; + /* 检查通信组件是否已启动 */ + virtual BOOL HasStarted () = 0; + /* 查看通信组件当前状态 */ + virtual EnServiceState GetState () = 0; + /* 获取连接数 */ + virtual DWORD GetConnectionCount () = 0; + /* 获取所有连接的 CONNID */ + virtual BOOL GetAllConnectionIDs (CONNID pIDs[], DWORD& dwCount) = 0; + /* 获取某个连接时长(毫秒) */ + virtual BOOL GetConnectPeriod (CONNID dwConnID, DWORD& dwPeriod) = 0; + /* 获取某个连接静默时间(毫秒) */ + virtual BOOL GetSilencePeriod (CONNID dwConnID, DWORD& dwPeriod) = 0; + /* 获取某个连接的本地地址信息 */ + virtual BOOL GetLocalAddress (CONNID dwConnID, TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort) = 0; + /* 获取某个连接的远程地址信息 */ + virtual BOOL GetRemoteAddress (CONNID dwConnID, TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort) = 0; + /* 获取最近一次失败操作的错误代码 */ + virtual EnSocketError GetLastError () = 0; + /* 获取最近一次失败操作的错误描述 */ + virtual LPCTSTR GetLastErrorDesc () = 0; + /* 获取连接中未发出数据的长度 */ + virtual BOOL GetPendingDataLength (CONNID dwConnID, int& iPending) = 0; + /* 获取连接的数据接收状态 */ + virtual BOOL IsPauseReceive (CONNID dwConnID, BOOL& bPaused) = 0; + /* 检测是否有效连接 */ + virtual BOOL IsConnected (CONNID dwConnID) = 0; + + /* 设置地址重用选项 */ + virtual void SetReuseAddressPolicy(EnReuseAddressPolicy enReusePolicy) = 0; + /* 设置数据发送策略(对 Linux 平台组件无效) */ + virtual void SetSendPolicy (EnSendPolicy enSendPolicy) = 0; + /* 设置 OnSend 事件同步策略(对 Linux 平台组件无效) */ + virtual void SetOnSendSyncPolicy (EnOnSendSyncPolicy enSyncPolicy) = 0; + /* 设置最大连接数(组件会根据设置值预分配内存,因此需要根据实际情况设置,不宜过大)*/ + virtual void SetMaxConnectionCount (DWORD dwMaxConnectionCount) = 0; + /* 设置 Socket 缓存对象锁定时间(毫秒,在锁定期间该 Socket 缓存对象不能被获取使用) */ + virtual void SetFreeSocketObjLockTime (DWORD dwFreeSocketObjLockTime) = 0; + /* 设置 Socket 缓存池大小(通常设置为平均并发连接数的 1/3 - 1/2) */ + virtual void SetFreeSocketObjPool (DWORD dwFreeSocketObjPool) = 0; + /* 设置内存块缓存池大小(通常设置为 Socket 缓存池大小的 2 - 3 倍) */ + virtual void SetFreeBufferObjPool (DWORD dwFreeBufferObjPool) = 0; + /* 设置 Socket 缓存池回收阀值(通常设置为 Socket 缓存池大小的 3 倍) */ + virtual void SetFreeSocketObjHold (DWORD dwFreeSocketObjHold) = 0; + /* 设置内存块缓存池回收阀值 */ + virtual void SetFreeBufferObjHold (DWORD dwFreeBufferObjHold) = 0; + /* 设置工作线程数量(通常设置为 2 * CPU + 2) */ + virtual void SetWorkerThreadCount (DWORD dwWorkerThreadCount) = 0; + /* 设置是否标记静默时间(设置为 TRUE 时 DisconnectSilenceConnections() 和 GetSilencePeriod() 才有效,默认:TRUE) */ + virtual void SetMarkSilence (BOOL bMarkSilence) = 0; + + /* 获取地址重用选项 */ + virtual EnReuseAddressPolicy GetReuseAddressPolicy () = 0; + /* 获取数据发送策略(对 Linux 平台组件无效) */ + virtual EnSendPolicy GetSendPolicy () = 0; + /* 获取 OnSend 事件同步策略(对 Linux 平台组件无效) */ + virtual EnOnSendSyncPolicy GetOnSendSyncPolicy () = 0; + /* 获取最大连接数 */ + virtual DWORD GetMaxConnectionCount () = 0; + /* 获取 Socket 缓存对象锁定时间 */ + virtual DWORD GetFreeSocketObjLockTime () = 0; + /* 获取 Socket 缓存池大小 */ + virtual DWORD GetFreeSocketObjPool () = 0; + /* 获取内存块缓存池大小 */ + virtual DWORD GetFreeBufferObjPool () = 0; + /* 获取 Socket 缓存池回收阀值 */ + virtual DWORD GetFreeSocketObjHold () = 0; + /* 获取内存块缓存池回收阀值 */ + virtual DWORD GetFreeBufferObjHold () = 0; + /* 获取工作线程数量 */ + virtual DWORD GetWorkerThreadCount () = 0; + /* 检测是否标记静默时间 */ + virtual BOOL IsMarkSilence () = 0; + +public: + virtual ~IComplexSocket() = default; +}; + +/************************************************************************ +名称:通信服务端组件接口 +描述:定义通信服务端组件的所有操作方法和属性访问方法 +************************************************************************/ +class IServer : public IComplexSocket +{ +public: + + /***********************************************************************/ + /***************************** 组件操作方法 *****************************/ + + /* + * 名称:启动通信组件 + * 描述:启动服务端通信组件,启动完成后可开始接收客户端连接并收发数据 + * + * 参数: lpszBindAddress -- 监听地址 + * usPort -- 监听端口 + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 GetLastError() 获取错误代码 + */ + virtual BOOL Start (LPCTSTR lpszBindAddress, USHORT usPort) = 0; + +public: + + /***********************************************************************/ + /***************************** 属性访问方法 *****************************/ + + /* 获取监听 Socket 的地址信息 */ + virtual BOOL GetListenAddress(TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort) = 0; +}; + +/************************************************************************ +名称:TCP 通信服务端组件接口 +描述:定义 TCP 通信服务端组件的所有操作方法和属性访问方法 +************************************************************************/ +class ITcpServer : public IServer +{ +public: + + /***********************************************************************/ + /***************************** 组件操作方法 *****************************/ + + /* + * 名称:发送小文件 + * 描述:向指定连接发送 4096 KB 以下的小文件 + * + * 参数: dwConnID -- 连接 ID + * lpszFileName -- 文件路径 + * pHead -- 头部附加数据 + * pTail -- 尾部附加数据 + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 + */ + virtual BOOL SendSmallFile(CONNID dwConnID, LPCTSTR lpszFileName, const LPWSABUF pHead = nullptr, const LPWSABUF pTail = nullptr) = 0; + +#ifdef _SSL_SUPPORT + /* + * 名称:初始化通信组件 SSL 环境参数 + * 描述:SSL 环境参数必须在 SSL 通信组件启动前完成初始化,否则启动失败 + * + * 参数: iVerifyMode -- SSL 验证模式(参考 EnSSLVerifyMode) + * lpszPemCertFile -- 证书文件 + * lpszPemKeyFile -- 私钥文件 + * lpszKeyPassword -- 私钥密码(没有密码则为空) + * lpszCAPemCertFileOrPath -- CA 证书文件或目录(单向验证或客户端可选) + * fnServerNameCallback -- SNI 回调函数指针(可选,如果为 nullptr 则使用 SNI 默认回调函数) + * + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 + */ + virtual BOOL SetupSSLContext(int iVerifyMode = SSL_VM_NONE, LPCTSTR lpszPemCertFile = nullptr, LPCTSTR lpszPemKeyFile = nullptr, LPCTSTR lpszKeyPassword = nullptr, LPCTSTR lpszCAPemCertFileOrPath = nullptr, Fn_SNI_ServerNameCallback fnServerNameCallback = nullptr) = 0; + + /* + * 名称:初始化通信组件 SSL 环境参数(通过内存加载证书) + * 描述:SSL 环境参数必须在 SSL 通信组件启动前完成初始化,否则启动失败 + * + * 参数: iVerifyMode -- SSL 验证模式(参考 EnSSLVerifyMode) + * lpszPemCert -- 证书内容 + * lpszPemKey -- 私钥内容 + * lpszKeyPassword -- 私钥密码(没有密码则为空) + * lpszCAPemCert -- CA 证书内容(单向验证或客户端可选) + * fnServerNameCallback -- SNI 回调函数指针(可选,如果为 nullptr 则使用 SNI 默认回调函数) + * + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 + */ + virtual BOOL SetupSSLContextByMemory(int iVerifyMode = SSL_VM_NONE, LPCSTR lpszPemCert = nullptr, LPCSTR lpszPemKey = nullptr, LPCSTR lpszKeyPassword = nullptr, LPCSTR lpszCAPemCert = nullptr, Fn_SNI_ServerNameCallback fnServerNameCallback = nullptr) = 0; + + /* + * 名称:增加 SNI 主机证书 + * 描述:SSL 服务端在 SetupSSLContext() 成功后可以调用本方法增加多个 SNI 主机证书 + * + * 参数: iVerifyMode -- SSL 验证模式(参考 EnSSLVerifyMode) + * lpszPemCertFile -- 证书文件 + * lpszPemKeyFile -- 私钥文件 + * lpszKeyPassword -- 私钥密码(没有密码则为空) + * lpszCAPemCertFileOrPath -- CA 证书文件或目录(单向验证可选) + * + * 返回值: 正数 -- 成功,并返回 SNI 主机证书对应的索引,该索引用于在 SNI 回调函数中定位 SNI 主机 + * 负数 -- 失败,可通过 SYS_GetLastError() 获取失败原因 + */ + virtual int AddSSLContext(int iVerifyMode = SSL_VM_NONE, LPCTSTR lpszPemCertFile = nullptr, LPCTSTR lpszPemKeyFile = nullptr, LPCTSTR lpszKeyPassword = nullptr, LPCTSTR lpszCAPemCertFileOrPath = nullptr) = 0; + + /* + * 名称:增加 SNI 主机证书(通过内存加载证书) + * 描述:SSL 服务端在 SetupSSLContext() 成功后可以调用本方法增加多个 SNI 主机证书 + * + * 参数: iVerifyMode -- SSL 验证模式(参考 EnSSLVerifyMode) + * lpszPemCert -- 证书内容 + * lpszPemKey -- 私钥内容 + * lpszKeyPassword -- 私钥密码(没有密码则为空) + * lpszCAPemCert -- CA 证书内容(单向验证可选) + * + * 返回值: 正数 -- 成功,并返回 SNI 主机证书对应的索引,该索引用于在 SNI 回调函数中定位 SNI 主机 + * 负数 -- 失败,可通过 SYS_GetLastError() 获取失败原因 + */ + virtual int AddSSLContextByMemory(int iVerifyMode = SSL_VM_NONE, LPCSTR lpszPemCert = nullptr, LPCSTR lpszPemKey = nullptr, LPCSTR lpszKeyPassword = nullptr, LPCSTR lpszCAPemCert = nullptr) = 0; + + /* + * 名称:绑定 SNI 主机域名 + * 描述:SSL 服务端在 AddSSLContext() 成功后可以调用本方法绑定主机域名到 SNI 主机证书 + * + * 参数: lpszServerName -- 主机域名 + * iContextIndex -- SNI 主机证书对应的索引 + * + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 + */ + virtual BOOL BindSSLServerName(LPCTSTR lpszServerName, int iContextIndex) = 0; + + /* + * 名称:清理通信组件 SSL 运行环境 + * 描述:清理通信组件 SSL 运行环境,回收 SSL 相关内存 + * 1、通信组件析构时会自动调用本方法 + * 2、当要重新设置通信组件 SSL 环境参数时,需要先调用本方法清理原先的环境参数 + * + * 参数: 无 + * + * 返回值:无 + */ + virtual void CleanupSSLContext() = 0; + + /* + * 名称:启动 SSL 握手 + * 描述:当通信组件设置为非自动握手时,需要调用本方法启动 SSL 握手 + * + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 + */ + virtual BOOL StartSSLHandShake(CONNID dwConnID) = 0; + +#endif + +public: + + /***********************************************************************/ + /***************************** 属性访问方法 *****************************/ + + /* 设置 EPOLL 等待事件的最大数量 */ + virtual void SetAcceptSocketCount (DWORD dwAcceptSocketCount) = 0; + /* 设置通信数据缓冲区大小(根据平均通信数据包大小调整设置,通常设置为 1024 的倍数) */ + virtual void SetSocketBufferSize (DWORD dwSocketBufferSize) = 0; + /* 设置监听 Socket 的等候队列大小(根据并发连接数量调整设置) */ + virtual void SetSocketListenQueue (DWORD dwSocketListenQueue) = 0; + /* 设置正常心跳包间隔(毫秒,0 则不发送心跳包,默认:60 * 1000) */ + virtual void SetKeepAliveTime (DWORD dwKeepAliveTime) = 0; + /* 设置异常心跳包间隔(毫秒,0 不发送心跳包,,默认:20 * 1000,如果超过若干次 [默认:WinXP 5 次, Win7 10 次] 检测不到心跳确认包则认为已断线) */ + virtual void SetKeepAliveInterval (DWORD dwKeepAliveInterval) = 0; + /* 设置是否开启 nodelay 模式(默认:FALSE,不开启) */ + virtual void SetNoDelay (BOOL bNoDelay) = 0; + + /* 获取 EPOLL 等待事件的最大数量 */ + virtual DWORD GetAcceptSocketCount () = 0; + /* 获取通信数据缓冲区大小 */ + virtual DWORD GetSocketBufferSize () = 0; + /* 获取监听 Socket 的等候队列大小 */ + virtual DWORD GetSocketListenQueue () = 0; + /* 获取正常心跳包间隔 */ + virtual DWORD GetKeepAliveTime () = 0; + /* 获取异常心跳包间隔 */ + virtual DWORD GetKeepAliveInterval () = 0; + /* 检查是否开启 nodelay 模式 */ + virtual BOOL IsNoDelay () = 0; + +#ifdef _SSL_SUPPORT + /* 设置通信组件握手方式(默认:TRUE,自动握手) */ + virtual void SetSSLAutoHandShake(BOOL bAutoHandShake) = 0; + /* 获取通信组件握手方式 */ + virtual BOOL IsSSLAutoHandShake() = 0; + + /* 设置 SSL 加密算法列表 */ + virtual void SetSSLCipherList(LPCTSTR lpszCipherList) = 0; + /* 获取 SSL 加密算法列表 */ + virtual LPCTSTR GetSSLCipherList() = 0; + + /* + * 名称:获取 SSL Session 信息 + * 描述:获取指定类型的 SSL Session 信息(输出类型参考:EnSSLSessionInfo) + * + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 + */ + virtual BOOL GetSSLSessionInfo(CONNID dwConnID, EnSSLSessionInfo enInfo, LPVOID* lppInfo) = 0; +#endif + +}; + +#ifdef _UDP_SUPPORT + +/************************************************************************ +名称:UDP 通信服务端组件接口 +描述:定义 UDP 通信服务端组件的所有操作方法和属性访问方法 +************************************************************************/ +class IUdpServer : public IServer +{ +public: + + /***********************************************************************/ + /***************************** 组件操作方法 *****************************/ + +public: + + /***********************************************************************/ + /***************************** 属性访问方法 *****************************/ + + /* 设置数据报文最大长度(建议在局域网环境下不超过 1432 字节,在广域网环境下不超过 548 字节) */ + virtual void SetMaxDatagramSize (DWORD dwMaxDatagramSize) = 0; + /* 获取数据报文最大长度 */ + virtual DWORD GetMaxDatagramSize () = 0; + + /* 设置 Receive 预投递数量(根据负载调整设置,Receive 预投递数量越大则丢包概率越小) */ + virtual void SetPostReceiveCount (DWORD dwPostReceiveCount) = 0; + /* 获取 Receive 预投递数量 */ + virtual DWORD GetPostReceiveCount () = 0; + + /* 设置监测包尝试次数(0 则不发送监测跳包,如果超过最大尝试次数则认为已断线) */ + virtual void SetDetectAttempts (DWORD dwDetectAttempts) = 0; + /* 设置监测包发送间隔(毫秒,0 不发送监测包) */ + virtual void SetDetectInterval (DWORD dwDetectInterval) = 0; + /* 获取心跳检查次数 */ + virtual DWORD GetDetectAttempts () = 0; + /* 获取心跳检查间隔 */ + virtual DWORD GetDetectInterval () = 0; +}; + +/************************************************************************ +名称:Server/Agent ARQ 模型组件接口 +描述:定义 Server/Agent 组件的 ARQ 模型组件的所有操作方法 +************************************************************************/ +class IArqSocket +{ +public: + + /***********************************************************************/ + /***************************** 组件操作方法 *****************************/ + +public: + + /***********************************************************************/ + /***************************** 属性访问方法 *****************************/ + + /* 设置是否开启 nodelay 模式(默认:FALSE,不开启) */ + virtual void SetNoDelay (BOOL bNoDelay) = 0; + /* 设置是否关闭拥塞控制(默认:FALSE,不关闭) */ + virtual void SetTurnoffCongestCtrl (BOOL bTurnOff) = 0; + /* 设置数据刷新间隔(毫秒,默认:60) */ + virtual void SetFlushInterval (DWORD dwFlushInterval) = 0; + /* 设置快速重传 ACK 跨越次数(默认:0,关闭快速重传) */ + virtual void SetResendByAcks (DWORD dwResendByAcks) = 0; + /* 设置发送窗口大小(数据包数量,默认:128) */ + virtual void SetSendWndSize (DWORD dwSendWndSize) = 0; + /* 设置接收窗口大小(数据包数量,默认:512) */ + virtual void SetRecvWndSize (DWORD dwRecvWndSize) = 0; + /* 设置最小重传超时时间(毫秒,默认:30) */ + virtual void SetMinRto (DWORD dwMinRto) = 0; + /* 设置快速握手次数限制(默认:5,如果为 0 则不限制) */ + virtual void SetFastLimit (DWORD dwFastLimit) = 0; + /* 设置最大传输单元(默认:0,与 SetMaxDatagramSize() 一致) */ + virtual void SetMaxTransUnit (DWORD dwMaxTransUnit) = 0; + /* 设置最大数据包大小(默认:4096) */ + virtual void SetMaxMessageSize (DWORD dwMaxMessageSize) = 0; + /* 设置握手超时时间(毫秒,默认:5000) */ + virtual void SetHandShakeTimeout (DWORD dwHandShakeTimeout) = 0; + + /* 检测是否开启 nodelay 模式 */ + virtual BOOL IsNoDelay () = 0; + /* 检测是否关闭拥塞控制 */ + virtual BOOL IsTurnoffCongestCtrl () = 0; + /* 获取数据刷新间隔 */ + virtual DWORD GetFlushInterval () = 0; + /* 获取快速重传 ACK 跨越次数 */ + virtual DWORD GetResendByAcks () = 0; + /* 获取发送窗口大小 */ + virtual DWORD GetSendWndSize () = 0; + /* 获取接收窗口大小 */ + virtual DWORD GetRecvWndSize () = 0; + /* 获取最小重传超时时间 */ + virtual DWORD GetMinRto () = 0; + /* 获取快速握手次数限制 */ + virtual DWORD GetFastLimit () = 0; + /* 获取最大传输单元 */ + virtual DWORD GetMaxTransUnit () = 0; + /* 获取最大数据包大小 */ + virtual DWORD GetMaxMessageSize () = 0; + /* 获取握手超时时间 */ + virtual DWORD GetHandShakeTimeout () = 0; + + /* 获取等待发送包数量 */ + virtual BOOL GetWaitingSendMessageCount (CONNID dwConnID, int& iCount) = 0; + +public: + virtual ~IArqSocket() = default; +}; + +/************************************************************************ +名称:UDP ARQ 通信服务端组件接口 +描述:继承了 ARQ 和 Server 接口 +************************************************************************/ +typedef DualInterface IUdpArqServer; + +#endif + +/************************************************************************ +名称:通信代理组件接口 +描述:定义通信代理组件的所有操作方法和属性访问方法,代理组件本质是一个同时连接多个服务器的客户端组件 +************************************************************************/ +class IAgent : public IComplexSocket +{ +public: + + /***********************************************************************/ + /***************************** 组件操作方法 *****************************/ + + /* + * 名称:启动通信组件 + * 描述:启动通信代理组件,启动完成后可开始连接远程服务器 + * + * 参数: lpszBindAddress -- 绑定地址(默认:nullptr,绑定任意地址) + * bAsyncConnect -- 是否采用异步 Connect + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 GetLastError() 获取错误代码 + */ + virtual BOOL Start (LPCTSTR lpszBindAddress = nullptr, BOOL bAsyncConnect = TRUE) = 0; + + /* + * 名称:连接服务器 + * 描述:连接服务器,连接成功后 IAgentListener 会接收到 OnConnect() / OnHandShake() 事件 + * + * 参数: lpszRemoteAddress -- 服务端地址 + * usPort -- 服务端端口 + * pdwConnID -- 连接 ID(默认:nullptr,不获取连接 ID) + * pExtra -- 连接附加数据(默认:nullptr) + * usLocalPort -- 本地端口(默认:0) + * lpszLocalAddress -- 本地地址(默认:nullptr,使用 Start() 方法中绑定的地址) + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 + */ + virtual BOOL Connect(LPCTSTR lpszRemoteAddress, USHORT usPort, CONNID* pdwConnID = nullptr, PVOID pExtra = nullptr, USHORT usLocalPort = 0, LPCTSTR lpszLocalAddress = nullptr) = 0; + + +public: + + /***********************************************************************/ + /***************************** 属性访问方法 *****************************/ + + /* 获取某个连接的远程主机信息 */ + virtual BOOL GetRemoteHost (CONNID dwConnID, TCHAR lpszHost[], int& iHostLen, USHORT& usPort) = 0; + +}; + +/************************************************************************ +名称:TCP 通信代理组件接口 +描述:定义 TCP 通信代理组件的所有操作方法和属性访问方法 +************************************************************************/ +class ITcpAgent : public IAgent +{ +public: + + /***********************************************************************/ + /***************************** 组件操作方法 *****************************/ + + /* + * 名称:发送小文件 + * 描述:向指定连接发送 4096 KB 以下的小文件 + * + * 参数: dwConnID -- 连接 ID + * lpszFileName -- 文件路径 + * pHead -- 头部附加数据 + * pTail -- 尾部附加数据 + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 + */ + virtual BOOL SendSmallFile(CONNID dwConnID, LPCTSTR lpszFileName, const LPWSABUF pHead = nullptr, const LPWSABUF pTail = nullptr) = 0; + +#ifdef _SSL_SUPPORT + /* + * 名称:初始化通信组件 SSL 环境参数 + * 描述:SSL 环境参数必须在 SSL 通信组件启动前完成初始化,否则启动失败 + * + * 参数: iVerifyMode -- SSL 验证模式(参考 EnSSLVerifyMode) + * lpszPemCertFile -- 证书文件(客户端可选) + * lpszPemKeyFile -- 私钥文件(客户端可选) + * lpszKeyPassword -- 私钥密码(没有密码则为空) + * lpszCAPemCertFileOrPath -- CA 证书文件或目录(单向验证或客户端可选) + * + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 + */ + virtual BOOL SetupSSLContext(int iVerifyMode = SSL_VM_NONE, LPCTSTR lpszPemCertFile = nullptr, LPCTSTR lpszPemKeyFile = nullptr, LPCTSTR lpszKeyPassword = nullptr, LPCTSTR lpszCAPemCertFileOrPath = nullptr) = 0; + + /* + * 名称:初始化通信组件 SSL 环境参数(通过内存加载证书) + * 描述:SSL 环境参数必须在 SSL 通信组件启动前完成初始化,否则启动失败 + * + * 参数: iVerifyMode -- SSL 验证模式(参考 EnSSLVerifyMode) + * lpszPemCert -- 证书内容 + * lpszPemKey -- 私钥内容 + * lpszKeyPassword -- 私钥密码(没有密码则为空) + * lpszCAPemCert -- CA 证书内容(单向验证或客户端可选) + * + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 + */ + virtual BOOL SetupSSLContextByMemory(int iVerifyMode = SSL_VM_NONE, LPCSTR lpszPemCert = nullptr, LPCSTR lpszPemKey = nullptr, LPCSTR lpszKeyPassword = nullptr, LPCSTR lpszCAPemCert = nullptr) = 0; + + /* + * 名称:清理通信组件 SSL 运行环境 + * 描述:清理通信组件 SSL 运行环境,回收 SSL 相关内存 + * 1、通信组件析构时会自动调用本方法 + * 2、当要重新设置通信组件 SSL 环境参数时,需要先调用本方法清理原先的环境参数 + * + * 参数: 无 + * + * 返回值:无 + */ + virtual void CleanupSSLContext() = 0; + + /* + * 名称:启动 SSL 握手 + * 描述:当通信组件设置为非自动握手时,需要调用本方法启动 SSL 握手 + * + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 + */ + virtual BOOL StartSSLHandShake(CONNID dwConnID) = 0; + +#endif + +public: + + /***********************************************************************/ + /***************************** 属性访问方法 *****************************/ + + /* 设置同步连接超时时间(毫秒) */ + virtual void SetSyncConnectTimeout (DWORD dwSyncConnectTimeout) = 0; + /* 设置通信数据缓冲区大小(根据平均通信数据包大小调整设置,通常设置为 1024 的倍数) */ + virtual void SetSocketBufferSize (DWORD dwSocketBufferSize) = 0; + /* 设置正常心跳包间隔(毫秒,0 则不发送心跳包,默认:60 * 1000) */ + virtual void SetKeepAliveTime (DWORD dwKeepAliveTime) = 0; + /* 设置异常心跳包间隔(毫秒,0 不发送心跳包,,默认:20 * 1000,如果超过若干次 [默认:WinXP 5 次, Win7 10 次] 检测不到心跳确认包则认为已断线) */ + virtual void SetKeepAliveInterval (DWORD dwKeepAliveInterval) = 0; + /* 设置是否开启 nodelay 模式(默认:FALSE,不开启) */ + virtual void SetNoDelay (BOOL bNoDelay) = 0; + + /* 获取同步连接超时时间 */ + virtual DWORD GetSyncConnectTimeout () = 0; + /* 获取通信数据缓冲区大小 */ + virtual DWORD GetSocketBufferSize () = 0; + /* 获取正常心跳包间隔 */ + virtual DWORD GetKeepAliveTime () = 0; + /* 获取异常心跳包间隔 */ + virtual DWORD GetKeepAliveInterval () = 0; + /* 检查是否开启 nodelay 模式 */ + virtual BOOL IsNoDelay () = 0; + +#ifdef _SSL_SUPPORT + /* 设置通信组件握手方式(默认:TRUE,自动握手) */ + virtual void SetSSLAutoHandShake(BOOL bAutoHandShake) = 0; + /* 获取通信组件握手方式 */ + virtual BOOL IsSSLAutoHandShake() = 0; + + /* 设置 SSL 加密算法列表 */ + virtual void SetSSLCipherList(LPCTSTR lpszCipherList) = 0; + /* 获取 SSL 加密算法列表 */ + virtual LPCTSTR GetSSLCipherList() = 0; + + /* + * 名称:获取 SSL Session 信息 + * 描述:获取指定类型的 SSL Session 信息(输出类型参考:EnSSLSessionInfo) + * + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 + */ + virtual BOOL GetSSLSessionInfo(CONNID dwConnID, EnSSLSessionInfo enInfo, LPVOID* lppInfo) = 0; +#endif + +}; + +/************************************************************************ +名称:通信客户端组件接口 +描述:定义通信客户端组件的所有操作方法和属性访问方法 +************************************************************************/ +class IClient +{ +public: + + /***********************************************************************/ + /***************************** 组件操作方法 *****************************/ + + /* + * 名称:启动通信组件 + * 描述:启动客户端通信组件并连接服务端,启动完成后可开始收发数据 + * + * 参数: lpszRemoteAddress -- 服务端地址 + * usPort -- 服务端端口 + * bAsyncConnect -- 是否采用异步 Connect + * lpszBindAddress -- 绑定地址(默认:nullptr,TcpClient/UdpClient -> 不执行绑定操作,UdpCast 绑定 -> 任意地址) + * usLocalPort -- 本地端口(默认:0) + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 GetLastError() 获取错误代码 + */ + virtual BOOL Start (LPCTSTR lpszRemoteAddress, USHORT usPort, BOOL bAsyncConnect = TRUE, LPCTSTR lpszBindAddress = nullptr, USHORT usLocalPort = 0) = 0; + + /* + * 名称:关闭通信组件 + * 描述:关闭客户端通信组件,关闭完成后断开与服务端的连接并释放所有资源 + * + * 参数: + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 GetLastError() 获取错误代码 + */ + virtual BOOL Stop () = 0; + + /* + * 名称:发送数据 + * 描述:向服务端发送数据 + * + * 参数: pBuffer -- 发送缓冲区 + * iLength -- 发送缓冲区长度 + * iOffset -- 发送缓冲区指针偏移量 + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 + */ + virtual BOOL Send (const BYTE* pBuffer, int iLength, int iOffset = 0) = 0; + + /* + * 名称:发送多组数据 + * 描述:向服务端发送多组数据 + * TCP - 顺序发送所有数据包 + * UDP - 把所有数据包组合成一个数据包发送(数据包的总长度不能大于设置的 UDP 包最大长度) + * + * 参数: pBuffers -- 发送缓冲区数组 + * iCount -- 发送缓冲区数目 + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 + */ + virtual BOOL SendPackets(const WSABUF pBuffers[], int iCount) = 0; + + /* + * 名称:暂停/恢复接收 + * 描述:暂停/恢复某个连接的数据接收工作 + * + * bPause -- TRUE - 暂停, FALSE - 恢复 + * 返回值: TRUE -- 成功 + * FALSE -- 失败 + */ + virtual BOOL PauseReceive(BOOL bPause = TRUE) = 0; + + /* + * 名称:等待 + * 描述:等待通信组件停止运行 + * + * 参数: dwMilliseconds -- 超时时间(毫秒,默认:-1,永不超时) + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 + */ + virtual BOOL Wait(DWORD dwMilliseconds = INFINITE) = 0; + +public: + + /***********************************************************************/ + /***************************** 属性访问方法 *****************************/ + + /* 设置连接的附加数据 */ + virtual void SetExtra (PVOID pExtra) = 0; + + /* 获取连接的附加数据 */ + virtual PVOID GetExtra () = 0; + + /* 检测是否为安全连接(SSL/HTTPS) */ + virtual BOOL IsSecure () = 0; + /* 检查通信组件是否已启动 */ + virtual BOOL HasStarted () = 0; + /* 查看通信组件当前状态 */ + virtual EnServiceState GetState () = 0; + /* 获取最近一次失败操作的错误代码 */ + virtual EnSocketError GetLastError () = 0; + /* 获取最近一次失败操作的错误描述 */ + virtual LPCTSTR GetLastErrorDesc() = 0; + /* 获取该组件对象的连接 ID */ + virtual CONNID GetConnectionID () = 0; + /* 获取 Client Socket 的地址信息 */ + virtual BOOL GetLocalAddress (TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort) = 0; + /* 获取连接的远程主机信息 */ + virtual BOOL GetRemoteHost (TCHAR lpszHost[], int& iHostLen, USHORT& usPort) = 0; + /* 获取连接中未发出数据的长度 */ + virtual BOOL GetPendingDataLength (int& iPending) = 0; + /* 获取连接的数据接收状态 */ + virtual BOOL IsPauseReceive (BOOL& bPaused) = 0; + /* 检测是否有效连接 */ + virtual BOOL IsConnected () = 0; + + /* 设置地址重用选项 */ + virtual void SetReuseAddressPolicy(EnReuseAddressPolicy enReusePolicy) = 0; + /* 设置内存块缓存池大小 */ + virtual void SetFreeBufferPoolSize (DWORD dwFreeBufferPoolSize) = 0; + /* 设置内存块缓存池回收阀值 */ + virtual void SetFreeBufferPoolHold (DWORD dwFreeBufferPoolHold) = 0; + + /* 获取地址重用选项 */ + virtual EnReuseAddressPolicy GetReuseAddressPolicy () = 0; + /* 获取内存块缓存池大小 */ + virtual DWORD GetFreeBufferPoolSize () = 0; + /* 获取内存块缓存池回收阀值 */ + virtual DWORD GetFreeBufferPoolHold () = 0; + +public: + virtual ~IClient() = default; +}; + +/************************************************************************ +名称:TCP 通信客户端组件接口 +描述:定义 TCP 通信客户端组件的所有操作方法和属性访问方法 +************************************************************************/ +class ITcpClient : public IClient +{ +public: + + /***********************************************************************/ + /***************************** 组件操作方法 *****************************/ + + /* + * 名称:发送小文件 + * 描述:向服务端发送 4096 KB 以下的小文件 + * + * 参数: lpszFileName -- 文件路径 + * pHead -- 头部附加数据 + * pTail -- 尾部附加数据 + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 + */ + virtual BOOL SendSmallFile(LPCTSTR lpszFileName, const LPWSABUF pHead = nullptr, const LPWSABUF pTail = nullptr) = 0; + +#ifdef _SSL_SUPPORT + /* + * 名称:初始化通信组件 SSL 环境参数 + * 描述:SSL 环境参数必须在 SSL 通信组件启动前完成初始化,否则启动失败 + * + * 参数: iVerifyMode -- SSL 验证模式(参考 EnSSLVerifyMode) + * lpszPemCertFile -- 证书文件(客户端可选) + * lpszPemKeyFile -- 私钥文件(客户端可选) + * lpszKeyPassword -- 私钥密码(没有密码则为空) + * lpszCAPemCertFileOrPath -- CA 证书文件或目录(单向验证或客户端可选) + * + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 + */ + virtual BOOL SetupSSLContext(int iVerifyMode = SSL_VM_NONE, LPCTSTR lpszPemCertFile = nullptr, LPCTSTR lpszPemKeyFile = nullptr, LPCTSTR lpszKeyPassword = nullptr, LPCTSTR lpszCAPemCertFileOrPath = nullptr) = 0; + + /* + * 名称:初始化通信组件 SSL 环境参数(通过内存加载证书) + * 描述:SSL 环境参数必须在 SSL 通信组件启动前完成初始化,否则启动失败 + * + * 参数: iVerifyMode -- SSL 验证模式(参考 EnSSLVerifyMode) + * lpszPemCert -- 证书内容 + * lpszPemKey -- 私钥内容 + * lpszKeyPassword -- 私钥密码(没有密码则为空) + * lpszCAPemCert -- CA 证书内容(单向验证或客户端可选) + * + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 + */ + virtual BOOL SetupSSLContextByMemory(int iVerifyMode = SSL_VM_NONE, LPCSTR lpszPemCert = nullptr, LPCSTR lpszPemKey = nullptr, LPCSTR lpszKeyPassword = nullptr, LPCSTR lpszCAPemCert = nullptr) = 0; + + /* + * 名称:清理通信组件 SSL 运行环境 + * 描述:清理通信组件 SSL 运行环境,回收 SSL 相关内存 + * 1、通信组件析构时会自动调用本方法 + * 2、当要重新设置通信组件 SSL 环境参数时,需要先调用本方法清理原先的环境参数 + * + * 参数: 无 + * + * 返回值:无 + */ + virtual void CleanupSSLContext() = 0; + + /* + * 名称:启动 SSL 握手 + * 描述:当通信组件设置为非自动握手时,需要调用本方法启动 SSL 握手 + * + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 + */ + virtual BOOL StartSSLHandShake() = 0; + +#endif + +public: + + /***********************************************************************/ + /***************************** 属性访问方法 *****************************/ + + /* 设置同步连接超时时间(毫秒) */ + virtual void SetSyncConnectTimeout (DWORD dwSyncConnectTimeout) = 0; + /* 设置通信数据缓冲区大小(根据平均通信数据包大小调整设置,通常设置为:(N * 1024) - sizeof(TBufferObj)) */ + virtual void SetSocketBufferSize (DWORD dwSocketBufferSize) = 0; + /* 设置正常心跳包间隔(毫秒,0 则不发送心跳包,默认:60 * 1000) */ + virtual void SetKeepAliveTime (DWORD dwKeepAliveTime) = 0; + /* 设置异常心跳包间隔(毫秒,0 不发送心跳包,,默认:20 * 1000,如果超过若干次 [默认:WinXP 5 次, Win7 10 次] 检测不到心跳确认包则认为已断线) */ + virtual void SetKeepAliveInterval (DWORD dwKeepAliveInterval) = 0; + /* 设置是否开启 nodelay 模式(默认:FALSE,不开启) */ + virtual void SetNoDelay (BOOL bNoDelay) = 0; + + /* 获取同步连接超时时间 */ + virtual DWORD GetSyncConnectTimeout () = 0; + /* 获取通信数据缓冲区大小 */ + virtual DWORD GetSocketBufferSize () = 0; + /* 获取正常心跳包间隔 */ + virtual DWORD GetKeepAliveTime () = 0; + /* 获取异常心跳包间隔 */ + virtual DWORD GetKeepAliveInterval () = 0; + /* 检查是否开启 nodelay 模式 */ + virtual BOOL IsNoDelay () = 0; + +#ifdef _SSL_SUPPORT + /* 设置通信组件握手方式(默认:TRUE,自动握手) */ + virtual void SetSSLAutoHandShake(BOOL bAutoHandShake) = 0; + /* 获取通信组件握手方式 */ + virtual BOOL IsSSLAutoHandShake() = 0; + + /* 设置 SSL 加密算法列表 */ + virtual void SetSSLCipherList(LPCTSTR lpszCipherList) = 0; + /* 获取 SSL 加密算法列表 */ + virtual LPCTSTR GetSSLCipherList() = 0; + + /* + * 名称:获取 SSL Session 信息 + * 描述:获取指定类型的 SSL Session 信息(输出类型参考:EnSSLSessionInfo) + * + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 + */ + virtual BOOL GetSSLSessionInfo(EnSSLSessionInfo enInfo, LPVOID* lppInfo) = 0; +#endif + +}; + +#ifdef _UDP_SUPPORT + +/************************************************************************ +名称:UDP 通信客户端组件接口 +描述:定义 UDP 通信客户端组件的所有操作方法和属性访问方法 +************************************************************************/ +class IUdpClient : public IClient +{ +public: + + /***********************************************************************/ + /***************************** 组件操作方法 *****************************/ + +public: + + /***********************************************************************/ + /***************************** 属性访问方法 *****************************/ + + /* 设置数据报文最大长度(建议在局域网环境下不超过 1432 字节,在广域网环境下不超过 548 字节) */ + virtual void SetMaxDatagramSize (DWORD dwMaxDatagramSize) = 0; + /* 获取数据报文最大长度 */ + virtual DWORD GetMaxDatagramSize() = 0; + + /* 设置监测包尝试次数(0 则不发送监测跳包,如果超过最大尝试次数则认为已断线) */ + virtual void SetDetectAttempts (DWORD dwDetectAttempts) = 0; + /* 设置监测包发送间隔(毫秒,0 不发送监测包) */ + virtual void SetDetectInterval (DWORD dwDetectInterval) = 0; + /* 获取心跳检查次数 */ + virtual DWORD GetDetectAttempts () = 0; + /* 获取心跳检查间隔 */ + virtual DWORD GetDetectInterval () = 0; +}; + +/************************************************************************ +名称:UDP 传播组件接口 +描述:定义 UDP 传播(组播或广播)组件的所有操作方法和属性访问方法 +************************************************************************/ +class IUdpCast : public IClient +{ +public: + + /***********************************************************************/ + /***************************** 组件操作方法 *****************************/ + +public: + + /***********************************************************************/ + /***************************** 属性访问方法 *****************************/ + + /* 设置数据报文最大长度(建议在局域网环境下不超过 1432 字节,在广域网环境下不超过 548 字节) */ + virtual void SetMaxDatagramSize (DWORD dwMaxDatagramSize) = 0; + /* 获取数据报文最大长度 */ + virtual DWORD GetMaxDatagramSize() = 0; + + /* 设置传播模式(组播或广播) */ + virtual void SetCastMode (EnCastMode enCastMode) = 0; + /* 获取传播模式 */ + virtual EnCastMode GetCastMode () = 0; + + /* 设置组播报文的 TTL(0 - 255) */ + virtual void SetMultiCastTtl (int iMCTtl) = 0; + /* 获取组播报文的 TTL */ + virtual int GetMultiCastTtl () = 0; + + /* 设置是否启用组播环路(TRUE or FALSE) */ + virtual void SetMultiCastLoop (BOOL bMCLoop) = 0; + /* 检测是否启用组播环路 */ + virtual BOOL IsMultiCastLoop () = 0; + + /* 获取当前数据报的远程地址信息(通常在 OnReceive 事件中调用) */ + virtual BOOL GetRemoteAddress (TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort) = 0; +}; + +/************************************************************************ +名称:UDP 节点组件接口 +描述:定义 UDP 节点组件的所有操作方法和属性访问方法 +************************************************************************/ +class IUdpNode +{ +public: + + /***********************************************************************/ + /***************************** 组件操作方法 *****************************/ + + /* + * 名称:启动通信组件 + * 描述:启动 UDP 节点通信组件,启动完成后可开始收发数据 + * + * 参数: lpszBindAddress -- 绑定地址(默认:nullptr,绑定任意地址) + * usPort -- 本地端口(默认:0) + * enCastMode -- 传播模式(默认:CM_UNICAST) + * lpszCastAddress -- 传播地址(默认:nullptr,当 enCaseMode 为 CM_MULTICAST 或 CM_BROADCAST 时有效) + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 GetLastError() 获取错误代码 + */ + virtual BOOL Start(LPCTSTR lpszBindAddress = nullptr, USHORT usPort = 0, EnCastMode enCastMode = CM_UNICAST, LPCTSTR lpszCastAddress = nullptr) = 0; + + /* + * 名称:关闭通信组件 + * 描述:关闭 UDP 节点通信组件,关闭完成后释放所有资源 + * + * 参数: + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 GetLastError() 获取错误代码 + */ + virtual BOOL Stop() = 0; + + /* + * 名称:发送数据 + * 描述:向指定地址发送数据 + * + * 参数: lpszRemoteAddress -- 远程地址 + * usRemotePort -- 远程端口 + * pBuffer -- 发送缓冲区 + * iLength -- 发送缓冲区长度 + * iOffset -- 发送缓冲区指针偏移量 + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 + */ + virtual BOOL Send(LPCTSTR lpszRemoteAddress, USHORT usRemotePort, const BYTE* pBuffer, int iLength, int iOffset = 0) = 0; + + /* + * 名称:发送多组数据 + * 描述:向指定地址发送多组数据,把所有数据包组合成一个数据包发送(数据包的总长度不能大于设置的 UDP 包最大长度) + * + * 参数: lpszRemoteAddress -- 远程地址 + * usRemotePort -- 远程端口 + * pBuffers -- 发送缓冲区数组 + * iCount -- 发送缓冲区数目 + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 + */ + virtual BOOL SendPackets(LPCTSTR lpszRemoteAddress, USHORT usRemotePort, const WSABUF pBuffers[], int iCount) = 0; + + /* + * 名称:发送数据 + * 描述:向传播地址发送数据 + * + * 参数: pBuffer -- 发送缓冲区 + * iLength -- 发送缓冲区长度 + * iOffset -- 发送缓冲区指针偏移量 + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 + */ + virtual BOOL SendCast(const BYTE* pBuffer, int iLength, int iOffset = 0) = 0; + + /* + * 名称:发送多组数据 + * 描述:向传播地址发送多组数据,把所有数据包组合成一个数据包发送(数据包的总长度不能大于设置的 UDP 包最大长度) + * + * 参数: pBuffers -- 发送缓冲区数组 + * iCount -- 发送缓冲区数目 + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 + */ + virtual BOOL SendCastPackets(const WSABUF pBuffers[], int iCount) = 0; + + /* + * 名称:等待 + * 描述:等待通信组件停止运行 + * + * 参数: dwMilliseconds -- 超时时间(毫秒,默认:-1,永不超时) + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 + */ + virtual BOOL Wait(DWORD dwMilliseconds = INFINITE) = 0; + +public: + + /***********************************************************************/ + /***************************** 属性访问方法 *****************************/ + + /* 设置附加数据 */ + virtual void SetExtra (PVOID pExtra) = 0; + + /* 获取附加数据 */ + virtual PVOID GetExtra () = 0; + + /* 检查通信组件是否已启动 */ + virtual BOOL HasStarted () = 0; + /* 查看通信组件当前状态 */ + virtual EnServiceState GetState () = 0; + /* 获取最近一次失败操作的错误代码 */ + virtual EnSocketError GetLastError () = 0; + /* 获取最近一次失败操作的错误描述 */ + virtual LPCTSTR GetLastErrorDesc () = 0; + /* 获取本节点地址 */ + virtual BOOL GetLocalAddress (TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort) = 0; + /* 获取本节点传播地址 */ + virtual BOOL GetCastAddress (TCHAR lpszAddress[], int& iAddressLen, USHORT& usPort) = 0; + /* 获取传播模式 */ + virtual EnCastMode GetCastMode () = 0; + /* 获取未发出数据的长度 */ + virtual BOOL GetPendingDataLength (int& iPending) = 0; + + /* 设置数据报文最大长度(建议在局域网环境下不超过 1432 字节,在广域网环境下不超过 548 字节) */ + virtual void SetMaxDatagramSize (DWORD dwMaxDatagramSize) = 0; + /* 获取数据报文最大长度 */ + virtual DWORD GetMaxDatagramSize() = 0; + + /* 设置组播报文的 TTL(0 - 255) */ + virtual void SetMultiCastTtl (int iMCTtl) = 0; + /* 获取组播报文的 TTL */ + virtual int GetMultiCastTtl () = 0; + + /* 设置是否启用组播环路(TRUE or FALSE) */ + virtual void SetMultiCastLoop (BOOL bMCLoop) = 0; + /* 检测是否启用组播环路 */ + virtual BOOL IsMultiCastLoop () = 0; + + /* 设置地址重用选项 */ + virtual void SetReuseAddressPolicy(EnReuseAddressPolicy enReusePolicy) = 0; + /* 设置工作线程数量(通常设置为 2 * CPU + 2) */ + virtual void SetWorkerThreadCount (DWORD dwWorkerThreadCount) = 0; + /* 设置 Receive 预投递数量(根据负载调整设置,Receive 预投递数量越大则丢包概率越小) */ + virtual void SetPostReceiveCount (DWORD dwPostReceiveCount) = 0; + /* 设置内存块缓存池大小 */ + virtual void SetFreeBufferPoolSize (DWORD dwFreeBufferPoolSize) = 0; + /* 设置内存块缓存池回收阀值 */ + virtual void SetFreeBufferPoolHold (DWORD dwFreeBufferPoolHold) = 0; + + /* 获取地址重用选项 */ + virtual EnReuseAddressPolicy GetReuseAddressPolicy() = 0; + /* 获取工作线程数量 */ + virtual DWORD GetWorkerThreadCount () = 0; + /* 获取 Receive 预投递数量 */ + virtual DWORD GetPostReceiveCount () = 0; + /* 获取内存块缓存池大小 */ + virtual DWORD GetFreeBufferPoolSize () = 0; + /* 获取内存块缓存池回收阀值 */ + virtual DWORD GetFreeBufferPoolHold () = 0; + +public: + virtual ~IUdpNode() = default; +}; + +/************************************************************************ +名称:Client ARQ 模型组件接口 +描述:定义 Client 组件的 ARQ 模型组件的所有操作方法 +************************************************************************/ +class IArqClient +{ +public: + + /***********************************************************************/ + /***************************** 组件操作方法 *****************************/ + +public: + + /***********************************************************************/ + /***************************** 属性访问方法 *****************************/ + + /* 设置是否开启 nodelay 模式(默认:FALSE,不开启) */ + virtual void SetNoDelay (BOOL bNoDelay) = 0; + /* 设置是否关闭拥塞控制(默认:FALSE,不关闭) */ + virtual void SetTurnoffCongestCtrl (BOOL bTurnOff) = 0; + /* 设置数据刷新间隔(毫秒,默认:60) */ + virtual void SetFlushInterval (DWORD dwFlushInterval) = 0; + /* 设置快速重传 ACK 跨越次数(默认:0,关闭快速重传) */ + virtual void SetResendByAcks (DWORD dwResendByAcks) = 0; + /* 设置发送窗口大小(数据包数量,默认:128) */ + virtual void SetSendWndSize (DWORD dwSendWndSize) = 0; + /* 设置接收窗口大小(数据包数量,默认:512) */ + virtual void SetRecvWndSize (DWORD dwRecvWndSize) = 0; + /* 设置最小重传超时时间(毫秒,默认:30) */ + virtual void SetMinRto (DWORD dwMinRto) = 0; + /* 设置快速握手次数限制(默认:5,如果为 0 则不限制) */ + virtual void SetFastLimit (DWORD dwFastLimit) = 0; + /* 设置最大传输单元(默认:0,与 SetMaxDatagramSize() 一致) */ + virtual void SetMaxTransUnit (DWORD dwMaxTransUnit) = 0; + /* 设置最大数据包大小(默认:4096) */ + virtual void SetMaxMessageSize (DWORD dwMaxMessageSize) = 0; + /* 设置握手超时时间(毫秒,默认:5000) */ + virtual void SetHandShakeTimeout (DWORD dwHandShakeTimeout) = 0; + + /* 检测是否开启 nodelay 模式 */ + virtual BOOL IsNoDelay () = 0; + /* 检测是否关闭拥塞控制 */ + virtual BOOL IsTurnoffCongestCtrl () = 0; + /* 获取数据刷新间隔 */ + virtual DWORD GetFlushInterval () = 0; + /* 获取快速重传 ACK 跨越次数 */ + virtual DWORD GetResendByAcks () = 0; + /* 获取发送窗口大小 */ + virtual DWORD GetSendWndSize () = 0; + /* 获取接收窗口大小 */ + virtual DWORD GetRecvWndSize () = 0; + /* 获取最小重传超时时间 */ + virtual DWORD GetMinRto () = 0; + /* 获取快速握手次数限制 */ + virtual DWORD GetFastLimit () = 0; + /* 获取最大传输单元 */ + virtual DWORD GetMaxTransUnit () = 0; + /* 获取最大数据包大小 */ + virtual DWORD GetMaxMessageSize () = 0; + /* 获取握手超时时间 */ + virtual DWORD GetHandShakeTimeout () = 0; + + /* 获取等待发送包数量 */ + virtual BOOL GetWaitingSendMessageCount (int& iCount) = 0; + +public: + virtual ~IArqClient() = default; +}; + +/************************************************************************ +名称:UDP ARQ 通信客户端组件接口 +描述:继承了 ARQ 和 Client 接口 +************************************************************************/ +typedef DualInterface IUdpArqClient; + +#endif + +/************************************************************************ +名称:Server/Agent PULL 模型组件接口 +描述:定义 Server/Agent 组件的 PULL 模型组件的所有操作方法 +************************************************************************/ +class IPullSocket +{ +public: + + /* + * 名称:抓取数据 + * 描述:用户通过该方法从 Socket 组件中抓取数据 + * + * 参数: dwConnID -- 连接 ID + * pData -- 抓取缓冲区 + * iLength -- 抓取数据长度 + * 返回值: EnFetchResult + */ + virtual EnFetchResult Fetch (CONNID dwConnID, BYTE* pData, int iLength) = 0; + + /* + * 名称:窥探数据(不会移除缓冲区数据) + * 描述:用户通过该方法从 Socket 组件中窥探数据 + * + * 参数: dwConnID -- 连接 ID + * pData -- 窥探缓冲区 + * iLength -- 窥探数据长度 + * 返回值: EnFetchResult + */ + virtual EnFetchResult Peek (CONNID dwConnID, BYTE* pData, int iLength) = 0; + +public: + virtual ~IPullSocket() = default; +}; + +/************************************************************************ +名称:Client PULL 模型组件接口 +描述:定义 Client 组件的 PULL 模型组件的所有操作方法 +************************************************************************/ +class IPullClient +{ +public: + + /* + * 名称:抓取数据 + * 描述:用户通过该方法从 Socket 组件中抓取数据 + * + * 参数: pData -- 抓取缓冲区 + * iLength -- 抓取数据长度 + * 返回值: EnFetchResult + */ + virtual EnFetchResult Fetch (BYTE* pData, int iLength) = 0; + + /* + * 名称:窥探数据(不会移除缓冲区数据) + * 描述:用户通过该方法从 Socket 组件中窥探数据 + * + * 参数: pData -- 窥探缓冲区 + * iLength -- 窥探数据长度 + * 返回值: EnFetchResult + */ + virtual EnFetchResult Peek (BYTE* pData, int iLength) = 0; + +public: + virtual ~IPullClient() = default; +}; + +/************************************************************************ +名称:TCP PULL 模型组件接口 +描述:继承了 PULL 和 Socket 接口 +************************************************************************/ +typedef DualInterface ITcpPullServer; +typedef DualInterface ITcpPullAgent; +typedef DualInterface ITcpPullClient; + +/************************************************************************ +名称:Server/Agent PACK 模型组件接口 +描述:定义 Server/Agent 组件的 PACK 模型组件的所有操作方法 +************************************************************************/ +class IPackSocket +{ +public: + + /***********************************************************************/ + /***************************** 属性访问方法 *****************************/ + + /* 设置数据包最大长度(有效数据包最大长度不能超过 4194303/0x3FFFFF 字节,默认:262144/0x40000) */ + virtual void SetMaxPackSize (DWORD dwMaxPackSize) = 0; + /* 设置包头标识(有效包头标识取值范围 0 ~ 1023/0x3FF,当包头标识为 0 时不校验包头,默认:0) */ + virtual void SetPackHeaderFlag (USHORT usPackHeaderFlag) = 0; + + /* 获取数据包最大长度 */ + virtual DWORD GetMaxPackSize () = 0; + /* 获取包头标识 */ + virtual USHORT GetPackHeaderFlag() = 0; + +public: + virtual ~IPackSocket() = default; +}; + +/************************************************************************ +名称:Client PACK 模型组件接口 +描述:定义 Client 组件的 PACK 模型组件的所有操作方法 +************************************************************************/ +class IPackClient +{ +public: + + /***********************************************************************/ + /***************************** 属性访问方法 *****************************/ + + /* 设置数据包最大长度(有效数据包最大长度不能超过 4194303/0x3FFFFF 字节,默认:262144/0x40000) */ + virtual void SetMaxPackSize (DWORD dwMaxPackSize) = 0; + /* 设置包头标识(有效包头标识取值范围 0 ~ 1023/0x3FF,当包头标识为 0 时不校验包头,默认:0) */ + virtual void SetPackHeaderFlag (USHORT usPackHeaderFlag) = 0; + + /* 获取数据包最大长度 */ + virtual DWORD GetMaxPackSize () = 0; + /* 获取包头标识 */ + virtual USHORT GetPackHeaderFlag() = 0; + +public: + virtual ~IPackClient() = default; +}; + +/************************************************************************ +名称:TCP PACK 模型组件接口 +描述:继承了 PACK 和 Socket 接口 +************************************************************************/ +typedef DualInterface ITcpPackServer; +typedef DualInterface ITcpPackAgent; +typedef DualInterface ITcpPackClient; + +/************************************************************************ +名称:Socket 监听器基接口 +描述:定义组件监听器的公共方法 +************************************************************************/ +template class ISocketListenerT +{ +public: + + /* + * 名称:握手完成通知 + * 描述:连接完成握手时,Socket 监听器将收到该通知,监听器接收到该通知后才能开始 + * 数据收发操作 + * + * 参数: pSender -- 事件源对象 + * dwConnID -- 连接 ID + * 返回值: HR_OK / HR_IGNORE -- 继续执行 + * HR_ERROR -- 引发 OnClose() 事件并关闭连接 + */ + virtual EnHandleResult OnHandShake(T* pSender, CONNID dwConnID) = 0; + + /* + * 名称:已发送数据通知 + * 描述:成功发送数据后,Socket 监听器将收到该通知 + * + * 参数: pSender -- 事件源对象 + * dwConnID -- 连接 ID + * pData -- 已发送数据缓冲区 + * iLength -- 已发送数据长度 + * 返回值: HR_OK / HR_IGNORE -- 继续执行 + * HR_ERROR -- 该通知不允许返回 HR_ERROR(调试模式下引发断言错误) + */ + virtual EnHandleResult OnSend(T* pSender, CONNID dwConnID, const BYTE* pData, int iLength) = 0; + + /* + * 名称:数据到达通知(PUSH 模型) + * 描述:对于 PUSH 模型的 Socket 通信组件,成功接收数据后将向 Socket 监听器发送该通知 + * + * 参数: pSender -- 事件源对象 + * dwConnID -- 连接 ID + * pData -- 已接收数据缓冲区 + * iLength -- 已接收数据长度 + * 返回值: HR_OK / HR_IGNORE -- 继续执行 + * HR_ERROR -- 引发 OnClose() 事件并关闭连接 + */ + virtual EnHandleResult OnReceive(T* pSender, CONNID dwConnID, const BYTE* pData, int iLength) = 0; + + /* + * 名称:数据到达通知(PULL 模型) + * 描述:对于 PULL 模型的 Socket 通信组件,成功接收数据后将向 Socket 监听器发送该通知 + * + * 参数: pSender -- 事件源对象 + * dwConnID -- 连接 ID + * iLength -- 已接收数据长度 + * 返回值: HR_OK / HR_IGNORE -- 继续执行 + * HR_ERROR -- 引发 OnClose() 事件并关闭连接 + */ + virtual EnHandleResult OnReceive(T* pSender, CONNID dwConnID, int iLength) = 0; + + /* + * 名称:通信错误通知 + * 描述:通信发生错误后,Socket 监听器将收到该通知,并关闭连接 + * + * 参数: pSender -- 事件源对象 + * dwConnID -- 连接 ID + * enOperation -- Socket 操作类型 + * iErrorCode -- 错误代码 + * 返回值: 忽略返回值 + */ + virtual EnHandleResult OnClose(T* pSender, CONNID dwConnID, EnSocketOperation enOperation, int iErrorCode) = 0; + +public: + virtual ~ISocketListenerT() = default; +}; + +template class IComplexSocketListenerT : public ISocketListenerT +{ +public: + + /* + * 名称:关闭通信组件通知 + * 描述:通信组件关闭时,Socket 监听器将收到该通知 + * + * 参数: pSender -- 事件源对象 + * 返回值: 忽略返回值 + */ + virtual EnHandleResult OnShutdown(T* pSender) = 0; + +}; + +/************************************************************************ +名称:服务端 Socket 监听器接口 +描述:定义服务端 Socket 监听器的所有事件 +************************************************************************/ +template class IServerListenerT : public IComplexSocketListenerT +{ +public: + + /* + * 名称:准备监听通知 + * 描述:通信服务端组件启动时,在监听 Socket 创建完成并开始执行监听前,Socket 监听 + * 器将收到该通知,监听器可以在通知处理方法中执行 Socket 选项设置等额外工作 + * + * 参数: pSender -- 事件源对象 + * soListen -- 监听 Socket + * 返回值: HR_OK / HR_IGNORE -- 继续执行 + * HR_ERROR -- 终止启动通信服务组件 + */ + virtual EnHandleResult OnPrepareListen(T* pSender, SOCKET soListen) = 0; + + /* + * 名称:接收连接通知 + * 描述:接收到客户端连接请求时,Socket 监听器将收到该通知,监听器可以在通知处理方 + * 法中执行 Socket 选项设置或拒绝客户端连接等额外工作 + * + * 参数: pSender -- 事件源对象 + * dwConnID -- 连接 ID + * soClient -- TCP: 客户端 Socket 句柄,UDP: 客户端 Socket SOCKADDR 指针 + * 返回值: HR_OK / HR_IGNORE -- 接受连接 + * HR_ERROR -- 拒绝连接 + */ + virtual EnHandleResult OnAccept(T* pSender, CONNID dwConnID, UINT_PTR soClient) = 0; +}; + +/************************************************************************ +名称:TCP 服务端 Socket 监听器接口 +描述:定义 TCP 服务端 Socket 监听器的所有事件 +************************************************************************/ +class ITcpServerListener : public IServerListenerT +{ +public: + +}; + +/************************************************************************ +名称:PUSH 模型服务端 Socket 监听器抽象基类 +描述:定义某些事件的默认处理方法(忽略事件) +************************************************************************/ +class CTcpServerListener : public ITcpServerListener +{ +public: + virtual EnHandleResult OnPrepareListen(ITcpServer* pSender, SOCKET soListen) override {return HR_IGNORE;} + virtual EnHandleResult OnAccept(ITcpServer* pSender, CONNID dwConnID, UINT_PTR soClient) override {return HR_IGNORE;} + virtual EnHandleResult OnHandShake(ITcpServer* pSender, CONNID dwConnID) override {return HR_IGNORE;} + virtual EnHandleResult OnReceive(ITcpServer* pSender, CONNID dwConnID, int iLength) override {return HR_IGNORE;} + virtual EnHandleResult OnSend(ITcpServer* pSender, CONNID dwConnID, const BYTE* pData, int iLength) override {return HR_IGNORE;} + virtual EnHandleResult OnShutdown(ITcpServer* pSender) override {return HR_IGNORE;} +}; + +/************************************************************************ +名称:PULL 模型服务端 Socket 监听器抽象基类 +描述:定义某些事件的默认处理方法(忽略事件) +************************************************************************/ +class CTcpPullServerListener : public CTcpServerListener +{ +public: + virtual EnHandleResult OnReceive(ITcpServer* pSender, CONNID dwConnID, int iLength) override = 0; + virtual EnHandleResult OnReceive(ITcpServer* pSender, CONNID dwConnID, const BYTE* pData, int iLength) override {return HR_IGNORE;} +}; + +#ifdef _UDP_SUPPORT + +/************************************************************************ +名称:UDP 服务端 Socket 监听器接口 +描述:定义 UDP 服务端 Socket 监听器的所有事件 +************************************************************************/ +class IUdpServerListener : public IServerListenerT +{ +public: + +}; + +/************************************************************************ +名称:UDP 服务端 Socket 监听器抽象基类 +描述:定义某些事件的默认处理方法(忽略事件) +************************************************************************/ +class CUdpServerListener : public IUdpServerListener +{ +public: + virtual EnHandleResult OnPrepareListen(IUdpServer* pSender, SOCKET soListen) override {return HR_IGNORE;} + virtual EnHandleResult OnAccept(IUdpServer* pSender, CONNID dwConnID, UINT_PTR pSockAddr) override {return HR_IGNORE;} + virtual EnHandleResult OnHandShake(IUdpServer* pSender, CONNID dwConnID) override {return HR_IGNORE;} + virtual EnHandleResult OnReceive(IUdpServer* pSender, CONNID dwConnID, int iLength) override {return HR_IGNORE;} + virtual EnHandleResult OnSend(IUdpServer* pSender, CONNID dwConnID, const BYTE* pData, int iLength) override {return HR_IGNORE;} + virtual EnHandleResult OnShutdown(IUdpServer* pSender) override {return HR_IGNORE;} +}; + +#endif + +/************************************************************************ +名称:通信代理 Socket 监听器接口 +描述:定义 通信代理 Socket 监听器的所有事件 +************************************************************************/ +template class IAgentListenerT : public IComplexSocketListenerT +{ +public: + + /* + * 名称:准备连接通知 + * 描述:通信客户端组件启动时,在客户端 Socket 创建完成并开始执行连接前,Socket 监听 + * 器将收到该通知,监听器可以在通知处理方法中执行 Socket 选项设置等额外工作 + * + * 参数: pSender -- 事件源对象 + * dwConnID -- 连接 ID + * socket -- 客户端 Socket + * 返回值: HR_OK / HR_IGNORE -- 继续执行 + * HR_ERROR -- 终止启动通信客户端组件 + */ + virtual EnHandleResult OnPrepareConnect(T* pSender, CONNID dwConnID, SOCKET socket) = 0; + + /* + * 名称:连接完成通知 + * 描述:与服务端成功建立连接时,Socket 监听器将收到该通知 + * + * 参数: pSender -- 事件源对象 + * dwConnID -- 连接 ID + * 返回值: HR_OK / HR_IGNORE -- 继续执行 + * HR_ERROR -- 同步连接:终止启动通信客户端组件 + * 异步连接:关闭连接 + */ + virtual EnHandleResult OnConnect(T* pSender, CONNID dwConnID) = 0; +}; + +/************************************************************************ +名称:TCP 通信代理 Socket 监听器接口 +描述:定义 TCP 通信代理 Socket 监听器的所有事件 +************************************************************************/ +class ITcpAgentListener : public IAgentListenerT +{ +public: + +}; + +/************************************************************************ +名称:PUSH 模型通信代理 Socket 监听器抽象基类 +描述:定义某些事件的默认处理方法(忽略事件) +************************************************************************/ +class CTcpAgentListener : public ITcpAgentListener +{ +public: + virtual EnHandleResult OnPrepareConnect(ITcpAgent* pSender, CONNID dwConnID, SOCKET socket) override {return HR_IGNORE;} + virtual EnHandleResult OnConnect(ITcpAgent* pSender, CONNID dwConnID) override {return HR_IGNORE;} + virtual EnHandleResult OnHandShake(ITcpAgent* pSender, CONNID dwConnID) override {return HR_IGNORE;} + virtual EnHandleResult OnReceive(ITcpAgent* pSender, CONNID dwConnID, int iLength) override {return HR_IGNORE;} + virtual EnHandleResult OnSend(ITcpAgent* pSender, CONNID dwConnID, const BYTE* pData, int iLength) override {return HR_IGNORE;} + virtual EnHandleResult OnShutdown(ITcpAgent* pSender) override {return HR_IGNORE;} +}; + +/************************************************************************ +名称:PULL 通信代理 Socket 监听器抽象基类 +描述:定义某些事件的默认处理方法(忽略事件) +************************************************************************/ +class CTcpPullAgentListener : public CTcpAgentListener +{ +public: + virtual EnHandleResult OnReceive(ITcpAgent* pSender, CONNID dwConnID, int iLength) override = 0; + virtual EnHandleResult OnReceive(ITcpAgent* pSender, CONNID dwConnID, const BYTE* pData, int iLength) override {return HR_IGNORE;} +}; + +/************************************************************************ +名称:客户端 Socket 监听器接口 +描述:定义客户端 Socket 监听器的所有事件 +************************************************************************/ + +template class IClientListenerT : public ISocketListenerT +{ +public: + + /* + * 名称:准备连接通知 + * 描述:通信客户端组件启动时,在客户端 Socket 创建完成并开始执行连接前,Socket 监听 + * 器将收到该通知,监听器可以在通知处理方法中执行 Socket 选项设置等额外工作 + * + * 参数: pSender -- 事件源对象 + * dwConnID -- 连接 ID + * socket -- 客户端 Socket + * 返回值: HR_OK / HR_IGNORE -- 继续执行 + * HR_ERROR -- 终止启动通信客户端组件 + */ + virtual EnHandleResult OnPrepareConnect(T* pSender, CONNID dwConnID, SOCKET socket) = 0; + + /* + * 名称:连接完成通知 + * 描述:与服务端成功建立连接时,Socket 监听器将收到该通知 + * + * 参数: pSender -- 事件源对象 + * dwConnID -- 连接 ID + * 返回值: HR_OK / HR_IGNORE -- 继续执行 + * HR_ERROR -- 同步连接:终止启动通信客户端组件 + * 异步连接:关闭连接 + */ + virtual EnHandleResult OnConnect(T* pSender, CONNID dwConnID) = 0; +}; + +/************************************************************************ +名称:TCP 客户端 Socket 监听器接口 +描述:定义 TCP 客户端 Socket 监听器的所有事件 +************************************************************************/ +class ITcpClientListener : public IClientListenerT +{ +public: + +}; + +/************************************************************************ +名称:PUSH 模型客户端 Socket 监听器抽象基类 +描述:定义某些事件的默认处理方法(忽略事件) +************************************************************************/ +class CTcpClientListener : public ITcpClientListener +{ +public: + virtual EnHandleResult OnPrepareConnect(ITcpClient* pSender, CONNID dwConnID, SOCKET socket) override {return HR_IGNORE;} + virtual EnHandleResult OnConnect(ITcpClient* pSender, CONNID dwConnID) override {return HR_IGNORE;} + virtual EnHandleResult OnHandShake(ITcpClient* pSender, CONNID dwConnID) override {return HR_IGNORE;} + virtual EnHandleResult OnReceive(ITcpClient* pSender, CONNID dwConnID, int iLength) override {return HR_IGNORE;} + virtual EnHandleResult OnSend(ITcpClient* pSender, CONNID dwConnID, const BYTE* pData, int iLength) override {return HR_IGNORE;} +}; + +/************************************************************************ +名称:PULL 客户端 Socket 监听器抽象基类 +描述:定义某些事件的默认处理方法(忽略事件) +************************************************************************/ +class CTcpPullClientListener : public CTcpClientListener +{ +public: + virtual EnHandleResult OnReceive(ITcpClient* pSender, CONNID dwConnID, int iLength) = 0; + virtual EnHandleResult OnReceive(ITcpClient* pSender, CONNID dwConnID, const BYTE* pData, int iLength) {return HR_IGNORE;} +}; + +#ifdef _UDP_SUPPORT + +/************************************************************************ +名称:UDP 客户端 Socket 监听器接口 +描述:定义 UDP 客户端 Socket 监听器的所有事件 +************************************************************************/ +class IUdpClientListener : public IClientListenerT +{ +public: + +}; + +/************************************************************************ +名称:UDP 户端 Socket 监听器抽象基类 +描述:定义某些事件的默认处理方法(忽略事件) +************************************************************************/ +class CUdpClientListener : public IUdpClientListener +{ +public: + virtual EnHandleResult OnPrepareConnect(IUdpClient* pSender, CONNID dwConnID, SOCKET socket) override {return HR_IGNORE;} + virtual EnHandleResult OnConnect(IUdpClient* pSender, CONNID dwConnID) override {return HR_IGNORE;} + virtual EnHandleResult OnHandShake(IUdpClient* pSender, CONNID dwConnID) override {return HR_IGNORE;} + virtual EnHandleResult OnReceive(IUdpClient* pSender, CONNID dwConnID, int iLength) override {return HR_IGNORE;} + virtual EnHandleResult OnSend(IUdpClient* pSender, CONNID dwConnID, const BYTE* pData, int iLength) override {return HR_IGNORE;} +}; + +/************************************************************************ +名称:UDP 传播 Socket 监听器接口 +描述:定义 UDP 传播 Socket 监听器的所有事件 +************************************************************************/ +class IUdpCastListener : public IClientListenerT +{ +public: + +}; + +/************************************************************************ +名称:UDP 传播 Socket 监听器抽象基类 +描述:定义某些事件的默认处理方法(忽略事件) +************************************************************************/ +class CUdpCastListener : public IUdpCastListener +{ +public: + virtual EnHandleResult OnPrepareConnect(IUdpCast* pSender, CONNID dwConnID, SOCKET socket) override {return HR_IGNORE;} + virtual EnHandleResult OnConnect(IUdpCast* pSender, CONNID dwConnID) override {return HR_IGNORE;} + virtual EnHandleResult OnHandShake(IUdpCast* pSender, CONNID dwConnID) override {return HR_IGNORE;} + virtual EnHandleResult OnReceive(IUdpCast* pSender, CONNID dwConnID, int iLength) override {return HR_IGNORE;} + virtual EnHandleResult OnSend(IUdpCast* pSender, CONNID dwConnID, const BYTE* pData, int iLength) override {return HR_IGNORE;} +}; + +/************************************************************************ +名称:UDP 节点 Socket 监听器接口 +描述:定义 UDP 节点 Socket 监听器的所有事件 +************************************************************************/ +class IUdpNodeListener +{ +public: + + /* + * 名称:准备监听通知 + * 描述:通信组件启动时,在监听 Socket 创建完成并开始执行监听前,Socket 监听器 + * 将收到该通知,监听器可以在通知处理方法中执行 Socket 选项设置等额外工作 + * + * 参数: pSender -- 事件源对象 + * soListen -- 监听 Socket + * 返回值: HR_OK / HR_IGNORE -- 继续执行 + * HR_ERROR -- 终止启动通信服务组件 + */ + virtual EnHandleResult OnPrepareListen(IUdpNode* pSender, SOCKET soListen) = 0; + + /* + * 名称:已发送数据通知 + * 描述:成功发送数据后,Socket 监听器将收到该通知 + * + * 参数: pSender -- 事件源对象 + * lpszRemoteAddress -- 远程地址 + * usRemotePort -- 远程端口 + * pData -- 已发送数据缓冲区 + * iLength -- 已发送数据长度 + * 返回值: 忽略返回值 + */ + virtual EnHandleResult OnSend(IUdpNode* pSender, LPCTSTR lpszRemoteAddress, USHORT usRemotePort, const BYTE* pData, int iLength) = 0; + + /* + * 名称:数据到达通知(PUSH 模型) + * 描述:成功接收数据后,Socket 监听器将收到该通知 + * + * 参数: pSender -- 事件源对象 + * lpszRemoteAddress -- 远程地址 + * usRemotePort -- 远程端口 + * pData -- 已发送数据缓冲区 + * iLength -- 已发送数据长度 + * 返回值: 忽略返回值 + */ + virtual EnHandleResult OnReceive(IUdpNode* pSender, LPCTSTR lpszRemoteAddress, USHORT usRemotePort, const BYTE* pData, int iLength) = 0; + + /* + * 名称:通信错误通知 + * 描述:通信发生错误后,Socket 监听器将收到该通知 + * + * 参数: pSender -- 事件源对象 + * lpszRemoteAddress -- 远程地址 + * usRemotePort -- 远程端口 + * enOperation -- Socket 操作类型 + * iErrorCode -- 错误代码 + * pData -- 本次事件关联的数据缓冲区 + * iLength -- 本次事件关联的数据长度 + * 返回值: 忽略返回值 + */ + virtual EnHandleResult OnError(IUdpNode* pSender, EnSocketOperation enOperation, int iErrorCode, LPCTSTR lpszRemoteAddress, USHORT usRemotePort, const BYTE* pData, int iLength) = 0; + + /* + * 名称:关闭通信组件通知 + * 描述:通信组件关闭时,Socket 监听器将收到该通知 + * + * 参数: pSender -- 事件源对象 + * 返回值: 忽略返回值 + */ + virtual EnHandleResult OnShutdown(IUdpNode* pSender) = 0; + +public: + virtual ~IUdpNodeListener() = default; +}; + +/************************************************************************ +名称:UDP 节点 Socket 监听器抽象基类 +描述:定义某些事件的默认处理方法(忽略事件) +************************************************************************/ +class CUdpNodeListener : public IUdpNodeListener +{ +public: + virtual EnHandleResult OnPrepareListen(IUdpNode* pSender, SOCKET soListen) override {return HR_IGNORE;} + virtual EnHandleResult OnSend(IUdpNode* pSender, LPCTSTR lpszRemoteAddress, USHORT usRemotePort, const BYTE* pData, int iLength) override {return HR_IGNORE;} + virtual EnHandleResult OnShutdown(IUdpNode* pSender) override {return HR_IGNORE;} +}; + +#endif + +/*****************************************************************************************************************************************************/ +/****************************************************************** HTTP Interfaces ******************************************************************/ +/*****************************************************************************************************************************************************/ + +#ifdef _HTTP_SUPPORT + +/************************************************************************ +名称:复合 Http 组件接口 +描述:定义复合 Http 组件的所有操作方法和属性访问方法,复合 Http 组件同时管理多个 Http 连接 +************************************************************************/ +class IComplexHttp +{ +public: + + /***********************************************************************/ + /***************************** 组件操作方法 *****************************/ + + /* + * 名称:启动 HTTP 通信 + * 描述:当通信组件设置为非自动启动 HTTP 通信时,需要调用本方法启动 HTTP 通信 + * + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 + */ + virtual BOOL StartHttp(CONNID dwConnID) = 0; + + /* + * 名称:发送 Chunked 数据分片 + * 描述:向对端发送 Chunked 数据分片 + * + * 参数: dwConnID -- 连接 ID + * pData -- Chunked 数据分片 + * iLength -- 数据分片长度(为 0 表示结束分片) + * lpszExtensions -- 扩展属性(默认:nullptr) + * 返回值: TRUE -- 成功 + * FALSE -- 失败 + */ + virtual BOOL SendChunkData(CONNID dwConnID, const BYTE* pData = nullptr, int iLength = 0, LPCSTR lpszExtensions = nullptr) = 0; + +public: + + /***********************************************************************/ + /***************************** 属性访问方法 *****************************/ + + /* 设置本地协议版本 */ + virtual void SetLocalVersion(EnHttpVersion usVersion) = 0; + /* 获取本地协议版本 */ + virtual EnHttpVersion GetLocalVersion() = 0; + + /* 检查是否升级协议 */ + virtual BOOL IsUpgrade(CONNID dwConnID) = 0; + /* 检查是否有 Keep-Alive 标识 */ + virtual BOOL IsKeepAlive(CONNID dwConnID) = 0; + /* 获取协议版本 */ + virtual USHORT GetVersion(CONNID dwConnID) = 0; + /* 获取内容长度 */ + virtual ULONGLONG GetContentLength(CONNID dwConnID) = 0; + /* 获取内容类型 */ + virtual LPCSTR GetContentType(CONNID dwConnID) = 0; + /* 获取内容编码 */ + virtual LPCSTR GetContentEncoding(CONNID dwConnID) = 0; + /* 获取传输编码 */ + virtual LPCSTR GetTransferEncoding(CONNID dwConnID) = 0; + /* 获取协议升级类型 */ + virtual EnHttpUpgradeType GetUpgradeType(CONNID dwConnID) = 0; + /* 获取解析错误代码 */ + virtual USHORT GetParseErrorCode(CONNID dwConnID, LPCSTR* lpszErrorDesc = nullptr) = 0; + + /* 获取某个请求头(单值) */ + virtual BOOL GetHeader(CONNID dwConnID, LPCSTR lpszName, LPCSTR* lpszValue) = 0; + /* 获取某个请求头(多值) */ + virtual BOOL GetHeaders(CONNID dwConnID, LPCSTR lpszName, LPCSTR lpszValue[], DWORD& dwCount) = 0; + /* 获取所有请求头 */ + virtual BOOL GetAllHeaders(CONNID dwConnID, THeader lpHeaders[], DWORD& dwCount) = 0; + /* 获取所有请求头名称 */ + virtual BOOL GetAllHeaderNames(CONNID dwConnID, LPCSTR lpszName[], DWORD& dwCount) = 0; + + /* 获取 Cookie */ + virtual BOOL GetCookie(CONNID dwConnID, LPCSTR lpszName, LPCSTR* lpszValue) = 0; + /* 获取所有 Cookie */ + virtual BOOL GetAllCookies(CONNID dwConnID, TCookie lpCookies[], DWORD& dwCount) = 0; + + /* + // !! maybe implemented in future !! // + + virtual BOOL GetParam(CONNID dwConnID, LPCSTR lpszName, LPCSTR* lpszValue) = 0; + virtual BOOL GetParams(CONNID dwConnID, LPCSTR lpszName, LPCSTR lpszValue[], DWORD& dwCount) = 0; + virtual BOOL GetAllParams(CONNID dwConnID, LPPARAM lpszParam[], DWORD& dwCount) = 0; + virtual BOOL GetAllParamNames(CONNID dwConnID, LPCSTR lpszName[], DWORD& dwCount) = 0; + */ + + /* 获取当前 WebSocket 消息状态,传入 nullptr 则不获取相应字段 */ + virtual BOOL GetWSMessageState(CONNID dwConnID, BOOL* lpbFinal, BYTE* lpiReserved, BYTE* lpiOperationCode, LPCBYTE* lpszMask, ULONGLONG* lpullBodyLen, ULONGLONG* lpullBodyRemain) = 0; + + /* 设置 HTTP 启动方式(默认:TRUE,自动启动) */ + virtual void SetHttpAutoStart(BOOL bAutoStart) = 0; + /* 获取 HTTP 启动方式 */ + virtual BOOL IsHttpAutoStart() = 0; + +public: + virtual ~IComplexHttp() = default; +}; + +/************************************************************************ +名称:复合 Http 请求者组件接口 +描述:定义复合 Http 请求者组件的所有操作方法和属性访问方法 +************************************************************************/ +class IComplexHttpRequester : public IComplexHttp +{ +public: + + /***********************************************************************/ + /***************************** 组件操作方法 *****************************/ + + /* + * 名称:发送 WebSocket 消息 + * 描述:向对端端发送 WebSocket 消息 + * + * 参数: dwConnID -- 连接 ID + * bFinal -- 是否结束帧 + * iReserved -- RSV1/RSV2/RSV3 各 1 位 + * iOperationCode -- 操作码:0x0 - 0xF + * lpszMask -- 掩码(nullptr 或 4 字节掩码,如果为 nullptr 则没有掩码) + * pData -- 消息体数据缓冲区 + * iLength -- 消息体数据长度 + * ullBodyLen -- 消息总长度 + * ullBodyLen = 0 -> 消息总长度为 iLength + * ullBodyLen = iLength -> 消息总长度为 ullBodyLen + * ullBodyLen > iLength -> 消息总长度为 ullBodyLen,后续消息体长度为 ullBOdyLen - iLength,后续消息体通过底层方法 Send() / SendPackets() 发送 + * ullBodyLen < iLength -> 错误参数,发送失败 + * 返回值: TRUE -- 成功 + * FALSE -- 失败 + */ + virtual BOOL SendWSMessage(CONNID dwConnID, BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], const BYTE* pData = nullptr, int iLength = 0, ULONGLONG ullBodyLen = 0) = 0; + + /* + * 名称:发送请求 + * 描述:向服务端发送 HTTP 请求 + * + * 参数: dwConnID -- 连接 ID + * lpszMethod -- 请求方法 + * lpszPath -- 请求路径 + * lpHeaders -- 请求头 + * iHeaderCount -- 请求头数量 + * pBody -- 请求体 + * iLength -- 请求体长度 + * 返回值: TRUE -- 成功 + * FALSE -- 失败 + */ + virtual BOOL SendRequest(CONNID dwConnID, LPCSTR lpszMethod, LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0, const BYTE* pBody = nullptr, int iLength = 0) = 0; + + /* + * 名称:发送本地文件 + * 描述:向指定连接发送 4096 KB 以下的小文件 + * + * 参数: dwConnID -- 连接 ID + * lpszFileName -- 文件路径 + * lpszMethod -- 请求方法 + * lpszPath -- 请求路径 + * lpHeaders -- 请求头 + * iHeaderCount -- 请求头数量 + * 返回值: TRUE -- 成功 + * FALSE -- 失败 + */ + virtual BOOL SendLocalFile(CONNID dwConnID, LPCSTR lpszFileName, LPCSTR lpszMethod, LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0) = 0; + + /* 发送 POST 请求 */ + virtual BOOL SendPost(CONNID dwConnID, LPCSTR lpszPath, const THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength) = 0; + /* 发送 PUT 请求 */ + virtual BOOL SendPut(CONNID dwConnID, LPCSTR lpszPath, const THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength) = 0; + /* 发送 PATCH 请求 */ + virtual BOOL SendPatch(CONNID dwConnID, LPCSTR lpszPath, const THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength) = 0; + /* 发送 GET 请求 */ + virtual BOOL SendGet(CONNID dwConnID, LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0) = 0; + /* 发送 DELETE 请求 */ + virtual BOOL SendDelete(CONNID dwConnID, LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0) = 0; + /* 发送 HEAD 请求 */ + virtual BOOL SendHead(CONNID dwConnID, LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0) = 0; + /* 发送 TRACE 请求 */ + virtual BOOL SendTrace(CONNID dwConnID, LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0) = 0; + /* 发送 OPTIONS 请求 */ + virtual BOOL SendOptions(CONNID dwConnID, LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0) = 0; + /* 发送 CONNECT 请求 */ + virtual BOOL SendConnect(CONNID dwConnID, LPCSTR lpszHost, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0) = 0; + +public: + + /***********************************************************************/ + /***************************** 属性访问方法 *****************************/ + + /* 获取 HTTP 状态码 */ + virtual USHORT GetStatusCode(CONNID dwConnID) = 0; + + /* 设置是否使用 Cookie(默认:TRUE) */ + virtual void SetUseCookie(BOOL bUseCookie) = 0; + /* 检查是否使用 Cookie */ + virtual BOOL IsUseCookie() = 0; +}; + +/************************************************************************ +名称:复合 Http 响应者组件接口 +描述:定义复合 Http 响应者组件的所有操作方法和属性访问方法 +************************************************************************/ +class IComplexHttpResponder : public IComplexHttp +{ +public: + + /***********************************************************************/ + /***************************** 组件操作方法 *****************************/ + + /* + * 名称:发送 WebSocket 消息 + * 描述:向对端端发送 WebSocket 消息 + * + * 参数: dwConnID -- 连接 ID + * bFinal -- 是否结束帧 + * iReserved -- RSV1/RSV2/RSV3 各 1 位 + * iOperationCode -- 操作码:0x0 - 0xF + * pData -- 消息体数据缓冲区 + * iLength -- 消息体数据长度 + * ullBodyLen -- 消息总长度 + * ullBodyLen = 0 -> 消息总长度为 iLength + * ullBodyLen = iLength -> 消息总长度为 ullBodyLen + * ullBodyLen > iLength -> 消息总长度为 ullBodyLen,后续消息体长度为 ullBOdyLen - iLength,后续消息体通过底层方法 Send() / SendPackets() 发送 + * ullBodyLen < iLength -> 错误参数,发送失败 + * 返回值: TRUE -- 成功 + * FALSE -- 失败 + */ + virtual BOOL SendWSMessage(CONNID dwConnID, BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE* pData = nullptr, int iLength = 0, ULONGLONG ullBodyLen = 0) = 0; + + /* + * 名称:回复请求 + * 描述:向客户端回复 HTTP 请求 + * + * 参数: dwConnID -- 连接 ID + * usStatusCode -- HTTP 状态码 + * lpszDesc -- HTTP 状态描述 + * lpHeaders -- 回复请求头 + * iHeaderCount -- 回复请求头数量 + * pData -- 回复请求体 + * iLength -- 回复请求体长度 + * 返回值: TRUE -- 成功 + * FALSE -- 失败 + */ + virtual BOOL SendResponse(CONNID dwConnID, USHORT usStatusCode, LPCSTR lpszDesc = nullptr, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0, const BYTE* pData = nullptr, int iLength = 0) = 0; + + /* + * 名称:发送本地文件 + * 描述:向指定连接发送 4096 KB 以下的小文件 + * + * 参数: dwConnID -- 连接 ID + * lpszFileName -- 文件路径 + * usStatusCode -- HTTP 状态码 + * lpszDesc -- HTTP 状态描述 + * lpHeaders -- 回复请求头 + * iHeaderCount -- 回复请求头数量 + * 返回值: TRUE -- 成功 + * FALSE -- 失败 + */ + virtual BOOL SendLocalFile(CONNID dwConnID, LPCSTR lpszFileName, USHORT usStatusCode = HSC_OK, LPCSTR lpszDesc = nullptr, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0) = 0; + + /* + * 名称:释放连接 + * 描述:把连接放入释放队列,等待某个时间(通过 SetReleaseDelay() 设置)关闭连接 + * + * 参数: dwConnID -- 连接 ID + * 返回值: TRUE -- 成功 + * FALSE -- 失败 + */ + virtual BOOL Release(CONNID dwConnID) = 0; + +public: + + /***********************************************************************/ + /***************************** 属性访问方法 *****************************/ + + /* 获取主机 */ + virtual LPCSTR GetHost(CONNID dwConnID) = 0; + + /* 设置连接释放延时(默认:3000 毫秒) */ + virtual void SetReleaseDelay(DWORD dwReleaseDelay) = 0; + /* 获取连接释放延时 */ + virtual DWORD GetReleaseDelay() = 0; + + /* 获取请求行 URL 域掩码(URL 域参考:EnHttpUrlField) */ + virtual USHORT GetUrlFieldSet(CONNID dwConnID) = 0; + /* 获取某个 URL 域值 */ + virtual LPCSTR GetUrlField(CONNID dwConnID, EnHttpUrlField enField) = 0; + /* 获取请求方法 */ + virtual LPCSTR GetMethod(CONNID dwConnID) = 0; +}; + +/************************************************************************ +名称:简单 HTTP 组件接口 +描述:定义 简单 HTTP 组件的所有操作方法和属性访问方法 +************************************************************************/ +class IHttp +{ +public: + + /***********************************************************************/ + /***************************** 组件操作方法 *****************************/ + + /* + * 名称:发送 WebSocket 消息 + * 描述:向对端端发送 WebSocket 消息 + * + * 参数: bFinal -- 是否结束帧 + * iReserved -- RSV1/RSV2/RSV3 各 1 位 + * iOperationCode -- 操作码:0x0 - 0xF + * lpszMask -- 掩码(nullptr 或 4 字节掩码,如果为 nullptr 则没有掩码) + * pData -- 消息体数据缓冲区 + * iLength -- 消息体数据长度 + * ullBodyLen -- 消息总长度 + * ullBodyLen = 0 -> 消息总长度为 iLength + * ullBodyLen = iLength -> 消息总长度为 ullBodyLen + * ullBodyLen > iLength -> 消息总长度为 ullBodyLen,后续消息体长度为 ullBOdyLen - iLength,后续消息体通过底层方法 Send() / SendPackets() 发送 + * ullBodyLen < iLength -> 错误参数,发送失败 + * 返回值: TRUE -- 成功 + * FALSE -- 失败 + */ + virtual BOOL SendWSMessage(BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], const BYTE* pData = nullptr, int iLength = 0, ULONGLONG ullBodyLen = 0) = 0; + + /* + * 名称:启动 HTTP 通信 + * 描述:当通信组件设置为非自动启动 HTTP 通信时,需要调用本方法启动 HTTP 通信 + * + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取失败原因 + */ + virtual BOOL StartHttp() = 0; + + /* + * 名称:发送 Chunked 数据分片 + * 描述:向对端发送 Chunked 数据分片 + * + * 参数: pData -- Chunked 数据分片 + * iLength -- 数据分片长度(为 0 表示结束分片) + * lpszExtensions -- 扩展属性(默认:nullptr) + * 返回值: TRUE -- 成功 + * FALSE -- 失败 + */ + virtual BOOL SendChunkData(const BYTE* pData = nullptr, int iLength = 0, LPCSTR lpszExtensions = nullptr) = 0; + +public: + + /***********************************************************************/ + /***************************** 属性访问方法 *****************************/ + + /* 设置本地协议版本 */ + virtual void SetLocalVersion(EnHttpVersion usVersion) = 0; + /* 获取本地协议版本 */ + virtual EnHttpVersion GetLocalVersion() = 0; + + /* 检查是否升级协议 */ + virtual BOOL IsUpgrade() = 0; + /* 检查是否有 Keep-Alive 标识 */ + virtual BOOL IsKeepAlive() = 0; + /* 获取协议版本 */ + virtual USHORT GetVersion() = 0; + /* 获取内容长度 */ + virtual ULONGLONG GetContentLength() = 0; + /* 获取内容类型 */ + virtual LPCSTR GetContentType() = 0; + /* 获取内容编码 */ + virtual LPCSTR GetContentEncoding() = 0; + /* 获取传输编码 */ + virtual LPCSTR GetTransferEncoding() = 0; + /* 获取协议升级类型 */ + virtual EnHttpUpgradeType GetUpgradeType() = 0; + /* 获取解析错误代码 */ + virtual USHORT GetParseErrorCode(LPCSTR* lpszErrorDesc = nullptr) = 0; + + /* 获取 HTTP 状态码 */ + virtual USHORT GetStatusCode() = 0; + + /* 获取某个请求头(单值) */ + virtual BOOL GetHeader(LPCSTR lpszName, LPCSTR* lpszValue) = 0; + /* 获取某个请求头(多值) */ + virtual BOOL GetHeaders(LPCSTR lpszName, LPCSTR lpszValue[], DWORD& dwCount) = 0; + /* 获取所有请求头 */ + virtual BOOL GetAllHeaders(THeader lpHeaders[], DWORD& dwCount) = 0; + /* 获取所有请求头名称 */ + virtual BOOL GetAllHeaderNames(LPCSTR lpszName[], DWORD& dwCount) = 0; + + /* 获取 Cookie */ + virtual BOOL GetCookie(LPCSTR lpszName, LPCSTR* lpszValue) = 0; + /* 获取所有 Cookie */ + virtual BOOL GetAllCookies(TCookie lpCookies[], DWORD& dwCount) = 0; + + /* + // !! maybe implemented in future !! // + + virtual BOOL GetParam(LPCSTR lpszName, LPCSTR* lpszValue) = 0; + virtual BOOL GetParams(LPCSTR lpszName, LPCSTR lpszValue[], DWORD& dwCount) = 0; + virtual BOOL GetAllParams(LPPARAM lpszParam[], DWORD& dwCount) = 0; + virtual BOOL GetAllParamNames(LPCSTR lpszName[], DWORD& dwCount) = 0; + */ + + /* 获取当前 WebSocket 消息状态,传入 nullptr 则不获取相应字段 */ + virtual BOOL GetWSMessageState(BOOL* lpbFinal, BYTE* lpiReserved, BYTE* lpiOperationCode, LPCBYTE* lpszMask, ULONGLONG* lpullBodyLen, ULONGLONG* lpullBodyRemain) = 0; + + /* 设置 HTTP 启动方式(默认:TRUE,自动启动) */ + virtual void SetHttpAutoStart(BOOL bAutoStart) = 0; + /* 获取 HTTP 启动方式 */ + virtual BOOL IsHttpAutoStart() = 0; + +public: + virtual ~IHttp() = default; +}; + +/************************************************************************ +名称:简单 Http 请求者组件接口 +描述:定义简单 Http 请求者组件的所有操作方法和属性访问方法 +************************************************************************/ +class IHttpRequester : public IHttp +{ +public: + + /***********************************************************************/ + /***************************** 组件操作方法 *****************************/ + + /* + * 名称:发送请求 + * 描述:向服务端发送 HTTP 请求 + * + * 参数: lpszMethod -- 请求方法 + * lpszPath -- 请求路径 + * lpHeaders -- 请求头 + * iHeaderCount -- 请求头数量 + * pBody -- 请求体 + * iLength -- 请求体长度 + * 返回值: TRUE -- 成功 + * FALSE -- 失败 + */ + virtual BOOL SendRequest(LPCSTR lpszMethod, LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0, const BYTE* pBody = nullptr, int iLength = 0) = 0; + + /* + * 名称:发送本地文件 + * 描述:向指定连接发送 4096 KB 以下的小文件 + * + * 参数: dwConnID -- 连接 ID + * lpszFileName -- 文件路径 + * lpszMethod -- 请求方法 + * lpszPath -- 请求路径 + * lpHeaders -- 请求头 + * iHeaderCount -- 请求头数量 + * 返回值: TRUE -- 成功 + * FALSE -- 失败 + */ + virtual BOOL SendLocalFile(LPCSTR lpszFileName, LPCSTR lpszMethod, LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0) = 0; + + /* 发送 POST 请求 */ + virtual BOOL SendPost(LPCSTR lpszPath, const THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength) = 0; + /* 发送 PUT 请求 */ + virtual BOOL SendPut(LPCSTR lpszPath, const THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength) = 0; + /* 发送 PATCH 请求 */ + virtual BOOL SendPatch(LPCSTR lpszPath, const THeader lpHeaders[], int iHeaderCount, const BYTE* pBody, int iLength) = 0; + /* 发送 GET 请求 */ + virtual BOOL SendGet(LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0) = 0; + /* 发送 DELETE 请求 */ + virtual BOOL SendDelete(LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0) = 0; + /* 发送 HEAD 请求 */ + virtual BOOL SendHead(LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0) = 0; + /* 发送 TRACE 请求 */ + virtual BOOL SendTrace(LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0) = 0; + /* 发送 OPTIONS 请求 */ + virtual BOOL SendOptions(LPCSTR lpszPath, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0) = 0; + /* 发送 CONNECT 请求 */ + virtual BOOL SendConnect(LPCSTR lpszHost, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0) = 0; + +public: + + /***********************************************************************/ + /***************************** 属性访问方法 *****************************/ + + /* 设置是否使用 Cookie(默认:TRUE) */ + virtual void SetUseCookie(BOOL bUseCookie) = 0; + /* 检查是否使用 Cookie */ + virtual BOOL IsUseCookie() = 0; +}; + +/************************************************************************ +名称:简单 Http 同步请求者组件接口 +描述:定义简单 Http 同步请求者组件的所有操作方法和属性访问方法 +************************************************************************/ +class IHttpSyncRequester : public IHttpRequester +{ +public: + + /* + * 名称:发送 URL 请求 + * 描述:向服务端发送 HTTP URL 请求 + * + * 参数: lpszMethod -- 请求方法 + * lpszUrl -- 请求 URL + * lpHeaders -- 请求头 + * iHeaderCount -- 请求头数量 + * pBody -- 请求体 + * iLength -- 请求体长度 + * bForceReconnect -- 强制重新连接(默认:FALSE,当请求 URL 的主机和端口与现有连接一致时,重用现有连接) + * 返回值: TRUE -- 成功 + * FALSE -- 失败 + */ + virtual BOOL OpenUrl(LPCSTR lpszMethod, LPCSTR lpszUrl, const THeader lpHeaders[] = nullptr, int iHeaderCount = 0, const BYTE* pBody = nullptr, int iLength = 0, BOOL bForceReconnect = FALSE) = 0; + + /***********************************************************************/ + /***************************** 组件操作方法 *****************************/ + + /* + * 名称:清除请求结果 + * 描述:清除上一次请求的响应头和响应体等结果信息(该方法会在每次发送请求前自动调用) + * + * 参数: + * 返回值: TRUE -- 成功 + * FALSE -- 失败 + */ + virtual BOOL CleanupRequestResult () = 0; + +public: + + /***********************************************************************/ + /***************************** 属性访问方法 *****************************/ + + /* 设置连接超时(毫秒,0:系统默认超时,默认:5000) */ + virtual void SetConnectTimeout (DWORD dwConnectTimeout) = 0; + /* 设置请求超时(毫秒,0:无限等待,默认:10000) */ + virtual void SetRequestTimeout (DWORD dwRequestTimeout) = 0; + + /* 获取连接超时 */ + virtual DWORD GetConnectTimeout () = 0; + /* 获取请求超时 */ + virtual DWORD GetRequestTimeout () = 0; + + /* 获取响应体 */ + virtual BOOL GetResponseBody (LPCBYTE* lpszBody, int* iLength) = 0; +}; + + +/************************************************************************ +名称:HTTP 组件接口 +描述:继承了 HTTP 和 Socket 接口 +************************************************************************/ +typedef DualInterface IHttpServer; +typedef DualInterface IHttpAgent; +typedef DualInterface IHttpClient; +typedef DualInterface IHttpSyncClient; + +/************************************************************************ +名称:IComplexHttp 组件监听器基接口 +描述:定义 IComplexHttp 组件监听器的所有事件 +************************************************************************/ +template class IHttpListenerT +{ +public: + + /* + * 名称:开始解析通知 + * 描述:开始解析 HTTP 报文时,向监听器发送该通知 + * + * 参数: pSender -- 事件源对象 + * dwConnID -- 连接 ID + * 返回值: HPR_OK -- 继续执行 + * HPR_ERROR -- 引发 OnParserError() 和 OnClose() 事件并关闭连接 + */ + virtual EnHttpParseResult OnMessageBegin(T* pSender, CONNID dwConnID) = 0; + + /* + * 名称:请求行解析完成通知(仅用于 HTTP 服务端) + * 描述:请求行解析完成后,向监听器发送该通知 + * + * 参数: pSender -- 事件源对象 + * dwConnID -- 连接 ID + * lpszMethod -- 请求方法名 + * lpszUrl -- 请求行中的 URL 域 + * 返回值: HPR_OK -- 继续执行 + * HPR_ERROR -- 引发 OnParserError() 和 OnClose() 事件并关闭连接 + */ + virtual EnHttpParseResult OnRequestLine(T* pSender, CONNID dwConnID, LPCSTR lpszMethod, LPCSTR lpszUrl) = 0; + + /* + * 名称:状态行解析完成通知(仅用于 HTTP 客户端) + * 描述:状态行解析完成后,向监听器发送该通知 + * + * 参数: pSender -- 事件源对象 + * dwConnID -- 连接 ID + * usStatusCode -- HTTP 状态码 + * lpszDesc -- 状态描述 + * 返回值: HPR_OK -- 继续执行 + * HPR_ERROR -- 引发 OnParserError() 和 OnClose() 事件并关闭连接 + */ + virtual EnHttpParseResult OnStatusLine(T* pSender, CONNID dwConnID, USHORT usStatusCode, LPCSTR lpszDesc) = 0; + + /* + * 名称:请求头通知 + * 描述:每当解析完成一个请求头后,向监听器发送该通知 + * + * 参数: pSender -- 事件源对象 + * dwConnID -- 连接 ID + * lpszName -- 请求头名称 + * lpszValue -- 请求头值 + * 返回值: HPR_OK -- 继续执行 + * HPR_ERROR -- 引发 OnParserError() 和 OnClose() 事件并关闭连接 + */ + virtual EnHttpParseResult OnHeader(T* pSender, CONNID dwConnID, LPCSTR lpszName, LPCSTR lpszValue) = 0; + + /* + * 名称:请求头完成通知 + * 描述:解析完成所有请求头后,向监听器发送该通知 + * + * 参数: pSender -- 事件源对象 + * dwConnID -- 连接 ID + * 返回值: HPR_OK -- 继续执行 + * HPR_SKIP_BODY -- 跳过当前请求的 HTTP BODY + * HPR_UPGRADE -- 升级协议 + * HPR_ERROR -- 引发 OnParserError() 和 OnClose() 事件并关闭连接 + */ + virtual EnHttpParseResult OnHeadersComplete(T* pSender, CONNID dwConnID) = 0; + + /* + * 名称:BODY 报文通知 + * 描述:每当接收到 HTTP BODY 报文,向监听器发送该通知 + * + * 参数: pSender -- 事件源对象 + * dwConnID -- 连接 ID + * pData -- 数据缓冲区 + * iLength -- 数据长度 + * 返回值: HPR_OK -- 继续执行 + * HPR_ERROR -- 引发 OnParserError() 和 OnClose() 事件并关闭连接 + */ + virtual EnHttpParseResult OnBody(T* pSender, CONNID dwConnID, const BYTE* pData, int iLength) = 0; + + /* + * 名称:Chunked 报文头通知 + * 描述:每当解析出一个 Chunked 报文头,向监听器发送该通知 + * + * 参数: pSender -- 事件源对象 + * dwConnID -- 连接 ID + * iLength -- Chunked 报文体数据长度 + * 返回值: HPR_OK -- 继续执行 + * HPR_ERROR -- 引发 OnParserError() 和 OnClose() 事件并关闭连接 + */ + virtual EnHttpParseResult OnChunkHeader(T* pSender, CONNID dwConnID, int iLength) = 0; + + /* + * 名称:Chunked 报文结束通知 + * 描述:每当解析完一个 Chunked 报文,向监听器发送该通知 + * + * 参数: pSender -- 事件源对象 + * dwConnID -- 连接 ID + * 返回值: HPR_OK -- 继续执行 + * HPR_ERROR -- 引发 OnParserError() 和 OnClose() 事件并关闭连接 + */ + virtual EnHttpParseResult OnChunkComplete(T* pSender, CONNID dwConnID) = 0; + + /* + * 名称:完成解析通知 + * 描述:每当解析完成一个完整 HTTP 报文,向监听器发送该通知 + * + * 参数: pSender -- 事件源对象 + * dwConnID -- 连接 ID + * 返回值: HPR_OK -- 继续执行 + * HPR_ERROR -- 引发 OnParserError() 和 OnClose() 事件并关闭连接 + */ + virtual EnHttpParseResult OnMessageComplete(T* pSender, CONNID dwConnID) = 0; + + /* + * 名称:升级协议通知 + * 描述:当需要升级协议时,向监听器发送该通知 + * + * 参数: pSender -- 事件源对象 + * dwConnID -- 连接 ID + * enUpgradeType -- 协议类型 + * 返回值: HPR_OK -- 继续执行 + * HPR_ERROR -- 引发 OnClose() 事件并关闭连接 + */ + virtual EnHttpParseResult OnUpgrade(T* pSender, CONNID dwConnID, EnHttpUpgradeType enUpgradeType) = 0; + + /* + * 名称:解析错误通知 + * 描述:当解析 HTTP 报文错误时,向监听器发送该通知 + * + * 参数: pSender -- 事件源对象 + * dwConnID -- 连接 ID + * iErrorCode -- 错误代码 + * lpszErrorDesc -- 错误描述 + * 返回值: HPR_OK -- 继续执行 + * HPR_ERROR -- 引发 OnClose() 事件并关闭连接 + */ + virtual EnHttpParseResult OnParseError(T* pSender, CONNID dwConnID, int iErrorCode, LPCSTR lpszErrorDesc) = 0; + + /* + * 名称:WebSocket 数据包头通知 + * 描述:当解析 WebSocket 数据包头时,向监听器发送该通知 + * + * 参数: pSender -- 事件源对象 + * dwConnID -- 连接 ID + * bFinal -- 是否结束帧 + * iReserved -- RSV1/RSV2/RSV3 各 1 位 + * iOperationCode -- 操作码:0x0 - 0xF + * lpszMask -- 掩码(nullptr 或 4 字节掩码,如果为 nullptr 则没有掩码) + * ullBodyLen -- 消息体长度 + * 返回值: HR_OK / HR_IGNORE -- 继续执行 + * HR_ERROR -- 引发 OnClose() 事件并关闭连接 + */ + virtual EnHandleResult OnWSMessageHeader(T* pSender, CONNID dwConnID, BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], ULONGLONG ullBodyLen) = 0; + + /* + * 名称:WebSocket 数据包体通知 + * 描述:当接收到 WebSocket 数据包体时,向监听器发送该通知 + * + * 参数: pSender -- 事件源对象 + * dwConnID -- 连接 ID + * pData -- 消息体数据缓冲区 + * iLength -- 消息体数据长度 + * 返回值: HR_OK / HR_IGNORE -- 继续执行 + * HR_ERROR -- 引发 OnClose() 事件并关闭连接 + */ + virtual EnHandleResult OnWSMessageBody(T* pSender, CONNID dwConnID, const BYTE* pData, int iLength) = 0; + + /* + * 名称:WebSocket 数据包完成通知 + * 描述:当完整接收一个 WebSocket 数据包时,向监听器发送该通知 + * + * 参数: pSender -- 事件源对象 + * dwConnID -- 连接 ID + * 返回值: HR_OK / HR_IGNORE -- 继续执行 + * HR_ERROR -- 引发 OnClose() 事件并关闭连接 + */ + virtual EnHandleResult OnWSMessageComplete(T* pSender, CONNID dwConnID) = 0; + +public: + virtual ~IHttpListenerT() = default; +}; + +/************************************************************************ +名称:IHttpServer 组件端监听器接口 +描述:定义 IHttpServer 监听器的所有事件 +************************************************************************/ +class IHttpServerListener : public IHttpListenerT, public ITcpServerListener +{ +public: + +}; + +/************************************************************************ +名称:IHttpAgent 组件端监听器接口 +描述:定义 IHttpAgent 监听器的所有事件 +************************************************************************/ +class IHttpAgentListener : public IHttpListenerT, public ITcpAgentListener +{ +public: + +}; + +/************************************************************************ +名称:IHttpClient 组件端监听器接口 +描述:定义 IHttpClient 监听器的所有事件 +************************************************************************/ +class IHttpClientListener : public IHttpListenerT, public ITcpClientListener +{ +public: + +}; + +/************************************************************************ +名称:IHttpServerListener 监听器抽象基类 +描述:定义某些事件的默认处理方法(忽略事件) +************************************************************************/ +class CHttpServerListener : public IHttpServerListener +{ +public: + virtual EnHandleResult OnPrepareListen(ITcpServer* pSender, SOCKET soListen) override {return HR_IGNORE;} + virtual EnHandleResult OnAccept(ITcpServer* pSender, CONNID dwConnID, UINT_PTR soClient) override {return HR_IGNORE;} + virtual EnHandleResult OnHandShake(ITcpServer* pSender, CONNID dwConnID) override {return HR_IGNORE;} + virtual EnHandleResult OnReceive(ITcpServer* pSender, CONNID dwConnID, int iLength) override {return HR_IGNORE;} + virtual EnHandleResult OnReceive(ITcpServer* pSender, CONNID dwConnID, const BYTE* pData, int iLength) override {return HR_IGNORE;} + virtual EnHandleResult OnSend(ITcpServer* pSender, CONNID dwConnID, const BYTE* pData, int iLength) override {return HR_IGNORE;} + virtual EnHandleResult OnShutdown(ITcpServer* pSender) override {return HR_IGNORE;} + + virtual EnHttpParseResult OnMessageBegin(IHttpServer* pSender, CONNID dwConnID) override {return HPR_OK;} + virtual EnHttpParseResult OnRequestLine(IHttpServer* pSender, CONNID dwConnID, LPCSTR lpszMethod, LPCSTR lpszUrl) override {return HPR_OK;} + virtual EnHttpParseResult OnStatusLine(IHttpServer* pSender, CONNID dwConnID, USHORT usStatusCode, LPCSTR lpszDesc) override {return HPR_OK;} + virtual EnHttpParseResult OnHeader(IHttpServer* pSender, CONNID dwConnID, LPCSTR lpszName, LPCSTR lpszValue) override {return HPR_OK;} + virtual EnHttpParseResult OnChunkHeader(IHttpServer* pSender, CONNID dwConnID, int iLength) override {return HPR_OK;} + virtual EnHttpParseResult OnChunkComplete(IHttpServer* pSender, CONNID dwConnID) override {return HPR_OK;} + virtual EnHttpParseResult OnUpgrade(IHttpServer* pSender, CONNID dwConnID, EnHttpUpgradeType enUpgradeType) override {return HPR_OK;} + + virtual EnHandleResult OnWSMessageHeader(IHttpServer* pSender, CONNID dwConnID, BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], ULONGLONG ullBodyLen) override {return HR_IGNORE;} + virtual EnHandleResult OnWSMessageBody(IHttpServer* pSender, CONNID dwConnID, const BYTE* pData, int iLength) override {return HR_IGNORE;} + virtual EnHandleResult OnWSMessageComplete(IHttpServer* pSender, CONNID dwConnID) override {return HR_IGNORE;} +}; + +/************************************************************************ +名称:IHttpAgentListener 监听器抽象基类 +描述:定义某些事件的默认处理方法(忽略事件) +************************************************************************/ +class CHttpAgentListener : public IHttpAgentListener +{ +public: + virtual EnHandleResult OnPrepareConnect(ITcpAgent* pSender, CONNID dwConnID, SOCKET socket) override {return HR_IGNORE;} + virtual EnHandleResult OnConnect(ITcpAgent* pSender, CONNID dwConnID) override {return HR_IGNORE;} + virtual EnHandleResult OnHandShake(ITcpAgent* pSender, CONNID dwConnID) override {return HR_IGNORE;} + virtual EnHandleResult OnReceive(ITcpAgent* pSender, CONNID dwConnID, int iLength) override {return HR_IGNORE;} + virtual EnHandleResult OnReceive(ITcpAgent* pSender, CONNID dwConnID, const BYTE* pData, int iLength) override {return HR_IGNORE;} + virtual EnHandleResult OnSend(ITcpAgent* pSender, CONNID dwConnID, const BYTE* pData, int iLength) override {return HR_IGNORE;} + virtual EnHandleResult OnShutdown(ITcpAgent* pSender) override {return HR_IGNORE;} + + virtual EnHttpParseResult OnMessageBegin(IHttpAgent* pSender, CONNID dwConnID) override {return HPR_OK;} + virtual EnHttpParseResult OnRequestLine(IHttpAgent* pSender, CONNID dwConnID, LPCSTR lpszMethod, LPCSTR lpszUrl) override {return HPR_OK;} + virtual EnHttpParseResult OnStatusLine(IHttpAgent* pSender, CONNID dwConnID, USHORT usStatusCode, LPCSTR lpszDesc) override {return HPR_OK;} + virtual EnHttpParseResult OnHeader(IHttpAgent* pSender, CONNID dwConnID, LPCSTR lpszName, LPCSTR lpszValue) override {return HPR_OK;} + virtual EnHttpParseResult OnChunkHeader(IHttpAgent* pSender, CONNID dwConnID, int iLength) override {return HPR_OK;} + virtual EnHttpParseResult OnChunkComplete(IHttpAgent* pSender, CONNID dwConnID) override {return HPR_OK;} + virtual EnHttpParseResult OnUpgrade(IHttpAgent* pSender, CONNID dwConnID, EnHttpUpgradeType enUpgradeType) override {return HPR_OK;} + + virtual EnHandleResult OnWSMessageHeader(IHttpAgent* pSender, CONNID dwConnID, BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], ULONGLONG ullBodyLen) override {return HR_IGNORE;} + virtual EnHandleResult OnWSMessageBody(IHttpAgent* pSender, CONNID dwConnID, const BYTE* pData, int iLength) override {return HR_IGNORE;} + virtual EnHandleResult OnWSMessageComplete(IHttpAgent* pSender, CONNID dwConnID) override {return HR_IGNORE;} +}; + +/************************************************************************ +名称:IHttpClientListener 监听器抽象基类 +描述:定义某些事件的默认处理方法(忽略事件) +************************************************************************/ + +class CHttpClientListener : public IHttpClientListener +{ +public: + virtual EnHandleResult OnPrepareConnect(ITcpClient* pSender, CONNID dwConnID, SOCKET socket) override {return HR_IGNORE;} + virtual EnHandleResult OnConnect(ITcpClient* pSender, CONNID dwConnID) override {return HR_IGNORE;} + virtual EnHandleResult OnHandShake(ITcpClient* pSender, CONNID dwConnID) override {return HR_IGNORE;} + virtual EnHandleResult OnReceive(ITcpClient* pSender, CONNID dwConnID, int iLength) override {return HR_IGNORE;} + virtual EnHandleResult OnReceive(ITcpClient* pSender, CONNID dwConnID, const BYTE* pData, int iLength) override {return HR_IGNORE;} + virtual EnHandleResult OnSend(ITcpClient* pSender, CONNID dwConnID, const BYTE* pData, int iLength) override {return HR_IGNORE;} + + virtual EnHttpParseResult OnMessageBegin(IHttpClient* pSender, CONNID dwConnID) override {return HPR_OK;} + virtual EnHttpParseResult OnRequestLine(IHttpClient* pSender, CONNID dwConnID, LPCSTR lpszMethod, LPCSTR lpszUrl) override {return HPR_OK;} + virtual EnHttpParseResult OnStatusLine(IHttpClient* pSender, CONNID dwConnID, USHORT usStatusCode, LPCSTR lpszDesc) override {return HPR_OK;} + virtual EnHttpParseResult OnHeader(IHttpClient* pSender, CONNID dwConnID, LPCSTR lpszName, LPCSTR lpszValue) override {return HPR_OK;} + virtual EnHttpParseResult OnChunkHeader(IHttpClient* pSender, CONNID dwConnID, int iLength) override {return HPR_OK;} + virtual EnHttpParseResult OnChunkComplete(IHttpClient* pSender, CONNID dwConnID) override {return HPR_OK;} + virtual EnHttpParseResult OnUpgrade(IHttpClient* pSender, CONNID dwConnID, EnHttpUpgradeType enUpgradeType) override {return HPR_OK;} + + virtual EnHandleResult OnWSMessageHeader(IHttpClient* pSender, CONNID dwConnID, BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], ULONGLONG ullBodyLen) override {return HR_IGNORE;} + virtual EnHandleResult OnWSMessageBody(IHttpClient* pSender, CONNID dwConnID, const BYTE* pData, int iLength) override {return HR_IGNORE;} + virtual EnHandleResult OnWSMessageComplete(IHttpClient* pSender, CONNID dwConnID) override {return HR_IGNORE;} +}; + +/************************************************************************ +名称:IHttpClientListener 监听器抽象基类 +描述:定义某些事件的默认处理方法(忽略事件) +************************************************************************/ + +class CHttpSyncClientListener : public CHttpClientListener +{ +public: + virtual EnHandleResult OnClose(ITcpClient* pSender, CONNID dwConnID, EnSocketOperation enOperation, int iErrorCode) override {return HR_IGNORE;} + + virtual EnHttpParseResult OnHeadersComplete(IHttpClient* pSender, CONNID dwConnID) override {return HPR_OK;} + virtual EnHttpParseResult OnBody(IHttpClient* pSender, CONNID dwConnID, const BYTE* pData, int iLength) override {return HPR_OK;} + virtual EnHttpParseResult OnMessageComplete(IHttpClient* pSender, CONNID dwConnID) override {return HPR_OK;} + virtual EnHttpParseResult OnParseError(IHttpClient* pSender, CONNID dwConnID, int iErrorCode, LPCSTR lpszErrorDesc) override {return HPR_OK;} + +}; + +#endif + +/*****************************************************************************************************************************************************/ +/************************************************************** Thread Pool Interfaces ***************************************************************/ +/*****************************************************************************************************************************************************/ + +/************************************************************************ +名称:线程池组件接口 +描述:定义线程池组件的所有操作方法和属性访问方法 +************************************************************************/ +class IHPThreadPool +{ +public: + + /***********************************************************************/ + /***************************** 组件操作方法 *****************************/ + + /* + * 名称:启动线程池组件 + * 描述: + * + * 参数: dwThreadCount -- 线程数量,(默认:0) + * >0 -> dwThreadCount + * =0 -> (CPU核数 * 2 + 2) + * <0 -> (CPU核数 * (-dwThreadCount)) + * dwMaxQueueSize -- 任务队列最大容量(默认:0,不限制) + * enRejectedPolicy -- 任务拒绝处理策略 + * TRP_CALL_FAIL(默认) :立刻返回失败 + * TRP_WAIT_FOR :等待(直到成功、超时或线程池关闭等原因导致失败) + * TRP_CALLER_RUN :调用者线程直接执行 + * dwStackSize -- 线程堆栈空间大小(默认:0 -> 操作系统默认) + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 + */ + virtual BOOL Start (DWORD dwThreadCount = 0, DWORD dwMaxQueueSize = 0, EnRejectedPolicy enRejectedPolicy = TRP_CALL_FAIL, DWORD dwStackSize = 0) = 0; + + /* + * 名称:关闭线程池组件 + * 描述:在规定时间内关闭线程池组件,如果工作线程在最大等待时间内未能正常关闭,会尝试强制关闭,这种情况下很可能会造成系统资源泄漏 + * + * 参数: dwMaxWait -- 最大等待时间(毫秒,默认:INFINITE,一直等待) + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 + */ + virtual BOOL Stop (DWORD dwMaxWait = INFINITE) = 0; + + /* + * 名称:提交任务 + * 描述:向线程池提交异步任务 + * + * 参数: fnTaskProc -- 任务处理函数 + * pvArg -- 任务参数 + * dwMaxWait -- 任务提交最大等待时间(仅对 TRP_WAIT_FOR 类型线程池生效,默认:INFINITE,一直等待) + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 + * 其中,错误码 ERROR_DESTINATION_ELEMENT_FULL 表示任务队列已满 + */ + virtual BOOL Submit (Fn_TaskProc fnTaskProc, PVOID pvArg, DWORD dwMaxWait = INFINITE) = 0; + + /* + * 名称:提交 Socket 任务 + * 描述:向线程池提交异步 Socket 任务 + * + * 参数: pTask -- 任务参数 + * dwMaxWait -- 任务提交最大等待时间(仅对 TRP_WAIT_FOR 类型线程池生效,默认:INFINITE,一直等待) + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 + * 其中,错误码 ERROR_DESTINATION_ELEMENT_FULL 表示任务队列已满 + * 注意:如果提交失败,需要手工调用 Destroy_HP_SocketTaskObj() 销毁 TSocketTask 对象 + */ + virtual BOOL Submit (LPTSocketTask pTask, DWORD dwMaxWait = INFINITE) = 0; + + /* + * 名称:调整线程池大小 + * 描述:增加或减少线程池的工作线程数量 + * + * 参数: dwNewThreadCount -- 线程数量 + * >0 -> dwNewThreadCount + * =0 -> (CPU核数 * 2 + 2) + * <0 -> (CPU核数 * (-dwNewThreadCount)) + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 + */ + virtual BOOL AdjustThreadCount(DWORD dwNewThreadCount) = 0; + + /* + * 名称:等待 + * 描述:等待线程池组件停止运行 + * + * 参数: dwMilliseconds -- 超时时间(毫秒,默认:-1,永不超时) + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 + */ + virtual BOOL Wait(DWORD dwMilliseconds = INFINITE) = 0; + +public: + + /***********************************************************************/ + /***************************** 属性访问方法 *****************************/ + + /* 检查线程池组件是否已启动 */ + virtual BOOL HasStarted () = 0; + /* 查看线程池组件当前状态 */ + virtual EnServiceState GetState () = 0; + /* 获取当前任务等待队列大小 */ + virtual DWORD GetQueueSize () = 0; + /* 获取当前正在执行的任务数量 */ + virtual DWORD GetTaskCount () = 0; + /* 获取工作线程数量 */ + virtual DWORD GetThreadCount () = 0; + /* 获取任务队列最大容量 */ + virtual DWORD GetMaxQueueSize () = 0; + /* 获取任务拒绝处理策略 */ + virtual EnRejectedPolicy GetRejectedPolicy () = 0; + +public: + virtual ~IHPThreadPool() = default; +}; + +/************************************************************************ +名称:线程池监听器接口 +描述:定义线程池监听器的所有事件 +************************************************************************/ +class IHPThreadPoolListener +{ +public: + + /* + * 名称:线程池启动通知 + * 描述:线程池启动时监听器将收到该通知,监听器可以在通知处理方法中执行预处理工作 + * + * 参数: pThreadPool -- 线程池对象 + * 返回值: 无 + */ + virtual void OnStartup(IHPThreadPool* pThreadPool) = 0; + + /* + * 名称:线程池启动关闭通知 + * 描述:线程池关闭时监听器将收到该通知,监听器可以在通知处理方法中执行后处理工作 + * + * 参数: pThreadPool -- 线程池对象 + * 返回值: 无 + */ + virtual void OnShutdown(IHPThreadPool* pThreadPool) = 0; + + /* + * 名称:工作线程启动通知 + * 描述:工作线程启动时监听器将收到该通知,监听器可以在通知处理方法中执行线程级别预处理工作 + * + * 参数: pThreadPool -- 线程池对象 + * dwThreadID -- 工作线程 ID + * 返回值: 无 + */ + virtual void OnWorkerThreadStart(IHPThreadPool* pThreadPool, THR_ID dwThreadID) = 0; + + /* + * 名称:工作线程退出通知 + * 描述:工作线程退出时监听器将收到该通知,监听器可以在通知处理方法中执行线程级别后处理工作 + * + * 参数: pThreadPool -- 线程池对象 + * dwThreadID -- 工作线程 ID + * 返回值: 无 + */ + virtual void OnWorkerThreadEnd(IHPThreadPool* pThreadPool, THR_ID dwThreadID) = 0; + +public: + virtual ~IHPThreadPoolListener() {}; +}; + +/************************************************************************ +名称:线程池监听器抽象基类 +描述:定义某些事件的默认处理方法(忽略事件) +************************************************************************/ +class CHPThreadPoolListener : public IHPThreadPoolListener +{ +public: + virtual void OnStartup(IHPThreadPool* pThreadPool) override {} + virtual void OnShutdown(IHPThreadPool* pThreadPool) override {} + virtual void OnWorkerThreadStart(IHPThreadPool* pThreadPool, THR_ID dwThreadID) override {} + virtual void OnWorkerThreadEnd(IHPThreadPool* pThreadPool, THR_ID dwThreadID) override {} +}; + +/************************************************************************ +名称:压缩器接口 +描述:定义压缩器的所有操作方法和属性访问方法 +************************************************************************/ +class IHPCompressor +{ +public: + + /***********************************************************************/ + /***************************** 组件操作方法 *****************************/ + + /* + * 名称:执行压缩 + * 描述:可循环调用以压缩流式或分段数据 + * + * 参数: pData -- 待压缩数据缓冲区 + * iLength -- 待压缩数据长度 + * bLast -- 是否最后一段待压缩数据 + * pContext -- 压缩回调函数 Fn_CompressDataCallback 的上下文参数 + * + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 + */ + virtual BOOL Process(const BYTE* pData, int iLength, BOOL bLast, PVOID pContext = nullptr) = 0; + + /* + * 名称:执行压缩 + * 描述:可循环调用以压缩流式或分段数据 + * + * 参数: pData -- 待压缩数据缓冲区 + * iLength -- 待压缩数据长度 + * bLast -- 是否最后一段待压缩数据 + * bFlush -- 是否强制刷新(强制刷新会降低压缩效率,但可对数据进行分段压缩) + * pContext -- 压缩回调函数 Fn_CompressDataCallback 的上下文参数 + * + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 + */ + virtual BOOL ProcessEx(const BYTE* pData, int iLength, BOOL bLast, BOOL bFlush = FALSE, PVOID pContext = nullptr) = 0; + + /* 重置压缩器 */ + virtual BOOL Reset() = 0; + + /***********************************************************************/ + /***************************** 属性访问方法 *****************************/ + + /* 检测压缩器是否可用 */ + virtual BOOL IsValid() = 0; + +public: + virtual ~IHPCompressor() = default; +}; + +/************************************************************************ +名称:解压器接口 +描述:定义解压器的所有操作方法和属性访问方法 +************************************************************************/ +class IHPDecompressor +{ +public: + + /***********************************************************************/ + /***************************** 组件操作方法 *****************************/ + + /* + * 名称:执行解压 + * 描述:可循环调用以解压流式或分段数据 + * + * 参数: pData -- 待解压数据缓冲区 + * iLength -- 待解压数据长度 + * pContext -- 解压回调函数 Fn_DecompressDataCallback 的上下文参数 + * + * 返回值: TRUE -- 成功 + * FALSE -- 失败,可通过 SYS_GetLastError() 获取错误代码 + */ + virtual BOOL Process(const BYTE* pData, int iLength, PVOID pContext = nullptr) = 0; + + /* 重置解压器 */ + virtual BOOL Reset() = 0; + +public: + + /***********************************************************************/ + /***************************** 属性访问方法 *****************************/ + + /* 检测解压器是否可用 */ + virtual BOOL IsValid() = 0; + +public: + virtual ~IHPDecompressor() = default; +}; diff --git a/libbrotli.a b/libbrotli.a new file mode 100644 index 0000000000000000000000000000000000000000..5a6edaaf29985a5fef704f513466781cbcb09530 GIT binary patch literal 1020996 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 w$hm%DfYqjT!OHm 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

kDMh7sbF9yZL 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

n)BMcO) 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 literal 0 HcmV?d00001 diff --git a/libbrotli.lib b/libbrotli.lib new file mode 100644 index 0000000000000000000000000000000000000000..5a6edaaf29985a5fef704f513466781cbcb09530 GIT binary patch literal 1020996 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 w$hm%DfYqjT!OHm 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

kDMh7sbF9yZL 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

n)BMcO) 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 literal 0 HcmV?d00001