Sleds/cppcore/client/syncandmessagedisp.cpp

525 lines
20 KiB
C++
Raw Normal View History

2025-03-13 21:28:38 +00:00
// Copyright (c) 2014, IOnU Security, Inc.
// Copyright (c) 2016, Sequence Logic, Inc. All rights reserved.
#include "syncandmessagedisp.h"
#include "jsonobject.h"
#include "../internal.h"
#include "websvcs.h"
#include "peerobserver.h"
#include "../component/slfile.h"
#include "../component/pubsubmessage.h"
#include "../component/storagenode.h"
#include "../cppcoreobjects/permissions.h"
#include "../../K2Client/platform_binding.h"
#include "../../K2Client/K2IPC.h"
#include <iomanip>
#include <iostream>
#include <fstream>
#include <mutex>
#include <sstream>
#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#endif
using namespace sequencelogic;
namespace
{
#ifdef WIN32
const char K2_LOGFILENAME[] = "slK2Samd";
char k2FileSuffix = '0';
unsigned int nK2SamdLogFileSize = 0;
int THREAD_ID_SIZE = 7;
std::ofstream k2LogStream;
std::mutex logFileMutex;
void closeK2SamdLog()
{
std::lock_guard<std::mutex> lock(logFileMutex);
if (k2LogStream.is_open())
{
k2FileSuffix = '0';
nK2SamdLogFileSize = 0;
k2LogStream.close();
}
}
void openK2SamdLog()
{
std::lock_guard<std::mutex> lock(logFileMutex);
if (k2LogStream.is_open())
k2LogStream.close();
// Open the file..
std::string logFilename;
// Get the path to the temp folder. Should be in "c:\Windows\Temp", unless this install of Windows is on
// a different drive..
logFilename.resize(MAX_PATH, 0);
DWORD nLen = ::GetTempPath(MAX_PATH, &logFilename[0]);
logFilename.resize(nLen);
logFilename += K2_LOGFILENAME;
if (k2FileSuffix > '0')
logFilename += k2FileSuffix;
logFilename += ".log";
++k2FileSuffix;
if (k2FileSuffix >= '4')
k2FileSuffix = '0';
k2LogStream.open(logFilename, std::ios_base::out | std::ios_base::trunc);
}
void logK2SamdMsg(const char *pFmt, ...)
{
static int nTimeLength = 21;
{
std::lock_guard<std::mutex> lock(logFileMutex);
if (k2LogStream.is_open())
{
EyeTime curTime;
::va_list args;
va_start(args, pFmt);
std::string formatMsg;
formatMsg.resize(2048);
int nNumChars = ::vsnprintf_s(&formatMsg[0], 2048, _TRUNCATE, pFmt, args);
if (nNumChars > 0)
{
int nThreadId = ::GetCurrentThreadId();
std::ostringstream os;
os << std::right << std::setw(THREAD_ID_SIZE) << nThreadId;
std::string strThreadId = os.str();
nK2SamdLogFileSize += nNumChars + nTimeLength + static_cast<int>(strThreadId.length()) + 1;
formatMsg.resize(nNumChars);
k2LogStream << strThreadId << " " << curTime.GetLocalISO8601Time() << ": " << formatMsg << std::endl;
}
}
}
if (nK2SamdLogFileSize >= (5 * 1024 * 1024))
{
closeK2SamdLog();
openK2SamdLog();
}
}
struct CriticalSectionLocker
{
CriticalSectionLocker (CRITICAL_SECTION &cs) : _critSec(cs) { ::EnterCriticalSection(&_critSec); }
~CriticalSectionLocker() { ::LeaveCriticalSection(&_critSec); }
private:
CRITICAL_SECTION &_critSec;
};
#endif
class EmptyPeerObserver : public PeerObserver
{
public:
virtual std::string getWorkingDirectory() { return ""; }
virtual void onSubscribedMessageReceived(const std::string &) { }
virtual void onResponseSent(const std::string &) { }
virtual void onMessageDisposed(const std::string &) { }
virtual void onMessagePersisted(const std::string &) { }
virtual void onMessageRemoved(const std::string &) { }
virtual std::string automaticReplyToManualMessage(const std::string &, const std::string &) { return NULL; }
virtual void onInactivityTimeReached() { }
virtual void onSyncStart(const std::string &) { }
virtual void onSyncComplete(const std::string &) { }
virtual void onSyncError(const std::string &, const std::string &) { }
virtual void onSyncUploaded(const std::string &) { }
virtual void onSyncDownload(const std::string &) { }
virtual void onSyncDelete(const std::string &) { }
virtual bool onConnectionRestored() { return true; }
virtual void onFatalError(int, const std::string &) { }
virtual void onServiceException(const std::string &, const std::string &) { }
virtual void onSyncAndDispatchStart(const std::string &) { }
virtual void onSyncAndDispatchStop(const std::string &) { }
};
const int STAT_BUF_SIZE = 4096;
const char DEBUG_MSG[] = "SAMD: DEBUG: ";
const char INFO_MSG[] = "SAMD: INFO: ";
const char ERROR_MSG[] = "SAMD: ERROR: ";
}
const char PeerObserver::ACTION_DOWNLOAD_FILE[] = "download";
const char PeerObserver::ACTION_UPLOAD_FILE[] = "upload";
const char PeerObserver::ACTION_METADATA[] = "metadata";
const char PeerObserver::ACTION_DELETE_FILE[] = "delete";
SyncAndMessageDispatch::UrnToDispMap SyncAndMessageDispatch::_k2ClientMap;
SyncAndMessageDispatch::SyncAndMessageDispatch(const std::string &K2Host, const std::string &devUrn, PeerObserver *pObserver, WebSvcs *pWebSvc) :
_pPeerObserver(pObserver), _pWebSvc(pWebSvc), _k2Host(K2Host), _devURN(devUrn), _k2Good(false), _k2Error(false), _inactivityLogoutTime(0)
{
}
SyncAndMessageDispatch::~SyncAndMessageDispatch()
{
}
PeerObserver *SyncAndMessageDispatch::getInstanceObserver()
{
static EmptyPeerObserver emptyObserver;
if (_pPeerObserver == NULL)
return &emptyObserver;
else
return _pPeerObserver;
}
void SyncAndMessageDispatch::terminate()
{
// Logout the K2 client...
K2_Stop();
_k2Status = "terminated";
_k2Good = false;
_k2Error = false;
}
bool SyncAndMessageDispatch::k2Login()
{
K2_Start(_devURN.c_str(), _k2Host.c_str(), &k2DataReceived);
_k2ClientMap.insert(std::make_pair(_devURN, this));
_k2Status = "login";
#ifdef WIN32
openK2SamdLog();
#endif
return true;
}
std::string SyncAndMessageDispatch::k2Statistics()
{
static char statBuf[STAT_BUF_SIZE];
statBuf[0] = '\0';
K2_GetStats(statBuf, STAT_BUF_SIZE);
return statBuf;
}
void SyncAndMessageDispatch::k2Logout()
{
K2_Stop();
_k2Status = "logout";
_k2Good = false;
_k2Error = false;
#ifdef WIN32
closeK2SamdLog();
#endif
_k2ClientMap.erase(_devURN);
}
void SyncAndMessageDispatch::k2StatusSelect(const std::string &offices)
{
K2_StatusSelect(offices.c_str());
}
void SyncAndMessageDispatch::k2DataReceived(const char *pK2Info)
{
if ((pK2Info != NULL) && (pK2Info[0] != '\0'))
{
// Look up proper sync/disp in map! For now, should only be one, until K2 is updated to support
// multiple clients.
#ifdef WIN32
logK2SamdMsg("K2 Data Received: %s", pK2Info);
#endif
if (!_k2ClientMap.empty())
{
SyncAndMessageDispatch *pThis = _k2ClientMap.begin()->second;
pThis->_k2Status = pK2Info;
pThis->_k2Status.erase(0, pThis->_k2Status.find(':')+1);
pThis->_k2Good = ( (::strncmp(pK2Info, K2_MESSAGE_PREFIX_FROM_CLOUD, sizeof(K2_MESSAGE_PREFIX_FROM_CLOUD)-1) == 0) ||
(::strncmp(pK2Info, K2_MESSAGE_PREFIX_DEVICE_STATUS, sizeof(K2_MESSAGE_PREFIX_DEVICE_STATUS)-1) == 0) );
if (::strncmp(pK2Info, K2_MESSAGE_PREFIX_INTERNAL, sizeof(K2_MESSAGE_PREFIX_INTERNAL)-1) == 0)
pThis->_k2Good |= ( (pThis->_k2Status.find("heartbeat") == 0) ||
(pThis->_k2Status.find("status") == 0) ||
(pThis->_k2Status.find("logged in") == 0) );
if (!pThis->_k2Error)
pThis->_k2Error = (pThis->_k2Status.find(K2_MESSAGE_2ND_PREFIX_FOR_ERRORS) == 0);
std::cout << DEBUG_MSG << __FILE__ << "(" << __LINE__ << "): k2DataReceived: handle: " << pK2Info <<
" with _instance " << pThis <<
" _k2Status: " << pThis->_k2Status <<
" _k2Good: " << ((pThis->_k2Good) ? "true" : "false") <<
" _k2Error: " << ((pThis->_k2Error) ? "true" : "false") << std::endl;
if (pThis->_k2Error && (pThis->_k2Status.find("heartbeat") == 0))
{
pThis->_k2Error = false;
std::cout << DEBUG_MSG << __FILE__ << "(" << __LINE__ << "): Invoking onConnectionRestored" << std::endl;
#ifdef WIN32
logK2SamdMsg("K2 reconnected...");
#endif
// We lost the K2 connection. Check for pubsub messages...
pThis->subscribeAndDispatch();
if ((pThis->getInstanceObserver() != NULL) && !pThis->getInstanceObserver()->onConnectionRestored())
return;
}
if (pThis->getInstanceObserver() != NULL)
{
PeerObserver *pObserver = pThis->getInstanceObserver();
if (::strncmp(pK2Info, K2_MESSAGE_PREFIX_FROM_CLOUD, sizeof(K2_MESSAGE_PREFIX_FROM_CLOUD)-1) == 0)
{
std::cout << DEBUG_MSG << __FILE__ << "(" << __LINE__ << "): Subscribing..." << std::endl;
pThis->subscribeAndDispatch();
// Hack! Using server version 1.2.8.5, k2 does not reliably notify
// all available events. Sweep up any pubsub messages still waiting.
// A Jira ticket will remind us to remove this once server logic is
// enhanced.
pThis->subscribeAndDispatch();
}
else if (::strncmp(pK2Info, K2_MESSAGE_PREFIX_INTERNAL, sizeof(K2_MESSAGE_PREFIX_INTERNAL)-1) == 0)
{
EyeTime curTime;
if (curTime.GetTimeMs() > pThis->getInactivityLogoutTime())
pObserver->onInactivityTimeReached();
}
}
}
}
}
std::string SyncAndMessageDispatch::getK2Status()
{
return _k2Status.c_str();
}
bool SyncAndMessageDispatch::isGoodK2Status()
{
return _k2Good;
}
long SyncAndMessageDispatch::getInactivityLogoutTime()
{
return _inactivityLogoutTime;
}
void SyncAndMessageDispatch::setInactivityLogoutTime(long nInactiveTime)
{
_inactivityLogoutTime = nInactiveTime;
}
void SyncAndMessageDispatch::subscribeAndDispatch()
{
if (_pPeerObserver != NULL)
_pPeerObserver->onSyncAndDispatchStart(_devURN.c_str());
if (_pWebSvc == NULL)
{
if (_pPeerObserver != NULL)
_pPeerObserver->onFatalError(PeerObserver::ERROR_CODES::ERR_NO_NETWORK_SVC, "No Web service interface provided for subscribe and dispatch");
}
else
{
#ifdef WIN32
// Limit access to this on a per thread basis...
static CRITICAL_SECTION sCS;
static bool bCSInited = false;
if (!bCSInited)
bCSInited = (::InitializeCriticalSectionAndSpinCount(&sCS, 0x00000400) == TRUE);
CriticalSectionLocker csLock(sCS);
#endif
// Invokes '/pubsub/subscribe' web service request
JSONObject msgsObj(_pWebSvc->getPubSubMessages());
if (msgsObj.gettype() == JSONObject::J_ARRAY)
{
#ifdef WIN32
logK2SamdMsg("SAMD got %d pubsub messages from Cloudguard.", msgsObj.getnumelements());
#endif
EyeTime lastMsgSync, lastGroupSync, lastCloudSync;
bool bDisMsgSync = false, bDidGroupSync = false, bDidCloudSync = false;
for (int i = 0; i < msgsObj.getnumelements(); ++i)
{
const JSONObject &msg = reinterpret_cast<const JSONObject &>(msgsObj[i]);
#ifdef WIN32
logK2SamdMsg("%s", msg.toString().c_str());
#endif
PubSubMessage pubSub (msg);
// Check to see if we can ignore this message. The parameters are:
// 1) Is this a SHARE?
// 2) Did we sync this mount yet?
// 3) If we did sync, is the time on this SHARE newer then the last sync time?
// We will process the SHARE if all of the above are true. We can ignore it, if (1) and (2) are true, but (3) is false.
EyeTime *pLastSync = NULL;
EyeTime msgTime;
bool bProcessMsg = true;
if (pubSub.getAction() == PubSubMessage::Action::SHARE)
{
// Check!
JSONObjectPtr pMsgData = pubSub.getActionData();
msgTime = pubSub.getIssueDate();
WebSvcs::MOUNT_TYPE msgMount = getMountType(pMsgData->getJSONString("mount"));
if ((msgMount == WebSvcs::MOUNT_TYPE::eMessages) && bDisMsgSync)
pLastSync = &lastMsgSync;
else if ((msgMount == WebSvcs::MOUNT_TYPE::eGroupShared) && bDidGroupSync)
pLastSync = &lastGroupSync;
else if ((msgMount == WebSvcs::MOUNT_TYPE::eSynchronized) && bDidCloudSync)
pLastSync = &lastCloudSync;
if (pLastSync != NULL)
{
pLastSync->SetTime(_pWebSvc->getLastSyncTime(msgMount).c_str());
if (msgTime.Compare(pLastSync) < 0)
bProcessMsg = false;
}
// At this point, we are either going to process the SHARE, because we haven't processed one for this
// mount yet. Or, we went through our checklist, and don't need to process (which means we had an initial
// SHARE processed already).
switch (msgMount)
{
case WebSvcs::MOUNT_TYPE::eMessages:
bDisMsgSync = true;
break;
case WebSvcs::MOUNT_TYPE::eGroupShared:
bDidGroupSync = true;
break;
case WebSvcs::MOUNT_TYPE::eSynchronized:
bDidCloudSync = true;
break;
}
}
if (bProcessMsg)
{
doRequest(pubSub);
if (_pPeerObserver != NULL)
_pPeerObserver->onSubscribedMessageReceived(pubSub.toString().c_str());
}
disposeOf(pubSub);
}
}
if (_pPeerObserver != NULL)
_pPeerObserver->onSyncAndDispatchStop(_devURN.c_str());
}
}
void SyncAndMessageDispatch::disposeOf(const PubSubMessage &msg)
{
if (_pWebSvc == NULL)
{
std::cout << ERROR_MSG << __FILE__ << "(" << __LINE__ << "): No Web service provided!" << std::endl;
if (_pPeerObserver != NULL)
_pPeerObserver->onFatalError(PeerObserver::ERROR_CODES::ERR_NO_NETWORK_SVC, "No Web service interface provided for disposing of message");
}
else
{
_pWebSvc->disposePubSubMessage(msg.toString().c_str());
if (_pPeerObserver != NULL)
_pPeerObserver->onMessageDisposed(msg.toString().c_str());
}
}
WebSvcs::MOUNT_TYPE SyncAndMessageDispatch::getMountType (const std::string &mount)
{
WebSvcs::MOUNT_TYPE mountType = WebSvcs::eNone;
if (mount == _pWebSvc->getMount(WebSvcs::eMessages))
mountType = WebSvcs::eMessages;
else if (mount == _pWebSvc->getMount(WebSvcs::eOffice))
mountType = WebSvcs::eOffice;
else if (mount == _pWebSvc->getMount(WebSvcs::eSynchronized))
mountType = WebSvcs::eSynchronized;
else if (mount == _pWebSvc->getMount(WebSvcs::eGroupShared))
mountType = WebSvcs::eGroupShared;
return mountType;
}
void SyncAndMessageDispatch::doRequest(const PubSubMessage &msg)
{
if (_pWebSvc == NULL)
{
std::cout << ERROR_MSG << __FILE__ << "(" << __LINE__ << "): No Web service provided!" << std::endl;
if (_pPeerObserver != NULL)
_pPeerObserver->onFatalError(PeerObserver::ERROR_CODES::ERR_NO_NETWORK_SVC, "No Web service interface provided for message request");
}
else
{
PubSubMessagePtr pReply;
PubSubMessage::Action replyAction = msg.getReplyToAction();
if ((msg.getInReplyToMessageIdentifier() == "") && !msg.isExpired() && (replyAction != PubSubMessage::Action::NONE))
{
if (msg.isManualUserReplyRequired())
{
// This is to allow automatic responses.
if (_pPeerObserver != NULL)
{
std::string replyStr;
if (pReply != NULL)
replyStr = _pPeerObserver->automaticReplyToManualMessage(msg.toString().c_str(), pReply->toString().c_str());
else
replyStr = _pPeerObserver->automaticReplyToManualMessage(msg.toString().c_str(), "");
if (replyStr != "")
pReply.reset(new PubSubMessage(replyStr));
else
pReply.reset();
}
if (pReply == NULL)
{
// Cannot reply automatically. Perisist locally.
switch (msg.getAction())
{
case PubSubMessage::Action::SHARE:
{
JSONObjectPtr pMsgData = msg.getActionData();
if (pMsgData == NULL)
pMsgData.reset(new JSONObject());
std::string sig = pMsgData->getJSONString(Permissions::SIGNATURE_KEY, "unknown");
File someFile;
std::string nodeStr = _pWebSvc->createNode(pMsgData->toString());
_pWebSvc->syncNode(nodeStr);
someFile = _pWebSvc->getNodeAbsolutePath(nodeStr);
// Do a delta sync too.
StorageNode node(nodeStr);
node.setAbsPath("");
node.setPath("/");
node.setURN("");
node.setType(StorageNodeType::SNT_DEVICE_ROOT);
_pWebSvc->syncNode(node.toString());
if (_pPeerObserver != NULL)
_pPeerObserver->onMessagePersisted(msg.toString().c_str());
pReply.reset();
}
break;
}
}
}
if ((pReply != NULL) && pReply->isValid())
{
std::string resStr = _pWebSvc->sendPubSubMessage(pReply->toString().c_str());
if (_pPeerObserver != NULL)
_pPeerObserver->onResponseSent(resStr);
}
}
}
}