Merge branch 'LinterOverlappedIO' into 'master'

Add new -o overlapped mode to ChannelLinter

See merge request C3/C3!203
master
Grzegorz Rychlik 2020-08-31 13:06:52 +01:00
commit f26233562d
6 changed files with 214 additions and 30 deletions

View File

@ -31,6 +31,9 @@ namespace FSecure::C3::Linter
/// Whether application channel should test sending and receiving through the channel /// Whether application channel should test sending and receiving through the channel
bool m_TestChannelIO; bool m_TestChannelIO;
/// Change test IO mode to perform Overlapped Read/Write on chunks.
bool m_OverlappedIO;
/// Command id and its arguments /// Command id and its arguments
std::optional<StringVector> m_Command; std::optional<StringVector> m_Command;
}; };

View File

@ -21,6 +21,7 @@ namespace FSecure::C3::Linter
m_ArgParser.addArgument("-c", "--complementary", '*'); m_ArgParser.addArgument("-c", "--complementary", '*');
m_ArgParser.addArgument("-i", "--test-io"); m_ArgParser.addArgument("-i", "--test-io");
m_ArgParser.addArgument("-x", "--command", '+'); m_ArgParser.addArgument("-x", "--command", '+');
m_ArgParser.addArgument("-o", "--overlapped");
m_ArgParser.useExceptions(true); m_ArgParser.useExceptions(true);
} }
@ -52,6 +53,10 @@ Options:
If -c is not present, complementary channel arguments are deduced by swapping If -c is not present, complementary channel arguments are deduced by swapping
parameters from Capability/create/arguments arrays. parameters from Capability/create/arguments arrays.
-o, --overlapped Changes way IO test is performed.
For each sent chunk of packet, complementary channel must read it.
Packet order test is not performed.
-x <ID> [ARGS... ], --command <ID> [ARGS... ] -x <ID> [ARGS... ], --command <ID> [ARGS... ]
Execute a command with a given <ID> and arguments [ARGS...] Execute a command with a given <ID> and arguments [ARGS...]
@ -85,6 +90,7 @@ Examples:
m_Config.m_ComplementaryChannelArguments = m_ArgParser.retrieve<std::vector<std::string>>("complementary"); m_Config.m_ComplementaryChannelArguments = m_ArgParser.retrieve<std::vector<std::string>>("complementary");
m_Config.m_TestChannelIO = m_ArgParser.exists("test-io"); m_Config.m_TestChannelIO = m_ArgParser.exists("test-io");
m_Config.m_OverlappedIO = m_ArgParser.exists("overlapped");
if (m_ArgParser.exists("command")) if (m_ArgParser.exists("command"))
m_Config.m_Command = m_ArgParser.retrieve<StringVector>("command"); m_Config.m_Command = m_ArgParser.retrieve<StringVector>("command");

View File

