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:
@@ -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());
|
||||
}
|
||||
+86
@@ -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);
|
||||
}
|
||||
+112
@@ -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());
|
||||
}
|
||||
|
||||
+60
@@ -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);
|
||||
}
|
||||
Vendored
+30
@@ -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);
|
||||
}
|
||||
+61
@@ -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());
|
||||
}
|
||||
Reference in New Issue
Block a user