13 #include <openssl/evp.h>
14 #include <openssl/rsa.h>
15 #include <openssl/x509.h>
16 #include <openssl/x509v3.h>
17 #include <openssl/pem.h>
83 std::map<uint32, NetworkChannel*>::iterator it, itEnd;
85 if (it->second != NULL) {
86 it->second->shutdown();
104 bool createdConnection =
false;
108 channelID = channel->
cid;
113 if (channel == NULL) {
118 channel->
cid = channelID;
121 createdConnection =
true;
125 if (!channel->
startListener(channelID, port, encryption, protocol, isAsync, protocolTimeout, isDefaultProtocol)) {
126 if (createdConnection) {
148NetworkChannel*
NetworkManager::createTCPConnection(
const char* addr, uint16 port, uint8 encryption, uint8 protocol,
bool isAsync,
bool autoreconnect, uint32 channelID,
NetworkReceiver* recv, uint64& conid, uint64& location, uint32 timeoutMS) {
149 bool createdConnection =
false;
153 if (channel == NULL) {
158 channel->
cid = channelID;
160 createdConnection =
true;
164 if ( (conid = channel->
createTCPConnection(addr, port, encryption, protocol, isAsync, autoreconnect, location, timeoutMS)) == 0) {
165 if (createdConnection) {
183 bool createdConnection =
false;
187 if (channel == NULL) {
190 if (!con->
connect(location, timeoutMS, NULL)) {
198 channel->
cid = channelID;
199 if ( (conid = channel->
startConnection(con, protocol, isAsync, autoreconnect)) == 0) {
204 createdConnection =
true;
208 if ( (conid = channel->
createTCPConnection(location, encryption, protocol, isAsync, autoreconnect, timeoutMS)) == 0) {
209 if (createdConnection) {
227NetworkChannel*
NetworkManager::createTCPConnection(
const uint32* addresses, uint16 addressCount, uint16 port, uint8 encryption, uint8 protocol,
bool isAsync,
bool autoreconnect, uint32 channelID,
NetworkReceiver* recv, uint64& conid, uint64& location, uint32 timeoutMS) {
228 bool createdConnection =
false;
232 if (channel == NULL) {
237 channel->
cid = channelID;
239 createdConnection =
true;
243 if ( (conid = channel->
createTCPConnection(addresses, addressCount, port, encryption, protocol, isAsync, autoreconnect, location, timeoutMS)) == 0) {
244 if (createdConnection) {
262 bool createdConnection =
false;
266 if (channel == NULL) {
271 channel->
cid = channelID;
273 createdConnection =
true;
278 if (createdConnection) {
295 bool createdConnection =
false;
299 if (channel == NULL) {
304 channel->
cid = channelID;
306 createdConnection =
true;
311 if (createdConnection) {
328NetworkChannel*
NetworkManager::addTCPConnection(
const char* addr, uint16 port, uint8 encryption, uint8 protocol,
bool isAsync, uint32 channelID,
NetworkReceiver* recv, uint64& conid, uint64& location, uint32 timeoutMS,
const char* greetingData, uint32 greetingSize) {
329 bool createdConnection =
false;
333 if (channel == NULL) {
338 channel->
cid = channelID;
340 createdConnection =
true;
344 if ((conid = channel->
addTCPConnection(addr, port, encryption, protocol, isAsync, location, timeoutMS, greetingData, greetingSize)) == 0) {
345 if (createdConnection) {
363 bool createdConnection =
false;
367 if (channel == NULL) {
372 channel->
cid = channelID;
374 createdConnection =
true;
378 if ((conid = channel->
addTCPConnection(location, encryption, protocol, isAsync, timeoutMS, greetingData, greetingSize)) == 0) {
379 if (createdConnection) {
399 bool createdConnection =
false;
403 if (channel == NULL) {
408 channel->
cid = channelID;
410 createdConnection =
true;
414 if ( (conid = channel->
createUDPConnection(port, protocol, isAsync, autoreconnect)) == 0) {
415 if (createdConnection) {
504 int8 encryption =
NOENC;
505 if (protocolString ==
"http") {}
506 else if (protocolString ==
"https")
512 if (!hostString.size())
524 if (!uriString.size())
528 if (content && contentSize)
541 uint64 conid, location;
561 std::map<std::string, std::string>& headerEntries,
const char* content,
const char* contentType, uint32 contentSize,
562 bool keepAlive, uint64 ifModifiedSince) {
567 uint64 conid, location;
574 if (!host.length() || !protocol.length())
577 int8 encryption =
NOENC;
578 if (
stricmp(protocol.c_str(),
"https") == 0) {
614 std::map<std::string, std::string>& headerEntries, std::map<std::string, HTTPPostEntry*>& bodyEntries,
615 bool keepAlive, uint64 ifModifiedSince) {
620 uint64 conid, location;
627 if (!host.length() || !protocol.length())
630 int8 encryption =
NOENC;
631 if (
stricmp(protocol.c_str(),
"https") == 0) {
642 if (bodyEntries.size() > 1) {
645 else if (bodyEntries.size() == 1) {
718 std::map<uint16, NetworkThread*>::iterator it, itEnd;
724 std::vector<NetworkThread*> stopListeners;
725 std::vector<NetworkThread*> stopConnections;
735 if ( (thread = it->second) != NULL) {
737 stopListeners.push_back(thread);
742 std::map<uint64, NetworkThread*>::iterator it2, it2End;
744 if ( (thread = it2->second) != NULL) {
747 stopConnections.push_back(thread);
755 for (
size_t i = 0; i < stopListeners.size(); i++) {
756 thread = stopListeners[i];
757 for (
int waited = 0; thread->
isRunning && waited < 200; waited += 5)
770 for (
size_t i = 0; i < stopConnections.size(); i++) {
771 thread = stopConnections[i];
772 for (
int waited = 0; thread->
isRunning && waited < 1000; waited += 5)
842 bool created =
false;
843 if (thread == NULL) {
846 conid =
manager->addConnection(
this);
852 if (
manager->sslCertPath.size())
862 if (isDefaultProtocol)
866 if ( isDefaultProtocol && (protocolTimeout == 0) )
884 if (isDefaultProtocol)
888 if ( isDefaultProtocol && (protocolTimeout == 0) )
901 if (thread == NULL) {
924 if (encryption ==
NOENC) {
926 if (!tcpCon->
connect(addr, port, location, timeoutMS, NULL)) {
932 else if (encryption ==
SSLENC) {
935 if (!sslCon->
init()) {
939 if (!sslCon->
connect(addr, port, location, timeoutMS, NULL)) {
945 return startConnection(con, protocol, isAsync, autoreconnect, timeoutMS);
950 if (encryption ==
NOENC) {
952 if (!tcpCon->
connect(location, timeoutMS, NULL)) {
958 else if (encryption ==
SSLENC) {
961 if (!sslCon->
connect(location, timeoutMS, NULL)) {
967 return startConnection(con, protocol, isAsync, autoreconnect, timeoutMS);
970uint64
NetworkChannel::createTCPConnection(
const uint32* addresses, uint16 addressCount, uint16 port, uint8 encryption, uint8 protocol,
bool isAsync,
bool autoreconnect, uint64& location, uint32 timeoutMS) {
972 if (encryption ==
NOENC) {
974 if (!tcpCon->
connect(addresses, addressCount, port, location, timeoutMS, NULL)) {
980 else if (encryption ==
SSLENC) {
983 if (!sslCon->
connect(addresses, addressCount, port, location, timeoutMS, NULL)) {
989 return startConnection(con, protocol, isAsync, autoreconnect, timeoutMS);
992uint64
NetworkChannel::addTCPConnection(
const char* addr, uint16 port, uint8 encryption, uint8 protocol,
bool isAsync, uint64& location, uint32 timeoutMS,
const char* greetingData, uint32 greetingSize) {
994 if (encryption ==
NOENC) {
996 if (greetingData && greetingSize)
998 if (!tcpCon->
delayedConnect(addr, port, location, timeoutMS, NULL)) {
1004 else if (encryption ==
SSLENC) {
1007 if (!sslCon->
init()) {
1011 if (greetingData && greetingSize)
1013 if (!sslCon->
delayedConnect(addr, port, location, timeoutMS, NULL)) {
1024 if (encryption ==
NOENC) {
1026 if (greetingData && greetingSize)
1034 else if (encryption ==
SSLENC) {
1037 if (!sslCon->
init()) {
1041 if (greetingData && greetingSize)
1058 int8 encryption =
NOENC;
1059 if (protocolString ==
"http") {}
1060 else if (protocolString ==
"https")
1066 if (!hostString.size())
1071 if (encryption ==
SSLENC)
1078 if (!uriString.size())
1081 return createWebsocketConnection(uriString.c_str(), hostString.c_str(), port, encryption, protocolName, origin, timeoutMS);
1088 if (encryption ==
NOENC) {
1090 if (!tcpCon->
connect(addr, port, location, timeoutMS, NULL)) {
1096 else if (encryption ==
SSLENC) {
1099 if (!sslCon->
connect(addr, port, location, timeoutMS, NULL)) {
1137 uint64 conid =
startConnection(con, protocol, isAsync, autoreconnect);
1143 if (thread == NULL) {
1170 if (!thread || !thread->
con)
1177 if (!thread || !thread->
con)
1183 if (!
channelMutex.enter(2000,
"NetworkChannel::endConnection")) {
1190 if (thread == NULL) {
1196 bool forced =
false;
1198 uint32 timeleft = 200;
1201 if ( (timeleft -= 5) <= 0)
1213 manager->removeConnection(conid);
1214 uint32 workerTMID = thread->
threadID;
1228 if (workerOSID != 0 && workerOSID != curOSID) {
1354 if ((thread == NULL) || (thread->
con == NULL))
1362 if ((thread == NULL) || (thread->
con == NULL))
1370 if ((thread == NULL) || (thread->
con == NULL))
1378 if ((thread == NULL) || (thread->
con == NULL))
1397 if ((thread == NULL) || (thread->
con == NULL))
1407 if ((thread == NULL) || (thread->
con == NULL))
1419 if ((thread == NULL) || (thread->
con == NULL))
1426 if ((thread == NULL) || (thread->
con == NULL))
1433 if ((thread == NULL) || (thread->
con == NULL))
1526 uint64 conid =
manager->addConnection(
this);
1559 uint64 conid =
manager->addConnection(
this);
1604 if ((thread == NULL) || (thread->
listener == NULL))
1628 if ((thread == NULL) || (thread->
con == NULL))
1634 uint32 maxSize = 1024;
1635 char* buffer =
new char[maxSize];
1645 delete(thread->
con);
1680 delete(thread->
con);
1686 if (protocol == 0) {
1688 LogPrint(0,
LOG_NETWORK,2,
"No valid protocol detected for incoming network connection, disconnecting...\n\n");
1690 delete(thread->
con);
1732 delete(thread->
con);
1745 if ((thread == NULL) || (thread->
con == NULL))
1749 bool disconnected =
false;
1751 bool upgradedToWebsocket =
false;
1761 if (upgradedToWebsocket) {
1771 uint64 endConid = thread->
id;
1793 upgradedToWebsocket =
true;
1805 if (!disconnected) {
1807 disconnected =
true;
1821 disconnected =
false;
1829 uint64 endConid = thread->
id;
1841 uint64 endConid = thread->
id;
1859 if ((thread == NULL) || (thread->
con == NULL))
1863 bool disconnected =
false;
1865 bool upgradedToWebsocket =
false;
1866 std::string wsOrigin;
1874 if (upgradedToWebsocket) {
1882 delete(wsDataReply);
1904 const char* version = req->
getHeaderEntry(
"Sec-WebSocket-Version");
1905 if (key && version) {
1919 upgradedToWebsocket =
true;
1925 LogPrint(0,
LOG_NETWORK, 1,
"Unable to upgrade HTTP Server %llu to Websocket, key and/or version not provided", thread->
id);
1946 uint64 endConid = thread->
id;
1958 uint64 endConid = thread->
id;
1980 if ((thread == NULL) || (thread->
con == NULL))
1984 bool disconnected =
false;
1985 bool wasConnected =
false;
1992 wasConnected =
true;
2011 if (!disconnected && wasConnected) {
2013 disconnected =
true;
2047 disconnected =
false;
2059 uint64 endConid = thread->
id;
2071 uint64 endConid = thread->
id;
2081 if ((thread == NULL) || (thread->
con == NULL))
2085 bool disconnected =
false;
2115 uint64 endConid = thread->
id;
2126 uint64 endConid = thread->
id;
2138 const uint16 PORT = 38101;
2139 const uint32 CONNECTIONS = 3;
2147 unittest::fail(
"NetworkManager delayed-connect test: could not start listening on port %u", PORT);
2154 msgConnect->
setString(
"URI",
"ExecutorConnect");
2156 std::vector<uint64> conIDs;
2158 uint64 location = 0;
2162 channel = manager->
addTCPConnection(
"localhost", PORT,
NOENC,
PROTOCOL_MESSAGE,
true, 0, NULL, conid, location, 1000, (
char*)msgConnect->
data, msgConnect->
getSize());
2163 if (!channel || !conid) {
2164 unittest::fail(
"NetworkManager delayed-connect test: could not create channel for connection 1");
2167 conIDs.push_back(conid);
2168 for (uint32 n = 1; n < CONNECTIONS; n++) {
2171 unittest::fail(
"NetworkManager delayed-connect test: could not add connection %u", n + 1);
2174 conIDs.push_back(conid);
2178 for (uint32 n = 0; n < CONNECTIONS; n++) {
2182 unittest::fail(
"NetworkManager delayed-connect test: greeting %u not received", n + 1);
2205 uint32 size = 70000;
2214 uint32 count = 100, subcount = 100, subcount2 = 10, n, m, k;
2217 PsyType type = { { 1,10001,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } };
2232 for (
int tries = 0; tries < 20 && !listen; tries++) {
2237 unittest::fail(
"NetworkManager test: could not start listening on port 38100");
2247 dat =
new char[size];
2248 memset(dat, 0, size);
2250 msg->
setData(
"Test", dat, size);
2262 unittest::fail(
"NetworkManager test: message sent and received mismatch");
2281 unittest::fail(
"NetworkManager test: message sent and received mismatch");
2293 count = 10, subcount = 10, subcount2 = 15;
2294 for (m = 0; m < count; m++) {
2297 for (n = 0; n < subcount; n++) {
2298 type.levels[15] = n;
2301 for (k=0; k<subcount2; k++) {
2303 unittest::fail(
"NetworkManager test: could not send TCP message %u", n);
2313 for (k=0; k<subcount2; k++) {
2315 type.levels[15] = n;
2317 unittest::fail(
"NetworkManager test: [%u/%u/%u] no TCP message received", n, m, k);
2321 unittest::fail(
"NetworkManager test: [%u] TCP message sent and received mismatch", n);
2328 unittest::detail(
"[%u/%u] Sent and received %u msgs (%u bytes), %.3fus per msg",
2329 (m+1)*subcount*subcount2, (m+1)*subcount*subcount2 * size,
2330 subcount*subcount2, subcount*subcount2 * size,
2331 ((
double)(end-start))/(subcount*subcount2));
2336 uint32 tcpMsgs = count*subcount*subcount2;
2337 double tcpUs = (double)t;
2338 unittest::detail(
"Total: Sent and received %u TCP msgs, %.3fus per msg", tcpMsgs, tcpUs/tcpMsgs);
2339 unittest::metric(
"tcp_msg_throughput", (
double)tcpMsgs / tcpUs * 1e6,
"msg/s",
true);
2341 unittest::metric(
"tcp_throughput", ((
double)tcpMsgs * size) / tcpUs,
"MB/s",
true);
2352 unittest::fail(
"NetworkManager test: could not start UDP listening on port 38101");
2364 dat =
new char[size];
2365 memset(dat, 0, size);
2367 msg->
setData(
"Test", dat, size);
2377 for (
int attempt = 0; attempt < 10 && !udpOk; attempt++) {
2379 unittest::fail(
"NetworkManager test: could not send UDP message");
2387 unittest::fail(
"NetworkManager test: UDP message sent and received mismatch");
2394 unittest::fail(
"NetworkManager test: no UDP round-trip on port 38101 after 10 attempts (loopback UDP may be blocked here)");
2407 count = 10, subcount = 10, subcount2 = 15;
2410 const int32 udpBudgetMs = 5000;
2411 for (m = 0; m < count &&
GetTimeAgeMS(udpLoopStart) < udpBudgetMs; m++) {
2414 for (n = 0; n < subcount; n++) {
2415 type.levels[15] = n;
2418 for (k=0; k<subcount2; k++) {
2420 unittest::fail(
"NetworkManager test: could not send UDP message %u", n);
2430 for (k=0; k<subcount2; k++) {
2442 unittest::detail(
"[%u/%u] Sent and received %u msgs (%u bytes), %.3fus per msg",
2443 (m+1)*subcount*subcount2, (m+1)*subcount*subcount2 * size,
2444 subcount*subcount2, subcount*subcount2 * size,
2445 ((
double)(end-start))/(subcount*subcount2));
2452 uint32 udpMsgs = count*subcount*subcount2;
2453 double udpUs = (double)t;
2455 unittest::detail(
"Total UDP: Sent and received %u msgs, %.3fus per msg", udpMsgs, udpUs/udpMsgs);
2456 unittest::metric(
"udp_msg_throughput", (
double)udpMsgs / udpUs * 1e6,
"msg/s",
true);
2492 snprintf(text, 512,
"Hello World %llu",
GetTimeNow());
2494 uint64 localAddr = 0;
2499 printf(
"Error generating HTML page...\n\n");
2503 printf(
"Error sending response...\n\n");
2525 printf(
"WebsocketTestServer received HTTPRequest...\n\n");
2536 wsDataOut->
setData(wsData->
TEXT,
false, str.c_str(), str.length());
2590 uint64 httpStart, httpEnd;
2599 unittest::fail(
"NetworkManager HTTP test: could not start listening on port 38102");
2614 unittest::fail(
"NetworkManager HTTP test: could not create request");
2619 if (reply == NULL) {
2620 unittest::fail(
"NetworkManager HTTP test: did not receive reply");
2628 for (n=0; n<count; n++) {
2633 unittest::fail(
"NetworkManager HTTP test: could not reconnect [%u]", n);
2641 unittest::fail(
"NetworkManager HTTP test: could not create request [%u]", n);
2646 if (reply == NULL) {
2647 unittest::fail(
"NetworkManager HTTP test: did not receive reply [%u]", n);
2654 double httpUs = (double)(httpEnd - httpStart);
2656 unittest::metric(
"http_request_rate", (
double)count / httpUs * 1e6,
"req/s",
true);
2720static std::string sslTestTempPath(
const char* filename) {
2722 const char* t = getenv(
"TEMP");
2723 if (!t) t = getenv(
"TMP");
2725 return std::string(t) +
"\\" + filename;
2727 return std::string(
"/tmp/") + filename;
2731static bool generateSelfSignedCert(
const char* certPath,
const char* keyPath) {
2733 EVP_PKEY* pkey = EVP_RSA_gen(2048);
2736 X509* x509 = X509_new();
2738 ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
2739 X509_gmtime_adj(X509_getm_notBefore(x509), 0);
2740 X509_gmtime_adj(X509_getm_notAfter(x509), (
long)60 * 60 * 24 * 3650);
2741 X509_set_pubkey(x509, pkey);
2742 X509_NAME* name = X509_get_subject_name(x509);
2743 X509_NAME_add_entry_by_txt(name,
"CN", MBSTRING_ASC,
2744 (
const unsigned char*)
"localhost", -1, -1, 0);
2745 X509_set_issuer_name(x509, name);
2746 if (X509_sign(x509, pkey, EVP_sha256())) {
2747 FILE* kf = fopen(keyPath,
"wb");
2748 FILE* cf = fopen(certPath,
"wb");
2750 PEM_write_PrivateKey(kf, pkey, NULL, NULL, 0, NULL, NULL) == 1 &&
2751 PEM_write_X509(cf, x509) == 1)
2758 EVP_PKEY_free(pkey);
2766bool NetworkManager::UnitTestHTTPS() {
2774 uint64 httpsStart, httpsEnd;
2776 std::string certPathS = sslTestTempPath(
"psytest_https_cert.pem");
2777 std::string keyPathS = sslTestTempPath(
"psytest_https_key.pem");
2778 const char* certPath = certPathS.c_str();
2779 const char* keyPath = keyPathS.c_str();
2782 if (!generateSelfSignedCert(certPath, keyPath)) {
2783 unittest::fail(
"NetworkManager HTTPS test: could not generate self-signed certificate");
2787 HTTPTestServer* testServer =
new HTTPTestServer();
2789 manager->setSSLCertificate(certPath, keyPath);
2793 manager->setSSLAllowSelfSigned(
true);
2798 unittest::fail(
"NetworkManager HTTPS test: could not start SSL listener on port 38112");
2805 unittest::fail(
"NetworkManager HTTPS test: could not connect / TLS handshake failed");
2810 req =
new HTTPRequest((uint64)0);
2811 if (!req->createRequest(
HTTP_GET,
"",
"/", NULL, 0,
true, 0)) {
2813 unittest::fail(
"NetworkManager HTTPS test: could not create request");
2816 reply = con->sendReceiveHTTPRequest(req, conid, 5000);
2818 if (reply == NULL) {
2819 unittest::fail(
"NetworkManager HTTPS test: did not receive reply over TLS");
2827 for (n=0; n<count; n++) {
2829 con->endConnection(conid);
2832 unittest::fail(
"NetworkManager HTTPS test: could not reconnect over TLS [%u]", n);
2836 req =
new HTTPRequest((uint64)0);
2837 if (!req->createRequest(
HTTP_GET,
"",
"/", NULL, 0,
true, 0)) {
2839 unittest::fail(
"NetworkManager HTTPS test: could not create request [%u]", n);
2842 reply = con->sendReceiveHTTPRequest(req, conid, 5000);
2844 if (reply == NULL) {
2845 unittest::fail(
"NetworkManager HTTPS test: did not receive reply over TLS [%u]", n);
2852 double httpsUs = (double)(httpsEnd - httpsStart);
2853 unittest::detail(
"HTTPS: %u TLS requests in %.3fms", count, httpsUs / 1000.0);
2854 unittest::metric(
"https_request_rate", (
double)count / httpsUs * 1e6,
"req/s",
true);
2857 con->endConnection(conid);
2884bool NetworkManager::UnitTestSSLVerify() {
2886 std::string certPathS = sslTestTempPath(
"psytest_sslverify_cert.pem");
2887 std::string keyPathS = sslTestTempPath(
"psytest_sslverify_key.pem");
2888 const char* certPath = certPathS.c_str();
2889 const char* keyPath = keyPathS.c_str();
2890 uint64 conid = 0, location = 0;
2896 SSLConnection* c1 =
new SSLConnection();
2897 if (!c1->init() || (c1->getVerifyMode() != SSL_VERIFY_PEER)) {
2898 unittest::fail(
"default client ctx verify mode is not SSL_VERIFY_PEER (got %d)", c1->getVerifyMode());
2903 SSLConnection* c2 =
new SSLConnection();
2904 c2->setAllowSelfSigned(
true);
2905 if (!c2->init() || (c2->getVerifyMode() != SSL_VERIFY_NONE)) {
2906 unittest::fail(
"allowselfsigned client ctx verify mode is not SSL_VERIFY_NONE (got %d)", c2->getVerifyMode());
2911 unittest::detail(
"ctx verify modes correct (PEER by default, NONE when allowselfsigned)");
2915 if (!generateSelfSignedCert(certPath, keyPath)) {
2920 HTTPTestServer* testServer =
new HTTPTestServer();
2922 manager->setSSLCertificate(certPath, keyPath);
2934 con = manager->createTCPConnection(
"localhost", 38113,
SSLENC,
PROTOCOL_HTTP_CLIENT,
true,
false, 0, NULL, conid, location, 3000);
2936 unittest::fail(
"connection to untrusted self-signed server was ACCEPTED with verification on");
2937 con->endConnection(conid);
2941 unittest::detail(
"verification on: TLS handshake to self-signed server correctly rejected");
2944 manager->setSSLAllowSelfSigned(
true);
2945 con = manager->createTCPConnection(
"localhost", 38113,
SSLENC,
PROTOCOL_HTTP_CLIENT,
true,
false, 0, NULL, conid, location, 3000);
2947 unittest::fail(
"connection to self-signed server FAILED despite allowselfsigned=true");
2951 unittest::detail(
"allowselfsigned: TLS handshake to self-signed server accepted");
2952 con->endConnection(conid);
2969static EVP_PKEY* sslTestGenKey() {
2970 return EVP_RSA_gen(2048);
2973static bool sslTestWritePEM(X509* cert, EVP_PKEY* key,
const char* certPath,
const char* keyPath) {
2975 FILE* cf = fopen(certPath,
"wb");
2976 FILE* kf = keyPath ? fopen(keyPath,
"wb") : NULL;
2977 if (cf && PEM_write_X509(cf, cert) == 1)
2980 ok = (kf && PEM_write_PrivateKey(kf, key, NULL, NULL, 0, NULL, NULL) == 1);
2986static bool sslTestAddExt(X509* cert, X509* issuer,
int nid,
const char* value) {
2988 X509V3_set_ctx_nodb(&ctx);
2989 X509V3_set_ctx(&ctx, issuer, cert, NULL, NULL, 0);
2990 X509_EXTENSION* ext = X509V3_EXT_conf_nid(NULL, &ctx, nid, value);
2993 X509_add_ext(cert, ext, -1);
2994 X509_EXTENSION_free(ext);
2999static bool sslTestGenCA(
const char* caCertPath, EVP_PKEY** caKeyOut, X509** caCertOut) {
3000 EVP_PKEY* key = sslTestGenKey();
3003 X509* x = X509_new();
3004 if (!x) { EVP_PKEY_free(key);
return false; }
3005 X509_set_version(x, 2);
3006 ASN1_INTEGER_set(X509_get_serialNumber(x), 1000);
3007 X509_gmtime_adj(X509_getm_notBefore(x), 0);
3008 X509_gmtime_adj(X509_getm_notAfter(x), (
long)60 * 60 * 24 * 365);
3009 X509_set_pubkey(x, key);
3010 X509_NAME* name = X509_get_subject_name(x);
3011 X509_NAME_add_entry_by_txt(name,
"CN", MBSTRING_ASC,
3012 (
const unsigned char*)
"Psyclone Test CA", -1, -1, 0);
3013 X509_set_issuer_name(x, name);
3014 bool ok = sslTestAddExt(x, x, NID_basic_constraints,
"critical,CA:TRUE") &&
3015 sslTestAddExt(x, x, NID_key_usage,
"critical,keyCertSign,cRLSign") &&
3016 (X509_sign(x, key, EVP_sha256()) != 0) &&
3017 sslTestWritePEM(x, NULL, caCertPath, NULL);
3018 if (!ok) { X509_free(x); EVP_PKEY_free(key);
return false; }
3025static bool sslTestGenLeaf(
const char* certPath,
const char* keyPath,
const char* cn,
3026 const char* san, X509* caCert, EVP_PKEY* caKey,
long serial) {
3027 EVP_PKEY* key = sslTestGenKey();
3030 X509* x = X509_new();
3031 if (!x) { EVP_PKEY_free(key);
return false; }
3032 X509_set_version(x, 2);
3033 ASN1_INTEGER_set(X509_get_serialNumber(x), serial);
3034 X509_gmtime_adj(X509_getm_notBefore(x), 0);
3035 X509_gmtime_adj(X509_getm_notAfter(x), (
long)60 * 60 * 24 * 365);
3036 X509_set_pubkey(x, key);
3037 X509_NAME* name = X509_get_subject_name(x);
3038 X509_NAME_add_entry_by_txt(name,
"CN", MBSTRING_ASC,
3039 (
const unsigned char*)cn, -1, -1, 0);
3040 X509_set_issuer_name(x, X509_get_subject_name(caCert));
3041 bool ok = sslTestAddExt(x, caCert, NID_basic_constraints,
"critical,CA:FALSE") &&
3042 sslTestAddExt(x, caCert, NID_subject_alt_name, san) &&
3043 (X509_sign(x, caKey, EVP_sha256()) != 0) &&
3044 sslTestWritePEM(x, key, certPath, keyPath);
3058bool NetworkManager::UnitTestSSLHostCA() {
3060 std::string caCertPathS = sslTestTempPath(
"psytest_sslhostca_ca.pem");
3061 std::string goodCertPathS = sslTestTempPath(
"psytest_sslhostca_good_cert.pem");
3062 std::string goodKeyPathS = sslTestTempPath(
"psytest_sslhostca_good_key.pem");
3063 std::string badCertPathS = sslTestTempPath(
"psytest_sslhostca_bad_cert.pem");
3064 std::string badKeyPathS = sslTestTempPath(
"psytest_sslhostca_bad_key.pem");
3065 const char* caCertPath = caCertPathS.c_str();
3066 const char* goodCertPath = goodCertPathS.c_str();
3067 const char* goodKeyPath = goodKeyPathS.c_str();
3068 const char* badCertPath = badCertPathS.c_str();
3069 const char* badKeyPath = badKeyPathS.c_str();
3070 EVP_PKEY* caKey = NULL;
3071 X509* caCert = NULL;
3072 uint64 conid = 0, location = 0;
3074 HTTPTestServer* testServer = NULL;
3081 if (!sslTestGenCA(caCertPath, &caKey, &caCert)) {
3085 if (!sslTestGenLeaf(goodCertPath, goodKeyPath,
"localhost",
"DNS:localhost", caCert, caKey, 1001) ||
3086 !sslTestGenLeaf(badCertPath, badKeyPath,
"wronghost.example",
"DNS:wronghost.example", caCert, caKey, 1002)) {
3087 unittest::fail(
"could not generate CA-signed server certificates");
3088 X509_free(caCert); EVP_PKEY_free(caKey);
3092 unittest::detail(
"CA + leaf certs generated (SAN localhost / wronghost.example)");
3094 testServer =
new HTTPTestServer();
3098 goodManager->setSSLCertificate(goodCertPath, goodKeyPath);
3107 unittest::progress(30,
"connecting with verify on, custom CA NOT provided (must be rejected)");
3108 con = goodManager->createTCPConnection(
"localhost", 38114,
SSLENC,
PROTOCOL_HTTP_CLIENT,
true,
false, 0, NULL, conid, location, 3000);
3110 unittest::fail(
"CA-signed server was ACCEPTED although its CA was not provided");
3111 con->endConnection(conid);
3117 unittest::progress(45,
"connecting with custom CA provided + matching hostname (must be accepted)");
3118 goodManager->setSSLCALocation(caCertPath, NULL);
3119 con = goodManager->createTCPConnection(
"localhost", 38114,
SSLENC,
PROTOCOL_HTTP_CLIENT,
true,
false, 0, NULL, conid, location, 3000);
3121 unittest::fail(
"connection FAILED despite custom CA provided and matching hostname");
3126 con->endConnection(conid);
3130 badManager->setSSLCertificate(badCertPath, badKeyPath);
3131 badManager->setSSLCALocation(caCertPath, NULL);
3140 unittest::progress(70,
"connecting by hostname to server with mismatching cert (must be rejected)");
3141 con = badManager->createTCPConnection(
"localhost", 38115,
SSLENC,
PROTOCOL_HTTP_CLIENT,
true,
false, 0, NULL, conid, location, 3000);
3143 unittest::fail(
"server with cert for wronghost.example was ACCEPTED for hostname localhost");
3144 con->endConnection(conid);
3148 unittest::detail(
"hostname mismatch: trusted cert for wrong host correctly rejected");
3150 unittest::progress(85,
"connecting by IP literal (no hostname check; must be accepted)");
3151 con = badManager->createTCPConnection(
"127.0.0.1", 38115,
SSLENC,
PROTOCOL_HTTP_CLIENT,
true,
false, 0, NULL, conid, location, 3000);
3153 unittest::fail(
"connection by IP literal FAILED (chain-of-trust only path broken)");
3157 unittest::detail(
"IP literal connect: chain-of-trust only, accepted (hostname check skipped)");
3158 con->endConnection(conid);
3162 delete(goodManager);
3167 EVP_PKEY_free(caKey);
3169 unlink(goodCertPath); unlink(goodKeyPath);
3170 unlink(badCertPath); unlink(badKeyPath);
3199 printf(
"Connecting...\n");
3202 printf(
"Could not connect to host %s:%u...\n\n", data->
host, data->
port);
3208 for (uint32 n=0; n<10; n++) {
3216 printf(
"Could not create request [%u]...\n\n", n);
3221 printf(
"[%u] Sending...\n", n);
3225 if (reply == NULL) {
3226 printf(
"Did not receive reply...\n\n");
3231 printf(
"Server unavailable...\n\n");
3236 printf(
"Server no reply...\n\n");
3241 printf(
"Server malformed reply...\n\n");
3273 unittest::fail(
"NetworkManager websocket test: could not start listening on port 38103");
3281 unittest::fail(
"NetworkManager websocket test: could not connect");
3289 unittest::fail(
"NetworkManager websocket test: could not create websocket upgrade request");
3294 if (reply == NULL) {
3295 unittest::fail(
"NetworkManager websocket test: did not receive handshake reply");
3300 unittest::fail(
"NetworkManager websocket test: reply was not a websocket upgrade");
3325 printf(
"Testing HTTP on %s:%u with random urls...\n\n", host, port);
3328 uint32 numThreads = 5;
3337 printf(
"Could not connect to host %s:%u...\n\n", host, port);
3342 printf(
"Host is available, starting test...\n\n");
3406 uint32 threadIDs[1000];
3408 for (n=0; n<loops; n++) {
3409 for (m=0; m<numThreads; m++) {
3410 testData[m].
host = host;
3411 testData[m].
port = port;
3412 testData[m].
urls = &urls;
3413 testData[m].
manager = manager;
3421 for (m=0; m<numThreads; m++) {
3424 if (testData[m].status > 10) {
3425 printf(
"[%u] Failed\n", m);
3429 printf(
"[%u] Done\n", m);
3432 if (m >= numThreads)
3444 "TCP and UDP message send/receive over loopback",
"network");
3446 "Delayed/greeting TCP connections to a loopback listener",
"network");
3448 "HTTP server/client request/reply over loopback",
"network");
3450 "Websocket upgrade handshake against loopback server",
"network");
3455 "HTTPS (TLS) server/client request/reply over loopback",
"network");
3457 "SSL client certificate verification (secure by default, allowselfsigned opt-out)",
"network");
3459 "SSL hostname verification (SSL_set1_host) and custom CA location (SSL_CTX_load_verify_locations)",
"network");
#define NETWORKERROR_GREETING_ERROR
The initial greeting/handshake data exchange failed.
#define NOENC
Plain, unencrypted transport.
#define SSLENC
SSL/TLS encryption (requires build with _USE_SSL_).
Connection/channel management layer: multi-protocol listeners, typed dispatch, HTTP client — and the ...
#define NETWORKEVENT_PROTOCOL_ERROR
Protocol parsing/framing error on the connection.
#define NETWORKEVENT_CONNECT
Connection established.
#define NETWORKEVENT_DISCONNECT_RETRYING
Connection lost; reconnection attempts in progress.
#define NETWORKEVENT_DISCONNECT
Connection closed for good.
#define NETWORKEVENT_RECONNECT
Connection re-established after a failure (autoreconnect).
#define NETWORKEVENT_UNPROCESSED_DATA
Bytes arrived that no protocol handler consumed.
#define HTTP_MALFORMED_URL
400 Bad Request
#define PROTOCOL_TELNET
Line-based Telnet-style text protocol.
#define HTTP_SERVER_MALFORMED_REPLY
500 (malformed backend reply)
#define PROTOCOL_HTTP_CLIENT
Speak HTTP as a client: send requests, parse replies.
#define HTTP_SWITCH_PROTOCOL
101 Switching Protocols (WebSocket upgrade)
#define HTTP_ACCESS_DENIED
403 Forbidden
#define HTTP_SERVER_NOREPLY
500 (no reply from backend server)
#define PROTOCOL_HTTP_SERVER
Serve HTTP: parse requests, send replies (server role).
#define HTTP_NOT_IMPLEMENTED
501 Not Implemented
#define PROTOCOL_MESSAGE
CMSDK binary DataMessage protocol (size-prefixed frames).
#define HTTP_SERVER_UNAVAILABLE
500 (backend server unavailable)
Small, dependency-free unit test harness used by all CMSDK object tests.
#define thread_ret_val(ret)
#define THREAD_FUNCTION_CALL
THREAD_RET(* THREAD_FUNCTION)(void *)
#define GETIPADDRESSQUADPORT(a)
#define GETIPADDRESSPORT(a, p)
The central Psyclone data container: a self-contained binary message with typed, named user entries.
uint32 getFrom()
getFrom() Get the sender id
bool setString(const char *key, const char *value)
setString(const char* key, const char* value)
DataMessageHeader * data
Pointer to the message's flat memory block (header + user entries).
PsyType getType()
getType()
uint32 getSize()
getSize() Get message size Many types of data of any size can be put into a message as user entries; ...
bool setType(PsyType &type)
setType(PsyType &type)
bool setData(const char *key, const char *value, uint32 size)
setData(const char* key, const char* value, uint32 size)
static bool SendHTTPReply(NetworkConnection *con, HTTPReply *reply)
Serialise and send a reply.
static HTTPReply * ReceiveHTTPReply(NetworkConnection *con, uint32 timeout)
Read a full reply from the connection.
static bool SendHTTPRequest(NetworkConnection *con, HTTPRequest *req)
Serialise and send a request.
static HTTPRequest * ReceiveHTTPRequest(NetworkConnection *con, uint32 timeout)
Read a full request from the connection.
static bool CheckBufferForCompatibility(const char *buffer, uint32 length)
static WebsocketData * ReceiveWebsocketData(NetworkConnection *con, uint32 timeout)
Read one complete WebSocket message (reassembling fragments).
static bool SendWebsocketData(NetworkConnection *con, WebsocketData *wsData)
Send a WebSocket frame.
A parsed or generated HTTP response.
bool isWebsocketUpgrade()
uint8 type
HTTP_* status id of this reply.
static HTTPReply * CreateErrorReply(uint8 type)
Build a canned error reply.
static HTTPReply * CreateWebsocketHTTPReply(const char *key, const char *version)
Build the "101 Switching Protocols" reply for a WebSocket handshake.
bool createPage(uint8 status, uint64 time, const char *serverName, uint64 lastMod, bool keepAlive, bool cache, const char *contentType, const char *content, uint32 contentSize=0, const char *additionalHeaderEntries=NULL)
Build a complete response with headers and content.
A parsed or generated HTTP request (also used for WebSocket upgrade handshakes).
const char * getHeaderEntry(const char *entry)
Look up a header field (case-insensitive).
uint32 headerLength
Length of the header block in data, in bytes.
uint32 contentLength
Body length (from Content-Length / parsing), in bytes.
static HTTPRequest * CreateWebsocketRequest(const char *uri, const char *host, const char *protocolName, const char *origin)
Build a client-side WebSocket upgrade request (RFC 6455 handshake).
bool createWebsocketRequest(const char *uri, const char *host, const char *protocolName, const char *origin)
Fill this object with a WebSocket upgrade handshake request.
bool createRequest(uint8 type, const char *host, const char *uri, const char *content, uint32 contentSize, bool keepAlive, uint64 ifModifiedSince)
Build a simple request with optional raw body.
bool createMultipartRequest(uint8 type, const char *host, const char *uri, std::map< std::string, std::string > &headerEntries, std::map< std::string, HTTPPostEntry * > &bodyEntries, bool keepAlive, uint64 ifModifiedSince)
Build a multipart/form-data request from several named parts.
bool isWebsocketUpgrade()
Minimal HTTP server used by the unit tests: replies with a canned page.
virtual bool receiveHTTPRequest(HTTPRequest *req, NetworkChannel *channel, uint64 conid)
Serve a test page for any request.
static bool SendMessage(NetworkConnection *con, DataMessage *msg, uint64 receiver=0)
Serialise and send one message.
static bool CheckBufferForCompatibility(const char *buffer, uint32 length)
static DataMessage * ReceiveMessage(NetworkConnection *con, uint32 timeout)
Read one full message frame.
One logical network interface: a group of listeners/connections with shared dispatch.
bool setNewReceiver(NetworkReceiver *recv)
Replace the channel's receiver for async dispatch.
bool isConnected(uint64 conid)
static THREAD_RET THREAD_FUNCTION_CALL TelnetServerRun(THREAD_ARG arg)
Thread entry: Telnet server connection loop.
HTTPReply * sendReceiveHTTPRequest(HTTPRequest *req, uint64 conid, uint32 timeout)
Send an HTTP request and block for its reply on the same connection.
NetworkManager * manager
Owning manager (not owned).
std::queue< HTTPReply * > queueHTTPReplies
bool sendHTTPRequest(HTTPRequest *req, uint64 conid)
Send an HTTP request without waiting for the reply (reply arrives via receiveHTTPReply()/waitForHTTPR...
utils::Mutex channelMutex
uint8 getConnectionType(uint64 conid)
HTTPReply * waitForHTTPReply(uint64 &conid, uint32 ms)
Wait for the next queued HTTP reply (sync-mode HTTP client).
bool enterHTTPReply(HTTPReply *reply, HTTPRequest *req, uint64 conid)
Queue or push an incoming HTTP reply.
uint64 getRemoteAddress(uint64 conid)
std::queue< NetworkEvent * > eventQueue
virtual ~NetworkChannel()
NetworkChannel(NetworkManager *manager)
utils::Semaphore eventQueueSemaphore
uint64 createWebsocketConnection(const char *url, const char *protocolName, const char *origin=NULL, uint32 timeoutMS=5000)
Open a client WebSocket connection from a full URL.
uint32 getOutputSpeed(uint64 conid)
std::map< uint16, NetworkThread * > listeners
TCP listener threads by port (owned).
WebsocketData * waitForWebsocketData(uint64 &conid, uint32 ms)
Wait for the next queued WebSocket message.
bool stopListener(uint16 port, uint8 protocol)
Stop a listener on this channel.
utils::Semaphore queueMessagesSemaphore
uint32 getInputSpeed(uint64 conid)
utils::Mutex queueHTTPRequestsMutex
bool enterTelnetLine(TelnetLine *line, uint64 conid)
Queue or push an incoming Telnet line.
static THREAD_RET THREAD_FUNCTION_CALL MessageConnectionRun(THREAD_ARG arg)
Thread entry: binary DataMessage connection loop.
utils::Mutex queueHTTPRepliesMutex
utils::Semaphore queueHTTPRepliesSemaphore
bool endUDPConnection(uint16 port)
Close the UDP connection bound to port.
bool sendMessage(DataMessage *msg, uint64 conid)
Send a DataMessage.
std::map< uint64, NetworkThread * > connectionThreads
Worker threads by connection id (owned).
utils::Semaphore queueTelnetLinesSemaphore
bool enterMessage(DataMessage *msg, uint64 conid)
Queue or push an incoming DataMessage.
utils::Mutex queueWebsocketDataMutex
bool endConnection(uint64 conid)
Gracefully close a connection.
static THREAD_RET THREAD_FUNCTION_CALL ConnectionAutodetectRun(THREAD_ARG arg)
Thread entry: protocol sniffing for a fresh connection (see autoDetectConnection()).
void applySSLClientPolicy(SSLConnection *con)
std::queue< WebsocketData * > queueWebsocketData
TelnetLine * sendReceiveTelnetLine(TelnetLine *line, uint64 conid, uint32 timeout, uint32 size=0)
Send a Telnet line and block for the response line.
friend class NetworkManager
uint64 addTCPConnection(const char *addr, uint16 port, uint8 encryption, uint8 protocol, bool isAsync, uint64 &location, uint32 timeoutMS=5000, const char *greetingData=NULL, uint32 greetingSize=0)
Connect with optional greeting bytes (no autoreconnect); see NetworkManager::addTCPConnection().
uint64 startConnection(NetworkConnection *con, uint8 protocol, bool isAsync, bool autoreconnect, uint32 timeoutMS=5000)
Start the protocol worker thread for an already-connected connection.
bool shutdown()
Stop all worker threads and close all listeners/connections of this channel.
NetworkEvent * waitForNetworkEvent(uint32 ms)
Wait for the next connection lifecycle event (sync mode).
static THREAD_RET THREAD_FUNCTION_CALL HTTPClientRun(THREAD_ARG arg)
Thread entry: HTTP client connection loop (send requests, parse replies).
std::queue< DataMessage * > queueMessages
bool enterNetworkEvent(uint8 type, uint8 protocol, uint64 conid)
Queue or push a lifecycle event.
utils::Mutex queueTelnetLinesMutex
utils::Semaphore queueHTTPRequestsSemaphore
NetworkReceiver * receiver
Async dispatch target (not owned; may be NULL).
std::queue< HTTPRequest * > queueHTTPRequests
bool sendTelnetLine(TelnetLine *line, uint64 conid)
Send a Telnet line.
utils::Semaphore queueWebsocketDataSemaphore
bool sendWebsocketData(WebsocketData *wsData, uint64 conid)
Send a WebSocket frame.
static THREAD_RET THREAD_FUNCTION_CALL NetworkListenerRun(THREAD_ARG arg)
Thread entry: accept loop for a TCP listener.
utils::Mutex eventQueueMutex
utils::Mutex queueMessagesMutex
bool enterHTTPRequest(HTTPRequest *req, uint64 conid)
Queue or push an incoming HTTP request.
std::map< uint16, NetworkThread * > udpListeners
UDP listener threads by port (owned).
bool startListener(uint64 cid, uint16 port, uint8 encryption, uint8 protocol, bool isAsync, uint32 protocolTimeout=3000, bool isDefaultProtocol=false)
Open a listener on this channel (see NetworkManager::createListener() for semantics).
std::queue< uint64 > queueMessageConIDs
uint64 createTCPConnection(const char *addr, uint16 port, uint8 encryption, uint8 protocol, bool isAsync, bool autoreconnect, uint64 &location, uint32 timeoutMS=5000)
Connect to a host by name/IP; see NetworkManager::createTCPConnection().
uint64 autoDetectConnection(NetworkConnection *con, uint16 port, uint32 autoProtocols, uint32 autoProtocolTimeout, uint32 defaultProtocol, bool isAsync, bool autoreconnect)
Adopt an incoming connection whose protocol is not yet known: sniff its first bytes against autoProto...
bool sendHTTPReply(HTTPReply *reply, uint64 conid)
Send an HTTP reply on a server connection.
static THREAD_RET THREAD_FUNCTION_CALL HTTPServerRun(THREAD_ARG arg)
Thread entry: HTTP/WebSocket server connection loop — the built-in web server's per-connection worker...
uint32 cid
Channel id within the manager.
uint64 createUDPConnection(uint16 port, uint8 protocol, bool isAsync, bool autoreconnect)
Bind a UDP port on this channel.
HTTPRequest * waitForHTTPRequest(uint64 &conid, uint32 ms)
Wait for the next queued HTTP request (sync-mode HTTP server).
bool enterWebsocketData(WebsocketData *wsData, uint64 conid)
Queue or push an incoming WebSocket message.
TelnetLine * waitForTelnetLine(uint64 &conid, uint32 ms)
Wait for the next queued Telnet line.
DataMessage * waitForMessage(uint64 &conid, uint32 ms)
Wait for the next queued DataMessage.
std::queue< TelnetLine * > queueTelnetLines
Abstract base class for all point-to-point network connections.
virtual bool send(const char *data, uint32 size, uint64 receiver=0)=0
Send raw bytes on the connection.
virtual uint32 getOutputSpeed()
virtual uint32 clearBuffer()
Discard all currently buffered input.
virtual uint64 getRemoteAddress()
bool setConnectTimeout(uint32 timeoutMS)
Set the timeout used by subsequent connect()/reconnect() attempts.
virtual bool receiveAvailable(char *data, uint32 &size, uint32 maxSize, uint32 timeout, bool peek=false)
Receive whatever bytes are available (up to maxSize).
virtual bool disconnect(uint16 error=0)
Close the connection and release the socket.
virtual uint8 getConnectionType()
virtual uint32 getInputSpeed()
char * greetingData
Owned copy of the greeting bytes (NULL if unset).
virtual bool isConnected(int timeout=0)
Test whether the connection is currently alive.
bool setGreetingData(const char *data, uint32 size)
Set greeting bytes sent automatically right after a connection is established (used e....
virtual bool reconnect(uint32 timeoutMS)=0
Re-establish the connection to the previously known remote endpoint.
uint32 greetingSize
Size of greetingData in bytes.
Central owner of all channels, listeners and connections in a process.
static bool WebsocketTest()
WebSocket upgrade + echo self-test.
friend class NetworkChannel
utils::Mutex udpOutputConMutex
Serialises use of udpOutputCon.
uint64 lastConnectionID
Last connection id issued.
bool endConnection(uint64 conid)
Gracefully close a connection (thread is asked to finish; entry kept for reuse).
uint8 getConnectionType(uint64 conid)
NetworkChannel * getUDPConnectionByPort(uint16 port)
bool endUDPConnection(uint16 port)
Close the UDP connection bound to port.
std::map< uint64, NetworkChannel * > channelsByConnection
Channel lookup by connection id.
static bool UnitTestHTTP()
Self-test of the built-in HTTP server and client.
NetworkChannel * addTCPConnection(const char *addr, uint16 port, uint8 encryption, uint8 protocol, bool isAsync, uint32 channelID, NetworkReceiver *recv, uint64 &conid, uint64 &location, uint32 timeoutMS=5000, const char *greetingData=NULL, uint32 greetingSize=0)
Like createTCPConnection() but sends optional greeting bytes right after connecting (peer identificat...
UDPConnection * udpOutputCon
Shared output-only UDP socket for sendUDPMessage().
static bool TestHTTP(const char *host, uint32 port, std::vector< std::string > &urls)
Fetch a list of URLs from a host and report results (manual test helper).
std::string sslKeyPath
PEM private key path for SSL listeners.
static bool UnitTest()
Basic TCP/message-protocol round-trip self-test.
NetworkChannel * createTCPConnection(const char *addr, uint16 port, uint8 encryption, uint8 protocol, bool isAsync, bool autoreconnect, uint32 channelID, NetworkReceiver *recv, uint64 &conid, uint64 &location, uint32 timeoutMS=5000)
Connect to a remote host by name/IP and start the protocol thread.
bool stopListener(uint16 port, uint8 protocol)
Stop a listener previously opened with createListener().
THREAD_RET THREAD_FUNCTION_CALL NetworkManagerRun(THREAD_ARG arg)
Thread entry point of the manager's supervision loop (do not call directly).
bool setSSLCertificate(const char *sslCertPath, const char *sslKeyPath)
Set the certificate/key used by SSL listeners created via this manager.
std::string sslCertPath
PEM certificate path for SSL listeners.
NetworkChannel * createWebsocketConnection(const char *url, uint32 channelID, NetworkReceiver *recv, uint64 &conid, const char *protocolName=NULL, const char *origin=NULL, uint32 timeoutMS=5000)
Open a client WebSocket connection from a full URL (ws:// or wss://).
uint32 lastChannelID
Last channel id issued.
NetworkChannel * getConnection(uint64 conid)
NetworkChannel * getTCPConnectionByPort(uint16 port)
uint64 addConnection(NetworkChannel *channel)
Register an externally created channel with the manager.
NetworkChannel * createListener(uint16 port, uint8 encryption, uint8 protocol, bool isAsync, uint32 protocolTimeout, bool isDefaultProtocol, uint32 channelID, NetworkReceiver *recv)
Open a listening port for one or more protocols.
uint64 getRemoteAddress(uint64 conid)
HTTPReply * makeHTTPRequest(const char *url, uint32 timeout, const char *content=NULL, uint32 contentSize=0)
Blocking HTTP(S) exchange from a URL string (GET, or POST when content given).
std::map< uint32, NetworkChannel * > channels
All channels by channel id (owned).
NetworkChannel * createUDPConnection(uint16 port, uint8 protocol, bool isAsync, bool autoreconnect, uint32 channelID, NetworkReceiver *recv, uint64 &conid)
Bind a UDP port for datagram traffic.
bool removeConnection(uint64 conid)
Close a connection and remove it from the manager's maps entirely.
virtual ~NetworkManager()
std::map< uint16, NetworkChannel * > listeners
TCP listeners by port.
std::map< uint16, NetworkChannel * > udpListeners
UDP listeners by port.
bool sendUDPMessage(DataMessage *msg, uint64 destination)
Send a DataMessage as a UDP datagram via the shared output socket.
static bool UnitTestDelayedConnect()
Self-test of non-blocking (delayed) connect handling.
Callback interface for asynchronous delivery of parsed network traffic.
Bookkeeping for one worker thread of a NetworkChannel (per listener or connection).
NetworkThread(NetworkChannel *parent, uint64 id)
uint32 defaultProtocol
Fallback PROTOCOL_* when auto-detection is inconclusive.
NetworkChannel * parent
Owning channel (not owned).
TCPListener * listener
Listener served (owned), or NULL for connections.
uint16 port
Local port (listeners) or 0.
uint32 autoProtocolTimeout
Milliseconds allowed for protocol sniffing.
NetworkConnection * con
Connection served (owned), or NULL for listeners.
bool isAsync
Push to receiver (true) or queue for waitFor*() (false).
uint64 id
Connection or listener id served by this thread.
bool autoreconnect
Re-establish the connection automatically on failure.
uint32 threadID
ThreadManager id of the worker thread.
bool isRunning
True while the worker loop is active.
uint32 autoProtocols
PROTOCOL_* bit set to auto-detect among.
bool shouldContinue
Loop control flag: thread exits when false.
HTTPRequest * lastRequest
Last request pending a reply on this HTTP connection.
SSL/TLS-encrypted TCP connection (OpenSSL) with configurable peer verification.
bool init()
Initialise the OpenSSL context for a client-side connection.
void setCALocation(const char *caFile, const char *caPath)
bool connect(SOCKET s, uint64 localAddr, NetworkDataReceiver *receiver=NULL)
Adopt an already-accepted socket and perform the server-side TLS handshake.
void setAllowSelfSigned(bool allow)
bool delayedConnect(uint64 addr, uint32 timeoutMS, NetworkDataReceiver *receiver)
Begin a non-blocking connect (TLS handshake completes in didConnect()).
Plain TCP stream connection (client-initiated or accepted from a listener).
bool connect(SOCKET s, uint64 localAddr, NetworkDataReceiver *receiver=NULL)
Adopt an already-connected socket (server side, from a TCPListener).
bool delayedConnect(uint64 addr, uint32 timeoutMS, NetworkDataReceiver *receiver)
Begin a non-blocking connect; completion is checked with didConnect().
TCP server socket: binds a port and accepts inbound connections (plain or SSL).
NetworkConnection * acceptConnection(uint32 timeout)
Synchronously wait for and accept one inbound connection.
bool setSSLCertificate(const char *sslCertPath, const char *sslKeyPath)
Set the SSL certificate and private key used for inbound SSL connections.
bool init(uint16 port, uint8 encryption, NetworkConnectionReceiver *receiver=NULL, NetworkDataReceiver *dataReceiver=NULL)
Bind and start listening on a port.
One line of Telnet-style text traffic.
static TelnetLine * ReceiveTelnetLine(NetworkConnection *con, uint32 timeout)
Read one line terminated by CR/LF.
static bool CheckBufferForCompatibility(const char *buffer, uint32 length)
static bool SendTelnetLine(NetworkConnection *con, TelnetLine *line)
Send one line (with its line ending).
static ThreadStats GetThreadStats(uint32 id)
Get a copy of the statistics record for a specific thread.
static bool CreateThread(THREAD_FUNCTION func, void *args, uint32 &newID, uint32 reqID=0)
Create a new native thread and start it immediately.
static bool IsThreadRunning(uint32 id)
Check whether the thread is still alive at the OS level.
static bool TerminateThread(uint32 id)
Forcibly terminate the thread and release its slot.
static bool Shutdown()
Terminate all managed threads, then destroy the singleton.
UDP datagram connection (bound port for input, or output-only sender).
bool connect(uint16 port, NetworkDataReceiver *receiver=NULL)
Bind a local UDP port for receiving datagrams.
static UnitTestRunner & instance()
Access the singleton (created on first use).
void registerTest(const char *name, UnitTestFunc func, const char *description="", const char *category="", bool inDefaultRun=true)
Register a test with the runner.
One WebSocket frame/message (RFC 6455): parsing, generation and control frames.
const char * getContent(uint64 &size)
Get the decoded (unmasked) payload.
bool isTerminationRequest()
enum cmlabs::WebsocketData::DataType dataType
static WebsocketData * CreateTerminationConfirmation()
bool setData(DataType dataType, bool maskData, const char *data=NULL, uint64 size=0)
Set the payload and build the serialised frame for sending.
Minimal WebSocket echo server used by the unit tests (handles upgrade + echo).
virtual bool receiveHTTPRequest(HTTPRequest *req, NetworkChannel *channel, uint64 conid)
Answer the WebSocket upgrade handshake.
virtual bool receiveWebsocketData(WebsocketData *wsData, NetworkChannel *channel, uint64 conid)
Echo received frames back to the client.
uint64 GetTimeNow()
Return the current absolute time (µs since year 0) according to the TMC.
std::string PrintTimeNowString(bool local=true, bool us=true, bool ms=true)
Format GetTimeNow().
std::string PrintTimeDifString(uint64 t, bool us=true, bool ms=true)
int32 GetTimeAgeMS(uint64 t)
Age of a timestamp relative to now, in milliseconds.
bool Sleep(uint32 ms)
Suspend the calling thread.
bool GetCurrentThreadUniqueID(uint32 &tid)
Get a process-unique id for the calling thread.
std::string GetURIFromURL(std::string url)
Extract the URI (path plus query) from a URL.
std::string GetProtocolFromURL(std::string url)
Extract the protocol/scheme from a URL.
std::string GetHostFromURL(std::string url)
Extract the host name (or IP literal) from a URL.
uint16 GetPortFromURL(std::string url)
Extract the port number from a URL.
void fail(const char *fmt,...)
Set an explanatory reason shown on the FAIL line.
void metric(const char *name, double value, const char *unit="", bool higherIsBetter=true)
Record a performance metric.
void detail(const char *fmt,...)
Verbose-only indented diagnostic line (shown only when verbose=1).
void progress(int percent, const char *action)
Report progress with a short description of the current action.
bool SeedRandomValues(uint32 seedvalue=0)
Seed the pseudo-random generator.
double RandomValue()
Uniform random double in [0,1).
bool GetLocalIPAddress(uint32 &address)
Get the primary local IPv4 address.
std::string StringFormat(const char *format,...)
printf into a std::string.
THREAD_RET THREAD_FUNCTION_CALL HTTPServerTest(THREAD_ARG arg)
static struct PsyType CTRL_TEST
void Register_NetworkManager_Tests()
Hierarchical message type identifier — the key used for publish/subscribe matching in Psyclone.
std::vector< std::string > * urls
Notification of a connection lifecycle change (connect, disconnect, buffer state.....
uint64 time
Event timestamp (ms epoch).
uint64 cid
Channel id the event belongs to.
uint8 protocol
PROTOCOL_* of the affected connection.
uint64 conid
Connection id within the channel.
uint8 type
NETWORKEVENT_* event type.
uint32 osID
Operating-system thread ID (as reported by the OS, not the slot ID).