From 3b69fc088973c7ff8eb98ce9598f4f0ab8fd8f09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=BF=D0=BE=D0=B4=D0=BE=D0=B1=D0=BD?= =?UTF-8?q?=D1=8B=D0=B9=20=D0=90=D0=BB=D0=B5=D0=BD?= Date: Mon, 12 Sep 2022 20:41:28 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D0=B4=D0=BF=D0=B8=D1=81=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D0=B0=D1=8F=20PGP=20=D0=BA=D0=BB=D1=8E=D1=87=D0=BE?= =?UTF-8?q?=D0=BC=20=D0=94=D0=A1=20Created=20=D1=81=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D0=BA=D0=B0=20=D1=81=D0=BD=D0=B0=D1=87=D0=B0=D0=BB=D0=B0=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=BB=D0=B6=D0=BD=D0=B0=20=D0=BE=D1=82=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D1=8F=D1=82=D1=8C=D1=81=D1=8F=20=D0=BF=D0=BE?= =?UTF-8?q?=20bitmessage,=20=D0=B7=D0=B0=D1=82=D0=B5=D0=BC=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=BB=D0=B6=D0=BD=D0=B0=20=D0=B1=D1=8B=D1=82=D1=8C=20=D0=BE?= =?UTF-8?q?=D1=82=D0=B4=D0=B0=D0=BD=D0=B0=20=D0=9C=D0=A1.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/Workers/WebService/WebService.cpp | 2 - src/modules/Workers/WebSocket/WebSocket.cpp | 319 ++++++++++-------- src/modules/Workers/WebSocket/WebSocket.hpp | 5 + 3 files changed, 186 insertions(+), 140 deletions(-) diff --git a/src/modules/Workers/WebService/WebService.cpp b/src/modules/Workers/WebService/WebService.cpp index 406552a..5113e52 100644 --- a/src/modules/Workers/WebService/WebService.cpp +++ b/src/modules/Workers/WebService/WebService.cpp @@ -55,8 +55,6 @@ namespace Apostol { m_Status = psStopped; CWebService::InitMethods(); - - InitServerList(); } //-------------------------------------------------------------------------------------------------------------- diff --git a/src/modules/Workers/WebSocket/WebSocket.cpp b/src/modules/Workers/WebSocket/WebSocket.cpp index a296481..32ee82f 100644 --- a/src/modules/Workers/WebSocket/WebSocket.cpp +++ b/src/modules/Workers/WebSocket/WebSocket.cpp @@ -39,6 +39,9 @@ Author: #define CONFIG_SECTION_NAME "module" //---------------------------------------------------------------------------------------------------------------------- +#define NOT_FOUND_ACTIVE_CONNECTION "Not found active connection. Try again later." +//---------------------------------------------------------------------------------------------------------------------- + extern "C++" { namespace Apostol { @@ -144,7 +147,7 @@ namespace Apostol { CHTTPRequest::Prepare(ARequest, "POST", "/api/v1/dm/service"); - const auto& caModuleAddress = m_Module["address"]; + const auto &caModuleAddress = m_Module["address"]; if (!caModuleAddress.IsEmpty()) ARequest->AddHeader("Module-Address", caModuleAddress); @@ -377,12 +380,12 @@ namespace Apostol { for (int i = 0; i < List.Count(); i++) { - const auto& uid = List[i]; + const auto &uid = List[i]; DebugMessage("%s (%s)\n", uid.Name.c_str(), uid.Desc.c_str()); - const auto& name = uid.Name.Lower(); - const auto& data = uid.Desc.Lower(); + const auto &name = uid.Name.Lower(); + const auto &data = uid.Desc.Lower(); if (name == "technical_data") { SplitColumns(data, Data, ';'); @@ -402,7 +405,7 @@ namespace Apostol { } } } else if (name.Find("bitcoin_key") != CString::npos) { - const auto& key = wallet::ec_public(data.c_str()); + const auto &key = wallet::ec_public(data.c_str()); if (verify(key)) KeyList.AddPair(name, key.encoded()); } @@ -412,7 +415,7 @@ namespace Apostol { for (int i = 1; i <= KeyList.Count(); i++) { Name = "bitcoin_key"; Name << i; - const auto& key = KeyList[Name]; + const auto &key = KeyList[Name]; if (!key.IsEmpty()) { BTCKeys.Add(key); } @@ -490,6 +493,57 @@ namespace Apostol { } //-------------------------------------------------------------------------------------------------------------- + CWebSocketClient *CWebSocketModule::GetConnectedClient(const CClientContext &Context) { + for (int i = 0; i < Context.ClientManager().Count(); i++) { + auto pClient = Context.ClientManager()[i]; + if (pClient->Connected()) + return pClient; + } + + return nullptr; + } + //-------------------------------------------------------------------------------------------------------------- + + int CWebSocketModule::CurrentContextIndex(const CString &Params) { + + int index = 0; + while (index < m_Servers.Count()) { + const auto &caContext = m_Servers[index].Value(); + if (caContext.Status() == Context::csRunning) { + if (Params.IsEmpty() || Params == caContext.URL().Origin()) { + auto pClient = GetConnectedClient(caContext); + if (pClient != nullptr) + return index; + } + } + + index++; + } + + return -1; + } + //-------------------------------------------------------------------------------------------------------------- + + CWebSocketClient *CWebSocketModule::GetConnectedClient(const CString &Params) { + + int index = 0; + while (index < m_Servers.Count()) { + const auto &caContext = m_Servers[index].Value(); + if (caContext.Status() == Context::csRunning) { + if (Params.IsEmpty() || Params == caContext.URL().Origin()) { + auto pClient = GetConnectedClient(caContext); + if (pClient != nullptr) + return pClient; + } + } + + index++; + } + + return nullptr; + } + //-------------------------------------------------------------------------------------------------------------- + void CWebSocketModule::DoClientConnected(CObject *Sender) { auto pConnection = dynamic_cast(Sender); if (pConnection != nullptr) { @@ -647,33 +701,33 @@ namespace Apostol { auto pServerRequest = AConnection->Request(); - const auto& caModuleAddress = m_Module["address"]; - const auto& caHost = pServerRequest->Headers["host"]; - const auto& caOrigin = pServerRequest->Headers["origin"]; - const auto& caUserAddress = pServerRequest->Params["address"]; + const auto &caModuleAddress = m_Module["address"]; + const auto &caHost = pServerRequest->Headers["host"]; + const auto &caOrigin = pServerRequest->Headers["origin"]; + const auto &caUserAddress = pServerRequest->Params["address"]; - const auto& pgpValue = pServerRequest->Params["pgp"]; - const auto& caServerParam = pServerRequest->Params["server"]; + const auto &pgpValue = pServerRequest->Params["pgp"]; + const auto &caServerParam = pServerRequest->Params["server"]; CStringList caClearText; CString sPayload; if (!pServerRequest->Content.IsEmpty()) { - const auto& ContentType = pServerRequest->Headers.Values(_T("content-type")); + const auto &ContentType = pServerRequest->Headers.Values(_T("content-type")); if (ContentType.Find("application/x-www-form-urlencoded") == 0) { const CStringList &FormData = pServerRequest->FormData; - const auto& formDate = FormData["date"]; - const auto& formAddress = FormData["address"]; - const auto& formBitmessage = FormData["bitmessage"]; - const auto& formKey = FormData["key"]; - const auto& formPGP = FormData["pgp"]; - const auto& formURL = FormData["url"]; - const auto& formFlags = FormData["flags"]; - const auto& formSign = FormData["sign"]; + const auto &formDate = FormData["date"]; + const auto &formAddress = FormData["address"]; + const auto &formBitmessage = FormData["bitmessage"]; + const auto &formKey = FormData["key"]; + const auto &formPGP = FormData["pgp"]; + const auto &formURL = FormData["url"]; + const auto &formFlags = FormData["flags"]; + const auto &formSign = FormData["sign"]; if (!formDate.IsEmpty()) { caClearText << formDate; @@ -712,14 +766,14 @@ namespace Apostol { CFormData FormData; CHTTPRequestParser::ParseFormData(pServerRequest, FormData); - const auto& formDate = FormData.Data("date"); - const auto& formAddress = FormData.Data("address"); - const auto& formBitmessage = FormData.Data("bitmessage"); - const auto& formKey = FormData.Data("key"); - const auto& formPGP = FormData.Data("pgp"); - const auto& formURL = FormData.Data("url"); - const auto& formFlags = FormData.Data("flags"); - const auto& formSign = FormData.Data("sign"); + const auto &formDate = FormData.Data("date"); + const auto &formAddress = FormData.Data("address"); + const auto &formBitmessage = FormData.Data("bitmessage"); + const auto &formKey = FormData.Data("key"); + const auto &formPGP = FormData.Data("pgp"); + const auto &formURL = FormData.Data("url"); + const auto &formFlags = FormData.Data("flags"); + const auto &formSign = FormData.Data("sign"); if (!formDate.IsEmpty()) { caClearText << formDate; @@ -757,13 +811,13 @@ namespace Apostol { const CJSON contextJson(pServerRequest->Content); - const auto& jsonDate = contextJson["date"].AsString(); - const auto& jsonAddress = contextJson["address"].AsString(); - const auto& jsonBitmessage = contextJson["bitmessage"].AsString(); - const auto& jsonKey = contextJson["key"].AsString(); - const auto& jsonPGP = contextJson["pgp"].AsString(); - const auto& jsonFlags = contextJson["flags"].AsString(); - const auto& jsonSign = contextJson["sign"].AsString(); + const auto &jsonDate = contextJson["date"].AsString(); + const auto &jsonAddress = contextJson["address"].AsString(); + const auto &jsonBitmessage = contextJson["bitmessage"].AsString(); + const auto &jsonKey = contextJson["key"].AsString(); + const auto &jsonPGP = contextJson["pgp"].AsString(); + const auto &jsonFlags = contextJson["flags"].AsString(); + const auto &jsonSign = contextJson["sign"].AsString(); if (!jsonDate.IsEmpty()) { caClearText << jsonDate; @@ -835,27 +889,12 @@ namespace Apostol { if (!sPayload.IsEmpty()) Json.Object().AddPair("payload", base64_encode(sPayload)); - int index = 0; - while (index < m_Servers.Count()) { - const auto &Context = m_Servers[index].Value(); - if (Context.Status() == Context::csRunning) { - if (caServerParam.IsEmpty()) { - break; - } else { - if (Context.URL().Origin() == caServerParam) - break; - } - } + auto pClient = GetConnectedClient(caServerParam); + + if (pClient == nullptr) { + throw Delphi::Exception::Exception(NOT_FOUND_ACTIVE_CONNECTION); } - if (index == m_Servers.Count()) { - throw Delphi::Exception::Exception("Not found active connection."); - } - - const auto &caContext = m_Servers[index].Value(); - - auto pClient = caContext.ClientManager().First(); - pClient->Send(URI, Json, OnRequest); } //-------------------------------------------------------------------------------------------------------------- @@ -876,34 +915,28 @@ namespace Apostol { auto pServerRequest = AConnection->Request(); - const auto& caModuleAddress = m_Module["address"]; - const auto& caModuleFee = m_Module["fee"]; + const auto &caModuleAddress = m_Module["address"]; + const auto &caModuleFee = m_Module["fee"]; const auto checkFee = CheckFee(caModuleFee); if (checkFee == -1) throw ExceptionFrm("Invalid module fee value: %s", caModuleFee.c_str()); - const auto& caHost = pServerRequest->Headers["host"]; - const auto& caOrigin = pServerRequest->Headers["origin"]; - const auto& caUserAddress = pServerRequest->Params["address"]; - const auto& pgpValue = pServerRequest->Params["pgp"]; - const auto& caServerParam = pServerRequest->Params["server"]; + const auto &caHost = pServerRequest->Headers["host"]; + const auto &caOrigin = pServerRequest->Headers["origin"]; - int index = 0; - while (index < m_Servers.Count()) { - const auto &Context = m_Servers[index].Value(); - if (Context.Status() == Context::csRunning) { - if (caServerParam.IsEmpty()) { - break; - } else { - if (Context.URL().Origin() == caServerParam) - break; - } - } - } + const auto &address = pServerRequest->Params["address"]; + const auto &code = pServerRequest->Params["code"]; + + const auto &caUserAddress = address.length() == 40 ? CString() : address; + const auto &caDealCode = !code.empty() ? code : address.length() == 40 ? address : CString(); + + const auto &pgpValue = pServerRequest->Params["pgp"]; + const auto &caServerParam = pServerRequest->Params["server"]; - if (index == m_Servers.Count()) { - throw Delphi::Exception::Exception("Not found active connection."); + const auto index = CurrentContextIndex(caServerParam); + if (index == -1) { + throw Delphi::Exception::Exception(NOT_FOUND_ACTIVE_CONNECTION); } const auto &caContext = m_Servers[index].Value(); @@ -914,27 +947,27 @@ namespace Apostol { if (!pServerRequest->Content.IsEmpty()) { - const auto& ContentType = pServerRequest->Headers.Values(_T("content-type")); + const auto &ContentType = pServerRequest->Headers.Values(_T("content-type")); if (ContentType.Find("application/x-www-form-urlencoded") == 0) { const CStringList &FormData = pServerRequest->FormData; - const auto& formType = FormData["type"]; - const auto& formAt = FormData["at"]; - const auto& formDate = FormData["date"]; - const auto& formSellerAddress = FormData["seller_address"]; - const auto& formSellerRating = FormData["seller_rating"]; - const auto& formSellerSignature = FormData["seller_signature"]; - const auto& formCustomerAddress = FormData["customer_address"]; - const auto& formCustomerRating = FormData["customer_rating"]; - const auto& formCustomerSignature = FormData["customer_signature"]; - const auto& formPaymentAddress = FormData["payment_address"]; - const auto& formPaymentUntil = FormData["payment_until"]; - const auto& formPaymentSum = FormData["payment_sum"]; - const auto& formFeedbackLeaveBefore = FormData["feedback_leave_before"]; - const auto& formFeedbackStatus = FormData["feedback_status"]; - const auto& formFeedbackComments = FormData["feedback_comments"]; + const auto &formType = FormData["type"]; + const auto &formAt = FormData["at"]; + const auto &formDate = FormData["date"]; + const auto &formSellerAddress = FormData["seller_address"]; + const auto &formSellerRating = FormData["seller_rating"]; + const auto &formSellerSignature = FormData["seller_signature"]; + const auto &formCustomerAddress = FormData["customer_address"]; + const auto &formCustomerRating = FormData["customer_rating"]; + const auto &formCustomerSignature = FormData["customer_signature"]; + const auto &formPaymentAddress = FormData["payment_address"]; + const auto &formPaymentUntil = FormData["payment_until"]; + const auto &formPaymentSum = FormData["payment_sum"]; + const auto &formFeedbackLeaveBefore = FormData["feedback_leave_before"]; + const auto &formFeedbackStatus = FormData["feedback_status"]; + const auto &formFeedbackComments = FormData["feedback_comments"]; CheckKeyForNull("order", Action.c_str()); CheckKeyForNull("type", formType.c_str()); @@ -1007,21 +1040,21 @@ namespace Apostol { CFormData FormData; CHTTPRequestParser::ParseFormData(pServerRequest, FormData); - const auto& formType = FormData.Data("type"); - const auto& formAt = FormData.Data("at"); - const auto& formDate = FormData.Data("date"); - const auto& formSellerAddress = FormData.Data("seller_address"); - const auto& formSellerRating = FormData.Data("seller_rating"); - const auto& formSellerSignature = FormData.Data("seller_signature"); - const auto& formCustomerAddress = FormData.Data("customer_address"); - const auto& formCustomerRating = FormData.Data("customer_rating"); - const auto& formCustomerSignature = FormData.Data("customer_signature"); - const auto& formPaymentAddress = FormData.Data("payment_address"); - const auto& formPaymentUntil = FormData.Data("payment_until"); - const auto& formPaymentSum = FormData.Data("payment_sum"); - const auto& formFeedbackLeaveBefore = FormData.Data("feedback_leave_before"); - const auto& formFeedbackStatus = FormData.Data("feedback_status"); - const auto& formFeedbackComments = FormData.Data("feedback_comments"); + const auto &formType = FormData.Data("type"); + const auto &formAt = FormData.Data("at"); + const auto &formDate = FormData.Data("date"); + const auto &formSellerAddress = FormData.Data("seller_address"); + const auto &formSellerRating = FormData.Data("seller_rating"); + const auto &formSellerSignature = FormData.Data("seller_signature"); + const auto &formCustomerAddress = FormData.Data("customer_address"); + const auto &formCustomerRating = FormData.Data("customer_rating"); + const auto &formCustomerSignature = FormData.Data("customer_signature"); + const auto &formPaymentAddress = FormData.Data("payment_address"); + const auto &formPaymentUntil = FormData.Data("payment_until"); + const auto &formPaymentSum = FormData.Data("payment_sum"); + const auto &formFeedbackLeaveBefore = FormData.Data("feedback_leave_before"); + const auto &formFeedbackStatus = FormData.Data("feedback_status"); + const auto &formFeedbackComments = FormData.Data("feedback_comments"); CheckKeyForNull("order", Action.c_str()); CheckKeyForNull("type", formType.c_str()); @@ -1093,35 +1126,35 @@ namespace Apostol { const CJSON jsonData(pServerRequest->Content); - const auto& formOrder = jsonData["order"].AsString().Lower(); - const auto& formType = jsonData["type"].AsString(); + const auto &formOrder = jsonData["order"].AsString().Lower(); + const auto &formType = jsonData["type"].AsString(); - const auto& formAt = jsonData["at"].AsString(); - const auto& formDate = jsonData["date"].AsString(); + const auto &formAt = jsonData["at"].AsString(); + const auto &formDate = jsonData["date"].AsString(); const CJSONValue &jsonSeller = jsonData["seller"]; - const auto& formSellerAddress = jsonSeller["address"].AsString(); - const auto& formSellerRating = jsonSeller["rating"].AsString(); - const auto& formSellerSignature = jsonSeller["signature"].AsString(); + const auto &formSellerAddress = jsonSeller["address"].AsString(); + const auto &formSellerRating = jsonSeller["rating"].AsString(); + const auto &formSellerSignature = jsonSeller["signature"].AsString(); const CJSONValue &jsonCustomer = jsonData["customer"]; - const auto& formCustomerAddress = jsonCustomer["address"].AsString(); - const auto& formCustomerRating = jsonCustomer["rating"].AsString(); - const auto& formCustomerSignature = jsonCustomer["signature"].AsString(); + const auto &formCustomerAddress = jsonCustomer["address"].AsString(); + const auto &formCustomerRating = jsonCustomer["rating"].AsString(); + const auto &formCustomerSignature = jsonCustomer["signature"].AsString(); const CJSONValue &jsonPayment = jsonData["payment"]; - const auto& formPaymentAddress = jsonPayment["address"].AsString(); - const auto& formPaymentUntil = jsonPayment["until"].AsString(); - const auto& formPaymentSum = jsonPayment["sum"].AsString(); + const auto &formPaymentAddress = jsonPayment["address"].AsString(); + const auto &formPaymentUntil = jsonPayment["until"].AsString(); + const auto &formPaymentSum = jsonPayment["sum"].AsString(); const CJSONValue &jsonFeedback = jsonData["feedback"]; - const auto& formFeedbackLeaveBefore = jsonFeedback["leave-before"].AsString(); - const auto& formFeedbackStatus = jsonFeedback["status"].AsString(); - const auto& formFeedbackComments = jsonFeedback["comments"].AsString(); + const auto &formFeedbackLeaveBefore = jsonFeedback["leave-before"].AsString(); + const auto &formFeedbackStatus = jsonFeedback["status"].AsString(); + const auto &formFeedbackComments = jsonFeedback["comments"].AsString(); const auto &action = Action.IsEmpty() ? formOrder : Action; @@ -1251,10 +1284,20 @@ namespace Apostol { Json.Object().AddPair("module", Module); Json.Object().AddPair("address", caUserAddress.IsEmpty() ? caModuleAddress : caUserAddress); + if (!caDealCode.IsEmpty()) { + CJSONValue Deal(jvtObject); + Deal.Object().AddPair("code", caDealCode); + Json.Object().AddPair("deal", Deal); + } + if (!sPayload.IsEmpty()) Json.Object().AddPair("payload", base64_encode(sPayload)); - auto pClient = caContext.ClientManager().First(); + auto pClient = GetConnectedClient(caContext); + + if (pClient == nullptr) { + throw Delphi::Exception::Exception(NOT_FOUND_ACTIVE_CONNECTION); + } pClient->Send(URI, Json, OnRequest); } @@ -1279,10 +1322,10 @@ namespace Apostol { return; } - const auto& caService = slRouts[0].Lower(); - const auto& caVersion = slRouts[1].Lower(); - const auto& caCommand = slRouts[2].Lower(); - const auto& caAction = slRouts.Count() == 4 ? slRouts[3].Lower() : ""; + const auto &caService = slRouts[0].Lower(); + const auto &caVersion = slRouts[1].Lower(); + const auto &caCommand = slRouts[2].Lower(); + const auto &caAction = slRouts.Count() == 4 ? slRouts[3].Lower() : ""; if (caService != "api") { AConnection->SendStockReply(CHTTPReply::not_found); @@ -1331,7 +1374,7 @@ namespace Apostol { } else if (caCommand == "bc" && caAction == "history") { - const auto& caAccount = pRequest->Params["account"]; + const auto &caAccount = pRequest->Params["account"]; if (caAccount.IsEmpty()) { AConnection->SendStockReply(CHTTPReply::bad_request); @@ -1354,8 +1397,8 @@ namespace Apostol { } else if (caCommand == "bc" && caAction == "header") { - const auto& caHeight = pRequest->Params["height"]; - const auto& caHash = pRequest->Params["hash"]; + const auto &caHeight = pRequest->Params["height"]; + const auto &caHash = pRequest->Params["hash"]; if (caHeight.IsEmpty() && caHash.IsEmpty()) { AConnection->SendStockReply(CHTTPReply::bad_request); @@ -1433,10 +1476,10 @@ namespace Apostol { return; } - const auto& caService = slRouts[0].Lower(); - const auto& caVersion = slRouts[1].Lower(); - const auto& caCommand = slRouts[2].Lower(); - const auto& caAction = slRouts.Count() == 4 ? slRouts[3].Lower() : ""; + const auto &caService = slRouts[0].Lower(); + const auto &caVersion = slRouts[1].Lower(); + const auto &caCommand = slRouts[2].Lower(); + const auto &caAction = slRouts.Count() == 4 ? slRouts[3].Lower() : ""; if (caService != "api") { AConnection->SendStockReply(CHTTPReply::not_found); @@ -1527,8 +1570,8 @@ namespace Apostol { m_OAuth2 = Config()->Prefix() + m_OAuth2; } - const auto& caPrivateKey = Config()->IniFile().ReadString("pgp", "private", "module.sec"); - const auto& caPublicKey = Config()->IniFile().ReadString("pgp", "public", "dm.pub"); + const auto &caPrivateKey = Config()->IniFile().ReadString("pgp", "private", "module.sec"); + const auto &caPublicKey = Config()->IniFile().ReadString("pgp", "public", "dm.pub"); m_pgpPassphrase = Config()->IniFile().ReadString("pgp", "passphrase", ""); @@ -1601,7 +1644,7 @@ namespace Apostol { //-------------------------------------------------------------------------------------------------------------- void CWebSocketModule::CheckDeal(const CDeal &Deal) { - const auto& Data = Deal.Data(); + const auto &Data = Deal.Data(); const auto DateTime = UTC(); const auto Date = StringToDate(Data.Date); diff --git a/src/modules/Workers/WebSocket/WebSocket.hpp b/src/modules/Workers/WebSocket/WebSocket.hpp index 4bf13d2..d24fa90 100644 --- a/src/modules/Workers/WebSocket/WebSocket.hpp +++ b/src/modules/Workers/WebSocket/WebSocket.hpp @@ -96,6 +96,11 @@ namespace Apostol { void FetchOAuth2(CContext &Context); + static CWebSocketClient *GetConnectedClient(const CClientContext &Context); + + int CurrentContextIndex(const CString &Params); + CWebSocketClient *GetConnectedClient(const CString &Params); + protected: void Heartbeat(CDateTime Now) override;