@ -113,9 +113,10 @@ namespace FSecure::C3::Linter
std::cout << "Creating complementary channel ... " << std::flush; std::cout << "Creating complementary channel ... " << std::flush;
auto complementaryArgs = GetComplementaryChannelArgs(); auto complementaryArgs = GetComplementaryChannelArgs();
auto complementaryChannel = MakeChannel(complementaryArgs); auto complementaryChannel = MakeChannel(complementaryArgs);
assert(complementaryChannel);
std::cout << "OK" << std::endl; std::cout << "OK" << std::endl;
TestChannelIO(channel, complementaryChannel); TestChannelIO(*channel.get(), *complementaryChannel.get(), m_Config.m_OverlappedIO);
} }
if (m_Config.m_Command) if (m_Config.m_Command)
@ -140,35 +141,44 @@ namespace FSecure::C3::Linter
return channelBridge; return channelBridge;
} }
void ChannelLinter::TestChannelIO(std::shared_ptr<MockDeviceBridge> const& channel, std::shared_ptr<MockDeviceBridge> const& complementary) void ChannelLinter::TestChannelIO(MockDeviceBridge& channel, MockDeviceBridge& complementary, bool overlapped)
{ {
assert(channel); TestChannelMTU(channel, complementary, overlapped);
assert(complementary);
if (!overlapped)
TestChannelOrder(channel, complementary);
}
void ChannelLinter::TestChannelMTU(MockDeviceBridge& channel, MockDeviceBridge& complementary, bool overlapped)
{
for (size_t packetLen : { 8, 64, 1024, 1024 * 1024}) for (size_t packetLen : { 8, 64, 1024, 1024 * 1024})
{ {
std::cout << "Testing channel with " << packetLen << " bytes of data ... " << std::flush; std::cout << "Testing channel with " << packetLen << " bytes of data ... " << std::flush;
auto data = ByteVector(FSecure::Utils::GenerateRandomData(packetLen));
channel->Send(data); auto data = ByteVector(FSecure::Utils::GenerateRandomData(packetLen));
if (data != complementary->Receive()[0]) auto passed = overlapped ? TestOverlapped(channel, complementary, data) : TestSequential(channel, complementary, data);
if (!passed)
throw std::exception("Data sent and received mismatch"); throw std::exception("Data sent and received mismatch");
std::cout << "OK" << std::endl; std::cout << "OK" << std::endl;
} }
}
auto numberOfTests = 10; void ChannelLinter::TestChannelOrder(MockDeviceBridge& channel, MockDeviceBridge& complementary)
auto packetSize = 64; {
std::cout << "Testing channel order with " << numberOfTests << " packets of " << packetSize << " bytes of data ... " << std::flush; constexpr auto numberOfTests = 10;
constexpr auto packetSize = 64;
std::vector<ByteVector> sent; std::vector<ByteVector> sent;
std::cout << "Testing channel order with " << numberOfTests << " packets of " << packetSize << " bytes of data ... " << std::flush;
for (auto i = 0; i < numberOfTests; ++i) for (auto i = 0; i < numberOfTests; ++i)
{ {
sent.push_back(FSecure::Utils::GenerateRandomData(packetSize)); sent.push_back(FSecure::Utils::GenerateRandomData(packetSize));
channel->Send(sent[i]); channel.Send(sent[i]);
} }
auto received = complementary->Receive(sent.size()); auto received = complementary.Receive(sent.size());
received.resize(sent.size()); received.resize(sent.size());
if (sent != received) if (sent != received)
@ -177,6 +187,35 @@ namespace FSecure::C3::Linter
std::cout << "OK" << std::endl; std::cout << "OK" << std::endl;
} }
bool ChannelLinter::TestOverlapped(MockDeviceBridge& channel, MockDeviceBridge& complementary, ByteView data)
{
auto sender = channel.GetChunkSender(data);
auto receiver = complementary.GetChunkReceiver();
for (auto noProgressCounter = 0; noProgressCounter < 10; ++noProgressCounter)
{
if (!sender.IsDone() && sender.Send())
noProgressCounter = 0;
if (receiver.Receive())
noProgressCounter = 0;
if (auto && packets = receiver.GetPackets(); !packets.empty())
return ByteView{ packets[0] } == data;
}
return false;
}
bool ChannelLinter::TestSequential(MockDeviceBridge& channel, MockDeviceBridge& complementary, ByteView data)
{
channel.Send(data);
auto received = complementary.Receive();
return data == ByteView{ received[0] };
}
void ChannelLinter::TestCommand(std::shared_ptr<MockDeviceBridge> const& channel) void ChannelLinter::TestCommand(std::shared_ptr<MockDeviceBridge> const& channel)
{ {
assert(m_Config.m_Command); assert(m_Config.m_Command);

View File

@ -26,10 +26,38 @@ namespace FSecure::C3::Linter
void TestCommand(std::shared_ptr<MockDeviceBridge> const& channel); void TestCommand(std::shared_ptr<MockDeviceBridge> const& channel);
/// Test channel pair permeability /// Test channel pair permeability
/// @param first of complementary channels /// @param channel first of channels. Used to send data.
/// @param second of complementary channels /// @param complementary second of channels. Used to receive data.
/// @param overlapped informs if operations should be performed overlapped, or first read must be preceded by write of all chunks .
/// @throws if Channel::OnSend or Channel::OnReceive throws /// @throws if Channel::OnSend or Channel::OnReceive throws
void TestChannelIO(std::shared_ptr<MockDeviceBridge> const& channel, std::shared_ptr<MockDeviceBridge> const& ch2); void TestChannelIO(MockDeviceBridge& channel, MockDeviceBridge& complementary, bool overlapped = false);
/// Test maximal packet size that can be delivered by channel.
/// @param channel first of channels. Used to send data.
/// @param complementary second of channels. Used to receive data.
/// @param overlapped informs if operations should be performed overlapped, or first read must be preceded by write of all chunks .
/// @throws if Channel::OnSend or Channel::OnReceive throws
void TestChannelMTU(MockDeviceBridge& channel, MockDeviceBridge& complementary, bool overlapped = false);
/// Test maximal packet size if read/write operations are overlapped.
/// @param channel first of channels. Used to send data.
/// @param complementary second of channels. Used to receive data.
/// @param overlapped informs if operations should be performed overlapped, or first read must be preceded by write of all chunks .
/// @throws if Channel::OnSend or Channel::OnReceive throws
bool TestOverlapped(MockDeviceBridge& channel, MockDeviceBridge& complementary, ByteView data);
/// Test maximal packet size if all chunks must be written before first read.
/// @param channel first of channels. Used to send data.
/// @param complementary second of channels. Used to receive data.
/// @param overlapped informs if operations should be performed overlapped, or first read must be preceded by write of all chunks .
/// @throws if Channel::OnSend or Channel::OnReceive throws
bool TestSequential(MockDeviceBridge& channel, MockDeviceBridge& complementary, ByteView data);
/// Test if packets are received in the same order as were passed to send.
/// @param channel first of channels. Used to send data.
/// @param complementary second of channels. Used to receive data.
/// @throws if Channel::OnSend or Channel::OnReceive throws
void TestChannelOrder(MockDeviceBridge& channel, MockDeviceBridge& complementary);
/// Create channel from string channel arguments /// Create channel from string channel arguments
/// @param channel arguments /// @param channel arguments

View File

@ -92,14 +92,13 @@ namespace FSecure::C3::Linter
void MockDeviceBridge::Send(ByteView blob) void MockDeviceBridge::Send(ByteView blob)
{ {
auto packetSplitter = m_QoS.GetPacketSplitter(blob); auto sender = GetChunkSender(blob);
for (auto noProgressCounter = 0; noProgressCounter < 10; ++noProgressCounter) for (auto noProgressCounter = 0; noProgressCounter < 10; ++noProgressCounter)
{ {
auto sent = GetDevice()->OnSendToChannelInternal(packetSplitter.NextChunk()); if (sender.Send())
if (packetSplitter.Update(sent))
noProgressCounter = 0; noProgressCounter = 0;
if (!packetSplitter.HasMore()) if (sender.IsDone())
return; return;
} }
@ -108,23 +107,71 @@ namespace FSecure::C3::Linter
std::vector<FSecure::ByteVector> MockDeviceBridge::Receive(size_t minExpectedSize) std::vector<FSecure::ByteVector> MockDeviceBridge::Receive(size_t minExpectedSize)
{ {
auto packets = std::vector<ByteVector>{}; auto receiver = GetChunkReceiver();
for (auto noProgressCounter = 0; noProgressCounter < 10; ++noProgressCounter) for (auto noProgressCounter = 0; noProgressCounter < 10; ++noProgressCounter)
{ {
std::this_thread::sleep_for(GetDevice()->GetUpdateDelay()); if (receiver.Receive())
for (auto&& chunk : std::static_pointer_cast<C3::AbstractChannel>(GetDevice())->OnReceiveFromChannelInternal())
{
if (m_QoS.PushReceivedChunk(chunk))
noProgressCounter = 0; noProgressCounter = 0;
if (auto packet = m_QoS.GetNextPacket(); !packet.empty()) // this form will ensure that packets are returned in same order they are available. if (auto && packets = receiver.GetPackets(); packets.size() >= minExpectedSize)
packets.emplace_back(std::move(packet));
}
if (packets.size() >= minExpectedSize)
return packets; return packets;
} }
throw std::runtime_error("Cannot receive data"); throw std::runtime_error("Cannot receive data");
} }
FSecure::C3::Linter::MockDeviceBridge::ChunkSender MockDeviceBridge::GetChunkSender(ByteView blob)
{
return { *m_Device, m_QoS, blob };
}
FSecure::C3::Linter::MockDeviceBridge::ChunkReceiver MockDeviceBridge::GetChunkReceiver()
{
return { *m_Device, m_QoS };
}
MockDeviceBridge::ChunkSender::ChunkSender(Device& device, QualityOfService& qos, ByteView blob)
: m_Device{ device }, m_Splitter{ qos.GetPacketSplitter(blob) }
{
}
bool MockDeviceBridge::ChunkSender::Send()
{
auto sent = m_Device.OnSendToChannelInternal(m_Splitter.NextChunk());
return m_Splitter.Update(sent);
}
bool MockDeviceBridge::ChunkSender::IsDone()
{
return !m_Splitter.HasMore();
}
MockDeviceBridge::ChunkReceiver::ChunkReceiver(Device& device, QualityOfService& qos)
: m_Device{ device }, m_QoS{ qos }
{
}
bool MockDeviceBridge::ChunkReceiver::Receive()
{
auto ret = false;
std::this_thread::sleep_for(m_Device.GetUpdateDelay());
for (auto&& chunk : static_cast<C3::AbstractChannel&>(m_Device).OnReceiveFromChannelInternal())
{
if (m_QoS.PushReceivedChunk(chunk))
ret = true;
if (auto packet = m_QoS.GetNextPacket(); !packet.empty()) // this form will ensure that packets are returned in same order they are available.
m_Packets.emplace_back(std::move(packet));
}
return ret;
}
std::vector<FSecure::ByteVector> const& MockDeviceBridge::ChunkReceiver::GetPackets()
{
return m_Packets;
}
} }

View File

@ -78,6 +78,67 @@ namespace FSecure::C3::Linter
/// @throws std::runtime_error if unable to return vector of required number of full packets. /// @throws std::runtime_error if unable to return vector of required number of full packets.
std::vector<ByteVector> Receive(size_t minExpectedSize = 1); std::vector<ByteVector> Receive(size_t minExpectedSize = 1);
/// Abstraction over sending chunks until all of data is transmitted.
/// This class does not participate in resource ownership.
/// Transmitting device, QoS object as well as data must be valid as long as ChunkSender is used.
class ChunkSender
{
public:
/// Constructor creating object responsible for sending blob of data in chunks.
/// @param device transmitting data over channel.
/// @param qos responsible for splitting data in chunks with correct identification headers.
/// @param blob data.
ChunkSender(Device& device, QualityOfService& qos, ByteView blob);
/// Sends data.
/// @return true if at least one valid chunk was send.
bool Send();
/// All of the data was correctly send.
/// @return true if there is no more data.
bool IsDone();
private:
Device& m_Device;
QualityOfService::PacketSplitter m_Splitter;
};
/// Abstraction over receiving data.
/// This class does not participate in resource ownership.
/// Transmitting device and QoS object must be valid as long as ChunkReceiver is used.
class ChunkReceiver
{
public:
/// Constructor creating object responsible for receiving data in chunks.
/// @param device receiving data from channel.
/// @param qos responsible for merging data from chunks.
ChunkReceiver(Device& device, QualityOfService& qos);
/// Receive data.
/// @return true if at least one valid chunk was received.
bool Receive();
/// Get all packets.
/// Packet sequential number is not used for reordering.
/// Packets are returned in same order, as were available for merging from chunks.
/// @return complete packets.
std::vector<ByteVector> const& GetPackets();
private:
Device& m_Device;
QualityOfService& m_QoS;
std::vector<ByteVector> m_Packets;
};
/// Create ChunkSender using current bridge to send data.
/// @param blob data.
/// @return new ChunkSender.
ChunkSender GetChunkSender(ByteView blob);
/// Create ChunkReceiver using current bridge to receive data.
/// @return ChunkReceiver.
ChunkReceiver GetChunkReceiver();
private: private:
/// Bridged device /// Bridged device
std::shared_ptr<Device> m_Device; std::shared_ptr<Device> m_Device;