obu-firmware builds against the vanetza-idf C-ITS library, which until now came from the colleague's microbu-esp32c5 tree beside the repository and was not tracked here, so a clone of this repository could not build the firmware it ships. The library alone is now part of obu-firmware, as obu-firmware/external/vanetza-idf: their external/vanetza-idf at commit cf4b99f, unchanged (9775 files; see its PROVENANCE.md). CMake takes it from there by default; -DVANETZA_IDF_DIR still points the build elsewhere. The rest of the colleague's tree (their own VAM firmware, PKI tooling, station-link Python tools, the V2X2MAP bridge) stays out of this repository and gitignored; nothing is pushed to their repository. NOTES.md, docs/06, TODO.md and the pcap verifier's usage line point at the new location.
236 lines
7.5 KiB
C++
236 lines
7.5 KiB
C++
#include <gtest/gtest.h>
|
|
#include <vanetza/access/data_request.hpp>
|
|
#include <vanetza/access/interface.hpp>
|
|
#include <vanetza/common/manual_runtime.hpp>
|
|
#include <vanetza/dcc/flow_control.hpp>
|
|
#include <vanetza/dcc/transmit_rate_control.hpp>
|
|
#include <chrono>
|
|
|
|
using namespace vanetza;
|
|
using namespace vanetza::dcc;
|
|
using namespace std::chrono;
|
|
|
|
static const TransmissionLite dp0 { Profile::DP0, 0 };
|
|
static const TransmissionLite dp1 { Profile::DP1, 0 };
|
|
static const TransmissionLite dp2 { Profile::DP2, 0 };
|
|
static const TransmissionLite dp3 { Profile::DP3, 0 };
|
|
|
|
class FakeAccessInterface : public access::Interface
|
|
{
|
|
public:
|
|
void request(const access::DataRequest& req, std::unique_ptr<ChunkPacket> packet) override
|
|
{
|
|
last_request = req;
|
|
last_packet = std::move(packet);
|
|
++transmissions;
|
|
}
|
|
|
|
boost::optional<access::DataRequest> last_request;
|
|
std::unique_ptr<ChunkPacket> last_packet;
|
|
unsigned transmissions = 0;
|
|
};
|
|
|
|
class FakeTransmitRateControl : public TransmitRateControl
|
|
{
|
|
public:
|
|
FakeTransmitRateControl(const Runtime& rt) :
|
|
runtime(rt), trc_off(milliseconds(200)), last_notify(Clock::time_point::min()) {}
|
|
|
|
Clock::duration delay(const Transmission&) override
|
|
{
|
|
auto delay = runtime.now() - last_notify + trc_off;
|
|
return delay < Clock::duration::zero() ? Clock::duration::zero() : delay;
|
|
}
|
|
|
|
Clock::duration interval(const Transmission&) override { return trc_off; }
|
|
void notify(const Transmission&) override { last_notify = runtime.now(); }
|
|
|
|
const Runtime& runtime;
|
|
Clock::duration trc_off;
|
|
Clock::time_point last_notify;
|
|
};
|
|
|
|
class FlowControlTest : public testing::Test
|
|
{
|
|
protected:
|
|
FlowControlTest() :
|
|
runtime(), trc(runtime),
|
|
flow_control(runtime, trc, access)
|
|
{}
|
|
|
|
std::unique_ptr<ChunkPacket> create_packet(std::size_t length = 0)
|
|
{
|
|
std::unique_ptr<ChunkPacket> packet { new ChunkPacket() };
|
|
packet->layer(OsiLayer::Application) = ByteBuffer(length);
|
|
return packet;
|
|
}
|
|
|
|
MacAddress mac(char x)
|
|
{
|
|
return MacAddress { 0, 0, 0, 0, 0, static_cast<uint8_t>(x) };
|
|
}
|
|
|
|
ManualRuntime runtime;
|
|
FakeTransmitRateControl trc;
|
|
FakeAccessInterface access;
|
|
FlowControl flow_control;
|
|
};
|
|
|
|
TEST_F(FlowControlTest, immediate_transmission)
|
|
{
|
|
ASSERT_EQ(milliseconds(0), trc.delay(dp1));
|
|
ASSERT_FALSE(access.last_request);
|
|
DataRequest request;
|
|
request.dcc_profile = Profile::DP1;
|
|
flow_control.request(request, create_packet());
|
|
ASSERT_TRUE(!!access.last_request);
|
|
EXPECT_EQ(access::AccessCategory::VI, access.last_request->access_category);
|
|
|
|
EXPECT_EQ(trc.interval(dp2), trc.delay(dp2));
|
|
request.dcc_profile = Profile::DP2;
|
|
access.last_request = boost::none;
|
|
flow_control.request(request, create_packet());
|
|
EXPECT_FALSE(access.last_request);
|
|
|
|
// DP0 bursts are implemented by TRC not by FlowControl
|
|
EXPECT_EQ(trc.interval(dp0), trc.delay(dp0));
|
|
request.dcc_profile = Profile::DP0;
|
|
flow_control.request(request, create_packet());
|
|
EXPECT_FALSE(access.last_request);
|
|
}
|
|
|
|
TEST_F(FlowControlTest, queuing)
|
|
{
|
|
DataRequest request;
|
|
request.lifetime = hours(1); // expired lifetime shall be no concern here
|
|
|
|
trc.notify(dp1);
|
|
EXPECT_LT(Clock::duration::zero(), trc.delay(dp1));
|
|
EXPECT_LT(Clock::duration::zero(), trc.delay(dp2));
|
|
EXPECT_LT(Clock::duration::zero(), trc.delay(dp3));
|
|
|
|
request.destination = mac(1);
|
|
request.dcc_profile = Profile::DP1;
|
|
flow_control.request(request, create_packet());
|
|
request.destination = mac(2);
|
|
request.dcc_profile = Profile::DP3;
|
|
flow_control.request(request, create_packet());
|
|
request.destination = mac(3);
|
|
request.dcc_profile = Profile::DP2;
|
|
flow_control.request(request, create_packet());
|
|
|
|
runtime.trigger(trc.delay(dp1));
|
|
ASSERT_TRUE(!!access.last_request);
|
|
EXPECT_EQ(mac(1), access.last_request->destination_addr);
|
|
EXPECT_EQ(1, access.transmissions);
|
|
|
|
runtime.trigger(trc.delay(dp2) / 2);
|
|
EXPECT_EQ(1, access.transmissions);
|
|
|
|
runtime.trigger(trc.delay(dp2));
|
|
EXPECT_EQ(2, access.transmissions);
|
|
EXPECT_EQ(mac(3), access.last_request->destination_addr);
|
|
|
|
request.destination = mac(4);
|
|
request.dcc_profile = Profile::DP2;
|
|
flow_control.request(request, create_packet());
|
|
request.destination = mac(5);
|
|
request.dcc_profile = Profile::DP3;
|
|
flow_control.request(request, create_packet());
|
|
|
|
runtime.trigger(trc.delay(dp2));
|
|
EXPECT_EQ(3, access.transmissions);
|
|
EXPECT_EQ(mac(4), access.last_request->destination_addr);
|
|
|
|
runtime.trigger(trc.delay(dp3));
|
|
EXPECT_EQ(4, access.transmissions);
|
|
EXPECT_EQ(mac(2), access.last_request->destination_addr);
|
|
|
|
runtime.trigger(trc.delay(dp3));
|
|
EXPECT_EQ(5, access.transmissions);
|
|
EXPECT_EQ(mac(5), access.last_request->destination_addr);
|
|
|
|
// no future transmissions queued anymore
|
|
runtime.trigger(Clock::time_point::max());
|
|
EXPECT_EQ(5, access.transmissions);
|
|
}
|
|
|
|
TEST_F(FlowControlTest, drop_expired)
|
|
{
|
|
std::list<access::AccessCategory> drops;
|
|
flow_control.set_packet_drop_hook([&drops](access::AccessCategory ac, const ChunkPacket*) {
|
|
drops.push_back(ac);
|
|
});
|
|
|
|
trc.notify(dp3);
|
|
DataRequest request;
|
|
request.dcc_profile = Profile::DP3;
|
|
request.lifetime = trc.delay(dp3) - milliseconds(10);
|
|
flow_control.request(request, create_packet());
|
|
runtime.trigger(trc.delay(dp3) + milliseconds(10));
|
|
EXPECT_FALSE(access.last_request);
|
|
ASSERT_FALSE(drops.empty());
|
|
EXPECT_EQ(access::AccessCategory::BK, drops.back());
|
|
EXPECT_EQ(0, access.transmissions);
|
|
|
|
trc.notify(dp3);
|
|
auto delay = trc.delay(dp3);
|
|
EXPECT_NE(Clock::duration::zero(), delay);
|
|
request.lifetime = delay;
|
|
flow_control.request(request, create_packet());
|
|
request.lifetime = delay / 2;
|
|
flow_control.request(request, create_packet());
|
|
request.lifetime = 3 * delay / 2;
|
|
flow_control.request(request, create_packet());
|
|
request.lifetime = 2 * delay;
|
|
flow_control.request(request, create_packet());
|
|
request.lifetime = delay / 4;
|
|
flow_control.request(request, create_packet());
|
|
runtime.trigger(delay);
|
|
EXPECT_EQ(3, drops.size());
|
|
EXPECT_EQ(1, access.transmissions);
|
|
runtime.trigger(delay);
|
|
EXPECT_EQ(4, drops.size());
|
|
EXPECT_EQ(2, access.transmissions);
|
|
|
|
// all queues should be empty now, no future transmissions
|
|
runtime.trigger(Clock::time_point::max());
|
|
EXPECT_EQ(2, access.transmissions);
|
|
}
|
|
|
|
TEST_F(FlowControlTest, queue_length)
|
|
{
|
|
// set queue length limit (default is unlimited)
|
|
flow_control.queue_length(2);
|
|
|
|
// count drops
|
|
std::size_t drops = 0;
|
|
flow_control.set_packet_drop_hook([&drops](access::AccessCategory, const ChunkPacket*) { ++drops; });
|
|
|
|
DataRequest request;
|
|
request.dcc_profile = Profile::DP1;
|
|
request.lifetime = std::chrono::seconds(5);
|
|
|
|
// cause enqueuing of arriving DP1 packets
|
|
trc.notify(dp1);
|
|
ASSERT_LT(Clock::duration::zero(), trc.delay(dp1));
|
|
|
|
flow_control.request(request, create_packet(1));
|
|
flow_control.request(request, create_packet(2));
|
|
EXPECT_EQ(0, access.transmissions);
|
|
EXPECT_EQ(0, drops);
|
|
|
|
flow_control.request(request, create_packet(3));
|
|
EXPECT_EQ(0, access.transmissions);
|
|
EXPECT_EQ(1, drops);
|
|
|
|
runtime.trigger(trc.delay(dp1));
|
|
EXPECT_EQ(1, access.transmissions);
|
|
EXPECT_EQ(2, access.last_packet->size());
|
|
|
|
runtime.trigger(trc.delay(dp1));
|
|
EXPECT_EQ(2, access.transmissions);
|
|
EXPECT_EQ(3, access.last_packet->size());
|
|
EXPECT_EQ(1, drops);
|
|
}
|