Keep the colleague's microbu-esp32c5 tree in this repository

obu-firmware builds against vanetza-idf from microbu-esp32c5/external, but
that tree was gitignored, so a clone of this repository could not build the
firmware it ships. It is now committed here as ordinary files in its own
folder, microbu-esp32c5/: the colleague's commit cf4b99f plus the V2X2MAP
bridge's signature verification (--trust) used on the bench. Nothing is
fetched from or pushed to the colleague's repository; this repository and
its remotes carry everything. The folder's own .gitignore keeps build output,
downloaded components and private key material out, as it did there; the
committed file set is identical to that repository's tracked files.

The ESP32-C5 is still flashed from obu-firmware/, which only takes
vanetza-idf from microbu-esp32c5/, so the two stay separate folders.
FLASHING.md says how to take a newer version of the colleague's tree (copy
it over the folder, rebuild, test, commit).
This commit is contained in:
Ashin Walpola
2026-09-23 17:46:40 +02:00
parent 2f60623e18
commit 0e9525162d
9881 changed files with 1582523 additions and 17 deletions
@@ -0,0 +1,15 @@
include(UseGTest)
configure_gtest_directory(LINK_LIBRARIES dcc)
add_gtest(BurstBudget burst_budget.cpp)
add_gtest(BurstyTransmitRateControl bursty_transmit_rate_control.cpp)
add_gtest(ChannelLoad channel_load.cpp)
add_gtest(FlowControl flow_control.cpp)
add_gtest(FullyMeshedStateMachine fully_meshed_state_machine.cpp)
add_gtest(GradualStateMachine gradual_state_machine.cpp)
add_gtest(Limeric limeric.cpp)
add_gtest(LimericBudget limeric_budget.cpp)
add_gtest(Mapping mapping.cpp)
add_gtest(SmoothingChannelProbeProcessor smoothing_channel_probe_processor.cpp)
add_gtest(StateMachineBudget state_machine_budget.cpp)
@@ -0,0 +1,63 @@
#include <gtest/gtest.h>
#include <vanetza/common/manual_runtime.hpp>
#include <vanetza/dcc/burst_budget.hpp>
using Runtime = vanetza::ManualRuntime;
using namespace vanetza::dcc;
static const vanetza::Clock::duration immediately = std::chrono::milliseconds(0);
TEST(BurstBudget, normal)
{
Runtime rt;
BurstBudget budget(rt);
// consume whole budget
for (unsigned i = 0; i < 20; ++i) {
rt.trigger(std::chrono::milliseconds(49));
EXPECT_EQ(immediately, budget.delay());
budget.notify();
}
// nothing left now
rt.trigger(std::chrono::milliseconds(20));
EXPECT_LT(std::chrono::seconds(9), budget.delay());
EXPECT_GT(std::chrono::seconds(10), budget.delay());
}
TEST(BurstBudget, too_many_messages)
{
Runtime rt;
BurstBudget budget(rt);
// consume whole budget immediately
for (unsigned i = 0; i < 20; ++i) {
EXPECT_EQ(immediately, budget.delay());
budget.notify();
}
// check if budget delay recovers gradually
EXPECT_EQ(std::chrono::seconds(10), budget.delay());
rt.trigger(std::chrono::seconds(5));
EXPECT_EQ(std::chrono::seconds(5), budget.delay());
rt.trigger(std::chrono::seconds(5));
EXPECT_EQ(immediately, budget.delay());
}
TEST(BurstBudget, too_long)
{
Runtime rt;
BurstBudget budget(rt);
// start burst with one consumption
EXPECT_EQ(immediately, budget.delay());
budget.notify();
// ensure we are still able to participate in burst
rt.trigger(std::chrono::milliseconds(990));
EXPECT_EQ(immediately, budget.delay());
// burst is over, we will have to wait for next one
rt.trigger(std::chrono::milliseconds(10));
EXPECT_EQ(std::chrono::seconds(9), budget.delay());
}
@@ -0,0 +1,86 @@
#include <gtest/gtest.h>
#include <vanetza/common/manual_runtime.hpp>
#include <vanetza/dcc/bursty_transmit_rate_control.hpp>
#include <vanetza/dcc/fully_meshed_state_machine.hpp>
using namespace std::chrono;
using namespace vanetza::dcc;
using vanetza::ManualRuntime;
static const vanetza::Clock::duration immediately = milliseconds(0);
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 BurstyTransmitRateControlTest : public ::testing::Test
{
protected:
BurstyTransmitRateControlTest() :
runtime(vanetza::Clock::time_point { seconds(4711) }),
trc(fsm, runtime) {}
ManualRuntime runtime;
FullyMeshedStateMachine fsm;
BurstyTransmitRateControl trc;
};
TEST_F(BurstyTransmitRateControlTest, burst)
{
for (unsigned i = 0; i < 20; ++i) {
runtime.trigger(milliseconds(49));
EXPECT_EQ(immediately, trc.delay(dp0));
trc.notify(dp0);
}
runtime.trigger(milliseconds(20));
EXPECT_GT(seconds(10), trc.delay(dp0));
EXPECT_LT(seconds(9), trc.delay(dp0));
}
TEST_F(BurstyTransmitRateControlTest, regular)
{
const auto tx_int = milliseconds(60);
ASSERT_EQ(tx_int, fsm.transmission_interval());
EXPECT_EQ(immediately, trc.delay(dp1));
trc.notify(dp1);
EXPECT_EQ(tx_int, trc.delay(dp1));
runtime.trigger(milliseconds(50));
EXPECT_EQ(milliseconds(10), trc.delay(dp1));
EXPECT_EQ(milliseconds(10), trc.delay(dp2));
EXPECT_EQ(milliseconds(10), trc.delay(dp3));
runtime.trigger(milliseconds(20));
EXPECT_EQ(immediately, trc.delay(dp1));
EXPECT_EQ(immediately, trc.delay(dp2));
EXPECT_EQ(immediately, trc.delay(dp3));
}
TEST_F(BurstyTransmitRateControlTest, burst_regular_independence)
{
ASSERT_EQ(immediately, trc.delay(dp1));
// consume whole burst budget
for (unsigned i = 0; i < 20; ++i) {
trc.notify(dp0);
}
ASSERT_LT(immediately, trc.delay(dp0));
// can send regular budget messages nonetheless
EXPECT_EQ(immediately, trc.delay(dp3));
// recover burst budget
runtime.trigger(std::chrono::seconds(20));
ASSERT_EQ(immediately, trc.delay(dp0));
// use regular budget
EXPECT_EQ(immediately, trc.delay(dp2));
trc.notify(dp2);
EXPECT_LT(immediately, trc.delay(dp2));
// burst budget is not influenced
EXPECT_EQ(immediately, trc.delay(dp0));
}
@@ -0,0 +1,23 @@
#include <gtest/gtest.h>
#include <vanetza/dcc/channel_load.hpp>
using namespace vanetza::dcc;
TEST(ChannelLoad, ctor)
{
ChannelLoad cl1;
EXPECT_DOUBLE_EQ(0.0, cl1.value());
ChannelLoad cl2(30, 250);
EXPECT_DOUBLE_EQ(0.12, cl2.value());
ChannelLoad cl3(0, 0);
EXPECT_DOUBLE_EQ(0.0, cl3.value());
}
TEST(ChannelLoadRational, less)
{
EXPECT_LT(ChannelLoad(30, 100), ChannelLoad(31, 100));
EXPECT_LT(ChannelLoad(30, 100), ChannelLoad(8, 25));
EXPECT_LT(ChannelLoad(0,10), ChannelLoad(1, 2));
}
@@ -0,0 +1,235 @@
#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);
}
@@ -0,0 +1,112 @@
#include <gtest/gtest.h>
#include <vanetza/dcc/fully_meshed_state_machine.hpp>
using std::chrono::milliseconds;
using namespace vanetza::dcc;
TEST(FullyMeshedStateMachine, ctor)
{
FullyMeshedStateMachine sm;
EXPECT_STREQ("Relaxed", sm.state().name());
EXPECT_EQ(milliseconds(60), sm.transmission_interval());
EXPECT_NEAR(16.66, sm.message_rate(), 0.01);
}
TEST(FullyMeshedStateMachine, ramp_up)
{
FullyMeshedStateMachine sm;
// keep below minChannelLoad at first: relaxed
sm.update(ChannelLoad(0.16));
EXPECT_STREQ("Relaxed", sm.state().name());
// now exceed minChannelLoad for 10 samples: active 1
for (unsigned i = 0; i < 9; ++i) {
sm.update(ChannelLoad(0.2));
EXPECT_STREQ("Relaxed", sm.state().name());
}
sm.update(ChannelLoad(0.2));
EXPECT_STREQ("Active 1", sm.state().name());
// now let's jump to active 3 directly
sm.update(ChannelLoad(0.4));
EXPECT_STREQ("Active 3", sm.state().name());
// jump to active 5
sm.update(ChannelLoad(0.55));
EXPECT_STREQ("Active 5", sm.state().name());
// ramp up to restrictive
for (unsigned i = 0; i < 9; ++i) {
sm.update(ChannelLoad(0.6));
EXPECT_STREQ("Active 5", sm.state().name());
}
sm.update(ChannelLoad(0.6));
EXPECT_STREQ("Restrictive", sm.state().name());
}
TEST(FullyMeshedStateMachine, ramp_down)
{
FullyMeshedStateMachine sm;
// fill up CL ring buffer for restrictive
for (unsigned i = 0; i < 10; ++i) {
sm.update(ChannelLoad(0.7));
}
ASSERT_STREQ("Restrictive", sm.state().name());
// insert 55 % CL for active 5 state (later on)
sm.update(ChannelLoad(0.55));
// cool down 48 of 50 samples to CL = 50% (active 4)
for (unsigned i = 0; i < 48; ++i) {
sm.update(ChannelLoad(0.5));
}
EXPECT_STREQ("Restrictive", sm.state().name());
// -> active 5 (one last 55 % CL sample)
sm.update(ChannelLoad(0.5));
EXPECT_STREQ("Active 5", sm.state().name());
// -> active 4
sm.update(ChannelLoad(0.5));
EXPECT_STREQ("Active 4", sm.state().name());
}
TEST(State, relaxed)
{
Relaxed relaxed;
EXPECT_STREQ("Relaxed", relaxed.name());
EXPECT_EQ(milliseconds(60), relaxed.transmission_interval());
}
TEST(State, active)
{
Active active;
EXPECT_STREQ("Active 1", active.name());
EXPECT_EQ(milliseconds(100), active.transmission_interval());
active.update(0.20, 0.36);
EXPECT_STREQ("Active 3", active.name());
EXPECT_EQ(milliseconds(260), active.transmission_interval());
active.update(0.51, 0.52);
EXPECT_STREQ("Active 5", active.name());
EXPECT_EQ(milliseconds(420), active.transmission_interval());
active.update(0.30, 0.44);
EXPECT_STREQ("Active 4", active.name());
EXPECT_EQ(milliseconds(340), active.transmission_interval());
active.update(0.20, 0.30);
EXPECT_STREQ("Active 2", active.name());
EXPECT_EQ(milliseconds(180), active.transmission_interval());
}
TEST(State, restrictive)
{
Restrictive restrictive;
EXPECT_STREQ("Restrictive", restrictive.name());
EXPECT_EQ(milliseconds(460), restrictive.transmission_interval());
}
@@ -0,0 +1,60 @@
#include <gtest/gtest.h>
#include <vanetza/dcc/gradual_state_machine.hpp>
#include <chrono>
using namespace vanetza::dcc;
using namespace std::chrono;
TEST(GradualStateMachine, initial_state)
{
GradualStateMachine fsm(etsiStates1ms);
EXPECT_EQ("Relaxed", fsm.state());
EXPECT_EQ(milliseconds(100), fsm.transmission_interval());
}
TEST(GradualStateMachine, transitions)
{
GradualStateMachine fsm(etsiStates1ms);
EXPECT_EQ("Relaxed", fsm.state());
// now ramp up to Active 3
fsm.update(ChannelLoad { 0.5 });
EXPECT_EQ("Active 1", fsm.state());
fsm.update(ChannelLoad { 0.5 });
EXPECT_EQ("Active 2", fsm.state());
fsm.update(ChannelLoad { 0.5 });
EXPECT_EQ("Active 3", fsm.state());
fsm.update(ChannelLoad { 0.5 });
EXPECT_EQ("Active 3", fsm.state());
// step down one
fsm.update(ChannelLoad { 0.495 });
EXPECT_EQ("Active 2", fsm.state());
// go up to Restrictive gradually
fsm.update(ChannelLoad { 0.55 });
EXPECT_EQ("Active 3", fsm.state());
fsm.update(ChannelLoad { 0.65 });
EXPECT_EQ("Restrictive", fsm.state());
EXPECT_EQ(milliseconds(1000), fsm.transmission_interval());
}
TEST(GradualStateMachine, empty_states)
{
GradualStateMachine fsm(GradualStateMachine::StateContainer {});
EXPECT_EQ("Relaxed", fsm.state());
EXPECT_EQ(seconds(0), fsm.transmission_interval());
}
TEST(GradualStateMachine, one_state)
{
GradualStateMachine fsm(GradualStateMachine::StateContainer {{ ChannelLoad(0.5), milliseconds(30) }});
EXPECT_EQ("Relaxed", fsm.state());
EXPECT_EQ(milliseconds(30), fsm.transmission_interval());
fsm.update(ChannelLoad { 0.0 });
EXPECT_EQ(milliseconds(30), fsm.transmission_interval());
fsm.update(ChannelLoad { 1.0 });
EXPECT_EQ(milliseconds(30), fsm.transmission_interval());
EXPECT_EQ("Relaxed", fsm.state());
}
@@ -0,0 +1,120 @@
#include <gtest/gtest.h>
#include <vanetza/common/manual_runtime.hpp>
#include <vanetza/dcc/limeric.hpp>
using namespace vanetza;
using namespace vanetza::dcc;
using std::chrono::milliseconds;
namespace vanetza {
void PrintTo(const UnitInterval& cl, std::ostream* os) { *os << cl.value(); }
}
class LimericTest : public ::testing::Test
{
public:
LimericTest() : runtime(Clock::time_point { milliseconds(567) }), limeric(runtime) {}
ManualRuntime runtime;
Limeric limeric;
};
TEST_F(LimericTest, init)
{
EXPECT_EQ(ChannelLoad { 0.0 }, limeric.average_cbr());
EXPECT_EQ(UnitInterval { 0.0153 }, limeric.permitted_duty_cycle());
}
TEST_F(LimericTest, average_cbr_only_measured)
{
limeric.update_cbr(ChannelLoad { 0.2 });
EXPECT_EQ(ChannelLoad { 0.2 }, limeric.average_cbr());
limeric.update_cbr(ChannelLoad { 0.4 });
EXPECT_EQ(ChannelLoad { 0.3 }, limeric.average_cbr());
// now internal buffer filled up, 0.3 is assumed to be "previous" average
limeric.update_cbr(ChannelLoad { 0.6 });
EXPECT_EQ(ChannelLoad { 0.4 }, limeric.average_cbr());
// previous average changes only at update cycle if buffer is full
limeric.update_cbr(ChannelLoad { 0.6 });
EXPECT_EQ(ChannelLoad { 0.45 }, limeric.average_cbr());
}
TEST_F(LimericTest, average_cbr_with_cycle)
{
limeric.update_cbr(ChannelLoad { 0.3 });
limeric.update_cbr(ChannelLoad { 0.4 });
EXPECT_EQ(ChannelLoad { 0.35 }, limeric.average_cbr());
runtime.trigger(milliseconds(200));
// internal average is set to 0.35 now
EXPECT_EQ(ChannelLoad { 0.35 }, limeric.average_cbr());
limeric.update_cbr(ChannelLoad { 0.2 });
EXPECT_EQ(ChannelLoad { 0.325}, limeric.average_cbr());
limeric.update_cbr(ChannelLoad { 0.1 });
EXPECT_EQ(ChannelLoad { 0.25 }, limeric.average_cbr());
limeric.update_cbr(ChannelLoad { 0.1 });
EXPECT_EQ(ChannelLoad { 0.225 }, limeric.average_cbr());
runtime.trigger(milliseconds(200));
// internal average is set to 0.225 now
EXPECT_EQ(ChannelLoad { 0.1625 }, limeric.average_cbr());
limeric.update_cbr(ChannelLoad { 0.3 });
limeric.update_cbr(ChannelLoad { 0.5 });
EXPECT_EQ(ChannelLoad { 0.3125 }, limeric.average_cbr());
}
TEST_F(LimericTest, scheduling)
{
unsigned invocation_count = 0;
limeric.on_duty_cycle_change = [&](const Limeric* limeric_on_change, Clock::time_point tp) {
EXPECT_EQ(&limeric, limeric_on_change);
// expectation: on_duty_cycle_change invocactions exactly at 200ms boundaries
EXPECT_EQ(milliseconds(0), tp.time_since_epoch() % milliseconds(200));
++invocation_count;
};
// start at 567 ms, expected first invocation at 800 ms
runtime.trigger(milliseconds(200)); // 767 ms
EXPECT_EQ(0, invocation_count);
runtime.trigger(milliseconds(50)); // 817 ms
EXPECT_EQ(1, invocation_count);
runtime.trigger(milliseconds(100)); // 917 ms
EXPECT_EQ(1, invocation_count);
runtime.trigger(milliseconds(50)); // 967 ms
EXPECT_EQ(1, invocation_count);
runtime.trigger(milliseconds(33)); // 1000 ms
EXPECT_EQ(2, invocation_count);
}
TEST_F(LimericTest, dual_alpha)
{
Limeric::DualAlphaParameters dual_params;
Limeric limeric_dual(runtime);
limeric_dual.configure_dual_alpha(dual_params);
auto update_cbr = [&](double cbr) {
limeric.update_cbr(ChannelLoad { cbr });
limeric_dual.update_cbr(ChannelLoad { cbr });
};
// set average CBR to 0.8
update_cbr(0.8);
update_cbr(0.8);
EXPECT_EQ(limeric.permitted_duty_cycle(), limeric_dual.permitted_duty_cycle());
runtime.trigger(milliseconds(200));
EXPECT_EQ(limeric.permitted_duty_cycle(), limeric_dual.permitted_duty_cycle());
// Limeric with dual-alpha is expected to converge earlier towards target CBR
for (int i = 0; i < 30; ++i) {
runtime.trigger(milliseconds(200));
}
EXPECT_GT(limeric.permitted_duty_cycle(), limeric_dual.permitted_duty_cycle());
}
@@ -0,0 +1,92 @@
#include <gtest/gtest.h>
#include <vanetza/common/manual_runtime.hpp>
#include <vanetza/dcc/duty_cycle_permit.hpp>
#include <vanetza/dcc/limeric_budget.hpp>
#include <chrono>
using namespace vanetza;
using namespace vanetza::dcc;
using std::chrono::milliseconds;
using std::chrono::microseconds;
namespace std { namespace chrono {
template<typename Rep, typename Period>
void PrintTo(const duration<Rep, Period> d, std::ostream* os)
{
duration<double, std::milli> ms = d;
*os << ms.count() << " ms";
}
}}
class LimericBudgetTest : public ::testing::Test
{
public:
LimericBudgetTest() : budget(dcp, runtime) {}
class MockDutyCyclePermit : public vanetza::dcc::DutyCyclePermit
{
public:
MockDutyCyclePermit() : m_duty_cycle(0.02) {}
UnitInterval permitted_duty_cycle() const { return m_duty_cycle; }
void permitted_duty_cycle(double dc) { m_duty_cycle = UnitInterval { dc }; }
private:
UnitInterval m_duty_cycle;
};
ManualRuntime runtime;
MockDutyCyclePermit dcp;
LimericBudget budget;
};
TEST_F(LimericBudgetTest, init)
{
EXPECT_EQ(milliseconds(25), budget.interval());
EXPECT_EQ(milliseconds(0), budget.delay());
}
TEST_F(LimericBudgetTest, notify)
{
budget.notify(milliseconds(2));
EXPECT_EQ(milliseconds(100), budget.interval());
EXPECT_EQ(budget.interval(), budget.delay());
runtime.trigger(milliseconds(60));
EXPECT_EQ(milliseconds(100), budget.interval());
EXPECT_EQ(milliseconds(40), budget.delay());
runtime.trigger(milliseconds(60));
EXPECT_EQ(milliseconds(0), budget.delay());
budget.notify(microseconds(100));
EXPECT_EQ(milliseconds(25), budget.interval()); // lower limit
budget.notify(milliseconds(30));
EXPECT_EQ(milliseconds(1000), budget.interval()); // upper limit
}
TEST_F(LimericBudgetTest, update)
{
budget.update(); // usually this should be called by Limeric's hook directly
EXPECT_EQ(milliseconds(25), budget.interval()); // no previous transmission duration known yet
budget.notify(milliseconds(1));
EXPECT_EQ(milliseconds(50), budget.interval());
runtime.trigger(milliseconds(10));
EXPECT_EQ(milliseconds(40), budget.delay());
dcp.permitted_duty_cycle(0.01); // half of previous duty cycle
budget.update();
EXPECT_EQ(milliseconds(90), budget.interval());
EXPECT_EQ(milliseconds(80), budget.delay());
runtime.trigger(milliseconds(62));
dcp.permitted_duty_cycle(0.04);
budget.update();
EXPECT_EQ(milliseconds(77), budget.interval());
EXPECT_EQ(milliseconds(5), budget.delay());
}
@@ -0,0 +1,16 @@
#include <gtest/gtest.h>
#include <vanetza/dcc/mapping.hpp>
using namespace vanetza;
using namespace vanetza::dcc;
TEST(Mapping, map_profile_onto_ac)
{
EXPECT_EQ(access::AccessCategory::VO, map_profile_onto_ac(Profile::DP0));
EXPECT_EQ(access::AccessCategory::VI, map_profile_onto_ac(Profile::DP1));
EXPECT_EQ(access::AccessCategory::BE, map_profile_onto_ac(Profile::DP2));
EXPECT_EQ(access::AccessCategory::BK, map_profile_onto_ac(Profile::DP3));
auto malicious_profile = static_cast<Profile>(4);
EXPECT_THROW(map_profile_onto_ac(malicious_profile), std::invalid_argument);
}
@@ -0,0 +1,30 @@
#include <gtest/gtest.h>
#include <vanetza/dcc/smoothing_channel_probe_processor.hpp>
using namespace vanetza::dcc;
TEST(SmoothingChannelProbeProcessor, smoothing) {
SmoothingChannelProbeProcessor cpp;
EXPECT_EQ(ChannelLoad(0.0), cpp.channel_load());
cpp.indicate(ChannelLoad(0.5));
EXPECT_EQ(ChannelLoad(0.25), cpp.channel_load());
cpp.indicate(ChannelLoad(1.0));
EXPECT_EQ(ChannelLoad(0.625), cpp.channel_load());
cpp.indicate(ChannelLoad(0.0));
EXPECT_EQ(ChannelLoad(0.3125), cpp.channel_load());
cpp.indicate(ChannelLoad(0.0));
EXPECT_EQ(ChannelLoad(0.15625), cpp.channel_load());
}
TEST(SmoothingChannelProbeProcessor, update_call) {
ChannelLoad tmp;
SmoothingChannelProbeProcessor cpp;
cpp.on_indication = [&tmp](ChannelLoad cl) { tmp = cl; };
cpp.indicate(ChannelLoad(0.5));
EXPECT_EQ(ChannelLoad(0.25), tmp);
}
@@ -0,0 +1,61 @@
#include <gtest/gtest.h>
#include <vanetza/common/clock.hpp>
#include <vanetza/common/manual_runtime.hpp>
#include <vanetza/dcc/fully_meshed_state_machine.hpp>
#include <vanetza/dcc/state_machine_budget.hpp>
using namespace vanetza::dcc;
using vanetza::ManualRuntime;
using std::chrono::milliseconds;
static const vanetza::Clock::duration immediately = milliseconds(0);
class StateMachineBudgetTest : public ::testing::Test
{
protected:
StateMachineBudgetTest() :
runtime(vanetza::Clock::time_point { std::chrono::seconds(4711) }),
budget(fsm, runtime) {}
ManualRuntime runtime;
FullyMeshedStateMachine fsm;
StateMachineBudget budget;
};
TEST_F(StateMachineBudgetTest, relaxed)
{
Relaxed relaxed;
const auto relaxed_tx_interval = relaxed.transmission_interval();
ASSERT_EQ(relaxed_tx_interval, fsm.transmission_interval());
EXPECT_EQ(immediately, budget.delay());
budget.notify();
EXPECT_EQ(relaxed_tx_interval, budget.delay());
runtime.trigger(relaxed_tx_interval - milliseconds(10));
EXPECT_EQ(milliseconds(10), budget.delay());
runtime.trigger(milliseconds(20));
EXPECT_EQ(immediately, budget.delay());
}
TEST_F(StateMachineBudgetTest, restrictive)
{
Restrictive restrictive;
const auto restrictive_tx_interval = restrictive.transmission_interval();
// put FSM into restrictive state
for (unsigned i = 0; i < 10; ++i) {
fsm.update(ChannelLoad(0.6));
}
ASSERT_STREQ("Restrictive", fsm.state().name());
EXPECT_EQ(immediately, budget.delay());
budget.notify();
EXPECT_EQ(restrictive_tx_interval, budget.delay());
runtime.trigger(restrictive_tx_interval / 2);
EXPECT_EQ(restrictive_tx_interval / 2, budget.delay());
runtime.trigger(restrictive_tx_interval / 2);
EXPECT_EQ(immediately, budget.delay());
}