C3/Src/Core/Profiler.cpp

1538 lines
65 KiB
C++

#include "StdAfx.h"
#include "Profiler.h"
#include "NodeRelay.h"
#include "GateRelay.h"
#include "ConnectorBridge.h"
#include "DeviceBridge.h"
#include "Common/FSecure/CppTools/Utils.h"
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
std::mutex FSecure::C3::Core::Profiler::Profile::m_Mutex;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::C3::Core::Profiler::Profiler(std::filesystem::path snapshotPath) : m_SnapshotPath(std::move(snapshotPath))
{
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::Initialize(std::string name, std::shared_ptr<GateRelay> gateway)
{
m_Gateway = Gateway{ shared_from_this(), name, gateway };
for (auto&& e : m_Gateway->m_Gateway.lock()->m_InterfaceFactory.GetMap<AbstractPeripheral>())
m_BindersMappings.emplace_back(e.first, e.second.m_ClousureConnectorHash);
RestoreFromSnapshot();
std::thread(&Profiler::DumpSnapshots, this).detach();
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::HandleActionsPacket(ByteView actionsPacket)
{
std::scoped_lock lock(m_AccessMutex);
try
{
auto actions = json::parse(std::string{ actionsPacket });
auto jActions = actions.find("Command");
if (jActions == actions.end())
throw std::runtime_error{ "Command object is missing" };
(*jActions)["ByteForm"] = base64::encode(TranslateCommand(*jActions));
// Helper lambda that read particular element from actions JSON.
auto ReadJsonElement = [&actions](std::string_view elementName)
{
auto element = actions.find(elementName);
if (element != actions.end())
return element;
throw std::runtime_error{ std::string{ elementName } +" is not specified." };
};
// Parse AgentId.
if (auto relayAgentId = ReadJsonElement("relayAgentId"); relayAgentId->is_null())
m_Gateway->ParseAndRunCommand(actions);
else if (auto agent = m_Gateway->m_Agents.Find(relayAgentId->get<std::string>()))
agent->ParseAndRunCommand(actions);
else
throw std::runtime_error{ "Unknown AgentId." };
}
catch (std::exception& exception)
{
if (auto gateway = m_Gateway->m_Gateway.lock())
gateway->Log({ "Caught an exception while parsing Action. "s + exception.what(), FSecure::C3::LogMessage::Severity::Error});
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::C3::Core::Profiler::Profile FSecure::C3::Core::Profiler::Get()
{
return Profile{ *m_Gateway };
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
uint32_t FSecure::C3::Core::Profiler::GetBinderTo(uint32_t id)
{
for (auto&& e : m_BindersMappings)
if (e.first == id)
return e.second;
else if (e.second == id)
return e.first;
return 0u;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::ByteVector FSecure::C3::Core::Profiler::TranslateCommand(json const& command)
{
return ByteVector::Create(command.at("id").get<uint16_t>()).Concat(TranslateArguments(command.at("arguments")));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::ByteVector FSecure::C3::Core::Profiler::TranslateArguments(json const& arguments)
{
ByteVector ret;
auto translate = [&ret](auto arg) { ret.Concat(Translate(arg.at("type"), arg.at("value"))); };
for (auto argument : arguments)
{
if (!argument.is_array())
translate(argument);
else
for (auto const subargument : argument)
translate(subargument);
}
return ret;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::ByteVector FSecure::C3::Core::Profiler::TranslateStartupCommand(json const& jcommand)
{
auto commandId = jcommand.at("id").get<std::uint16_t>();
auto const& createCommands = m_Gateway->m_CreateCommands;
auto command = std::find_if(createCommands.cbegin(), createCommands.cend(), [commandId](Gateway::CreateCommand const& cc) { return cc.m_IsDevice && cc.m_Id == commandId; });
// this function will only support create commands
if (command == createCommands.cend() || !command->m_IsDevice)
throw std::logic_error{ "Failed to find a create command" };
return ByteVector::Create(command->m_IsNegotiableChannel, command->m_Hash).Concat(TranslateArguments(jcommand.at("arguments")));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::ByteVector FSecure::C3::Core::Profiler::Translate(std::string const& type, json::value_type const& value)
{
if (type == "uint8")
return ByteVector{}.Write(value.is_string() ? FSecure::Utils::SafeCast<uint8_t>(std::stoull(value.get<std::string>())) : value.get<uint8_t>());
if (type == "uint16")
return ByteVector{}.Write(value.is_string() ? FSecure::Utils::SafeCast<uint16_t>(std::stoull(value.get<std::string>())) : value.get<uint16_t>());
if (type == "uint32")
return ByteVector{}.Write(value.is_string() ? FSecure::Utils::SafeCast<uint32_t>(std::stoull(value.get<std::string>())) : value.get<uint32_t>());
if (type == "uint64")
return ByteVector{}.Write(value.is_string() ? FSecure::Utils::SafeCast<uint64_t>(std::stoull(value.get<std::string>())) : value.get<uint64_t>());
if (type == "int8")
return ByteVector{}.Write(value.is_string() ? FSecure::Utils::SafeCast<int8_t>(std::stoll(value.get<std::string>())) : value.get<int8_t>());
if (type == "int16")
return ByteVector{}.Write(value.is_string() ? FSecure::Utils::SafeCast<int16_t>(std::stoll(value.get<std::string>())) : value.get<int16_t>());
if (type == "int32")
return ByteVector{}.Write(value.is_string() ? FSecure::Utils::SafeCast<int32_t>(std::stoll(value.get<std::string>())) : value.get<int32_t>());
if (type == "int64")
return ByteVector{}.Write(value.is_string() ? FSecure::Utils::SafeCast<int64_t>(std::stoll(value.get<std::string>())) : value.get<int64_t>());
if (type == "float")
return ByteVector{}.Write(value.is_string() ? std::stof(value.get<std::string>()) : value.get<float>());
if (type == "boolean")
return ByteVector{}.Write(value.get<bool>());
if (type == "string" || type == "ip")
return ByteVector{}.Write(value.get<std::string>());
if (type == "binary")
return ByteVector{}.Write(base64::decode<ByteVector>(value.get<std::string>()));
throw std::invalid_argument{ "Unrecognized argument type: " + type };
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::DumpSnapshots()
{
auto sp = GetSnapshotProxy();
while (true)
{
if (sp.CheckUpdates())
{
const auto snapshotTmpPath = std::filesystem::path(m_SnapshotPath).replace_extension(".tmp");
{
std::ofstream snapshotTmp{ snapshotTmpPath };
snapshotTmp << sp.GetSnapshot().dump() << std::endl;
}
std::filesystem::rename(snapshotTmpPath, m_SnapshotPath);
}
std::this_thread::sleep_for(1s);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::RestoreFromSnapshot()
{
json snapshot;
// get snapshot
{
auto ifile = std::ifstream{ m_SnapshotPath };
auto str = std::string((std::istreambuf_iterator<char>(ifile)), std::istreambuf_iterator<char>());
if (str.empty())
return;
snapshot = json::parse(str);
}
// restore gateway.
{
for (auto&& b : snapshot["_RegisteredBuilds"])
{
m_Gateway->AddAgentBuild
(
b.at("_bid").get<BuildId::UnderlyingIntegerType>(),
BuildProperties
{
b.at("_x64").get<bool>(),
b.at("_ban").get<bool>(),
b.at("_startCmd"),
}
);
}
for (auto&& channel : snapshot["channels"])
{
auto gateRelay = Get().m_Gateway.m_Gateway.lock();
if (!gateRelay)
throw std::logic_error("Parent GateRelay cannot be locked");
auto byteForm = base64::decode<ByteVector>(channel["startupCommand"]["ByteForm"].get<std::string>());
auto readView = ByteView{ byteForm };
readView.remove_prefix(sizeof(uint16_t)); // remove number of command
auto did = DeviceId{ channel["iId"].get<std::string>() };
auto device = gateRelay->CreateAndAttachDevice
(
did,
channel["type"].get<HashT>(),
channel["isNegotiationChannel"].get<bool>(),
readView
);
auto jitter = std::pair{ FSecure::Utils::ToMilliseconds(channel["jitter"][0].get<float>()), FSecure::Utils::ToMilliseconds(channel["jitter"][1].get<float>()) };
device->SetUpdateDelay(jitter.first, jitter.second);
auto profile = Get(); // we need to take profile each time, as it is also taken in CreateAndAttachDevice and that would lead to deadlock.
auto channelProfile = profile.m_Gateway.m_Channels.Find(did);
channelProfile->m_StartupArguments = channel["startupCommand"];
channelProfile->m_Jitter = jitter;
}
for (auto&& connector : snapshot["connectors"])
{
auto gateRelay = Get().m_Gateway.m_Gateway.lock();
if (!gateRelay)
throw std::logic_error("Parent GateRelay cannot be locked");
auto byteForm = base64::decode<ByteVector>(connector["startupCommand"]["ByteForm"].get<std::string>());
auto readView = ByteView{ byteForm };
readView.remove_prefix(sizeof(uint16_t)); // remove number of command
auto did = connector["type"].get<HashT>();
gateRelay->TurnOnConnector
(
did,
readView
);
auto profile = Get(); // we need to take profile each time, as it is also taken in CreateAndAttachDevice and that would lead to deadlock.
auto connectorProfile = profile.m_Gateway.m_Connectors.Find(did);
connectorProfile->m_StartupArguments = connector["startupCommand"];
};
}
// restore agents
{
auto profile = Get();
for (auto&& route : snapshot["routes"])
profile.m_Gateway.ReAddRoute(RouteId(route["destinationAgent"].get<std::string>(), route["receivingInterface"].get<std::string>()), route["outgoingInterface"].get<std::string>(), route["isNeighbour"].get<bool>());
for (auto&& relay : snapshot["relays"])
{
auto agent = profile.m_Gateway.ReAddAgent
(
relay["agentId"].get<std::string>(),
relay["buildId"].get<std::string>(),
base64::decode<ByteVector>(relay["publicKey"].get<std::string>()),
false,
relay["timestamp"].get<int32_t>(),
HostInfo(relay["hostInfo"])
);
agent->m_LastDeviceId = relay["_LastDeviceId"].get<DeviceId::UnderlyingIntegerType>();
for (auto&& channel : relay["channels"])
{
auto device = agent->ReAddChannel(channel["iId"].get<std::string>(), channel["type"].get<HashT>(), channel["isReturnChannel"].get<bool>(), channel["isNegotiationChannel"].get<bool>());
device->m_StartupArguments = channel["startupCommand"];
device->m_Jitter = std::pair{ FSecure::Utils::ToMilliseconds(channel["jitter"][0].get<float>()), FSecure::Utils::ToMilliseconds(channel["jitter"][1].get<float>()) };
}
for (auto&& peripheral : relay["peripherals"])
{
auto device = agent->ReAddPeripheral(peripheral["iId"].get<std::string>(), peripheral["type"].get<HashT>());
device->m_StartupArguments = peripheral["startupCommand"];
device->m_Jitter = std::pair{ FSecure::Utils::ToMilliseconds(peripheral["jitter"][0].get<float>()), FSecure::Utils::ToMilliseconds(peripheral["jitter"][1].get<float>()) };
}
for (auto&& route : relay["routes"])
agent->ReAddRoute(RouteId(route["destinationAgent"].get<std::string>(), route["receivingInterface"].get<std::string>()), route["outgoingInterface"].get<std::string>(), route["isNeighbour"].get<bool>());
}
}
// Restore real routing table
{
auto profile = Get();
auto gateRelay = profile.m_Gateway.m_Gateway.lock();
if (!gateRelay)
throw std::logic_error("Parent GateRelay cannot be locked");
auto const& routes = profile.m_Gateway.m_Routes.GetUnderlyingContainer();
for (auto const& route : routes)
{
auto outgoingDevice = gateRelay->FindDevice(route.m_OutgoingDevice);
if (!outgoingDevice)
throw std::logic_error("Outgoing device cannot be locked");
gateRelay->AddRoute(route.m_Id, outgoingDevice);
}
profile.m_Gateway.m_LastDeviceId = snapshot["_LastDeviceId"].get<DeviceId::UnderlyingIntegerType>();
}
// restore connections in connector. It must be last step in the chain.
{
auto gateway = m_Gateway->m_Gateway.lock();
if (!gateway)
return;
for (auto&& relay : snapshot["relays"])
{
auto profile = Get();
for (auto&& peripheral : relay["peripherals"])
{
auto connectorHash = GetBinderTo(peripheral["type"].get<HashT>());
auto connector = gateway->m_Connectors.Find([&](auto const& e) { return e->GetNameHash() == connectorHash; });
if (!connector)
continue;
auto route = gateway->FindRoute(AgentId{ relay["agentId"].get<std::string>() });
if (!route)
continue;
auto creationCommand = base64::decode<ByteVector>(peripheral["startupCommand"]["ByteForm"].get<std::string>());
auto readView = ByteView{ creationCommand };
auto connectionId = ByteVector::Create(RouteId{ route->m_RouteId.GetAgentId(), DeviceId{ peripheral["iId"].get<std::string>() } });
readView.remove_prefix(sizeof(uint16_t)); // remove command id
connector->PeripheralCreationCommand(connectionId, readView); // throw away the response.
connector->OnCommandFromBinder(connectionId, base64::decode<ByteVector>(peripheral["startupCommand"]["FirstResponse"].get<std::string>()));
}
}
}
// Gateway peripherals will not be restored. Supporting this case is not important, and they would need new staging to be done in this function. Might be added in the future.
//for (auto&& peripheral : snapshot["peripherals"])
//{
// auto gateRelay = Get().m_Gateway.m_Gateway.lock();
// if (!gateRelay)
// throw std::logic_error("Parent GateRelay cannot be locked");
// auto byteForm = base64::decode<ByteVector>(peripheral["startupCommand"]["ByteForm"].get<std::string>());
// auto readView = ByteView{ byteForm };
// readView.remove_prefix(sizeof(uint16_t)); // remove number of command
// auto did = DeviceId{ peripheral["iId"].get<std::string>() };
// gateRelay->CreateAndAttachDevice
// (
// did,
// peripheral["type"].get<HashT>(),
// false,
// readView
// );
// auto profile = Get(); // we need to take profile each time, as it is also taken in CreateAndAttachDevice and that would lead to deadlock.
// auto peripheralProfile = profile.m_Gateway.m_Peripherals.Find(did);
// peripheralProfile->m_StartupArguments = peripheral["startupCommand"];
//}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
json FSecure::C3::Core::Profiler::HandleNewBuildMessage(json const& message)
{
auto newBuildId = message.at("BuildId").get<BuildId::UnderlyingIntegerType>();
auto const& command = message.at("Command");
auto commandBinary = TranslateStartupCommand(command);
auto response = json
{
{"BinaryCommand", base64::encode(commandBinary)},
};
{
// Add build to profiler
auto arch = message.at("Arch").get<std::string>();
std::transform(arch.begin(), arch.end(), arch.begin(), [](char c) { return std::tolower(c); });
auto isX64 = arch == "x64";
Get().m_Gateway.AddAgentBuild(newBuildId, { isX64, false, command });
}
return response;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
json FSecure::C3::Core::Profiler::Gateway::CreateProfileSnapshot() const
{
auto gateway = m_Gateway.lock();
if (!gateway)
return {};
// Fill in basic data.
json profile = {
{ "agentId", gateway->m_AgentId.ToString() },
{ "buildId", gateway->m_BuildId.ToString() },
{"_LastDeviceId", m_LastDeviceId },
{ "timestamp", m_LastSeen },
{ "isActive", true}
};
if (!m_ErrorState.empty())
profile["error"] = m_ErrorState;
// Populate output json with all containers and their elements Profiles.
profile["channels"] = m_Channels.CreateProfileSnapshot();
profile["peripherals"] = m_Peripherals.CreateProfileSnapshot();
profile["routes"] = m_Routes.CreateProfileSnapshot();
profile["connectors"] = m_Connectors.CreateProfileSnapshot();
profile["relays"] = m_Agents.CreateProfileSnapshot();
json registeredBuilds;
for (auto b : m_AgentBuilds)
{
registeredBuilds.push_back
(
{
{"_bid", b.first.ToUnderlyingType()},
{"_ban", b.second.m_IsBanned},
{"_x64", b.second.m_IsX64},
{"_startCmd", b.second.m_StartupCmd},
}
);
}
profile["_RegisteredBuilds"] = registeredBuilds;
return profile;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::C3::Core::Profiler::Agent::Agent(std::weak_ptr<Profiler> owner, AgentId agentId, BuildId buildId, FSecure::Crypto::PublicKey encryptionKey, bool isBanned, int32_t lastSeen, bool isX64, HostInfo hostInfo)
: Relay(owner, agentId, buildId, lastSeen)
, m_EncryptionKey(encryptionKey)
, m_HostInfo(std::move(hostInfo))
, m_IsBanned(isBanned)
, m_IsX64(isX64)
{
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
json FSecure::C3::Core::Profiler::Agent::CreateProfileSnapshot() const
{
// Fill in basic data.
json profile = {
{ "agentId", m_Id.ToString() },
{ "buildId", m_BuildId.ToString() },
{ "publicKey", m_EncryptionKey.ToBase64() },
{ "_LastDeviceId", m_LastDeviceId },
{ "timestamp", m_LastSeen },
{ "hostInfo", m_HostInfo },
{ "isActive", FSecure::Utils::TimeSinceEpoch() - m_LastSeen < 300 }
};
if (!m_ErrorState.empty())
profile["error"] = m_ErrorState;
// Populate output json with all containers and their elements Profiles.
profile["channels"] = m_Channels.CreateProfileSnapshot();
profile["peripherals"] = m_Peripherals.CreateProfileSnapshot();
profile["routes"] = m_Routes.CreateProfileSnapshot();
return profile;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::Agent::ParseAndRunCommand(json const& jCommandElement) noexcept(false)
{
auto profiler = m_Owner.lock();
if (!profiler)
return; // probably shutting down
// check if the command should be run on device
std::optional<DeviceId> deviceId;
bool deviceIsChannel = false; // have meaning only when it is command for device.
if (auto jChannelId = jCommandElement.find("channelId"); jChannelId != jCommandElement.end() && !jChannelId->is_null())
{
if (auto channel = m_Channels.Find(jChannelId->get<std::string>()); channel)
{
deviceId = channel->m_Id;
deviceIsChannel = true;
}
else
throw std::runtime_error{ "Unknown ChannelId." };
}
else if (auto jPeripheralId = jCommandElement.find("peripheralId"); jPeripheralId != jCommandElement.end() && !jPeripheralId->is_null())
{
if (auto peripheral = m_Peripherals.Find(jPeripheralId->get<std::string>()); peripheral)
deviceId = peripheral->m_Id;
else
throw std::runtime_error{ "Unknown PeripheralId." };
}
auto commandWithArgs = base64::decode<ByteVector>(jCommandElement["Command"]["ByteForm"].get<std::string>());
auto gateRelay = profiler->m_Gateway->m_Gateway.lock();
if (!gateRelay)
return; // probably shutting down
if (deviceId)
{
std::function<void()> finalizer = []() {};
auto commandReadView = ByteView{ commandWithArgs };
auto commandId = commandReadView.Read<std::uint16_t>();
if (commandId > static_cast<std::uint16_t>(-256))
{
// generic interface commands
switch (static_cast<FSecure::C3::Command>(commandId))
{
case FSecure::C3::Command::Close:
finalizer = [&]()
{
if (deviceIsChannel)
{
m_Channels.TryRemove(*deviceId);
m_Routes.RemoveIf([deviceId](Profiler::Route const& route) { return route.m_OutgoingDevice == deviceId; });
}
else
{
// Find connector hash
auto element = m_Peripherals.Find(*deviceId);
if (!element)
return;
auto connectorHash = profiler->GetBinderTo(element->m_TypeHash);
// remove peripheral
m_Peripherals.TryRemove(*deviceId);
// Get connector
auto connector = gateRelay->m_Connectors.Find([&](auto const& e) { return e->GetNameHash() == connectorHash; });
if (!connector)
return;
// Remove connection.
connector->CloseConnection(ByteVector::Create(RouteId{ m_Id, *deviceId }));
}
};
break;
case FSecure::C3::Command::UpdateJitter:
finalizer = [this, deviceId, deviceIsChannel, commandReadView]() mutable
{
Device* device = deviceIsChannel ? m_Channels.Find(*deviceId) : m_Peripherals.Find(*deviceId);
if (!device)
throw std::runtime_error{ "Device not found" };
device->m_Jitter.first = FSecure::Utils::ToMilliseconds(commandReadView.Read<float>());
device->m_Jitter.second = FSecure::Utils::ToMilliseconds(commandReadView.Read<float>());
};
break;
default:
break;
}
}
auto route = gateRelay->FindRoute(m_Id);
if (!route)
throw std::runtime_error("Failed to find route to agent id = " + m_Id.ToString());
auto outgoingChannel = route->m_Channel.lock();
if (!outgoingChannel)
throw std::runtime_error("Tried to send command through dead channel"); // TODO maybe try through different route
auto query = ProceduresG2X::RunCommandOnDeviceQuery::Create(route->m_RouteId, gateRelay->m_Signature, m_EncryptionKey, gateRelay->m_DecryptionKey, *deviceId, ByteView{ commandWithArgs });
gateRelay->LockAndSendPacket(query->ComposeQueryPacket(), outgoingChannel);
finalizer();
}
else// If we're here then let NodeRelay run Command on itself.
{
PerformCreateCommand(jCommandElement);
RunCommand(commandWithArgs);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::Agent::RunCommand(ByteView commandWithArguments)
{
auto commandReadView = commandWithArguments;
auto commandId = commandReadView.Read<std::uint16_t>();
// this function handle only last part of commands range.
if (commandId <= static_cast<uint16_t>(-256))
return;
auto profiler = m_Owner.lock();
if (!profiler)
return; // probably shutting down
auto gateRelay = profiler->m_Gateway->m_Gateway.lock();
if (!gateRelay)
return; // probably shutting down
auto route = gateRelay->FindRoute(m_Id);
if (!route)
throw std::runtime_error("Failed to find route to agent id = " + m_Id.ToString());
std::function<void()> finalizer = []() {};
switch (static_cast<Command>(commandId))
{
case Command::Close:
{
finalizer = [&]()
{
auto owner = m_Owner.lock();
if (!owner)
throw std::runtime_error{ "Cannot obtain owner" };
m_Channels.Clear();
for (auto&& element : m_Peripherals.GetUnderlyingContainer())
{
auto connectorHash = profiler->GetBinderTo(element.m_TypeHash);
auto connector = gateRelay->m_Connectors.Find([&](auto const& e) { return e->GetNameHash() == connectorHash; });
if (!connector)
break;
// Remove connection.
connector->CloseConnection(ByteVector::Create(RouteId{ m_Id, element.m_Id }));
}
m_Peripherals.Clear();
owner->Get().m_Gateway.m_Agents.Remove(m_Id);
};
break;
}
case Command::CreateRoute:
{
finalizer = [this, commandReadView]() mutable
{
auto [ridStr, didStr, isNbr] = commandReadView.Read<std::string_view, std::string_view, bool>();
ReAddRoute(ridStr, didStr, isNbr);
};
break;
}
case Command::RemoveRoute:
{
finalizer = [this, commandReadView]() mutable
{
ReRemoveRoute(commandReadView.Read<std::string_view>());
};
break;
}
case Command::SetGRC:
{
finalizer = [this, commandReadView]() mutable
{
auto did = DeviceId{ commandReadView.Read<std::string_view>() };
if (!m_Channels.Find(did))
throw std::runtime_error{ "Channel not found" };
for (auto& e : m_Channels.GetUnderlyingContainer())
e.m_IsReturnChannel = e.m_Id == did;
};
break;
}
case Command::Ping:
break;
default:
throw std::runtime_error("Profiler received an unknown command for agent id: " + m_Id.ToString());
}
auto outgoingChannel = route->m_Channel.lock();
if (!outgoingChannel)
throw std::runtime_error("Tried to send command through dead channel"); // TODO maybe try through different route
auto query = ProceduresG2X::RunCommandOnAgentQuery::Create(route->m_RouteId, gateRelay->m_Signature, m_EncryptionKey, gateRelay->m_DecryptionKey, commandWithArguments);
gateRelay->LockAndSendPacket(query->ComposeQueryPacket(), outgoingChannel);
finalizer();
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::Agent::PerformCreateCommand(json const& jCommandElement)
{
auto profiler = m_Owner.lock();
if (!profiler)
return; // probably shutting down
auto gateRelay = profiler->m_Gateway->m_Gateway.lock();
if (!gateRelay)
return; // probably shutting down
auto route = gateRelay->FindRoute(m_Id);
if (!route)
throw std::runtime_error("Failed to find route to agent id = " + m_Id.ToString());
auto commandWithArguments = base64::decode<ByteVector>(jCommandElement["Command"]["ByteForm"].get<std::string>());
// check if command is a create device command -> then we need to assign deviceId and retreive its hash and repack commandWithArguments
// [CommandId{CreateAndAttachDevice}(u16)][NewDeviceId(u16)][NewDeviceTypeHash(HashT)][arguments]
auto commandReadView = ByteView{ commandWithArguments };
auto commandId = commandReadView.Read<std::uint16_t>();
auto const& createCommands = profiler->m_Gateway->m_CreateCommands;
auto command = std::find_if(createCommands.cbegin(), createCommands.cend(), [commandId](Gateway::CreateCommand const& cc) { return cc.m_IsDevice && cc.m_Id == commandId; });
// this function will only suport create commands
if (command == createCommands.cend())
return;
// it is a create command
DeviceId newDeviceId = ++m_LastDeviceId;
ByteVector repacked;
repacked.Write(Command::AddDevice, newDeviceId, command->m_IsNegotiableChannel, command->m_Hash);
if (auto binder = profiler->GetBinderTo(command->m_Hash); binder && command->m_IsDevice) // peripheral, check if payload is needed.
{
auto connector = profiler->m_Gateway->m_Gateway.lock()->GetConnector(binder);
if (!connector)
throw std::runtime_error{ "Connector for requested peripheral is closed" };
auto updatedArguments = connector->PeripheralCreationCommand(ByteVector::Create(RouteId{ route->m_RouteId.GetAgentId(), newDeviceId }), commandReadView, m_IsX64);
repacked.Concat(updatedArguments);
}
else
{
repacked.Concat(commandReadView);
}
commandWithArguments = repacked;
auto outgoingChannel = route->m_Channel.lock();
if (!outgoingChannel)
throw std::runtime_error("Tried to send command through dead channel"); // TODO maybe try through different route
// push json with startup arguments to some container, use it if channel is created.
AddScheduledDevice(newDeviceId, jCommandElement["Command"]);
auto query = ProceduresG2X::RunCommandOnAgentQuery::Create(route->m_RouteId, gateRelay->m_Signature, m_EncryptionKey, gateRelay->m_DecryptionKey, commandWithArguments);
gateRelay->LockAndSendPacket(query->ComposeQueryPacket(), outgoingChannel);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::C3::Core::Profiler::Channel* FSecure::C3::Core::Profiler::Agent::ReAddChannel(Device::Id did, HashT typeNameHash, bool isReturnChannel /*= false*/, bool isNegotiationChannel /*= false*/)
{
auto channel = Relay::ReAddChannel(did, typeNameHash, isReturnChannel, isNegotiationChannel);
if (auto it = m_ScheduledDevices.find(did.ToUnderlyingType()); it != m_ScheduledDevices.cend())
channel->m_StartupArguments = it->second;
return channel;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::C3::Core::Profiler::Device* FSecure::C3::Core::Profiler::Agent::ReAddPeripheral(Device::Id did, HashT typeNameHash)
{
auto device = Relay::ReAddPeripheral(did, typeNameHash);
if (auto it = m_ScheduledDevices.find(did.ToUnderlyingType()); it != m_ScheduledDevices.cend())
device->m_StartupArguments = it->second;
return device;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::Agent::AddScheduledDevice(DeviceId deviceId, json command)
{
m_ScheduledDevices.emplace(deviceId.ToUnderlyingType(), std::move(command));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::C3::Core::Profiler::Channel* FSecure::C3::Core::Profiler::Agent::FindGrc()
{
for (auto& e : m_Channels.GetUnderlyingContainer())
if (e.m_IsReturnChannel)
return &e;
return nullptr;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::C3::Core::Profiler::Gateway::Gateway(std::weak_ptr<Profiler> owner, std::string name, std::shared_ptr<GateRelay> gateway)
: Relay(owner, gateway->m_AgentId, gateway->m_BuildId, FSecure::Utils::TimeSinceEpoch())
, m_Name(std::move(name))
, m_Gateway(gateway)
{
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::Gateway::ParseAndRunCommand(json const& jCommandElement) noexcept(false)
{
auto commandWithArgs = base64::decode<ByteVector>(jCommandElement["Command"]["ByteForm"].get<std::string>());
auto commandReadView = ByteView{ commandWithArgs };
auto conditionalRun = [&](auto jsonEntryToFind, bool isDevice) -> bool
{
if (auto jConnectorId = jCommandElement.find(jsonEntryToFind); jConnectorId != jCommandElement.end() && !jConnectorId->is_null())
{
auto id = std::stoull(jConnectorId->template get<std::string>(), nullptr, 16); // throws on error.
if (isDevice)
{
auto gateway = m_Gateway.lock();
if (auto device = gateway->FindDevice(FSecure::Utils::SafeCast<DeviceId::UnderlyingIntegerType>(id)); device)
{
auto localView = commandReadView;
switch (FSecure::C3::Command(localView.Read<std::uint16_t>()))
{
case FSecure::C3::Command::UpdateJitter:
{
Device* profilerElement = m_Channels.Find(device->GetDid());
if (!profilerElement)
profilerElement = m_Peripherals.Find(device->GetDid());
if (!profilerElement)
throw std::runtime_error{ "Device not found" };
profilerElement->m_Jitter.first = FSecure::Utils::ToMilliseconds(localView.Read<float>());
profilerElement->m_Jitter.second = FSecure::Utils::ToMilliseconds(localView.Read<float>());
break;
}
case FSecure::C3::Command::Close:
{
if (auto profilerElement = m_Peripherals.Find(device->GetDid()))
{
auto connectorHash = m_Owner.lock()->GetBinderTo(profilerElement->m_TypeHash);
auto connector = m_Gateway.lock()->m_Connectors.Find([&](auto const& e) { return e->GetNameHash() == connectorHash; });
if (!connector)
break;
// Remove connection.
connector->CloseConnection(ByteVector::Create(RouteId{ m_Id, device->GetDid() }));
}
else if (auto profilerElemnt = m_Channels.Find(device->GetDid()))
{
m_Routes.RemoveIf([did = device->GetDid()](Profiler::Route const& route) { return route.m_OutgoingDevice == did; });
}
break;
}
default:
break;
}
device->RunCommand(commandReadView);
return true;
}
}
else
{
if (auto connector = m_Connectors.Find(FSecure::Utils::SafeCast<HashT>(id)); connector)
return connector->RunCommand(commandReadView), true;
}
throw std::runtime_error{ "Unknown Id." };
}
return false;
};
if (conditionalRun("connectorId", false)) return;
if (conditionalRun("channelId", true)) return;
if (conditionalRun("peripheralId", true)) return;
// If we're here then let NodeRelay run Command on itself.
RunCommand(commandReadView);
// try performing create command.
PerformCreateCommand(jCommandElement);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
json FSecure::C3::Core::Profiler::Gateway::GetCapability()
{
// Request access to Gateway object.
auto gateway = m_Gateway.lock();
if (!gateway)
return {};
// TODO This function require refactoring.
// Construct the InitialPacket.
json initialPacket = json::parse(gateway->m_InterfaceFactory.GetCapability());
for (auto& interface : initialPacket["channels"])
EnsureCreateExists(interface);
// Create method in interface is constructor. It must be a relay/gateway command.
// initialPacket is copied to original to prevent iterator invalidation.
// last 256 commands will be reserved for common commands.
auto modifyEntry = [&, id = static_cast<uint16_t>(-257), oryginal = initialPacket](std::vector<std::string> const& relayTypes, auto interfaceType, std::vector<std::string> prefix, bool isDevice) mutable
{
auto idToErase = 0;
std::vector<std::unordered_map<std::string, std::vector<json>>> buffer;
buffer.resize(prefix.size());
for (auto&& element : oryginal[interfaceType])
{
try
{
for (auto i = 0u; i < prefix.size(); ++i)
{
auto arguments = element.at("create").at("arguments");
if (i) // NegotiationChannel command
{
if (arguments.empty() || arguments[0].size() != 2)
continue;
arguments[0] = { {"type", "string"}, {"name", "Negotiation Identifier"}, {"randomize", true}, {"description", "One identifier used to negotiate identifiers for each joining relay"} };
}
for (auto&& relayType : relayTypes)
buffer[i][relayType].push_back(json{ {"name", prefix[i] + element["name"].template get<std::string>()}, {"arguments", arguments}, {"id", id} });
m_CreateCommands.push_back({ id, initialPacket[interfaceType][idToErase]["type"].template get<uint32_t>(), isDevice, !!i }); // store command id and hash.
--id;
}
// modify initial Packet with extra entries.
initialPacket[interfaceType][idToErase].erase("create");
AddBuildInCommands(initialPacket[interfaceType][idToErase], isDevice);
}
catch (std::exception& e)
{
m_Gateway.lock()->Log({ std::string("Exception caught when parsing interface capability. ") + e.what(), LogMessage::Severity::Error });
}
++idToErase;
}
// move commands from buffer to modified json in correct order.
for (auto prefixEntryIterator = buffer.rbegin(); prefixEntryIterator != buffer.rend(); ++prefixEntryIterator)
for (auto&& relayEntry : *prefixEntryIterator)
for (auto &&e : relayEntry.second)
initialPacket[relayEntry.first]["commands"].push_back(std::move(e));
};
m_CreateCommands.clear(); // clear old create commands.
modifyEntry({ "gateway", "relay"}, "channels", { "AddChannel", "AddNegotiationChannel" }, true);
modifyEntry({ "gateway", "relay" }, "peripherals", { "AddPeripheral" }, true);
modifyEntry({ "gateway" }, "connectors", { "TurnOnConnector" }, false);
// Add common commands for relay.
auto addRelayCommand = [&](std::vector<std::string> relayTypes, json const& newCommand)
{
for (auto&& relayType : relayTypes)
initialPacket[relayType]["commands"].push_back(newCommand);
};
addRelayCommand({ "gateway", "relay" }, json{ {"name", "Close"}, {"id", static_cast<std::underlying_type_t<Command>>(Command::Close) }, {"arguments", json::array()} });
addRelayCommand({ "gateway", "relay" }, json{ {"name", "CreateRoute"}, {"id", static_cast<std::underlying_type_t<Command>>(Command::CreateRoute) }, {"arguments", {
{{"type", "string"}, {"name", "RouteID"}, {"description", "Id of route in string form."}, {"min", 1}},
{{"type", "string"}, {"name", "DeviceId"}, {"description", "Id of device in string form."}, {"min", 1}},
{{"type", "boolean"}, {"name", "Neighbor"}, {"description", "Informs if relay is direct neighbor."}, {"defaultValue", true}}
}} });
addRelayCommand({ "gateway", "relay" }, json{ {"name", "RemoveRoute"}, {"id", static_cast<std::underlying_type_t<Command>>(Command::RemoveRoute) }, {"arguments", {
{{"type", "string"}, {"name", "RouteID"}, {"description", "Id of route in string form."}, {"min", 1}}
}} });
addRelayCommand({ "relay" }, json{ {"name", "SetGatewayReturnChannel"}, {"id", static_cast<std::underlying_type_t<Command>>(Command::SetGRC) }, {"arguments", {
{{"type", "string"}, {"name", "DeviceID"}, {"description", "Id of device in string form."}, {"min", 1}}
}} });
addRelayCommand({ "relay" }, json{ {"name", "Ping"}, {"id", static_cast<std::underlying_type_t<Command>>(Command::Ping) }, {"arguments", json::array() } });
addRelayCommand({ "gateway" }, json{ {"name", "ClearNetwork"}, {"id", static_cast<std::underlying_type_t<Command>>(Command::ClearNetwork) }, {"arguments", {
{{"type", "boolean"}, {"name", "Are you sure?"}, {"description", "Confirm clearing the network. All network state will be lost, this can not be undone."}, {"default", false}}
}} });
// add extra fields.
auto gatewayPushBack = [&](auto key, auto value)
{
initialPacket["gateway"][key] = value;
};
gatewayPushBack("agentId", m_Id.ToString());
gatewayPushBack("buildId", m_BuildId.ToString());
gatewayPushBack("publicKey", Crypto::ExtractPublic(gateway->m_Signature).ToBase64());
gatewayPushBack("broadcastKey", gateway->m_BroadcastKey.ToBase64());
gatewayPushBack("name", m_Name);
return initialPacket;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::Gateway::EnsureCreateExists(json& interface)
{
if (!interface.contains("create"))
interface["create"] = json::parse(R"(
{
"arguments" :
[
{
"type": "binary",
"description": "Blob of data that will be provided to Channel constructor.",
"name": "arguments"
}
]
}
)");
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::Gateway::AddBuildInCommands(json& interface, bool isDevice)
{
interface["commands"].push_back(json{ {"name", isDevice ? "Close" : "TurnOff"}, {"id", static_cast<std::underlying_type_t<Command>>(Command::Close) }, {"arguments", json::array()} });
if (isDevice)
interface["commands"].push_back(json{ {"name", "Set UpdateDelayJitter"}, {"description", "Set delay between receiving function calls."}, {"id", static_cast<std::underlying_type_t<Command>>(Command::UpdateJitter) },
{"arguments", {
{{"type", "float"}, {"name", "Min"}, {"description", "Minimal delay in seconds"}, {"min", 0.03}},
{{"type", "float"}, {"name", "Max"}, {"description", "Maximal delay in seconds. "}, {"min", 0.03}}
}} });
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::Gateway::RunCommand(ByteView commandWithArguments)
{
auto pin = m_Gateway.lock();
if (!pin)
return;
switch (static_cast<Command>(commandWithArguments.Read<std::underlying_type_t<Command>>()))
{
case Command::Close:
pin->Close();
break;
case Command::CreateRoute:
{
pin->CreateRoute(commandWithArguments);
auto [ridStr, didStr, isNbr] = commandWithArguments.Read<std::string_view, std::string_view, bool>();
ReAddRoute(ridStr, didStr, isNbr);
break;
}
case Command::RemoveRoute:
{
pin->RemoveRoute(commandWithArguments);
ReRemoveRoute(commandWithArguments.Read<std::string_view>());
break;
}
case Command::ClearNetwork:
{
// Clear real gateway
pin->Reset();
// clear profiler view
{
auto profiler = m_Owner.lock();
auto profile = profiler->Get();
profile.m_Gateway.Reset();
}
break;
}
default:
break;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::Gateway::PerformCreateCommand(json const& jCommandElement)//GateRelay& gate, std::uint16_t commandId, ByteView arguments)
{
auto pin = m_Gateway.lock();
if (!pin)
throw std::runtime_error{ "Cannot lock gateway" };
auto& gate = *pin;
auto command = base64::decode<ByteVector>(jCommandElement["Command"]["ByteForm"].get<std::string>());
auto arguments = ByteView{ command };
auto commandId = arguments.Read<uint16_t>();
auto profiler = m_Owner.lock(); // bad code, refactor.
auto createCommandIt = std::find_if(m_CreateCommands.cbegin(), m_CreateCommands.cend(), [commandId](auto const& cc) {return cc.m_Id == commandId; });
if (createCommandIt == m_CreateCommands.cend())
return;
auto createCommand = *createCommandIt;
if (createCommand.m_IsDevice)
{
ByteVector updatedArguments;
//createCommand.
auto binder = profiler->GetBinderTo(createCommand.m_Hash);
if (binder)
{
auto connector = gate.GetConnector(binder);
if (!connector)
throw std::runtime_error{ "Connector for requested peripheral is closed" };
updatedArguments = connector->PeripheralCreationCommand(ByteVector::Create(RouteId{ gate.GetAgentId(), DeviceId(m_LastDeviceId + 1) }), arguments, FSecure::Utils::IsProcess64bit());
arguments = updatedArguments;
}
auto device = gate.CreateAndAttachDevice(++m_LastDeviceId, createCommand.m_Hash, createCommand.m_IsNegotiableChannel, arguments);
// TODO refactor - seriously bad code CreateAndAttach calls ReAddChannel / ReAddDevice and here we search for it
Device* deviceView;
if (device->IsChannel())
deviceView = profiler->Get().m_Gateway.m_Channels.Find(device->GetDid());
else
deviceView = profiler->Get().m_Gateway.m_Peripherals.Find(device->GetDid());
if (deviceView)
deviceView->m_StartupArguments = jCommandElement["Command"];
}
else
{
gate.TurnOnConnector(createCommand.m_Hash, arguments);
auto connectorView = profiler->Get().m_Gateway.m_Connectors.Find(createCommand.m_Hash);
if (connectorView)
connectorView->m_StartupArguments = jCommandElement["Command"];
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::Gateway::ReTurnOnConnector(HashT typeNameHash, std::shared_ptr<ConnectorBridge> connector)
{
m_Connectors.Add(typeNameHash, Connector{ m_Owner, typeNameHash, connector });
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::Gateway::ReTurnOffConnector(HashT connectorNameHash)
{
m_Connectors.Remove(connectorNameHash);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::Gateway::ReDeleteChannel(DeviceId iidOfDeviceToDetach)
{
m_Channels.Remove(iidOfDeviceToDetach);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::Gateway::ReDeletePeripheral(DeviceId iidOfDeviceToDetach)
{
m_Peripherals.Remove(iidOfDeviceToDetach);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::C3::Core::Profiler::Agent* FSecure::C3::Core::Profiler::Gateway::ReAddAgent(AgentId agentId, BuildId buildId, FSecure::Crypto::PublicKey encryptionKey, bool isBanned, int32_t lastSeen, HostInfo hostInfo)
{
auto build = m_AgentBuilds.find(buildId);
if (build == m_AgentBuilds.cend())
throw std::runtime_error{ "Tried to add agent with unknown buildId" };
if (build->second.m_IsBanned)
return nullptr;
auto agent = m_Agents.Add(agentId, Agent{ m_Owner, agentId, buildId, encryptionKey, isBanned, lastSeen, build->second.m_IsX64, std::move(hostInfo) });
agent->AddScheduledDevice(0u, build->second.m_StartupCmd);
return agent;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::C3::Core::Profiler::Agent* FSecure::C3::Core::Profiler::Gateway::ReAddRemoteAgent(RouteId childRouteId, BuildId buildId, FSecure::Crypto::PublicKey encryptionKey, RouteId ridOfConectionPlace, HashT childGrcHash, int32_t lastSeen, HostInfo hostInfo)
{
// add agent
auto newAgent = ReAddAgent(childRouteId.GetAgentId(), buildId, encryptionKey, false, lastSeen, std::move(hostInfo)); // TODO check if is banned
// add routes
Relay* current = this;
while (current->m_Id != ridOfConectionPlace.GetAgentId())
{
DeviceId outDevice = current->FindDirectionDevice(ridOfConectionPlace.GetAgentId());
current->ReAddRoute(childRouteId, outDevice, false);
current = FindNeighborOnDevice(*current, outDevice);
}
// add route to neighboring device
current->ReAddRoute(childRouteId, ridOfConectionPlace.GetInterfaceId(), true);
// add return channel to new agent
newAgent->ReAddChannel(childRouteId.GetInterfaceId(), childGrcHash, true);
return newAgent;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::Relay::UpdateFromNegotiationChannel(DeviceId negotiationDid, DeviceId newDeviceId, std::string newInputId, std::string newOutputId)
{
auto oldDeviceProfile = m_Channels.Find(negotiationDid);
auto newDeviceProfile = m_Channels.Find(newDeviceId);
if (oldDeviceProfile && newDeviceProfile)
{
newDeviceProfile->m_StartupArguments = oldDeviceProfile->m_StartupArguments;
newDeviceProfile->m_StartupArguments["arguments"][0] = { { {"name", "Input ID"}, {"type", "string"}, {"value", newInputId} }, { {"name", "Output ID"}, {"type", "string"}, {"value", newOutputId} } };
auto oldDeviceCmd = base64::decode<ByteVector>(oldDeviceProfile->m_StartupArguments["ByteForm"].get<std::string>());
auto oldDeviceCmdReadView = ByteView{ oldDeviceCmd };
auto [cmdId, uniqueId] = oldDeviceCmdReadView.Read<uint16_t, std::string>();
auto newDeviceCmd = ByteVector{}.Write(cmdId, newInputId, newOutputId).Concat(oldDeviceCmdReadView);
newDeviceProfile->m_StartupArguments["ByteForm"] = base64::encode(newDeviceCmd);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::C3::Core::Profiler::Agent* FSecure::C3::Core::Profiler::Gateway::FindNeighborOnDevice(Relay& relay, DeviceId did)
{
auto& routes = relay.m_Routes.GetUnderlyingContainer();
auto it = std::find_if(routes.begin(), routes.end(), [&](auto& e) { return e.m_IsNeighbour && e.m_OutgoingDevice == did; });
if (it == routes.end())
throw std::logic_error{ "There is no route from provided agent" };
auto ret = m_Agents.Find(it->m_Id.GetAgentId());
if (!ret)
throw std::logic_error{ "There is no route from provided agent" };
return ret;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::Gateway::UpdateRouteTimestamps(AgentId agentId, int32_t timestamp)
{
Agent* agent = m_Agents.Find(agentId);
if (!agent)
throw std::runtime_error{ "Agent not found" };
for (auto e : GetPathFromAgent(agent))
e->m_LastSeen = timestamp;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::C3::Core::Profiler::Agent* FSecure::C3::Core::Profiler::Gateway::FindGatewaySideAgent(Agent* agent)
{
auto& channels = agent->m_Channels.GetUnderlyingContainer();
auto grcIt = std::find_if(channels.begin(), channels.end(), [](auto& e) {return e.m_IsReturnChannel; });
if (grcIt == channels.end())
throw std::runtime_error{ "GRC not found" };
for (auto& current : m_Agents.GetUnderlyingContainer())
{
for (auto& route : current.m_Routes.GetUnderlyingContainer())
{
if (route.m_IsNeighbour && route.m_Id.GetAgentId() == agent->m_Id && route.m_Id.GetInterfaceId() == grcIt->m_Id)
return &current;
}
}
return nullptr;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
std::vector<FSecure::C3::Core::Profiler::Agent*> FSecure::C3::Core::Profiler::Gateway::GetPathFromAgent(Agent* agent)
{
std::vector<Agent*> ret;
auto depthLimit = 128;
while (agent && agent->m_Id != m_Id)
{
if (!(--depthLimit))
return {};
ret.push_back(agent);
agent = FindGatewaySideAgent(agent);
}
return ret;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool FSecure::C3::Core::Profiler::Gateway::ConnectionExist(AgentId agentId)
{
if (auto path = GetPathFromAgent(m_Agents.Find(agentId)); !path.empty())
for (auto& e : m_Routes.GetUnderlyingContainer())
if (e.m_IsNeighbour && e.m_Id.GetAgentId() == path.back()->m_Id)
return true;
return false;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::Gateway::AddAgentBuild(BuildId bid, BuildProperties properties)
{
auto emplaced = m_AgentBuilds.emplace(bid, properties);
if (!emplaced.second)
throw std::logic_error{ "Tried to add new build with existing buildId" };
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::Gateway::ConditionalUpdateChannelParameters(RouteId connectionPlace)
{
try
{
// parent is known
Relay* parent = connectionPlace.GetAgentId() == m_Id ? static_cast<Relay*>(this) : m_Agents.Find(connectionPlace.GetAgentId());
if (!parent)
return;
auto parentChannel = parent->m_Channels.Find(connectionPlace.GetInterfaceId());
if (!parentChannel || parentChannel->m_StartupArguments.is_null())
return;
Agent* agent = FindNeighborOnDevice(*parent, connectionPlace.GetInterfaceId());
auto agentChannel = agent->FindGrc();
if (!agentChannel)
return;
// Channel does not have startup arguments.
if (!agentChannel->m_StartupArguments.is_null())
return;
// build started with neg channel
auto buildIterator = m_AgentBuilds.find(agent->m_BuildId);
if (buildIterator == m_AgentBuilds.end() || buildIterator->second.m_StartupCmd["command"].get<std::string>().find("AddNegotiationChannel") == std::string::npos)
return;
// update
json startupArguments = { {"arguments", json::array()} };
startupArguments["arguments"][0][0] = parentChannel->m_StartupArguments["arguments"][0][1];
startupArguments["arguments"][0][1] = parentChannel->m_StartupArguments["arguments"][0][0];
for (auto i = 1u; i < buildIterator->second.m_StartupCmd["arguments"].size(); ++i)
startupArguments["arguments"][i] = buildIterator->second.m_StartupCmd["arguments"][i];
agentChannel->m_StartupArguments = startupArguments;
}
catch (const std::exception&)
{
return;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::Gateway::Reset()
{
m_Agents.Clear();
m_Connectors.Clear();
m_Peripherals.Clear();
m_Channels.Clear();
m_Routes.Clear();
m_AgentBuilds.clear();
m_LastDeviceId = 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::Device::RunCommand(ByteView commandWithArguments)
{
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::C3::Core::Profiler::Device::Device(std::weak_ptr<Profiler> owner, Id id, HashT typeHash)
: ProfileElement(owner)
, m_Id(id)
, m_TypeHash(typeHash)
{
if (auto ptrCh = InterfaceFactory::Instance().GetInterfaceData<AbstractChannel>(m_TypeHash); ptrCh)
m_Jitter = ptrCh->m_StartupJitter;
else if (auto ptrP = InterfaceFactory::Instance().GetInterfaceData<AbstractPeripheral>(m_TypeHash); ptrP)
m_Jitter = ptrP->m_StartupJitter;
else
throw std::runtime_error("Type hash does is not device: " + std::to_string(m_TypeHash));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::C3::Core::Profiler::Route::Route(std::weak_ptr<Profiler> owner, RouteId id, Device::Id outgoingDeviceId, bool isNeighbour)
: ProfileElement(owner)
, m_OutgoingDevice(outgoingDeviceId)
, m_Id(id)
, m_IsNeighbour(isNeighbour)
{
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
json FSecure::C3::Core::Profiler::Route::CreateProfileSnapshot() const
{
// Construct profile.
json profile = {
{ "outgoingInterface", m_OutgoingDevice.ToString() },
{ "destinationAgent", m_Id.GetAgentId().ToString() },
{ "receivingInterface", m_Id.GetInterfaceId().ToString() },
{ "isNeighbour", m_IsNeighbour},
};
// Add state indicators if needed.
if (!m_ErrorState.empty())
profile["error"] = m_ErrorState;
// Return profile.
return profile;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::Route::RunCommand(ByteView commandWithArguments)
{
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::C3::Core::Profiler::Relay::Relay(std::weak_ptr<Profiler> owner, AgentId agentId, BuildId buildId, int32_t lastSeen)
: ProfileElement(owner)
, m_Id(agentId)
, m_BuildId(buildId)
, m_LastSeen(lastSeen)
{
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::Relay::ParseAndRunCommand(json const& jCommandElement) noexcept(false)
{
throw std::runtime_error("Relay::ParseAndRunCommand should be overriden");
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::Relay::RunCommand(ByteView commandWithArguments)
{
throw std::runtime_error("Relay::RunCommand should be overriden");
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::C3::Core::Profiler::Channel* FSecure::C3::Core::Profiler::Relay::ReAddChannel(Device::Id did, HashT typeNameHash, bool isReturnChannel /*= false*/, bool isNegotiationChannel /*= false*/)
{
auto ret = m_Channels.Add(did, Channel{ m_Owner, did, typeNameHash, isReturnChannel, isNegotiationChannel });
return ret;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::C3::Core::Profiler::Device* FSecure::C3::Core::Profiler::Relay::ReAddPeripheral(Device::Id did, HashT typeNameHash)
{
auto ret = m_Peripherals.Add(did, Device{ m_Owner, did, typeNameHash });
return ret;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::Relay::ReAddRoute(RouteId receivingRid, DeviceId outgoingInterface, bool isNeighbour)
{
m_Routes.Add(receivingRid, Route{ m_Owner, receivingRid, outgoingInterface, isNeighbour });
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::Relay::ReRemoveRoute(RouteId rid)
{
m_Routes.Remove(rid);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::C3::DeviceId FSecure::C3::Core::Profiler::Relay::FindDirectionDevice(AgentId aid)
{
auto& routes = m_Routes.GetUnderlyingContainer();
auto it = std::find_if(routes.begin(), routes.end(), [&](auto& e) {return e.m_Id.GetAgentId() == aid; });
if (it == routes.end())
throw std::logic_error{ "There is no route to provided agent" };
return it->m_OutgoingDevice;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSecure::C3::Core::Profiler::Gateway::Connector::RunCommand(ByteView commandWithArguments)
{
if (auto pin = m_Connector.lock(); pin)
pin->RunCommand(commandWithArguments);
else
throw std::runtime_error{ "Connector cannot be obtained" };
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::C3::Core::Profiler::Gateway::Connector::Connector(std::weak_ptr<Profiler> owner, Id id, std::shared_ptr<ConnectorBridge> connector)
: ProfileElement(owner)
, m_Id(id)
, m_Connector(connector)
{
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::C3::Core::Profiler::Profile::Profile(Gateway& gateway)
: m_Gateway(gateway)
, m_Lock(m_Mutex)
{
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
json FSecure::C3::Core::Profiler::ProfileElement::CreateProfileSnapshot() const
{
// Start with empty JSON.
json profile;
if (!m_ErrorState.empty())
profile["error"] = m_ErrorState;
// Done.
return profile;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::C3::Core::Profiler::ProfileElement::ProfileElement(std::weak_ptr<Profiler> owner) : m_Owner(owner)
{
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
json FSecure::C3::Core::Profiler::Gateway::Connector::CreateProfileSnapshot() const
{
auto profile = __super::CreateProfileSnapshot();
profile["iId"] = Identifier(m_Id).ToString();
profile["type"] = m_Id;
profile["startupCommand"] = m_StartupArguments;
if (auto lock = m_Connector.lock(); lock && !lock->GetErrorStatus().empty())
profile["error"] = lock->GetErrorStatus();
return profile;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
json FSecure::C3::Core::Profiler::Device::CreateProfileSnapshot() const
{
auto profile = __super::CreateProfileSnapshot();
profile["iId"] = m_Id.ToString();
profile["type"] = m_TypeHash;
profile["startupCommand"] = m_StartupArguments;
profile["jitter"] = { FSecure::Utils::DoubleSeconds(m_Jitter.first).count(), FSecure::Utils::DoubleSeconds(m_Jitter.second).count() };
// get error here.
return profile;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::C3::Core::Profiler::Channel::Channel(std::weak_ptr<Profiler> owner, Id id, HashT typeHash, bool isReturnChannel /*= false*/, bool isNegotiationChannel /*= false*/)
: Device(owner, id, typeHash)
, m_IsReturnChannel(isReturnChannel)
, m_IsNegotiationChannel(isNegotiationChannel)
{
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
json FSecure::C3::Core::Profiler::Channel::CreateProfileSnapshot() const
{
auto profile = __super::CreateProfileSnapshot();
profile["isReturnChannel"] = m_IsReturnChannel;
profile["isNegotiationChannel"] = m_IsNegotiationChannel;
return profile;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::C3::Core::Profiler::SnapshotProxy::SnapshotProxy(Profiler& profiler) :
m_Profiler(profiler)
{
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool FSecure::C3::Core::Profiler::SnapshotProxy::CheckUpdates()
{
m_CurrentSnapshot = m_Profiler.Get().m_Gateway.CreateProfileSnapshot();
auto currentHash = std::hash<json>{}(m_CurrentSnapshot);
if (m_PreviousHash && currentHash == *m_PreviousHash)
return false;
m_PreviousHash = currentHash;
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
json const& FSecure::C3::Core::Profiler::SnapshotProxy::GetSnapshot() const
{
return m_CurrentSnapshot;
}