Loading src/event/iocp.cpp +7 −5 Original line number Diff line number Diff line Loading @@ -180,10 +180,10 @@ namespace netplus { // This triggers the raw WSARecv. For SSL, it fills the internal _rx_netbuf. if (c.csock->_Type == sockettype::SSL) { static_cast<ssl*>(c.csock.get())->recvDataWSA(buf, 0); static_cast<ssl*>(c.csock.get())->recvDataWSA(buf, &ctx->readCtx.overlapped, 0); } else { static_cast<tcp*>(c.csock.get())->recvDataWSA(buf, 0); static_cast<tcp*>(c.csock.get())->recvDataWSA(buf, &ctx->readCtx.overlapped, 0); } } Loading @@ -201,12 +201,14 @@ namespace netplus { size_t toSend = (std::min)((size_t)BLOCKSIZE, c.SendData.size()); buffer out(c.SendData.data(), toSend); // Ensure the socket's internal overlapped is NOT used implicitly; // EventWorker will pass the client's persistent write overlapped to the socket. size_t consumed = 0; if (c.csock->_Type == sockettype::SSL) { consumed = static_cast<ssl*>(c.csock.get())->sendDataWSA(out, 0); consumed = static_cast<ssl*>(c.csock.get())->sendDataWSA(out, &ctx->writeCtx.overlapped, 0); } else { consumed = static_cast<tcp*>(c.csock.get())->sendDataWSA(out, 0); consumed = static_cast<tcp*>(c.csock.get())->sendDataWSA(out, &ctx->writeCtx.overlapped, 0); } // Remove the plaintext from the queue that was actually "consumed" into a TLS record Loading Loading @@ -320,7 +322,7 @@ namespace netplus { // 2. Process all complete TLS records currently in the buffer buffer plain(BLOCKSIZE); size_t decrypted = 0; while ((decrypted = sslSocket->recvDataWSA(plain, 0)) > 0) { while ((decrypted = sslSocket->recvDataWSA(plain, nullptr, 0)) > 0) { c.RecvData.append(plain.data.buf, decrypted); std::cerr << "[IOCP] RequestEvent (SSL) fd=" << c.csock->fd() << " appending " << decrypted << " bytes total_recv=" << c.RecvData.size() << std::endl; eargs->event->RequestEvent(c, tid, (ULONG_PTR)eargs->args); Loading src/socket.h +8 −8 Original line number Diff line number Diff line Loading @@ -107,8 +107,8 @@ namespace netplus { virtual void accept(std::unique_ptr<socket> &csock)=0; #ifdef Windows virtual void accept(LPFN_ACCEPTEX lpfnAcceptEx, std::unique_ptr<socket> &csock)=0; virtual size_t sendDataWSA(buffer &data, int flags = 0)=0; virtual size_t recvDataWSA(buffer &data, int flags = 0)=0; virtual size_t sendDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags)=0; virtual size_t recvDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags)=0; #endif virtual void bind()=0; virtual void listen()=0; Loading Loading @@ -160,8 +160,8 @@ namespace netplus { ~tcp(); #ifdef Windows void accept(LPFN_ACCEPTEX lpfnAcceptEx, std::unique_ptr<socket> &csock); size_t sendDataWSA(buffer &data, int flags = 0); // Renamed from sendData size_t recvDataWSA(buffer &data, int flags = 0); // Renamed from recvData size_t sendDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags); size_t recvDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags); #endif void accept(std::unique_ptr <socket> &csock); void bind(); Loading Loading @@ -196,8 +196,8 @@ namespace netplus { void accept(std::unique_ptr<socket> &csock); #ifdef Windows void accept(LPFN_ACCEPTEX lpfnAcceptEx, std::unique_ptr<socket> &csock); size_t sendDataWSA(buffer &data, int flags = 0); // Renamed from sendData size_t recvDataWSA(buffer &data, int flags = 0); // Renamed from recvData size_t sendDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags); size_t recvDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags); #endif void bind(); void listen(); Loading Loading @@ -237,8 +237,8 @@ namespace netplus { size_t recvData(buffer &data, int flags = 0) override; #ifdef Windows void accept(LPFN_ACCEPTEX lpfnAcceptEx, std::unique_ptr<socket>& csock) override; size_t sendDataWSA(buffer &data, int flags = 0); size_t recvDataWSA(buffer &data, int flags = 0); size_t sendDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags); size_t recvDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags); #endif bool loadServerPrivateKeyDer(const std::string& keyDerPath); Loading src/ssl.cpp +137 −181 Original line number Diff line number Diff line Loading @@ -296,7 +296,7 @@ namespace netplus { if (len == 0 || len > TLS_MAX_RECORD) { netplus::NetException e; e[netplus::NetException::Error] << "ssl::accept: invalid TLS record length " << len; e[netplus::NetException::Error] << "ssl::accept: invalid TLS record length " << static_cast<unsigned long long>(len); throw e; } Loading @@ -308,11 +308,20 @@ namespace netplus { } }; netplus::ssl::ssl(const netplus::x509cert &cert) : _cert(cert) , _aes(nullptr) { netplus::ssl::ssl(const netplus::x509cert &cert) : _cert(cert), _aes(nullptr), _handshakeDone(false) { _Type=sockettype::SSL; } netplus::ssl::ssl(const netplus::x509cert &cert,int sock) : tcp (sock), _cert(cert) ,_aes(nullptr) { netplus::ssl::ssl(const netplus::x509cert &cert,int sock) : tcp(sock), _cert(cert), _aes(nullptr), _handshakeDone(false) { _Type=sockettype::SSL; }; Loading Loading @@ -1004,7 +1013,8 @@ void netplus::ssl::accept(LPFN_ACCEPTEX lpfnAcceptEx, std::unique_ptr<socket>& c // Create accept socket (overlapped) const addrinfo* ai = reinterpret_cast<const addrinfo*>(this->_SocketInfo); cssock->_Socket = WSASocket(ai->ai_family, ai->ai_socktype, ai->ai_protocol, nullptr, 0, WSA_FLAG_OVERLAPPED); // Use the wide-char variant to avoid deprecated-API warning on MSVC. cssock->_Socket = WSASocketW(ai->ai_family, ai->ai_socktype, ai->ai_protocol, nullptr, 0, WSA_FLAG_OVERLAPPED); if (cssock->_Socket == INVALID_SOCKET) { int err = WSAGetLastError(); exception[NetException::Error] << "AcceptEx: WSASocket failed: " << err; Loading Loading @@ -1485,7 +1495,7 @@ size_t netplus::ssl::sendData(buffer& data, int flags) { _send_record.clear(); _send_record.reserve(5 + fragLen); _send_record.push_back(recordType); _send_record.push_back(0x03); _send_record.push_back(0x03); _send_record.push_back(0x03); _send_record.push_back(0x03); // TLS 1.2 _send_record.push_back((fragLen >> 8) & 0xFF); _send_record.push_back(fragLen & 0xFF); _send_record.insert(_send_record.end(), iv.begin(), iv.end()); Loading @@ -1511,7 +1521,7 @@ size_t netplus::ssl::recvData(buffer& data, int flags) { // 0) Serve buffered plaintext first if (_recv_off < _recv_record.size()) { const size_t avail = _recv_record.size() - _recv_off; const size_t outLen = std::min<size_t>(data.size, avail); const size_t outLen = (std::min)((size_t)data.size, avail); std::memcpy(data.data.buf, _recv_record.data() + _recv_off, outLen); _recv_off += outLen; data.size = outLen; Loading Loading @@ -1568,10 +1578,10 @@ size_t netplus::ssl::recvData(buffer& data, int flags) { static constexpr size_t TLS_MAX_RECORD = TLS_MAX_PLAINTEXT + TLS_MAX_CBC_OVERHEAD; if (recLen == 0 || recLen > TLS_MAX_RECORD) { throwSSL(NetException::Error, "invalid TLS record length " + std::to_string(recLen)); throwSSL(NetException::Error, "invalid TLS record length " + std::to_string((unsigned long long)recLen)); } if (ver != 0x0303) { throwSSL(NetException::Error, "unexpected TLS version " + std::to_string(ver)); throwSSL(NetException::Error, "unexpected TLS version 0x" + std::to_string(ver)); } const size_t total = 5 + (size_t)recLen; Loading Loading @@ -1629,14 +1639,11 @@ size_t netplus::ssl::recvData(buffer& data, int flags) { } const size_t contentLen = noPadLen - macLen; std::vector<uint8_t> content(plain.begin(), plain.begin() + contentLen); std::vector<uint8_t> recvMac(plain.begin() + contentLen, plain.begin() + noPadLen); std::vector<uint8_t> calcMac = _calculateHMAC(content, type, _recv_seq, _client_mac_key); if (calcMac.size() != recvMac.size() || !std::equal(calcMac.begin(), calcMac.end(), recvMac.begin())) { if (calcMac.size() != recvMac.size() || !std::equal(calcMac.begin(), calcMac.end(), recvMac.begin())) { throwSSL(NetException::Error, "bad mac"); } Loading @@ -1651,16 +1658,13 @@ size_t netplus::ssl::recvData(buffer& data, int flags) { continue; } const size_t outLen = std::min<size_t>(data.size, _recv_record.size()); const size_t outLen = (std::min)((size_t)data.size, _recv_record.size()); std::memcpy(data.data.buf, _recv_record.data(), outLen); _recv_off = outLen; data.size = outLen; if (_recv_off == _recv_record.size()) { _recv_record.clear(); _recv_off = 0; } return outLen; } } Loading Loading @@ -1834,13 +1838,15 @@ std::vector<uint8_t> netplus::ssl::_decryptRecordCBC(uint8_t recType, return content; // content = handshake/appdata plaintext (no mac/pad) } bool loadServerPrivateKeyDer(const std::string& keyDerPath); #ifdef Windows size_t netplus::ssl::sendDataWSA(buffer& data, int flags) { // 1) During handshake, we send raw TCP (usually for ServerHello/Certificates) // Ensure WSA-overlapped variants required by the Windows IOCP path are defined. // These forward to the non-overlapped logic where appropriate and perform // TLS record framing for IOCP usage. size_t netplus::ssl::sendDataWSA(buffer & data, WSAOVERLAPPED * pOv, int flags) { // During handshake, forward to raw TCP overlapped send if (!_handshakeDone) { return tcp::sendDataWSA(data, flags); return tcp::sendDataWSA(data, pOv, flags); } auto throwSSL = [&](int etype, const std::string& msg) -> void { Loading @@ -1849,35 +1855,28 @@ size_t netplus::ssl::sendDataWSA(buffer& data, int flags) { throw e; }; // 2) IOCP Constraint: If a previous record is still being sent by the kernel, // we cannot start a new WSASend on this context. // In netplus IOCP, SendData usually manages the queue, but SSL needs to // ensure the record is fully formed before the raw send. // If a previous record is still being sent, don't start another if (!_send_record.empty() && _send_off < _send_record.size()) { return 0; // Kernel is still busy with the previous encrypted record return 0; } if (data.size == 0) return 0; // 3) Encapsulation: Build the TLS Record static constexpr size_t TLS_MAX_PLAINTEXT = 16384; const size_t take = (std::min)((size_t)data.size, TLS_MAX_PLAINTEXT); const uint8_t recordType = 0x17; // ApplicationData if (!_aes) throwSSL(NetException::Error, "_aes is null"); if (_mac_key.empty()) throwSSL(NetException::Error, "send MAC key missing"); // Copy plaintext for processing std::vector<uint8_t> content( (const uint8_t*)data.data.buf, (const uint8_t*)data.data.buf + take ); // Calculate HMAC (Sequence, Type, Version, Length, Content) std::vector<uint8_t> mac = _calculateHMAC(content, recordType, _send_seq, _mac_key); // TLS 1.2 Record Construction: [Content] + [MAC] + [Padding] std::vector<uint8_t> inner; inner.reserve(content.size() + mac.size() + 32); inner.insert(inner.end(), content.begin(), content.end()); Loading @@ -1889,41 +1888,36 @@ size_t netplus::ssl::sendDataWSA(buffer& data, int flags) { uint8_t padVal = (uint8_t)(padBytes - 1); inner.insert(inner.end(), padBytes, padVal); // Generate Explicit IV (TLS 1.1+) std::vector<uint8_t> iv(block); { std::random_device rd; for (auto& b : iv) b = (uint8_t)(rd() & 0xFF); } { std::random_device rd; for (auto& b : iv) b = (uint8_t)(rd() & 0xFF); } // Encrypt the inner block std::vector<uint8_t> ct = _aes->encryptCBC(inner, iv); const uint16_t fragLen = (uint16_t)(iv.size() + ct.size()); // 4) Prepare _send_record for the Overlapped I/O _send_record.clear(); _send_record.reserve(5 + fragLen); _send_record.push_back(recordType); _send_record.push_back(0x03); _send_record.push_back(0x03); // TLS 1.2 (0x0303) _send_record.push_back(0x03); _send_record.push_back(0x03); // TLS 1.2 _send_record.push_back((fragLen >> 8) & 0xFF); _send_record.push_back(fragLen & 0xFF); _send_record.insert(_send_record.end(), iv.begin(), iv.end()); _send_record.insert(_send_record.end(), ct.begin(), ct.end()); _send_off = 0; // 5) Initiate Asynchronous Send // We pass the encrypted record to the parent TCP WSASend handler. // NOTE: In IOCP, _send_seq++ should happen in the EventWorker once // GetQueuedCompletionStatus confirms all bytes of the record were sent. buffer out((const char*)_send_record.data(), _send_record.size()); // This call triggers WSASend with the Overlapped structure. tcp::sendDataWSA(out, flags); // Start overlapped send on underlying TCP tcp::sendDataWSA(out, pOv, flags); // Return 'take' to inform the application how many plaintext bytes were consumed // Return number of plaintext bytes consumed return take; } size_t netplus::ssl::recvDataWSA(buffer& data, int flags) { // If handshake is not done, we shouldn't be processing encrypted records yet size_t netplus::ssl::recvDataWSA(buffer & data, WSAOVERLAPPED* /*pOv*/, int flags) { // For IOCP the raw bytes are appended to _rx_netbuf by the EventWorker; // this method decodes any complete TLS records present in _rx_netbuf. if (!_handshakeDone) return 0; auto throwSSL = [&](int etype, const std::string& msg) -> void { Loading @@ -1932,14 +1926,12 @@ size_t netplus::ssl::recvDataWSA(buffer& data, int flags) { throw e; }; // 0) Serve already decrypted plaintext from the internal buffer first // 0) Serve buffered plaintext if (_recv_off < _recv_record.size()) { const size_t avail = _recv_record.size() - _recv_off; const size_t outLen = (std::min)((size_t)data.size, avail); std::memcpy(data.data.buf, _recv_record.data() + _recv_off, outLen); _recv_off += outLen; // Reset buffer if fully consumed if (_recv_off == _recv_record.size()) { _recv_record.clear(); _recv_off = 0; Loading @@ -1947,52 +1939,31 @@ size_t netplus::ssl::recvDataWSA(buffer& data, int flags) { return outLen; } // 1) Loop to process the ciphertext currently in _rx_netbuf // Note: In IOCP, EventWorker has already appended raw bytes to _rx_netbuf // 1) Process ciphertext already present in _rx_netbuf for (;;) { // We need at least 5 bytes for a TLS Record Header if (_rx_netbuf.size() < 5) { return 0; // "Would block" - Wait for next IOCP completion } if (_rx_netbuf.size() < 5) return 0; // need header const uint8_t type = _rx_netbuf[0]; const uint16_t ver = (uint16_t(_rx_netbuf[1]) << 8) | uint16_t(_rx_netbuf[2]); const uint16_t recLen = (uint16_t(_rx_netbuf[3]) << 8) | uint16_t(_rx_netbuf[4]); static constexpr size_t TLS_MAX_RECORD = 16384 + 2048; if (recLen == 0 || recLen > TLS_MAX_RECORD) { throwSSL(NetException::Error, "invalid TLS record length " + std::to_string(recLen)); throwSSL(NetException::Error, "invalid TLS record length " + std::to_string((unsigned long long)recLen)); } // Validate TLS Version (Standard check for TLS 1.2) if (ver != 0x0303) { throwSSL(NetException::Error, "unexpected TLS version 0x" + std::to_string(ver)); } const size_t total = 5 + (size_t)recLen; if (_rx_netbuf.size() < total) return 0; // incomplete // Ensure the full record is present in the buffer if (_rx_netbuf.size() < total) { return 0; // Record incomplete, wait for more data from IOCP } // Extract encrypted fragment and remove from raw network buffer std::vector<uint8_t> frag(_rx_netbuf.begin() + 5, _rx_netbuf.begin() + total); _rx_netbuf.erase(_rx_netbuf.begin(), _rx_netbuf.begin() + total); // Handle Handshake records mid-stream (re-keying or sequence updates) if (type == 0x16) { _recv_seq++; continue; } // Validate record type (Application Data = 0x17, Alert = 0x15) if (type != 0x17 && type != 0x15) { throwSSL(NetException::Error, "unsupported TLS record type " + std::to_string((int)type)); } if (type == 0x16) { _recv_seq++; continue; } if (type != 0x17 && type != 0x15) throwSSL(NetException::Error, "unsupported TLS record type " + std::to_string((int)type)); // CBC explicit IV Decryption logic constexpr size_t block = 16; if (frag.size() < 2 * block || ((frag.size() - block) % block) != 0) { throwSSL(NetException::Error, "invalid CBC fragment size " + std::to_string(frag.size())); Loading @@ -2002,11 +1973,8 @@ size_t netplus::ssl::recvDataWSA(buffer& data, int flags) { std::vector<uint8_t> iv(frag.begin(), frag.begin() + block); std::vector<uint8_t> ciphertext(frag.begin() + block, frag.end()); // Decrypt using AES-CBC std::vector<uint8_t> plain = _aes_recv->decryptCBC(ciphertext, iv); // MAC and Padding Validation constexpr size_t macLen = 20; if (plain.size() < macLen + 1) throwSSL(NetException::Error, "bad padding/mac (too short)"); Loading @@ -2016,8 +1984,6 @@ size_t netplus::ssl::recvDataWSA(buffer& data, int flags) { const size_t noPadLen = plain.size() - padBytes; if (noPadLen < macLen) throwSSL(NetException::Error, "bad padding/mac (noPad < mac)"); // Verify Padding bytes for (size_t i = noPadLen; i < plain.size(); ++i) { if (plain[i] != padLen) throwSSL(NetException::Error, "bad padding bytes"); } Loading @@ -2025,36 +1991,26 @@ size_t netplus::ssl::recvDataWSA(buffer& data, int flags) { const size_t contentLen = noPadLen - macLen; std::vector<uint8_t> content(plain.begin(), plain.begin() + contentLen); std::vector<uint8_t> recvMac(plain.begin() + contentLen, plain.begin() + noPadLen); // Verify HMAC std::vector<uint8_t> calcMac = _calculateHMAC(content, type, _recv_seq, _client_mac_key); if (calcMac.size() != recvMac.size() || !std::equal(calcMac.begin(), calcMac.end(), recvMac.begin())) { if (calcMac.size() != recvMac.size() || !std::equal(calcMac.begin(), calcMac.end(), recvMac.begin())) { throwSSL(NetException::Error, "bad mac"); } _recv_seq++; // Buffer the plaintext _recv_record = std::move(content); _recv_off = 0; if (_recv_record.empty()) continue; // Copy decrypted data to the output buffer const size_t outLen = (std::min)((size_t)data.size, _recv_record.size()); std::memcpy(data.data.buf, _recv_record.data(), outLen); _recv_off = outLen; // Cleanup internal buffer if finished if (_recv_off == _recv_record.size()) { _recv_record.clear(); _recv_off = 0; } } return outLen; } } #endif src/windows/ssl.cpp 0 → 100644 +1200 −0 File added.Preview size limit exceeded, changes collapsed. Show changes src/windows/tcp.cpp +6 −7 Original line number Diff line number Diff line Loading @@ -365,15 +365,14 @@ void netplus::tcp::getAddress(std::string& addr) { } } // tcp.cpp size_t netplus::tcp::sendDataWSA(buffer& data, int flags) { // New overloads that accept explicit OVERLAPPED pointer size_t netplus::tcp::sendDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags) { NetException exception; _wsaBuf.buf = data.ptr ? (char*)data.data.ptr : data.data.buf; _wsaBuf.len = static_cast<ULONG>(data.size); DWORD dwBytes = 0; // Note: We do not reset _Overlapped here because it is managed by the IOCP context int rval = ::WSASend(_Socket, &_wsaBuf, 1, &dwBytes, flags, &_Overlapped, nullptr); int rval = ::WSASend(_Socket, &_wsaBuf, 1, &dwBytes, flags, pOv, nullptr); if (rval == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING) { exception[NetException::Error] << "WSASend failed: " << GetLastError(); Loading @@ -382,14 +381,14 @@ size_t netplus::tcp::sendDataWSA(buffer& data, int flags) { return (size_t)dwBytes; } size_t netplus::tcp::recvDataWSA(buffer& data, int flags) { size_t netplus::tcp::recvDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags) { NetException exception; _wsaBuf.buf = data.data.buf; _wsaBuf.len = static_cast<ULONG>(data.size); DWORD dwBytes = 0; DWORD dwFlags = (DWORD)flags; int rval = ::WSARecv(_Socket, &_wsaBuf, 1, &dwBytes, &dwFlags, &_Overlapped, nullptr); int rval = ::WSARecv(_Socket, &_wsaBuf, 1, &dwBytes, &dwFlags, pOv, nullptr); if (rval == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING) { exception[NetException::Error] << "WSARecv failed: " << GetLastError(); Loading Loading
src/event/iocp.cpp +7 −5 Original line number Diff line number Diff line Loading @@ -180,10 +180,10 @@ namespace netplus { // This triggers the raw WSARecv. For SSL, it fills the internal _rx_netbuf. if (c.csock->_Type == sockettype::SSL) { static_cast<ssl*>(c.csock.get())->recvDataWSA(buf, 0); static_cast<ssl*>(c.csock.get())->recvDataWSA(buf, &ctx->readCtx.overlapped, 0); } else { static_cast<tcp*>(c.csock.get())->recvDataWSA(buf, 0); static_cast<tcp*>(c.csock.get())->recvDataWSA(buf, &ctx->readCtx.overlapped, 0); } } Loading @@ -201,12 +201,14 @@ namespace netplus { size_t toSend = (std::min)((size_t)BLOCKSIZE, c.SendData.size()); buffer out(c.SendData.data(), toSend); // Ensure the socket's internal overlapped is NOT used implicitly; // EventWorker will pass the client's persistent write overlapped to the socket. size_t consumed = 0; if (c.csock->_Type == sockettype::SSL) { consumed = static_cast<ssl*>(c.csock.get())->sendDataWSA(out, 0); consumed = static_cast<ssl*>(c.csock.get())->sendDataWSA(out, &ctx->writeCtx.overlapped, 0); } else { consumed = static_cast<tcp*>(c.csock.get())->sendDataWSA(out, 0); consumed = static_cast<tcp*>(c.csock.get())->sendDataWSA(out, &ctx->writeCtx.overlapped, 0); } // Remove the plaintext from the queue that was actually "consumed" into a TLS record Loading Loading @@ -320,7 +322,7 @@ namespace netplus { // 2. Process all complete TLS records currently in the buffer buffer plain(BLOCKSIZE); size_t decrypted = 0; while ((decrypted = sslSocket->recvDataWSA(plain, 0)) > 0) { while ((decrypted = sslSocket->recvDataWSA(plain, nullptr, 0)) > 0) { c.RecvData.append(plain.data.buf, decrypted); std::cerr << "[IOCP] RequestEvent (SSL) fd=" << c.csock->fd() << " appending " << decrypted << " bytes total_recv=" << c.RecvData.size() << std::endl; eargs->event->RequestEvent(c, tid, (ULONG_PTR)eargs->args); Loading
src/socket.h +8 −8 Original line number Diff line number Diff line Loading @@ -107,8 +107,8 @@ namespace netplus { virtual void accept(std::unique_ptr<socket> &csock)=0; #ifdef Windows virtual void accept(LPFN_ACCEPTEX lpfnAcceptEx, std::unique_ptr<socket> &csock)=0; virtual size_t sendDataWSA(buffer &data, int flags = 0)=0; virtual size_t recvDataWSA(buffer &data, int flags = 0)=0; virtual size_t sendDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags)=0; virtual size_t recvDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags)=0; #endif virtual void bind()=0; virtual void listen()=0; Loading Loading @@ -160,8 +160,8 @@ namespace netplus { ~tcp(); #ifdef Windows void accept(LPFN_ACCEPTEX lpfnAcceptEx, std::unique_ptr<socket> &csock); size_t sendDataWSA(buffer &data, int flags = 0); // Renamed from sendData size_t recvDataWSA(buffer &data, int flags = 0); // Renamed from recvData size_t sendDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags); size_t recvDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags); #endif void accept(std::unique_ptr <socket> &csock); void bind(); Loading Loading @@ -196,8 +196,8 @@ namespace netplus { void accept(std::unique_ptr<socket> &csock); #ifdef Windows void accept(LPFN_ACCEPTEX lpfnAcceptEx, std::unique_ptr<socket> &csock); size_t sendDataWSA(buffer &data, int flags = 0); // Renamed from sendData size_t recvDataWSA(buffer &data, int flags = 0); // Renamed from recvData size_t sendDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags); size_t recvDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags); #endif void bind(); void listen(); Loading Loading @@ -237,8 +237,8 @@ namespace netplus { size_t recvData(buffer &data, int flags = 0) override; #ifdef Windows void accept(LPFN_ACCEPTEX lpfnAcceptEx, std::unique_ptr<socket>& csock) override; size_t sendDataWSA(buffer &data, int flags = 0); size_t recvDataWSA(buffer &data, int flags = 0); size_t sendDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags); size_t recvDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags); #endif bool loadServerPrivateKeyDer(const std::string& keyDerPath); Loading
src/ssl.cpp +137 −181 Original line number Diff line number Diff line Loading @@ -296,7 +296,7 @@ namespace netplus { if (len == 0 || len > TLS_MAX_RECORD) { netplus::NetException e; e[netplus::NetException::Error] << "ssl::accept: invalid TLS record length " << len; e[netplus::NetException::Error] << "ssl::accept: invalid TLS record length " << static_cast<unsigned long long>(len); throw e; } Loading @@ -308,11 +308,20 @@ namespace netplus { } }; netplus::ssl::ssl(const netplus::x509cert &cert) : _cert(cert) , _aes(nullptr) { netplus::ssl::ssl(const netplus::x509cert &cert) : _cert(cert), _aes(nullptr), _handshakeDone(false) { _Type=sockettype::SSL; } netplus::ssl::ssl(const netplus::x509cert &cert,int sock) : tcp (sock), _cert(cert) ,_aes(nullptr) { netplus::ssl::ssl(const netplus::x509cert &cert,int sock) : tcp(sock), _cert(cert), _aes(nullptr), _handshakeDone(false) { _Type=sockettype::SSL; }; Loading Loading @@ -1004,7 +1013,8 @@ void netplus::ssl::accept(LPFN_ACCEPTEX lpfnAcceptEx, std::unique_ptr<socket>& c // Create accept socket (overlapped) const addrinfo* ai = reinterpret_cast<const addrinfo*>(this->_SocketInfo); cssock->_Socket = WSASocket(ai->ai_family, ai->ai_socktype, ai->ai_protocol, nullptr, 0, WSA_FLAG_OVERLAPPED); // Use the wide-char variant to avoid deprecated-API warning on MSVC. cssock->_Socket = WSASocketW(ai->ai_family, ai->ai_socktype, ai->ai_protocol, nullptr, 0, WSA_FLAG_OVERLAPPED); if (cssock->_Socket == INVALID_SOCKET) { int err = WSAGetLastError(); exception[NetException::Error] << "AcceptEx: WSASocket failed: " << err; Loading Loading @@ -1485,7 +1495,7 @@ size_t netplus::ssl::sendData(buffer& data, int flags) { _send_record.clear(); _send_record.reserve(5 + fragLen); _send_record.push_back(recordType); _send_record.push_back(0x03); _send_record.push_back(0x03); _send_record.push_back(0x03); _send_record.push_back(0x03); // TLS 1.2 _send_record.push_back((fragLen >> 8) & 0xFF); _send_record.push_back(fragLen & 0xFF); _send_record.insert(_send_record.end(), iv.begin(), iv.end()); Loading @@ -1511,7 +1521,7 @@ size_t netplus::ssl::recvData(buffer& data, int flags) { // 0) Serve buffered plaintext first if (_recv_off < _recv_record.size()) { const size_t avail = _recv_record.size() - _recv_off; const size_t outLen = std::min<size_t>(data.size, avail); const size_t outLen = (std::min)((size_t)data.size, avail); std::memcpy(data.data.buf, _recv_record.data() + _recv_off, outLen); _recv_off += outLen; data.size = outLen; Loading Loading @@ -1568,10 +1578,10 @@ size_t netplus::ssl::recvData(buffer& data, int flags) { static constexpr size_t TLS_MAX_RECORD = TLS_MAX_PLAINTEXT + TLS_MAX_CBC_OVERHEAD; if (recLen == 0 || recLen > TLS_MAX_RECORD) { throwSSL(NetException::Error, "invalid TLS record length " + std::to_string(recLen)); throwSSL(NetException::Error, "invalid TLS record length " + std::to_string((unsigned long long)recLen)); } if (ver != 0x0303) { throwSSL(NetException::Error, "unexpected TLS version " + std::to_string(ver)); throwSSL(NetException::Error, "unexpected TLS version 0x" + std::to_string(ver)); } const size_t total = 5 + (size_t)recLen; Loading Loading @@ -1629,14 +1639,11 @@ size_t netplus::ssl::recvData(buffer& data, int flags) { } const size_t contentLen = noPadLen - macLen; std::vector<uint8_t> content(plain.begin(), plain.begin() + contentLen); std::vector<uint8_t> recvMac(plain.begin() + contentLen, plain.begin() + noPadLen); std::vector<uint8_t> calcMac = _calculateHMAC(content, type, _recv_seq, _client_mac_key); if (calcMac.size() != recvMac.size() || !std::equal(calcMac.begin(), calcMac.end(), recvMac.begin())) { if (calcMac.size() != recvMac.size() || !std::equal(calcMac.begin(), calcMac.end(), recvMac.begin())) { throwSSL(NetException::Error, "bad mac"); } Loading @@ -1651,16 +1658,13 @@ size_t netplus::ssl::recvData(buffer& data, int flags) { continue; } const size_t outLen = std::min<size_t>(data.size, _recv_record.size()); const size_t outLen = (std::min)((size_t)data.size, _recv_record.size()); std::memcpy(data.data.buf, _recv_record.data(), outLen); _recv_off = outLen; data.size = outLen; if (_recv_off == _recv_record.size()) { _recv_record.clear(); _recv_off = 0; } return outLen; } } Loading Loading @@ -1834,13 +1838,15 @@ std::vector<uint8_t> netplus::ssl::_decryptRecordCBC(uint8_t recType, return content; // content = handshake/appdata plaintext (no mac/pad) } bool loadServerPrivateKeyDer(const std::string& keyDerPath); #ifdef Windows size_t netplus::ssl::sendDataWSA(buffer& data, int flags) { // 1) During handshake, we send raw TCP (usually for ServerHello/Certificates) // Ensure WSA-overlapped variants required by the Windows IOCP path are defined. // These forward to the non-overlapped logic where appropriate and perform // TLS record framing for IOCP usage. size_t netplus::ssl::sendDataWSA(buffer & data, WSAOVERLAPPED * pOv, int flags) { // During handshake, forward to raw TCP overlapped send if (!_handshakeDone) { return tcp::sendDataWSA(data, flags); return tcp::sendDataWSA(data, pOv, flags); } auto throwSSL = [&](int etype, const std::string& msg) -> void { Loading @@ -1849,35 +1855,28 @@ size_t netplus::ssl::sendDataWSA(buffer& data, int flags) { throw e; }; // 2) IOCP Constraint: If a previous record is still being sent by the kernel, // we cannot start a new WSASend on this context. // In netplus IOCP, SendData usually manages the queue, but SSL needs to // ensure the record is fully formed before the raw send. // If a previous record is still being sent, don't start another if (!_send_record.empty() && _send_off < _send_record.size()) { return 0; // Kernel is still busy with the previous encrypted record return 0; } if (data.size == 0) return 0; // 3) Encapsulation: Build the TLS Record static constexpr size_t TLS_MAX_PLAINTEXT = 16384; const size_t take = (std::min)((size_t)data.size, TLS_MAX_PLAINTEXT); const uint8_t recordType = 0x17; // ApplicationData if (!_aes) throwSSL(NetException::Error, "_aes is null"); if (_mac_key.empty()) throwSSL(NetException::Error, "send MAC key missing"); // Copy plaintext for processing std::vector<uint8_t> content( (const uint8_t*)data.data.buf, (const uint8_t*)data.data.buf + take ); // Calculate HMAC (Sequence, Type, Version, Length, Content) std::vector<uint8_t> mac = _calculateHMAC(content, recordType, _send_seq, _mac_key); // TLS 1.2 Record Construction: [Content] + [MAC] + [Padding] std::vector<uint8_t> inner; inner.reserve(content.size() + mac.size() + 32); inner.insert(inner.end(), content.begin(), content.end()); Loading @@ -1889,41 +1888,36 @@ size_t netplus::ssl::sendDataWSA(buffer& data, int flags) { uint8_t padVal = (uint8_t)(padBytes - 1); inner.insert(inner.end(), padBytes, padVal); // Generate Explicit IV (TLS 1.1+) std::vector<uint8_t> iv(block); { std::random_device rd; for (auto& b : iv) b = (uint8_t)(rd() & 0xFF); } { std::random_device rd; for (auto& b : iv) b = (uint8_t)(rd() & 0xFF); } // Encrypt the inner block std::vector<uint8_t> ct = _aes->encryptCBC(inner, iv); const uint16_t fragLen = (uint16_t)(iv.size() + ct.size()); // 4) Prepare _send_record for the Overlapped I/O _send_record.clear(); _send_record.reserve(5 + fragLen); _send_record.push_back(recordType); _send_record.push_back(0x03); _send_record.push_back(0x03); // TLS 1.2 (0x0303) _send_record.push_back(0x03); _send_record.push_back(0x03); // TLS 1.2 _send_record.push_back((fragLen >> 8) & 0xFF); _send_record.push_back(fragLen & 0xFF); _send_record.insert(_send_record.end(), iv.begin(), iv.end()); _send_record.insert(_send_record.end(), ct.begin(), ct.end()); _send_off = 0; // 5) Initiate Asynchronous Send // We pass the encrypted record to the parent TCP WSASend handler. // NOTE: In IOCP, _send_seq++ should happen in the EventWorker once // GetQueuedCompletionStatus confirms all bytes of the record were sent. buffer out((const char*)_send_record.data(), _send_record.size()); // This call triggers WSASend with the Overlapped structure. tcp::sendDataWSA(out, flags); // Start overlapped send on underlying TCP tcp::sendDataWSA(out, pOv, flags); // Return 'take' to inform the application how many plaintext bytes were consumed // Return number of plaintext bytes consumed return take; } size_t netplus::ssl::recvDataWSA(buffer& data, int flags) { // If handshake is not done, we shouldn't be processing encrypted records yet size_t netplus::ssl::recvDataWSA(buffer & data, WSAOVERLAPPED* /*pOv*/, int flags) { // For IOCP the raw bytes are appended to _rx_netbuf by the EventWorker; // this method decodes any complete TLS records present in _rx_netbuf. if (!_handshakeDone) return 0; auto throwSSL = [&](int etype, const std::string& msg) -> void { Loading @@ -1932,14 +1926,12 @@ size_t netplus::ssl::recvDataWSA(buffer& data, int flags) { throw e; }; // 0) Serve already decrypted plaintext from the internal buffer first // 0) Serve buffered plaintext if (_recv_off < _recv_record.size()) { const size_t avail = _recv_record.size() - _recv_off; const size_t outLen = (std::min)((size_t)data.size, avail); std::memcpy(data.data.buf, _recv_record.data() + _recv_off, outLen); _recv_off += outLen; // Reset buffer if fully consumed if (_recv_off == _recv_record.size()) { _recv_record.clear(); _recv_off = 0; Loading @@ -1947,52 +1939,31 @@ size_t netplus::ssl::recvDataWSA(buffer& data, int flags) { return outLen; } // 1) Loop to process the ciphertext currently in _rx_netbuf // Note: In IOCP, EventWorker has already appended raw bytes to _rx_netbuf // 1) Process ciphertext already present in _rx_netbuf for (;;) { // We need at least 5 bytes for a TLS Record Header if (_rx_netbuf.size() < 5) { return 0; // "Would block" - Wait for next IOCP completion } if (_rx_netbuf.size() < 5) return 0; // need header const uint8_t type = _rx_netbuf[0]; const uint16_t ver = (uint16_t(_rx_netbuf[1]) << 8) | uint16_t(_rx_netbuf[2]); const uint16_t recLen = (uint16_t(_rx_netbuf[3]) << 8) | uint16_t(_rx_netbuf[4]); static constexpr size_t TLS_MAX_RECORD = 16384 + 2048; if (recLen == 0 || recLen > TLS_MAX_RECORD) { throwSSL(NetException::Error, "invalid TLS record length " + std::to_string(recLen)); throwSSL(NetException::Error, "invalid TLS record length " + std::to_string((unsigned long long)recLen)); } // Validate TLS Version (Standard check for TLS 1.2) if (ver != 0x0303) { throwSSL(NetException::Error, "unexpected TLS version 0x" + std::to_string(ver)); } const size_t total = 5 + (size_t)recLen; if (_rx_netbuf.size() < total) return 0; // incomplete // Ensure the full record is present in the buffer if (_rx_netbuf.size() < total) { return 0; // Record incomplete, wait for more data from IOCP } // Extract encrypted fragment and remove from raw network buffer std::vector<uint8_t> frag(_rx_netbuf.begin() + 5, _rx_netbuf.begin() + total); _rx_netbuf.erase(_rx_netbuf.begin(), _rx_netbuf.begin() + total); // Handle Handshake records mid-stream (re-keying or sequence updates) if (type == 0x16) { _recv_seq++; continue; } // Validate record type (Application Data = 0x17, Alert = 0x15) if (type != 0x17 && type != 0x15) { throwSSL(NetException::Error, "unsupported TLS record type " + std::to_string((int)type)); } if (type == 0x16) { _recv_seq++; continue; } if (type != 0x17 && type != 0x15) throwSSL(NetException::Error, "unsupported TLS record type " + std::to_string((int)type)); // CBC explicit IV Decryption logic constexpr size_t block = 16; if (frag.size() < 2 * block || ((frag.size() - block) % block) != 0) { throwSSL(NetException::Error, "invalid CBC fragment size " + std::to_string(frag.size())); Loading @@ -2002,11 +1973,8 @@ size_t netplus::ssl::recvDataWSA(buffer& data, int flags) { std::vector<uint8_t> iv(frag.begin(), frag.begin() + block); std::vector<uint8_t> ciphertext(frag.begin() + block, frag.end()); // Decrypt using AES-CBC std::vector<uint8_t> plain = _aes_recv->decryptCBC(ciphertext, iv); // MAC and Padding Validation constexpr size_t macLen = 20; if (plain.size() < macLen + 1) throwSSL(NetException::Error, "bad padding/mac (too short)"); Loading @@ -2016,8 +1984,6 @@ size_t netplus::ssl::recvDataWSA(buffer& data, int flags) { const size_t noPadLen = plain.size() - padBytes; if (noPadLen < macLen) throwSSL(NetException::Error, "bad padding/mac (noPad < mac)"); // Verify Padding bytes for (size_t i = noPadLen; i < plain.size(); ++i) { if (plain[i] != padLen) throwSSL(NetException::Error, "bad padding bytes"); } Loading @@ -2025,36 +1991,26 @@ size_t netplus::ssl::recvDataWSA(buffer& data, int flags) { const size_t contentLen = noPadLen - macLen; std::vector<uint8_t> content(plain.begin(), plain.begin() + contentLen); std::vector<uint8_t> recvMac(plain.begin() + contentLen, plain.begin() + noPadLen); // Verify HMAC std::vector<uint8_t> calcMac = _calculateHMAC(content, type, _recv_seq, _client_mac_key); if (calcMac.size() != recvMac.size() || !std::equal(calcMac.begin(), calcMac.end(), recvMac.begin())) { if (calcMac.size() != recvMac.size() || !std::equal(calcMac.begin(), calcMac.end(), recvMac.begin())) { throwSSL(NetException::Error, "bad mac"); } _recv_seq++; // Buffer the plaintext _recv_record = std::move(content); _recv_off = 0; if (_recv_record.empty()) continue; // Copy decrypted data to the output buffer const size_t outLen = (std::min)((size_t)data.size, _recv_record.size()); std::memcpy(data.data.buf, _recv_record.data(), outLen); _recv_off = outLen; // Cleanup internal buffer if finished if (_recv_off == _recv_record.size()) { _recv_record.clear(); _recv_off = 0; } } return outLen; } } #endif
src/windows/ssl.cpp 0 → 100644 +1200 −0 File added.Preview size limit exceeded, changes collapsed. Show changes
src/windows/tcp.cpp +6 −7 Original line number Diff line number Diff line Loading @@ -365,15 +365,14 @@ void netplus::tcp::getAddress(std::string& addr) { } } // tcp.cpp size_t netplus::tcp::sendDataWSA(buffer& data, int flags) { // New overloads that accept explicit OVERLAPPED pointer size_t netplus::tcp::sendDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags) { NetException exception; _wsaBuf.buf = data.ptr ? (char*)data.data.ptr : data.data.buf; _wsaBuf.len = static_cast<ULONG>(data.size); DWORD dwBytes = 0; // Note: We do not reset _Overlapped here because it is managed by the IOCP context int rval = ::WSASend(_Socket, &_wsaBuf, 1, &dwBytes, flags, &_Overlapped, nullptr); int rval = ::WSASend(_Socket, &_wsaBuf, 1, &dwBytes, flags, pOv, nullptr); if (rval == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING) { exception[NetException::Error] << "WSASend failed: " << GetLastError(); Loading @@ -382,14 +381,14 @@ size_t netplus::tcp::sendDataWSA(buffer& data, int flags) { return (size_t)dwBytes; } size_t netplus::tcp::recvDataWSA(buffer& data, int flags) { size_t netplus::tcp::recvDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags) { NetException exception; _wsaBuf.buf = data.data.buf; _wsaBuf.len = static_cast<ULONG>(data.size); DWORD dwBytes = 0; DWORD dwFlags = (DWORD)flags; int rval = ::WSARecv(_Socket, &_wsaBuf, 1, &dwBytes, &dwFlags, &_Overlapped, nullptr); int rval = ::WSARecv(_Socket, &_wsaBuf, 1, &dwBytes, &dwFlags, pOv, nullptr); if (rval == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING) { exception[NetException::Error] << "WSARecv failed: " << GetLastError(); Loading