C3/Src/Core/Profiler.h

623 lines
24 KiB
C++

#pragma once
#include "GateRelay.h"
#include "Common/FSecure/Sockets/Sockets.hpp"
namespace FSecure::C3::Core
{
/// Virtualizes whole C3 Network with it's remote elements.
struct Profiler : std::enable_shared_from_this<Profiler>
{
/// Build properties
struct BuildProperties
{
bool m_IsX64 = false; ///< Is x64 build (true = x64, false = x86)
bool m_IsBanned = false; ///< Is build banned
json m_StartupCmd; ///< Build startup command
};
// An std::vector-based container used by Profiler to manage its sub-types.
template<typename Element>
struct Manager
{
/// Finds Element specified by ID.
/// @param id ID of the Element to find.
/// @return Element object if existed, otherwise null.
Element* Find(typename Element::Id const& id)
{
auto it = FindElementIterator(id);
return it == m_Elements.end() ? nullptr : &*it;
}
/// Adds a new Element.
/// @param id ID of the Element to add.
/// @param element Element to add.
/// @return the newly created Element.
/// @throw std::invalid_argument if specified ID is already in use.
Element* Add(typename Element::Id id, Element const& element)
{
if (FindElementIterator(id) != m_Elements.end())
throw std::invalid_argument{ OBF("Element with specified ID already exists.") };
m_Elements.push_back(std::move(element));
return &m_Elements.back();
}
/// Removes Element from container.
/// @param id ID of the Element to remove.
/// @throw std::invalid_argument on an attempt of removal of a non-existent Element.
void Remove(typename Element::Id id)
{
if (auto it = FindElementIterator(id); it != m_Elements.end())
m_Elements.erase(it);
else
throw std::invalid_argument{ OBF("Attempt of removing Element that doesn't exist.") };
}
/// Tries to remove Element from container.
/// @param id ID of the Element to remove.
bool TryRemove(typename Element::Id id)
{
if (auto it = FindElementIterator(id); it != m_Elements.end())
{
m_Elements.erase(it);
return true;
}
else
return false;
}
/// Dumps container contents to JSON format.
/// @return Container description in JSON format.
virtual json CreateProfileSnapshot() const
{
auto profile = json::array();
for (auto& element : m_Elements)
profile += element.CreateProfileSnapshot();
return profile;
}
/// Remove all elements
void Clear() noexcept
{
m_Elements.clear();
}
// TODO remove this method
auto const& GetUnderlyingContainer() const noexcept
{
return m_Elements;
}
// TODO remove this method
auto& GetUnderlyingContainer() noexcept
{
return m_Elements;
}
protected:
/// Finds Element iterator specified by ID.
/// @param id ID of the Element to find.
/// @return Element iterator if existed, otherwise null.
typename std::vector<Element>::iterator FindElementIterator(typename Element::Id const& id)
{
return std::find_if(m_Elements.begin(), m_Elements.end(), [&id](Element const& element) { return id == element.m_Id; });
}
private:
std::vector<Element> m_Elements; ///< Elements Container.
};
/// Basic class of a remote C3 Network elements.
struct ProfileElement
{
/// Constuct a profile elment
/// @param owner - Profile that owns this element
ProfileElement(std::weak_ptr<Profiler> owner);
/// Destructor
virtual ~ProfileElement() = default;
/// Dumps current Profile to JSON.
/// @return Network Profile in JSON format.
virtual json CreateProfileSnapshot() const;
/// Performs C3 Command on itself.
/// @param commandWithArguments whole Command in binary format.
virtual void RunCommand(ByteView commandWithArguments) = 0;
std::string m_ErrorState; ///< A message like "BuildId collision" or empty if working correctly.
std::weak_ptr<Profiler> m_Owner; ///< Owner Profiler.
};
/// Virtual image of Device.
struct Device : ProfileElement
{
using Id = DeviceId; ///< ID typedef.
/// Public ctor.
/// @param id Device Identifier.
/// @param typeHash Type of the Device.
Device(std::weak_ptr<Profiler> owner, Id id, HashT typeHash);
/// Destructor
virtual ~Device() = default;
/// Dumps current Profile to JSON.
/// @return Network Profile in JSON format.
json CreateProfileSnapshot() const override;
/// Performs C3 Command on itself.
/// @param commandWithArguments whole Command in binary format.
void RunCommand(ByteView commandWithArguments) override;
Id m_Id; ///< Device Identifier.
HashT m_TypeHash; ///< Type name hash of the Device.
json m_StartupArguments; ///< Device's startup arguments
std::pair<std::chrono::milliseconds, std::chrono::milliseconds> m_Jitter; ///< Current jitter pm device
};
/// Virtual image of Device.
struct Channel : Device
{
using Id = DeviceId; ///< ID typedef.
/// Public ctor.
/// @param id Device Identifier.
/// @param typeHash Type of the Device.
Channel(std::weak_ptr<Profiler> owner, Id id, HashT typeHash, bool isReturnChannel = false, bool isNegotiationChannel = false);
/// Destructor
virtual ~Channel() = default;
/// Dumps current Profile to JSON.
/// @return Network Profile in JSON format.
json CreateProfileSnapshot() const override;
bool m_IsReturnChannel;
bool m_IsNegotiationChannel;
};
/// Forward declaration.
struct Agent;
/// Route is used by Relays to indicate that a particular Device (actually a Channel) is used to transport packets towards a specific Agent.
struct Route : ProfileElement
{
/// Route constructor
/// @param owner - owner of this element
/// @param rid - route id
/// @param outgoingDeviceId
/// @param isNeighbour - if agent specified by rid is neighbouring the owner agent
Route(std::weak_ptr<Profiler> owner, RouteId rid, Device::Id outgoingDeviceId, bool isNeighbour = false);
/// Destructor
virtual ~Route() = default;
/// Dumps current Profile to JSON.
/// @return Network Profile in JSON format.
json CreateProfileSnapshot() const override;
/// Performs C3 Command on itself.
/// @param commandWithArguments whole Command in binary format.
void RunCommand(ByteView commandWithArguments) override;
Device::Id m_OutgoingDevice;
using Id = RouteId; ///< ID typedef.
Id m_Id; ///< Route Identifier.
bool m_IsNeighbour = false; ///< Is m_ReceivingAgent just after one Route hop?
};
/// Abstract class for all Relays.
struct Relay : ProfileElement
{
/// A public ctor.
/// @param agentId dynamic ID of the Relay.
/// @param buildId Build identifier.
/// @param encryptionKey asymmetric public key used to encrypt all outgoing transmission.
/// @param isBanned flag indicating whether Agent should be added to the black-list.
Relay(std::weak_ptr<Profiler> owner, AgentId agentId, BuildId buildId, int32_t lastSeen);
/// Destructor
virtual ~Relay() = default;
using Id = AgentId; ///< ID typedef.
Id m_Id; ///< Dynamic ID of the Relay.
BuildId m_BuildId; ///< Static ID of the Relay binary.
/// Performs Command on a Device indicated by provided JSON format.
/// @param jCommandElement JSON element to parse.
/// @param commandWithArgs Command in binary format to run on Device.
virtual void ParseAndRunCommand(json const& jCommandElement) noexcept(false);
/// Performs C3 Command on itself.
/// @param commandWithArguments whole Command in binary format.
void RunCommand(ByteView commandWithArguments) override;
/// Reprofile: AddNewChannel.
/// @param did ID of the CHannel that was added.
/// @param typeNameHash type name hash of a CHannel that was added.
/// @param isReturnChannel if channel is a return channel.
/// @param isNegotiationChannel if channel is a negotiation channel.
virtual Channel* ReAddChannel(Device::Id did, HashT typeNameHash, bool isReturnChannel = false, bool isNegotiationChannel = false);
/// Reprofile: AddNewPeripheral.
/// @param did ID of the Peripheral that was added.
/// @param typeNameHash type name hash of a Peripheral that was added.
virtual Device* ReAddPeripheral(Device::Id did, HashT typeNameHash);
/// Find a device that lies in direction of Agent
/// @param aid - agent id to be reached
/// @returns DeviceId through which agent aid can be reached
DeviceId FindDirectionDevice(AgentId aid);
/// Update new channel parameters based on negotiation channel parameters
/// @param negotiationDid - negotiation channel device Id
/// @param newDeviceId - newly created (negotiated) device
/// @param newInputId - negotiated InputId
/// @param newOutputId - negotiated OutputId
void UpdateFromNegotiationChannel(DeviceId negotiationDid, DeviceId newDeviceId, std::string newInputId, std::string newOutputId);
/// Add new route
/// @param receivingRid - Destination receiving route id
/// @param outgoingInterface - channel pointed towards receiving agent
/// @param isNeighbour - true if agent specified in receivingRid is neigbour
void ReAddRoute(RouteId receivingRid, DeviceId outgoingInterface, bool isNeighbour);
/// Remove route
/// @param rid - route to remove
void ReRemoveRoute(RouteId rid);
Manager<Route> m_Routes; ///< Container for Routes.
Manager<Channel> m_Channels; ///< Container for Channels.
Manager<Device> m_Peripherals; ///< Container for Peripherals.
DeviceId::UnderlyingIntegerType m_LastDeviceId = 0; ///< This value gets increased with every creation of an Device.
int32_t m_LastSeen; // fixed size instead of 32 or 64 bits time_t
};
/// Virtual image of NodeRelay.
struct Agent : Relay
{
/// A public ctor.
/// @param agentId dynamic ID of the Relay.
/// @param buildId Build identifier.
/// @param encryptionKey asymmetric public key used to encrypt all outgoing transmission.
/// @param isBanned flag indicating whether Agent should be added to the black-list.
/// @param lastSeen timestamp when agent was last seen (responded)
/// @param hostInfo agent's host information
Agent(std::weak_ptr<Profiler> owner, AgentId agentId, BuildId buildId, FSecure::Crypto::PublicKey encryptionKey, bool isBanned, int32_t lastSeen, bool isX64, HostInfo hostInfo);
/// Destructor
virtual ~Agent() = default;
/// Dumps current Profile to JSON.
/// @return Network Profile in JSON format.
json CreateProfileSnapshot() const override;
/// Performs Command on a Device indicated by provided JSON format.
/// @param jCommandElement JSON element to parse.
/// @param commandWithArgs Command in binary format to run on Device.
void ParseAndRunCommand(json const& jCommandElement) noexcept(false) override;
/// Performs C3 Command on remote NodeRelay.
/// @param commandWithArguments whole Command in json format.
void RunCommand(ByteView commandWithArguments) override;
/// Performs a create command (create new device)
/// @param commandWithArguments whole Command in json format.
void PerformCreateCommand(json const& jCommandElement);
/// Add channel
/// @param did - new channel DeviceId
/// @param typeNameHash - new channel typename hash
/// @param isReturnChannel - true if channel is a gateway return channel
/// @param isNegotiationChannel - true if channel is a negotiation channel
/// @returns a pointer to newly created channel
Channel* ReAddChannel(Device::Id did, HashT typeNameHash, bool isReturnChannel = false, bool isNegotiationChannel = false) override;
/// Add peripheral
/// @param did - new peripheral DeviceId
/// @param typeNameHash - new peripheral typename hash
/// @returns a pointer to newly created peripheral
Device* ReAddPeripheral(Device::Id did, HashT typeNameHash) override;
/// Add scheduled device parameters
/// @param deviceId - scheduled device Id
/// @param command - scheduled device command
void AddScheduledDevice(DeviceId deviceId, json command);
/// Find gateway return channel
/// @returns return channel or nullptr
Channel* FindGrc();
FSecure::Crypto::PublicKey m_EncryptionKey; ///< Agent's public key.
HostInfo m_HostInfo; ///< Agent's Host information
bool m_IsBanned; ///< Is Agent black-listed?
bool m_IsX64;
private:
std::unordered_map<DeviceId::UnderlyingIntegerType, json> m_ScheduledDevices;
};
/// Virtual image of Gateway.
struct Gateway : Relay
{
/// A public ctor.
/// @param gateway pointer to Gate Relay.
Gateway(std::weak_ptr<Profiler> owner, std::string name, std::shared_ptr<GateRelay> gateway);
/// Destructor
virtual ~Gateway() = default;
/// Reprofile: TurnOnConnector.
/// @param typeNameHash type name hash of a Connector that was turned on.
/// @param connector pointer to the Connector object.
void ReTurnOnConnector(HashT typeNameHash, std::shared_ptr<ConnectorBridge> connector);
/// Reprofile: AddAgent.
/// @param agentId - new agent Id
/// @param buildId - new agents' build Id
/// @param encryptionKey - new agent's public encryption key
/// @param isBanned - is agent banned
/// @param lastSeen - when agent was last seen
/// @param hostInfo - new agnet's host information
Agent* ReAddAgent(AgentId agentId, BuildId buildId, FSecure::Crypto::PublicKey encryptionKey, bool isBanned, int32_t lastSeen, HostInfo hostInfo);
/// Reprofile: Add remote agent (agent not neigbouring with gateway)
/// @param agentId - new agent Id
/// @param buildId - new agents' build Id
/// @param encryptionKey - new agent's public encryption key
/// @param ridOfConectionPlace
/// @param childGrcHash - new agent's
/// @param lastSeen - when agent was last seen
/// @param hostInfo - new agnet's host information
Agent* ReAddRemoteAgent(RouteId childRouteId, BuildId buildId, FSecure::Crypto::PublicKey encryptionKey, RouteId ridOfConectionPlace, HashT childGrcHash, int32_t lastSeen, HostInfo hostInfo);
/// Find an agent directly connected to relay through given channel
/// @param relay - relay whose neighbour to find
/// @param did - device id through which the agent is connected
/// @returns direct neighbour to relay
/// @throws std::logic_error
Agent* FindNeighborOnDevice(Relay& relay, DeviceId did);
/// Add new known build
/// @oaram bid - new build Id
/// @param properties - build properties
void AddAgentBuild(BuildId bid, BuildProperties properties);
/// Get a list of all agents on the path from agent to gateway
/// @param agent - starting point of the path
/// @returns list of agents on path from agent to gateway
/// @throws std::runtime_error if any agent on the path doesn't have a gateway return channel
std::vector<Agent*> GetPathFromAgent(Agent* agent);
/// Dumps current Profile to JSON.
/// @return Network Profile in JSON format.
json CreateProfileSnapshot() const override;
/// Adds a default 'create' property
/// @param interface - json definition of interface
static void EnsureCreateExists(json& interface);
/// Adds built-in command definitions (Close/TurnOff and UpdateDelay)
/// @param interface - json definition of interface
/// @param isDevice - whether interfce is a device (channel/peripheral) or not (connstors)
static void AddBuildInCommands(json& interface, bool isDevice);
/// Get JSON representing available Commands.
/// @return Network's Capability in JSON format.
json GetCapability();
/// Performs C3 Command on itself.
/// @param commandWithArguments whole Command in binary format.
void RunCommand(ByteView commandWithArguments) override;
/// Performs Command on a Device indicated by provided JSON format.
/// @param jCommandElement JSON element to parse.
/// @param commandWithArgs Command in binary format to run on Device.
void ParseAndRunCommand(json const& jCommandElement) noexcept(false) override;
std::string m_Name; ///< Gateway's Name
std::weak_ptr<GateRelay> m_Gateway; ///< The "physical" Gateway.
Manager<Agent> m_Agents; ///< Table of Agents.
std::map<BuildId, BuildProperties> m_AgentBuilds; ///< Known agent builds
/// Virtual image of Connector.
struct Connector : ProfileElement
{
using Id = HashT; ///< ID typedef.
/// Public ctor.
/// @param id Connector identifier.
/// @param connector the Connector object.
Connector(std::weak_ptr<Profiler> owner, Id id, std::shared_ptr<ConnectorBridge> connector);
/// Destructor
virtual ~Connector() = default;
/// Dumps Profile to JSON.
/// @return Network Profile in JSON format.
json CreateProfileSnapshot() const override;
/// Performs C3 Command on itself.
/// @param commandWithArguments whole Command in binary format.
void RunCommand(ByteView commandWithArguments) override;
Id m_Id; ///< Hash of the name of the Connector.
std::weak_ptr<ConnectorBridge> m_Connector; ///< Pointer to the Connector object.
json m_StartupArguments;
};
/// Performs a create command (create new device)
/// @param commandWithArguments whole Command in json format.
void PerformCreateCommand(json const& jCommandElement);
struct CreateCommand
{
uint16_t m_Id;
uint32_t m_Hash;
bool m_IsDevice;
bool m_IsNegotiableChannel;
};
std::vector<CreateCommand> m_CreateCommands;
Manager<Connector> m_Connectors; ///< Container for Connectors.
/// Reprofile - Turn off connector
/// Removes connector from profiler
/// @param connectorNameHash - connector typename hash
void ReTurnOffConnector(HashT connectorNameHash);
/// Reprofile - Delete channel
/// Removes channel from profiler
/// @param iidOfDeviceToDetach - channel's device id
void ReDeleteChannel(DeviceId iidOfDeviceToDetach);
/// Reprofile - Delete peripheral
/// Removes peripheral from profiler
/// @param iidOfDeviceToDetach - peripheral's device id
void ReDeletePeripheral(DeviceId iidOfDeviceToDetach);
/// Update last-seen timestamps on route to given agent
/// @param agentId - the last agent on the route (origin on the message)
/// @param timestamp - current timestamp
void UpdateRouteTimestamps(AgentId agentId, int32_t timestamp);
/// Find a (parent) agent directly connected through (child) agent's return channel
/// @param agent - child agent
/// @returns agent directly connected through gateway return channel
Agent* FindGatewaySideAgent(Agent* agent);
/// Check if connection to agent exists
/// @param agentId - agent to check connection to
/// @returns true if any route to agent exists
bool ConnectionExist(AgentId agentId);
/// Update negotiated channel parameters (arguments) if all the necessary information is available.
/// @param connecitonPlace - route Id constructed from agent and device channel ID's to update
void ConditionalUpdateChannelParameters(RouteId connectionPlace);
/// Reset profile state - remove all elements
void Reset();
};
/// Public ctor.
Profiler(std::filesystem::path snapshotPath);
/// @param gateway pointer to Gate Relay.
void Initialize(std::string name, std::shared_ptr<GateRelay> gateway);
/// Performs Actions parsed from provided packet.
/// @param actionsPacket packet to parse.
void HandleActionsPacket(ByteView actionsPacket);
/// NewBuild message handler
/// @param message - NewBuild message to process.
/// @returns json representation of NewBuild response
json HandleNewBuildMessage(json const& message);
/// Translate command from JSON to binary representation
/// @param command - command in JSON format
/// @returns Binary representation of command [commandId][packed arguments]
/// @throws nlohmann::basic_json::exception
static ByteVector TranslateCommand(json const& command);
/// Translate arguments from JSON to binary representation
/// @param command - command in JSON format
/// @returns Binary representation of arguments [packed arguments]
/// @throws nlohmann::basic_json::exception
static ByteVector TranslateArguments(json const& arguments);
/// Translate startup command from JSON to binary representation
/// @param command - command in JSON format
/// @returns Binary representation of command [add device command arguments]
/// @throws nlohmann::basic_json::exception
ByteVector TranslateStartupCommand(json const& jcommand);
/// Translate single argument from JSON to binary representation
/// @param type - string decryption of argument
/// @param value - json value to translate
/// @returns Binary representation of argument [packed argument]
/// @throws std::out_of_range if any integer value casting fails
/// @throws std::invalid_argument if type is not any of [int8, int16, int32, int64, uint8, uint16, uint32, uint64, float, boolean, string, ip, binary]
static ByteVector Translate(std::string const& type, json::value_type const& value);
/// Current Profile snapshot with synchronized access.
struct Profile
{
/// Public ctor.
/// @param gateway which profile is locked
Profile(Gateway& gateway);
Gateway& m_Gateway; ///< Gateway profile.
private:
std::unique_lock<std::mutex> m_Lock; ///< Access lock.
static std::mutex m_Mutex; ///< Lock object.
};
/// Profile getter.
/// @return Current Profile snapshot.
Profile Get();
/// Maps peripheral type hash to connector type hash and other way around
/// @param id beacon hash
/// @returns binder id
uint32_t GetBinderTo(uint32_t);
/// Helper to wrap calls to CreateProfileShnapshot
class SnapshotProxy
{
public:
/// Create a snapshot proxy
/// @param profiler to wrap CreateProfileShnapshot calls
SnapshotProxy(Profiler& profiler);
/// Check if new version of snapshot is available.
/// @param ob. Observer token. If observer is not registered function will always return true.
/// @returns true if new snapshot is available.
bool CheckUpdates();
/// Create a snapshot
/// @return std::nullopt if snaphot hasn't change since the last call
json const& GetSnapshot() const;
private:
/// Proxied profiler
Profiler& m_Profiler;
/// helper state variable
std::optional<size_t> m_PreviousHash;
/// Current snapshot
json m_CurrentSnapshot;
};
/// Create Snapshot proxy for this profiler
/// @returns snapshot proxy for this profiler
SnapshotProxy GetSnapshotProxy() { return SnapshotProxy(*this); }
protected:
std::optional<Gateway> m_Gateway; ///< The "virtual gateway object".
mutable std::mutex m_AccessMutex; ///< Mutex for synchronization.
/// Contains hashes of binders. This allows to call: auto tsConnectorhash = GetBinderTo(hashBeacona);. First hash in pair is Peripheral hash and second one is corresponding Connector.
std::vector<std::pair<std::uint32_t, std::uint32_t>> m_BindersMappings;
private:
/// Dump snapshot file in regular intervals
/// Creates a snapshot .tmp file and replaces previous snapshot file
void DumpSnapshots();
/// Restore Gateway and profiler state from snapshot
void RestoreFromSnapshot();
std::filesystem::path m_SnapshotPath; ///< Snapshot dump path
};
}