mirror of https://github.com/infosecn1nja/C3.git
Allow retriving N bytes of data without coping using variadic ByteView::Read
parent
17d2604c26
commit
ffe0d66fcd
|
@ -385,7 +385,7 @@ bool MWR::C3::Interfaces::Connectors::TeamServer::Connection::SecondThreadStarte
|
||||||
|
|
||||||
MWR::ByteVector MWR::C3::Interfaces::Connectors::TeamServer::PeripheralCreationCommand(ByteView connectionId, ByteView data, bool isX64)
|
MWR::ByteVector MWR::C3::Interfaces::Connectors::TeamServer::PeripheralCreationCommand(ByteView connectionId, ByteView data, bool isX64)
|
||||||
{
|
{
|
||||||
auto [pipeName, maxConnectionTrials, delayBetweenConnectionTrials/*, payload*/] = data.Read<std::string, uint16_t, uint16_t/*, ByteVector*/>();
|
auto [pipeName, maxConnectionTrials, delayBetweenConnectionTrials/*, payload*/] = data.Read<std::string, uint16_t, uint16_t/*, ByteView*/>();
|
||||||
|
|
||||||
// custom payload is removed from release.
|
// custom payload is removed from release.
|
||||||
//if (!payload.empty())
|
//if (!payload.empty())
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
MWR::C3::Interfaces::Peripherals::Beacon::Beacon(ByteView arguments)
|
MWR::C3::Interfaces::Peripherals::Beacon::Beacon(ByteView arguments)
|
||||||
{
|
{
|
||||||
auto [pipeName, maxConnectionTrials, delayBetweenConnectionTrials, payload] = arguments.Read<std::string, uint16_t, uint16_t, ByteVector>();
|
auto [pipeName, maxConnectionTrials, delayBetweenConnectionTrials, payload] = arguments.Read<std::string, uint16_t, uint16_t, ByteView>();
|
||||||
|
|
||||||
// Arguments validation.
|
// Arguments validation.
|
||||||
if (payload.empty())
|
if (payload.empty())
|
||||||
|
|
|
@ -40,6 +40,10 @@ namespace MWR
|
||||||
/// @returns std::vector<std::string> tokenized string.
|
/// @returns std::vector<std::string> tokenized string.
|
||||||
std::vector<std::string> SplitAndCopy(std::string_view stringToBeSplitted, std::string_view delimiter);
|
std::vector<std::string> SplitAndCopy(std::string_view stringToBeSplitted, std::string_view delimiter);
|
||||||
|
|
||||||
|
/// Idiom for detecting ByteView::Get.
|
||||||
|
template <typename X>
|
||||||
|
constexpr bool IsGetter = false;
|
||||||
|
|
||||||
/// Non owning container.
|
/// Non owning container.
|
||||||
class ByteView : std::basic_string_view<ByteVector::value_type>
|
class ByteView : std::basic_string_view<ByteVector::value_type>
|
||||||
{
|
{
|
||||||
|
@ -136,6 +140,7 @@ namespace MWR
|
||||||
using Super::npos;
|
using Super::npos;
|
||||||
using Super::value_type;
|
using Super::value_type;
|
||||||
|
|
||||||
|
|
||||||
/// @returns ByteVector. Owning container with the read bytes.
|
/// @returns ByteVector. Owning container with the read bytes.
|
||||||
/// @param byteCount. How many bytes should be read.
|
/// @param byteCount. How many bytes should be read.
|
||||||
/// @remarks Read is not compatible with Read<ByteVector>.
|
/// @remarks Read is not compatible with Read<ByteVector>.
|
||||||
|
@ -187,7 +192,7 @@ namespace MWR
|
||||||
/// Read bytes and remove them from ByteView.
|
/// Read bytes and remove them from ByteView.
|
||||||
/// @returns ByteArray. Owning container with the read bytes.
|
/// @returns ByteArray. Owning container with the read bytes.
|
||||||
/// @throws std::out_of_range. If ByteView is too short to hold size of object to return.
|
/// @throws std::out_of_range. If ByteView is too short to hold size of object to return.
|
||||||
template<typename T, typename std::enable_if_t<IsByteArray<T>, int> = 0>
|
template<typename T>
|
||||||
std::enable_if_t<IsByteArray<T>, T> Read()
|
std::enable_if_t<IsByteArray<T>, T> Read()
|
||||||
{
|
{
|
||||||
if (std::tuple_size<typename T>::value > size())
|
if (std::tuple_size<typename T>::value > size())
|
||||||
|
@ -223,6 +228,35 @@ namespace MWR
|
||||||
return TupleGenerator<T>::Generate(*this);
|
return TupleGenerator<T>::Generate(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Class allowing reading N bytes without coping data like in ByeView::Read<ByteArray<N>> or ByteView::Reed(size_t).
|
||||||
|
template <size_t N>
|
||||||
|
class Get
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// Define size marker.
|
||||||
|
static constexpr size_t size = N;
|
||||||
|
static constexpr size_t Size = size;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Private constructor.
|
||||||
|
/// This class should never be instantiated.
|
||||||
|
Get() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Read bytes and remove them from ByteView.
|
||||||
|
/// @returns ByteView. Non owning container with the read bytes.
|
||||||
|
/// @throws std::out_of_range. If ByteView is too short to hold size of object to return.
|
||||||
|
template<typename T>
|
||||||
|
std::enable_if_t<IsGetter<T>, ByteView> Read()
|
||||||
|
{
|
||||||
|
if (T::Size > size())
|
||||||
|
throw std::out_of_range{ OBF(": Cannot read data from ByteView") };
|
||||||
|
|
||||||
|
auto retVal = SubString(0, T::Size);
|
||||||
|
remove_prefix(T::Size);
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
/// Read tuple and remove bytes from ByteView.
|
/// Read tuple and remove bytes from ByteView.
|
||||||
/// @tparam T. Parameter pack of types to be stored in tuple. Must consist more then one type. Each of types must be parsable by Reed template for one type.
|
/// @tparam T. Parameter pack of types to be stored in tuple. Must consist more then one type. Each of types must be parsable by Reed template for one type.
|
||||||
/// @returns std::tuple. Tuple will store types provided in parameter pack.
|
/// @returns std::tuple. Tuple will store types provided in parameter pack.
|
||||||
|
@ -273,7 +307,7 @@ namespace MWR
|
||||||
{
|
{
|
||||||
auto current = std::make_tuple(self.Read<T>());
|
auto current = std::make_tuple(self.Read<T>());
|
||||||
auto rest = VariadicTupleGenerator<Rest...>::Generate(self);
|
auto rest = VariadicTupleGenerator<Rest...>::Generate(self);
|
||||||
return std::tuple_cat(current, rest);
|
return std::tuple_cat(std::move(current), std::move(rest));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -325,6 +359,14 @@ namespace MWR
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Specialization of idiom for detecting ByteView::Get.
|
||||||
|
template<size_t N>
|
||||||
|
constexpr bool IsGetter<ByteView::Get<N>> = true;
|
||||||
|
|
||||||
|
/// Alias for simpler use of ByteView::Get.
|
||||||
|
template<size_t N>
|
||||||
|
using Bytes = ByteView::Get<N>;
|
||||||
|
|
||||||
namespace Literals
|
namespace Literals
|
||||||
{
|
{
|
||||||
/// Create ByteView with syntax ""_bvec
|
/// Create ByteView with syntax ""_bvec
|
||||||
|
|
|
@ -47,7 +47,7 @@ void MWR::C3::Core::GateRelay::OnProtocolS2G(ByteView packet0, std::shared_ptr<D
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
auto decrypted = MWR::Crypto::DecryptFromAnonymous(packet0.SubString(1), m_AuthenticationKey, m_DecryptionKey);
|
auto decrypted = MWR::Crypto::DecryptFromAnonymous(packet0.SubString(1), m_AuthenticationKey, m_DecryptionKey);
|
||||||
auto [procedure, rid, timestamp] = ByteView{ decrypted }.Read<ProceduresUnderlyingType, ByteArray<RouteId::BinarySize>, int32_t>();
|
auto [procedure, rid, timestamp] = ByteView{ decrypted }.Read<ProceduresUnderlyingType, Bytes<RouteId::BinarySize>, int32_t>();
|
||||||
if (!m_Profiler->Get().m_Gateway.ConnectionExist(RouteId::FromByteView(rid).GetAgentId()))
|
if (!m_Profiler->Get().m_Gateway.ConnectionExist(RouteId::FromByteView(rid).GetAgentId()))
|
||||||
throw std::runtime_error{ "S2G packet received from not connected source." };
|
throw std::runtime_error{ "S2G packet received from not connected source." };
|
||||||
|
|
||||||
|
@ -274,10 +274,10 @@ void MWR::C3::Core::GateRelay::On(ProceduresN2N::InitializeRouteQuery&& query)
|
||||||
auto readView = ByteView{ decryptedPacket };
|
auto readView = ByteView{ decryptedPacket };
|
||||||
auto newRelayBuildId = BuildId{ readView.Read<BuildId::UnderlyingIntegerType>() };
|
auto newRelayBuildId = BuildId{ readView.Read<BuildId::UnderlyingIntegerType>() };
|
||||||
// todo prepending fixed-size key with 4 byte length is unnecessary, (remember to change writing, not only reading)
|
// todo prepending fixed-size key with 4 byte length is unnecessary, (remember to change writing, not only reading)
|
||||||
auto newRelayPublicKey = Crypto::PublicKey{ readView.Read<ByteVector>() };
|
auto newRelayPublicKey = Crypto::PublicKey{ readView.Read<ByteView>() };
|
||||||
auto hash = readView.Read<HashT>();
|
auto hash = readView.Read<HashT>();
|
||||||
auto lastSeen = readView.Read<int32_t>();
|
auto lastSeen = readView.Read<int32_t>();
|
||||||
HostInfo hostInfo(ByteView{ readView.Read<ByteVector>() });
|
HostInfo hostInfo(readView.Read<ByteView>());
|
||||||
|
|
||||||
auto receivedFrom = query.GetSenderChannel().lock();
|
auto receivedFrom = query.GetSenderChannel().lock();
|
||||||
if (!receivedFrom)
|
if (!receivedFrom)
|
||||||
|
@ -299,17 +299,17 @@ void MWR::C3::Core::GateRelay::On(ProceduresS2G::InitializeRouteQuery&& query)
|
||||||
auto readView = ByteView{ decryptedPacket };
|
auto readView = ByteView{ decryptedPacket };
|
||||||
|
|
||||||
// part of message created by parent Node
|
// part of message created by parent Node
|
||||||
auto [procedureID, parentRid, timestamp, childRid, childSideDid] = readView.Read<ProceduresUnderlyingType, ByteArray<RouteId::BinarySize>, int32_t, ByteArray<RouteId::BinarySize>, ByteArray<DeviceId::BinarySize>>();
|
auto [procedureID, parentRid, timestamp, childRid, childSideDid] = readView.Read<ProceduresUnderlyingType, Bytes<RouteId::BinarySize>, int32_t, Bytes<RouteId::BinarySize>, Bytes<DeviceId::BinarySize>>();
|
||||||
|
|
||||||
// part of message from new relay. encrypted once again because it was blob for parent relay.
|
// part of message from new relay. encrypted once again because it was blob for parent relay.
|
||||||
auto childPacket = Crypto::DecryptFromAnonymous(readView, m_AuthenticationKey, m_DecryptionKey);
|
auto childPacket = Crypto::DecryptFromAnonymous(readView, m_AuthenticationKey, m_DecryptionKey);
|
||||||
readView = ByteView{ childPacket };
|
readView = ByteView{ childPacket };
|
||||||
|
|
||||||
auto newRelayBuildId = BuildId{ readView.Read<BuildId::UnderlyingIntegerType>() }; // todo blocking
|
auto newRelayBuildId = BuildId{ readView.Read<BuildId::UnderlyingIntegerType>() }; // todo blocking
|
||||||
auto newRelayPublicKey = Crypto::PublicKey{ readView.Read<ByteVector>() };
|
auto newRelayPublicKey = Crypto::PublicKey{ readView.Read<ByteView>() };
|
||||||
auto hash = readView.Read<HashT>();
|
auto hash = readView.Read<HashT>();
|
||||||
auto lastSeen = readView.Read<int32_t>();
|
auto lastSeen = readView.Read<int32_t>();
|
||||||
HostInfo hostInfo(ByteView{ readView.Read<ByteVector>() });
|
HostInfo hostInfo(readView.Read<ByteView>());
|
||||||
|
|
||||||
auto receivedFrom = query.GetSenderChannel().lock();
|
auto receivedFrom = query.GetSenderChannel().lock();
|
||||||
if (!receivedFrom)
|
if (!receivedFrom)
|
||||||
|
@ -337,9 +337,9 @@ void MWR::C3::Core::GateRelay::On(ProceduresS2G::DeliverToBinder query)
|
||||||
auto readView = ByteView{ decryptedPacket };
|
auto readView = ByteView{ decryptedPacket };
|
||||||
|
|
||||||
auto unusedProtocolId = readView.Read<int8_t>();
|
auto unusedProtocolId = readView.Read<int8_t>();
|
||||||
auto senderRid = RouteId::FromByteView(readView.Read<ByteArray<RouteId::BinarySize>>());
|
auto senderRid = RouteId::FromByteView(readView.Read<Bytes<RouteId::BinarySize>>());
|
||||||
auto timestamp = readView.Read<int32_t>();
|
auto timestamp = readView.Read<int32_t>();
|
||||||
auto deviceId = readView.Read<ByteArray<DeviceId::BinarySize>>();
|
auto deviceId = readView.Read<Bytes<DeviceId::BinarySize>>();
|
||||||
auto connectorHash = readView.Read<HashT>();
|
auto connectorHash = readView.Read<HashT>();
|
||||||
|
|
||||||
auto connector = m_Connectors.Find([&](auto const& e){ return e->GetNameHash() == connectorHash; });
|
auto connector = m_Connectors.Find([&](auto const& e){ return e->GetNameHash() == connectorHash; });
|
||||||
|
@ -372,7 +372,7 @@ void MWR::C3::Core::GateRelay::On(ProceduresS2G::AddDeviceResponse response)
|
||||||
{
|
{
|
||||||
auto decryptedPacket = response.GetQueryPacket(m_AuthenticationKey, m_DecryptionKey);
|
auto decryptedPacket = response.GetQueryPacket(m_AuthenticationKey, m_DecryptionKey);
|
||||||
auto readView = ByteView{ decryptedPacket };
|
auto readView = ByteView{ decryptedPacket };
|
||||||
auto [unsusedProcedureNo, unusedSenderRouteId, timestamp, deviceId, deviceTypeHash, flags] = readView.Read<ProceduresUnderlyingType, ByteArray<RouteId::BinarySize>, int32_t, DeviceId::UnderlyingIntegerType, HashT, std::uint8_t>();
|
auto [unsusedProcedureNo, unusedSenderRouteId, timestamp, deviceId, deviceTypeHash, flags] = readView.Read<ProceduresUnderlyingType, Bytes<RouteId::BinarySize>, int32_t, DeviceId::UnderlyingIntegerType, HashT, std::uint8_t>();
|
||||||
bool isChannel = flags & 1;
|
bool isChannel = flags & 1;
|
||||||
bool isNegotiationChannel = flags & (1 << 1);
|
bool isNegotiationChannel = flags & (1 << 1);
|
||||||
|
|
||||||
|
@ -393,7 +393,7 @@ void MWR::C3::Core::GateRelay::On(ProceduresS2G::AddDeviceResponse response)
|
||||||
void MWR::C3::Core::GateRelay::On(ProceduresN2N::ChannelIdExchangeStep1 query)
|
void MWR::C3::Core::GateRelay::On(ProceduresN2N::ChannelIdExchangeStep1 query)
|
||||||
{
|
{
|
||||||
auto readView = ByteView{ query.GetQueryPacket() };
|
auto readView = ByteView{ query.GetQueryPacket() };
|
||||||
auto newOutputId = readView.Read<ByteVector>();
|
auto newOutputId = readView.Read<ByteView>();
|
||||||
|
|
||||||
auto newInputId = MWR::Utils::GenerateRandomString(newOutputId.size());
|
auto newInputId = MWR::Utils::GenerateRandomString(newOutputId.size());
|
||||||
auto sender = query.GetSenderChannel().lock();
|
auto sender = query.GetSenderChannel().lock();
|
||||||
|
@ -426,7 +426,7 @@ void MWR::C3::Core::GateRelay::On(ProceduresS2G::NewNegotiatedChannelNotificatio
|
||||||
{
|
{
|
||||||
auto decryptedPacket = query.GetQueryPacket(m_AuthenticationKey, m_DecryptionKey);
|
auto decryptedPacket = query.GetQueryPacket(m_AuthenticationKey, m_DecryptionKey);
|
||||||
auto readView = ByteView{ decryptedPacket };
|
auto readView = ByteView{ decryptedPacket };
|
||||||
auto [unsusedProcedureNo, unusedSenderRouteId, timestamp, newDeviceId, negotiatorId, inId, outId] = readView.Read<ProceduresUnderlyingType, ByteArray<RouteId::BinarySize>, int32_t, DeviceId::UnderlyingIntegerType, DeviceId::UnderlyingIntegerType, std::string, std::string>();
|
auto [unsusedProcedureNo, unusedSenderRouteId, timestamp, newDeviceId, negotiatorId, inId, outId] = readView.Read<ProceduresUnderlyingType, Bytes<RouteId::BinarySize>, int32_t, DeviceId::UnderlyingIntegerType, DeviceId::UnderlyingIntegerType, std::string, std::string>();
|
||||||
|
|
||||||
auto agent = m_Profiler->Get().m_Gateway.m_Agents.Find(query.GetSenderRouteId().GetAgentId());
|
auto agent = m_Profiler->Get().m_Gateway.m_Agents.Find(query.GetSenderRouteId().GetAgentId());
|
||||||
if (!agent)
|
if (!agent)
|
||||||
|
@ -448,7 +448,7 @@ void MWR::C3::Core::GateRelay::On(ProceduresS2G::Notification query)
|
||||||
{
|
{
|
||||||
auto decryptedPacket = query.GetQueryPacket(m_AuthenticationKey, m_DecryptionKey);
|
auto decryptedPacket = query.GetQueryPacket(m_AuthenticationKey, m_DecryptionKey);
|
||||||
auto readView = ByteView{ decryptedPacket };
|
auto readView = ByteView{ decryptedPacket };
|
||||||
auto [unsusedProcedureNo, unusedSenderRouteId, timestamp, blob] = readView.Read<ProceduresUnderlyingType, ByteArray<RouteId::BinarySize>, int32_t, ByteVector>();
|
auto [unsusedProcedureNo, unusedSenderRouteId, timestamp, blob] = readView.Read<ProceduresUnderlyingType, Bytes<RouteId::BinarySize>, int32_t, ByteView>();
|
||||||
// blob has not defined structure. Currently Notification is used as ping response.
|
// blob has not defined structure. Currently Notification is used as ping response.
|
||||||
|
|
||||||
auto agent = m_Profiler->Get().m_Gateway.m_Agents.Find(query.GetSenderRouteId().GetAgentId());
|
auto agent = m_Profiler->Get().m_Gateway.m_Agents.Find(query.GetSenderRouteId().GetAgentId());
|
||||||
|
|
|
@ -247,7 +247,7 @@ void MWR::C3::Core::NodeRelay::On(ProceduresG2X::RunCommandOnAgentQuery query)
|
||||||
void MWR::C3::Core::NodeRelay::On(ProceduresG2X::AddRoute query)
|
void MWR::C3::Core::NodeRelay::On(ProceduresG2X::AddRoute query)
|
||||||
{
|
{
|
||||||
auto recipient = query.GetRecipientRouteId();
|
auto recipient = query.GetRecipientRouteId();
|
||||||
auto [newRouteBA, directionDidBA] = ByteView{ query.GetPacketBody() }.Read<ByteArray<RouteId::BinarySize>, ByteArray<DeviceId::BinarySize>>();
|
auto [newRouteBA, directionDidBA] = ByteView{ query.GetPacketBody() }.Read<Bytes<RouteId::BinarySize>, Bytes<DeviceId::BinarySize>>();
|
||||||
auto newRoute = RouteId::FromByteView(newRouteBA);
|
auto newRoute = RouteId::FromByteView(newRouteBA);
|
||||||
auto directionDid = DeviceId(directionDidBA);
|
auto directionDid = DeviceId(directionDidBA);
|
||||||
std::shared_ptr<DeviceBridge> bridge;
|
std::shared_ptr<DeviceBridge> bridge;
|
||||||
|
@ -301,7 +301,7 @@ void MWR::C3::Core::NodeRelay::On(ProceduresG2X::DeliverToBinder query)
|
||||||
void MWR::C3::Core::NodeRelay::On(ProceduresN2N::ChannelIdExchangeStep1 query)
|
void MWR::C3::Core::NodeRelay::On(ProceduresN2N::ChannelIdExchangeStep1 query)
|
||||||
{
|
{
|
||||||
auto readView = ByteView{ query.GetQueryPacket() };
|
auto readView = ByteView{ query.GetQueryPacket() };
|
||||||
auto newOutputId = readView.Read<ByteVector>();
|
auto newOutputId = readView.Read<ByteView>();
|
||||||
|
|
||||||
auto newInputId = MWR::Utils::GenerateRandomString(newOutputId.size());
|
auto newInputId = MWR::Utils::GenerateRandomString(newOutputId.size());
|
||||||
auto sender = query.GetSenderChannel().lock();
|
auto sender = query.GetSenderChannel().lock();
|
||||||
|
@ -324,7 +324,7 @@ void MWR::C3::Core::NodeRelay::On(ProceduresN2N::ChannelIdExchangeStep1 query)
|
||||||
void MWR::C3::Core::NodeRelay::On(ProceduresN2N::ChannelIdExchangeStep2 query)
|
void MWR::C3::Core::NodeRelay::On(ProceduresN2N::ChannelIdExchangeStep2 query)
|
||||||
{
|
{
|
||||||
auto readView = ByteView{ query.GetQueryPacket() };
|
auto readView = ByteView{ query.GetQueryPacket() };
|
||||||
auto newOutputId = readView.Read<ByteVector>();
|
auto newOutputId = readView.Read<ByteView>();
|
||||||
|
|
||||||
auto sender = query.GetSenderChannel().lock();
|
auto sender = query.GetSenderChannel().lock();
|
||||||
|
|
||||||
|
@ -334,7 +334,7 @@ void MWR::C3::Core::NodeRelay::On(ProceduresN2N::ChannelIdExchangeStep2 query)
|
||||||
|
|
||||||
ByteVector args = sender->GetChannelParameters();
|
ByteVector args = sender->GetChannelParameters();
|
||||||
auto hash = sender->GetTypeNameHash();
|
auto hash = sender->GetTypeNameHash();
|
||||||
auto newInputId = ByteVector{ sender->GetInputId() };
|
auto newInputId = sender->GetInputId() ;
|
||||||
DetachDevice(sender->GetDid());
|
DetachDevice(sender->GetDid());
|
||||||
sender.reset();
|
sender.reset();
|
||||||
// Don't use sender below;
|
// Don't use sender below;
|
||||||
|
|
|
@ -41,7 +41,7 @@ std::shared_ptr<MWR::C3::Core::DeviceBridge> MWR::C3::Core::Relay::CreateAndAtta
|
||||||
if (isNegotiationChannel)
|
if (isNegotiationChannel)
|
||||||
{
|
{
|
||||||
auto readView = commandLine;
|
auto readView = commandLine;
|
||||||
auto negotiationId = readView.Read<ByteVector>();
|
auto negotiationId = readView.Read<ByteView>();
|
||||||
helper.reserve(commandLine.size() + negotiationId.size() + sizeof(std::uint32_t));
|
helper.reserve(commandLine.size() + negotiationId.size() + sizeof(std::uint32_t));
|
||||||
auto generatedId = MWR::Utils::GenerateRandomString(negotiationId.size());
|
auto generatedId = MWR::Utils::GenerateRandomString(negotiationId.size());
|
||||||
if (negotiationClient)
|
if (negotiationClient)
|
||||||
|
|
Loading…
Reference in New Issue