Keep vanetza-idf in obu-firmware, so a plain clone builds the firmware
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.
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
set(CXX_SOURCES
|
||||
burst_budget.cpp
|
||||
bursty_transmit_rate_control.cpp
|
||||
channel_load.cpp
|
||||
flow_control.cpp
|
||||
fully_meshed_state_machine.cpp
|
||||
gradual_state_machine.cpp
|
||||
hooked_channel_probe_processor.cpp
|
||||
limeric.cpp
|
||||
limeric_budget.cpp
|
||||
limeric_transmit_rate_control.cpp
|
||||
mapping.cpp
|
||||
single_reactive_transmit_rate_control.cpp
|
||||
state_machine_budget.cpp
|
||||
smoothing_channel_probe_processor.cpp
|
||||
transmission.cpp
|
||||
)
|
||||
|
||||
add_vanetza_component(dcc ${CXX_SOURCES})
|
||||
target_link_libraries(dcc PUBLIC access net)
|
||||
|
||||
add_test_subdirectory(tests)
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
#include <vanetza/common/runtime.hpp>
|
||||
#include <vanetza/dcc/burst_budget.hpp>
|
||||
#include <cassert>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
// these constants are given in C2C-CC Basic System Profile (last v1.3.0)
|
||||
constexpr Clock::duration T_Burst = std::chrono::seconds(1);
|
||||
constexpr Clock::duration T_BurstPeriod = std::chrono::seconds(10);
|
||||
constexpr std::size_t N_Burst = 20;
|
||||
} // namespace
|
||||
|
||||
|
||||
BurstBudget::BurstBudget(const Runtime& rt) :
|
||||
m_runtime(rt), m_messages(N_Burst), m_burst_duration(T_Burst), m_burst_period(T_BurstPeriod)
|
||||
{
|
||||
}
|
||||
|
||||
BurstBudget::~BurstBudget()
|
||||
{
|
||||
}
|
||||
|
||||
Clock::duration BurstBudget::delay()
|
||||
{
|
||||
assert(m_burst_duration < m_burst_period);
|
||||
Clock::duration delay = Clock::duration::max();
|
||||
|
||||
if (m_messages.empty()) {
|
||||
delay = Clock::duration::zero();
|
||||
} else if (m_messages.front() + m_burst_duration > m_runtime.now() && !m_messages.full()) {
|
||||
delay = Clock::duration::zero();
|
||||
} else if (m_messages.front() + m_burst_period < m_runtime.now()) {
|
||||
m_messages.clear();
|
||||
delay = Clock::duration::zero();
|
||||
} else {
|
||||
delay = m_messages.front() + m_burst_period - m_runtime.now();
|
||||
}
|
||||
|
||||
return delay;
|
||||
}
|
||||
|
||||
void BurstBudget::notify()
|
||||
{
|
||||
m_messages.push_back(m_runtime.now());
|
||||
}
|
||||
|
||||
void BurstBudget::burst_messages(std::size_t n)
|
||||
{
|
||||
// circular_buffer::resize removes last elements if necessary
|
||||
m_messages.resize(n);
|
||||
}
|
||||
|
||||
void BurstBudget::burst_duration(Clock::duration d)
|
||||
{
|
||||
assert(d > Clock::duration::zero());
|
||||
m_burst_duration = d;
|
||||
}
|
||||
|
||||
void BurstBudget::burst_period(Clock::duration p)
|
||||
{
|
||||
assert(p > Clock::duration::zero());
|
||||
m_burst_period = p;
|
||||
}
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,73 @@
|
||||
#ifndef BURST_BUDGET_HPP_U5GXDCZN
|
||||
#define BURST_BUDGET_HPP_U5GXDCZN
|
||||
|
||||
#include <vanetza/common/clock.hpp>
|
||||
#include <boost/circular_buffer.hpp>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
|
||||
// forward declaration
|
||||
class Runtime;
|
||||
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
/**
|
||||
* BurstBudget: TRC restrictions for DP0 message bursts as per C2C-CC BSP.
|
||||
*/
|
||||
class BurstBudget
|
||||
{
|
||||
public:
|
||||
BurstBudget(const Runtime&);
|
||||
~BurstBudget();
|
||||
|
||||
/**
|
||||
* Get current delay to remain in budget
|
||||
* \return shortest delay not exceeding budget
|
||||
*/
|
||||
Clock::duration delay();
|
||||
|
||||
/**
|
||||
* Notify budget of consumption
|
||||
*/
|
||||
void notify();
|
||||
|
||||
/**
|
||||
* Set upper limit of messages per burst
|
||||
* \param n burst limit
|
||||
*/
|
||||
void burst_messages(std::size_t n);
|
||||
|
||||
/**
|
||||
* Set maximum duration per burst
|
||||
* \param d burst duration
|
||||
*/
|
||||
void burst_duration(Clock::duration d);
|
||||
|
||||
/**
|
||||
* Set minimum duration between bursts
|
||||
* \param p burst period
|
||||
*/
|
||||
void burst_period(Clock::duration p);
|
||||
|
||||
/**
|
||||
* Get burst interval
|
||||
* \return bust interval
|
||||
*/
|
||||
Clock::duration burst_period() const { return m_burst_period; }
|
||||
|
||||
private:
|
||||
const Runtime& m_runtime;
|
||||
boost::circular_buffer<Clock::time_point> m_messages;
|
||||
Clock::duration m_burst_duration;
|
||||
Clock::duration m_burst_period;
|
||||
};
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* BURST_BUDGET_HPP_U5GXDCZN */
|
||||
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
#include <vanetza/common/runtime.hpp>
|
||||
#include <vanetza/dcc/bursty_transmit_rate_control.hpp>
|
||||
#include <vanetza/dcc/state_machine.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
BurstyTransmitRateControl::BurstyTransmitRateControl(const StateMachine& fsm, const Runtime& rt) :
|
||||
m_burst_budget(rt), m_fsm_budget(fsm, rt), m_fsm(fsm)
|
||||
{
|
||||
}
|
||||
|
||||
Clock::duration BurstyTransmitRateControl::delay(const Transmission& tx)
|
||||
{
|
||||
Clock::duration delay = Clock::duration::max();
|
||||
|
||||
switch (tx.profile()) {
|
||||
case Profile::DP0:
|
||||
delay = m_burst_budget.delay();
|
||||
break;
|
||||
case Profile::DP1:
|
||||
case Profile::DP2:
|
||||
case Profile::DP3:
|
||||
delay = m_fsm_budget.delay();
|
||||
break;
|
||||
default:
|
||||
throw std::invalid_argument("Invalid DCC Profile");
|
||||
break;
|
||||
};
|
||||
|
||||
return delay;
|
||||
}
|
||||
|
||||
Clock::duration BurstyTransmitRateControl::interval(const Transmission& tx)
|
||||
{
|
||||
Clock::duration interval = Clock::duration::max();
|
||||
|
||||
switch (tx.profile()) {
|
||||
case Profile::DP0:
|
||||
interval = m_burst_budget.burst_period();
|
||||
break;
|
||||
case Profile::DP1:
|
||||
case Profile::DP2:
|
||||
case Profile::DP3:
|
||||
interval = m_fsm.transmission_interval();
|
||||
break;
|
||||
default:
|
||||
throw std::invalid_argument("Invalid DCC Profile");
|
||||
break;
|
||||
}
|
||||
|
||||
return interval;
|
||||
}
|
||||
|
||||
void BurstyTransmitRateControl::notify(const Transmission& tx)
|
||||
{
|
||||
switch (tx.profile()) {
|
||||
case Profile::DP0:
|
||||
m_burst_budget.notify();
|
||||
break;
|
||||
case Profile::DP1:
|
||||
case Profile::DP2:
|
||||
case Profile::DP3:
|
||||
m_fsm_budget.notify();
|
||||
break;
|
||||
default:
|
||||
throw std::invalid_argument("Invalid DCC Profile");
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
#ifndef BURSTY_TRANSMIT_RATE_CONTROL_HPP_AM7LROYD
|
||||
#define BURSTY_TRANSMIT_RATE_CONTROL_HPP_AM7LROYD
|
||||
|
||||
#include <vanetza/common/clock.hpp>
|
||||
#include <vanetza/dcc/burst_budget.hpp>
|
||||
#include <vanetza/dcc/profile.hpp>
|
||||
#include <vanetza/dcc/state_machine_budget.hpp>
|
||||
#include <vanetza/dcc/transmit_rate_control.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
|
||||
// forward declarations
|
||||
class Runtime;
|
||||
namespace dcc { class StateMachine; }
|
||||
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
/**
|
||||
* Transmit Rate Control with occasional DP0 message bursts.
|
||||
* DP1, DP2 and DP3 messages are controlled by a state machine only.
|
||||
*/
|
||||
class BurstyTransmitRateControl : public TransmitRateControl
|
||||
{
|
||||
public:
|
||||
BurstyTransmitRateControl(const StateMachine&, const Runtime& rt);
|
||||
|
||||
Clock::duration delay(const Transmission&) override;
|
||||
Clock::duration interval(const Transmission&) override;
|
||||
void notify(const Transmission&) override;
|
||||
|
||||
private:
|
||||
BurstBudget m_burst_budget;
|
||||
StateMachineBudget m_fsm_budget;
|
||||
const StateMachine& m_fsm;
|
||||
};
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* BURSTY_TRANSMIT_RATE_CONTROL_HPP_AM7LROYD */
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
#include "channel_load.hpp"
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
ChannelLoad::ChannelLoad(const UnitInterval& interval) :
|
||||
UnitInterval(interval)
|
||||
{
|
||||
}
|
||||
|
||||
ChannelLoad::ChannelLoad(unsigned probes_busy, unsigned probes_total) :
|
||||
UnitInterval(create_from_probes(probes_busy, probes_total))
|
||||
{
|
||||
}
|
||||
|
||||
UnitInterval ChannelLoad::create_from_probes(unsigned probes_busy, unsigned probes_total)
|
||||
{
|
||||
double fraction = 0.0;
|
||||
if (probes_total != 0) {
|
||||
fraction = probes_busy;
|
||||
fraction /= probes_total;
|
||||
}
|
||||
|
||||
return UnitInterval(fraction);
|
||||
}
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
#ifndef CHANNEL_LOAD_HPP_D1JOCNLP
|
||||
#define CHANNEL_LOAD_HPP_D1JOCNLP
|
||||
|
||||
#include <vanetza/common/unit_interval.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
class ChannelLoad : public UnitInterval
|
||||
{
|
||||
public:
|
||||
using UnitInterval::UnitInterval;
|
||||
ChannelLoad() = default;
|
||||
ChannelLoad(const UnitInterval&);
|
||||
|
||||
/**
|
||||
* Create ChannelLoad from rational probes
|
||||
* \see ChannelLoad::create_from_probes
|
||||
*
|
||||
* \param probes_busy number of probes above busy threshold
|
||||
* \param probes_total total number of probes
|
||||
*/
|
||||
ChannelLoad(unsigned probes_busy, unsigned probes_total);
|
||||
|
||||
/**
|
||||
* Calculate UnitInterval representing ChannelLoad from rational probes
|
||||
* \param probes_busy number of probes above busy threshold
|
||||
* \param probes_total total number of probes
|
||||
* \return interval representing channel load (capped if probes_total < probes_busy)
|
||||
*/
|
||||
static UnitInterval create_from_probes(unsigned probes_busy, unsigned probes_total);
|
||||
};
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* CHANNEL_LOAD_HPP_D1JOCNLP */
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
#ifndef CHANNEL_PROBE_PROCESSOR_HPP_QBFTHSVC
|
||||
#define CHANNEL_PROBE_PROCESSOR_HPP_QBFTHSVC
|
||||
|
||||
#include <vanetza/dcc/channel_load.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
/**
|
||||
* Access point for radio layers to propagate their local channel load measurements
|
||||
*/
|
||||
class ChannelProbeProcessor
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Indicate a new channel load measurement
|
||||
* \see TS 102 686 V1.1.1 Annex A.1.2 for definition of "channel load"
|
||||
*
|
||||
* \param cl locally measured channel load
|
||||
*/
|
||||
virtual void indicate(ChannelLoad cl) = 0;
|
||||
|
||||
virtual ~ChannelProbeProcessor() = default;
|
||||
};
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* CHANNEL_PROBE_PROCESSOR_HPP_QBFTHSVC */
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef DATA_REQUEST_HPP_A5DWTTJN
|
||||
#define DATA_REQUEST_HPP_A5DWTTJN
|
||||
|
||||
#include <vanetza/common/byte_order.hpp>
|
||||
#include <vanetza/common/clock.hpp>
|
||||
#include <vanetza/dcc/profile.hpp>
|
||||
#include <vanetza/net/mac_address.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
struct DataRequest
|
||||
{
|
||||
DataRequest() : dcc_profile(Profile::DP0) {}
|
||||
|
||||
uint16be_t ether_type;
|
||||
MacAddress source;
|
||||
MacAddress destination;
|
||||
Profile dcc_profile;
|
||||
Clock::duration lifetime;
|
||||
};
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* DATA_REQUEST_HPP_A5DWTTJN */
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
#ifndef DUTY_CYCLE_PERMIT_HPP_9QUTPOPH
|
||||
#define DUTY_CYCLE_PERMIT_HPP_9QUTPOPH
|
||||
|
||||
#include <vanetza/common/unit_interval.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
/**
|
||||
* Interface for controlling channel usage by duty cycle limits
|
||||
*/
|
||||
class DutyCyclePermit
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Get allowed channel occupancy for local station in current time window
|
||||
* \return permitted duty cycle
|
||||
*/
|
||||
virtual UnitInterval permitted_duty_cycle() const = 0;
|
||||
|
||||
virtual ~DutyCyclePermit() = default;
|
||||
};
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* DUTY_CYCLE_PERMIT_HPP_9QUTPOPH */
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
#ifndef ENTITY_HPP_KUAWS3PK
|
||||
#define ENTITY_HPP_KUAWS3PK
|
||||
|
||||
#include <vanetza/dcc/channel_probe_processor.hpp>
|
||||
#include <vanetza/dcc/transmit_rate_control.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
class Entity
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Provide TRC interface for Facilities
|
||||
*
|
||||
* Cooperative Awareness adapts its message rate according to TRC (T_GenCam_Dcc).
|
||||
* \see EN 302 637-2 V1.3.2 (section 6.1.3)
|
||||
*/
|
||||
virtual TransmitRateControl& transmit_rate_control() = 0;
|
||||
|
||||
/**
|
||||
* Provide interface for reporting channel probes.
|
||||
*
|
||||
* Usually, radio hardware will generate these reports periodically.
|
||||
*/
|
||||
virtual ChannelProbeProcessor& channel_probe_processor() = 0;
|
||||
|
||||
virtual ~Entity() = default;
|
||||
};
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* ENTITY_HPP_KUAWS3PK */
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
#include "data_request.hpp"
|
||||
#include "flow_control.hpp"
|
||||
#include "mapping.hpp"
|
||||
#include "transmit_rate_control.hpp"
|
||||
#include <vanetza/access/data_request.hpp>
|
||||
#include <vanetza/access/interface.hpp>
|
||||
#include <vanetza/common/runtime.hpp>
|
||||
#include <algorithm>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
FlowControl::FlowControl(Runtime& runtime, TransmitRateControl& trc, access::Interface& ifc) :
|
||||
m_runtime(runtime), m_trc(trc), m_access(ifc), m_queue_length(0)
|
||||
{
|
||||
}
|
||||
|
||||
FlowControl::~FlowControl()
|
||||
{
|
||||
m_runtime.cancel(this);
|
||||
}
|
||||
|
||||
void FlowControl::request(const DataRequest& request, std::unique_ptr<ChunkPacket> packet)
|
||||
{
|
||||
drop_expired();
|
||||
|
||||
const TransmissionLite transmission { request.dcc_profile, packet->size() };
|
||||
if (transmit_immediately(transmission)) {
|
||||
m_trc.notify(transmission);
|
||||
transmit(request, std::move(packet));
|
||||
} else {
|
||||
enqueue(request, std::move(packet));
|
||||
}
|
||||
}
|
||||
|
||||
void FlowControl::trigger()
|
||||
{
|
||||
drop_expired();
|
||||
auto transmission = dequeue();
|
||||
if (transmission) {
|
||||
m_trc.notify(*transmission);
|
||||
transmit(transmission->request, std::move(transmission->packet));
|
||||
}
|
||||
|
||||
PendingTransmission* next = next_transmission();
|
||||
if (next) {
|
||||
schedule_trigger(*next);
|
||||
}
|
||||
}
|
||||
|
||||
void FlowControl::schedule_trigger(const Transmission& tx)
|
||||
{
|
||||
auto callback_delay = m_trc.delay(tx);
|
||||
m_runtime.schedule(callback_delay, std::bind(&FlowControl::trigger, this), this);
|
||||
}
|
||||
|
||||
void FlowControl::enqueue(const DataRequest& request, std::unique_ptr<ChunkPacket> packet)
|
||||
{
|
||||
const bool first_packet = empty();
|
||||
const auto ac = map_profile_onto_ac(request.dcc_profile);
|
||||
auto expiry = m_runtime.now() + request.lifetime;
|
||||
while (m_queue_length > 0 && m_queues[ac].size() >= m_queue_length) {
|
||||
m_queues[ac].pop_front();
|
||||
m_packet_drop_hook(ac, packet.get());
|
||||
}
|
||||
m_queues[ac].emplace_back(expiry, request, std::move(packet));
|
||||
|
||||
if (first_packet) {
|
||||
schedule_trigger(m_queues[ac].back());
|
||||
}
|
||||
}
|
||||
|
||||
boost::optional<FlowControl::PendingTransmission> FlowControl::dequeue()
|
||||
{
|
||||
boost::optional<PendingTransmission> transmission;
|
||||
Queue* queue = next_queue();
|
||||
if (queue) {
|
||||
transmission = std::move(queue->front());
|
||||
queue->pop_front();
|
||||
}
|
||||
|
||||
return transmission;
|
||||
}
|
||||
|
||||
bool FlowControl::transmit_immediately(const Transmission& transmission) const
|
||||
{
|
||||
const auto ac = map_profile_onto_ac(transmission.profile());
|
||||
|
||||
// is there any packet enqueued with equal or higher priority?
|
||||
bool contention = false;
|
||||
for (auto it = m_queues.cbegin(); it != m_queues.end(); ++it) {
|
||||
if (it->first >= ac && !it->second.empty()) {
|
||||
contention = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return !contention && m_trc.delay(transmission) == Clock::duration::zero();
|
||||
}
|
||||
|
||||
bool FlowControl::empty() const
|
||||
{
|
||||
return std::all_of(m_queues.cbegin(), m_queues.cend(),
|
||||
[](const std::pair<access::AccessCategory, const Queue&>& kv) {
|
||||
return kv.second.empty();
|
||||
});
|
||||
}
|
||||
|
||||
FlowControl::Queue* FlowControl::next_queue()
|
||||
{
|
||||
Queue* next = nullptr;
|
||||
Clock::duration min_delay = Clock::duration::max();
|
||||
|
||||
for (auto& kv : m_queues) {
|
||||
Queue& queue = kv.second;
|
||||
if (!queue.empty()) {
|
||||
const auto delay = m_trc.delay(queue.front());
|
||||
if (delay < min_delay) {
|
||||
min_delay = delay;
|
||||
next = &queue;
|
||||
}
|
||||
}
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
FlowControl::PendingTransmission* FlowControl::next_transmission()
|
||||
{
|
||||
Queue* queue = next_queue();
|
||||
return queue ? &queue->front() : nullptr;
|
||||
}
|
||||
|
||||
void FlowControl::drop_expired()
|
||||
{
|
||||
for (auto& kv : m_queues) {
|
||||
access::AccessCategory ac = kv.first;
|
||||
Queue& queue = kv.second;
|
||||
queue.remove_if([this, ac](const PendingTransmission& transmission) {
|
||||
bool drop = transmission.expiry < m_runtime.now();
|
||||
if (drop) {
|
||||
m_packet_drop_hook(ac, transmission.packet.get());
|
||||
}
|
||||
return drop;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void FlowControl::transmit(const DataRequest& request, std::unique_ptr<ChunkPacket> packet)
|
||||
{
|
||||
access::DataRequest access_request;
|
||||
access_request.source_addr = request.source;
|
||||
access_request.destination_addr = request.destination;
|
||||
access_request.ether_type = request.ether_type;
|
||||
access_request.access_category = map_profile_onto_ac(request.dcc_profile);
|
||||
|
||||
m_packet_transmit_hook(access_request.access_category, packet.get());
|
||||
m_access.request(access_request, std::move(packet));
|
||||
}
|
||||
|
||||
void FlowControl::set_packet_drop_hook(PacketDropHook::callback_type&& cb)
|
||||
{
|
||||
m_packet_drop_hook = std::move(cb);
|
||||
}
|
||||
|
||||
void FlowControl::set_packet_transmit_hook(PacketTransmitHook::callback_type&& cb)
|
||||
{
|
||||
m_packet_transmit_hook = std::move(cb);
|
||||
}
|
||||
|
||||
void FlowControl::queue_length(std::size_t length)
|
||||
{
|
||||
m_queue_length = length;
|
||||
}
|
||||
|
||||
void FlowControl::reschedule()
|
||||
{
|
||||
PendingTransmission* next = next_transmission();
|
||||
if (next) {
|
||||
m_runtime.cancel(this);
|
||||
schedule_trigger(*next);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,126 @@
|
||||
#ifndef FLOW_CONTROL_HPP_PG7RKD8V
|
||||
#define FLOW_CONTROL_HPP_PG7RKD8V
|
||||
|
||||
#include <vanetza/common/clock.hpp>
|
||||
#include <vanetza/common/hook.hpp>
|
||||
#include <vanetza/dcc/data_request.hpp>
|
||||
#include <vanetza/dcc/interface.hpp>
|
||||
#include <vanetza/dcc/profile.hpp>
|
||||
#include <vanetza/dcc/transmission.hpp>
|
||||
#include <vanetza/net/chunk_packet.hpp>
|
||||
#include <boost/optional/optional.hpp>
|
||||
#include <vanetza/access/access_category.hpp>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
|
||||
// forward declarations
|
||||
namespace access { class Interface; }
|
||||
class Runtime;
|
||||
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
// forward declarations
|
||||
class TransmitRateControl;
|
||||
|
||||
/**
|
||||
* FlowControl is a gatekeeper above access layer.
|
||||
*
|
||||
* There is a queue for each access category. Packets might be enqueued
|
||||
* because of exceeded transmission intervals determined by Scheduler.
|
||||
* If a packet's lifetime expires before transmission it will be dropped.
|
||||
*/
|
||||
class FlowControl : public RequestInterface
|
||||
{
|
||||
public:
|
||||
using PacketDropHook = Hook<access::AccessCategory, const ChunkPacket*>;
|
||||
using PacketTransmitHook = Hook<access::AccessCategory, const ChunkPacket*>;
|
||||
|
||||
/**
|
||||
* Create FlowControl instance
|
||||
* \param rt Runtime used for timed actions, e.g. packet expiry
|
||||
* \param scheduler Scheduler providing transmission intervals
|
||||
* \param access Interface to access layer
|
||||
*/
|
||||
FlowControl(Runtime&, TransmitRateControl&, access::Interface&);
|
||||
~FlowControl();
|
||||
|
||||
/**
|
||||
* Request packet transmission
|
||||
* \param request DCC request parameters
|
||||
* \param packet Packet data
|
||||
*/
|
||||
void request(const DataRequest&, std::unique_ptr<ChunkPacket>) override;
|
||||
|
||||
/**
|
||||
* Set callback to be invoked at packet drop. Replaces any previous callback.
|
||||
* \param cb Callback
|
||||
*/
|
||||
void set_packet_drop_hook(PacketDropHook::callback_type&&);
|
||||
|
||||
/**
|
||||
* Set callback to be invoked at packet transmission. Replaces any previous callback.
|
||||
* \param cb Callback
|
||||
*/
|
||||
void set_packet_transmit_hook(PacketTransmitHook::callback_type&&);
|
||||
|
||||
/**
|
||||
* Set length of each queue
|
||||
*
|
||||
* The first queue element is dropped when the length limit is hit.
|
||||
* \param length Maximum number of queue elements, 0 for unlimited length
|
||||
*/
|
||||
void queue_length(std::size_t length);
|
||||
|
||||
/**
|
||||
* Reschedule queued transmissions
|
||||
* This reevaluates TRC restrictions as well, packets may get transmitted earlier.
|
||||
*/
|
||||
void reschedule();
|
||||
|
||||
private:
|
||||
struct PendingTransmission : public Transmission
|
||||
{
|
||||
PendingTransmission(Clock::time_point expiry, const DataRequest& request, std::unique_ptr<ChunkPacket> packet) :
|
||||
expiry(expiry), request(request), packet(std::move(packet)) {}
|
||||
|
||||
Clock::time_point expiry;
|
||||
DataRequest request;
|
||||
std::unique_ptr<ChunkPacket> packet;
|
||||
|
||||
Profile profile() const override { return request.dcc_profile; }
|
||||
const access::DataRateG5* data_rate() const override { return &access::G5_6Mbps; }
|
||||
std::size_t body_length() const override { return packet ? packet->size() : 0; }
|
||||
};
|
||||
|
||||
using Queue = std::list<PendingTransmission>;
|
||||
|
||||
void enqueue(const DataRequest&, std::unique_ptr<ChunkPacket>);
|
||||
boost::optional<PendingTransmission> dequeue();
|
||||
void transmit(const DataRequest&, std::unique_ptr<ChunkPacket>);
|
||||
bool transmit_immediately(const Transmission&) const;
|
||||
void drop_expired();
|
||||
bool empty() const;
|
||||
void trigger();
|
||||
void schedule_trigger(const Transmission&);
|
||||
PendingTransmission* next_transmission();
|
||||
Queue* next_queue();
|
||||
|
||||
Runtime& m_runtime;
|
||||
TransmitRateControl& m_trc;
|
||||
access::Interface& m_access;
|
||||
std::map<access::AccessCategory, Queue, std::greater<access::AccessCategory>> m_queues;
|
||||
std::size_t m_queue_length;
|
||||
PacketDropHook m_packet_drop_hook;
|
||||
PacketTransmitHook m_packet_transmit_hook;
|
||||
};
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* FLOW_CONTROL_HPP_PG7RKD8V */
|
||||
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
#include "channel_load.hpp"
|
||||
#include "fully_meshed_state_machine.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
static constexpr std::size_t N_samples_up = std::chrono::seconds(1) / NDL_minDccSampling;
|
||||
static constexpr std::size_t N_samples_down = std::chrono::seconds(5) / NDL_minDccSampling;
|
||||
|
||||
static constexpr double NDL_minChannelLoad = 0.19;
|
||||
static constexpr double NDL_maxChannelLoad = 0.59;
|
||||
|
||||
Clock::duration Relaxed::transmission_interval() const
|
||||
{
|
||||
return std::chrono::milliseconds(60);
|
||||
}
|
||||
|
||||
const char* Relaxed::name() const
|
||||
{
|
||||
return "Relaxed";
|
||||
}
|
||||
|
||||
Clock::duration Restrictive::transmission_interval() const
|
||||
{
|
||||
return std::chrono::milliseconds(460);
|
||||
}
|
||||
|
||||
const char* Restrictive::name() const
|
||||
{
|
||||
return "Restrictive";
|
||||
}
|
||||
|
||||
const std::size_t Active::sc_substates = 5;
|
||||
|
||||
Active::Active() : m_substate(0)
|
||||
{
|
||||
}
|
||||
|
||||
void Active::update(double min_cl, double max_cl)
|
||||
{
|
||||
assert(min_cl <= max_cl);
|
||||
|
||||
static const std::array<double, sc_substates> channel_loads {{
|
||||
0.27, 0.35, 0.43, 0.51, 0.59
|
||||
}};
|
||||
|
||||
auto state_up_it = std::upper_bound(channel_loads.begin(), channel_loads.end(), min_cl);
|
||||
auto state_up = std::distance(channel_loads.begin(), state_up_it);
|
||||
|
||||
auto state_down_it = std::upper_bound(channel_loads.begin(), channel_loads.end(), max_cl);
|
||||
auto state_down = std::distance(channel_loads.begin(), state_down_it);
|
||||
|
||||
m_substate = std::max(state_up, state_down);
|
||||
m_substate = std::min(sc_substates - 1, m_substate);
|
||||
assert(m_substate < sc_substates);
|
||||
}
|
||||
|
||||
Clock::duration Active::transmission_interval() const
|
||||
{
|
||||
static const std::array<Clock::duration, sc_substates> tx_intervals {{
|
||||
std::chrono::milliseconds(100),
|
||||
std::chrono::milliseconds(180),
|
||||
std::chrono::milliseconds(260),
|
||||
std::chrono::milliseconds(340),
|
||||
std::chrono::milliseconds(420),
|
||||
}};
|
||||
|
||||
const std::size_t index = std::min(tx_intervals.size() - 1, m_substate);
|
||||
return tx_intervals[index];
|
||||
}
|
||||
|
||||
const char* Active::name() const
|
||||
{
|
||||
static const std::array<const char*, sc_substates> names {{
|
||||
"Active 1",
|
||||
"Active 2",
|
||||
"Active 3",
|
||||
"Active 4",
|
||||
"Active 5"
|
||||
}};
|
||||
|
||||
assert(m_substate < sc_substates);
|
||||
return names[m_substate];
|
||||
}
|
||||
|
||||
FullyMeshedStateMachine::FullyMeshedStateMachine() :
|
||||
m_state(&m_relaxed),
|
||||
m_channel_loads(std::max(N_samples_up, N_samples_down))
|
||||
{
|
||||
}
|
||||
|
||||
FullyMeshedStateMachine::~FullyMeshedStateMachine()
|
||||
{
|
||||
}
|
||||
|
||||
void FullyMeshedStateMachine::update(ChannelLoad cl)
|
||||
{
|
||||
m_channel_loads.push_front(cl);
|
||||
|
||||
if (m_state == &m_relaxed) {
|
||||
if (min_channel_load() >= NDL_minChannelLoad) {
|
||||
m_state = &m_active;
|
||||
m_active.update(min_channel_load(), max_channel_load());
|
||||
}
|
||||
} else if (m_state == &m_restrictive) {
|
||||
if (max_channel_load() < NDL_maxChannelLoad) {
|
||||
m_state = &m_active;
|
||||
m_active.update(min_channel_load(), max_channel_load());
|
||||
}
|
||||
} else {
|
||||
if (max_channel_load() < NDL_minChannelLoad) {
|
||||
m_state = &m_relaxed;
|
||||
} else if (min_channel_load() >= NDL_maxChannelLoad) {
|
||||
m_state = &m_restrictive;
|
||||
} else {
|
||||
m_state = &m_active;
|
||||
m_active.update(min_channel_load(), max_channel_load());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double FullyMeshedStateMachine::message_rate() const
|
||||
{
|
||||
std::chrono::duration<double> one_sec = std::chrono::seconds(1);
|
||||
return one_sec / transmission_interval();
|
||||
}
|
||||
|
||||
Clock::duration FullyMeshedStateMachine::transmission_interval() const
|
||||
{
|
||||
return m_state->transmission_interval();
|
||||
}
|
||||
|
||||
const State& FullyMeshedStateMachine::state() const
|
||||
{
|
||||
assert(m_state != nullptr);
|
||||
return *m_state;
|
||||
}
|
||||
|
||||
double FullyMeshedStateMachine::min_channel_load() const
|
||||
{
|
||||
assert(N_samples_up > 0);
|
||||
double min_cl = std::numeric_limits<double>::infinity();
|
||||
std::size_t sample_cnt = 0;
|
||||
for (auto sample : m_channel_loads) {
|
||||
if (sample_cnt >= N_samples_up) {
|
||||
break;
|
||||
} else if (sample.value() < min_cl) {
|
||||
min_cl = sample.value();
|
||||
}
|
||||
++sample_cnt;
|
||||
}
|
||||
|
||||
return std::isinf(min_cl) ? 0.0 : min_cl;
|
||||
}
|
||||
|
||||
double FullyMeshedStateMachine::max_channel_load() const
|
||||
{
|
||||
assert(N_samples_down > 0);
|
||||
double max_cl = 0.0;
|
||||
std::size_t sample_cnt = 0;
|
||||
for (auto sample : m_channel_loads) {
|
||||
if (sample_cnt >= N_samples_down) {
|
||||
break;
|
||||
} else if (sample.value() > max_cl) {
|
||||
max_cl = sample.value();
|
||||
}
|
||||
++sample_cnt;
|
||||
}
|
||||
|
||||
return max_cl;
|
||||
}
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
#ifndef FULLY_MESHED_STATE_MACHINE_HPP_YPE958OH
|
||||
#define FULLY_MESHED_STATE_MACHINE_HPP_YPE958OH
|
||||
|
||||
#include <vanetza/common/clock.hpp>
|
||||
#include <vanetza/dcc/channel_load.hpp>
|
||||
#include <vanetza/dcc/state_machine.hpp>
|
||||
#include <boost/circular_buffer.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
// constants
|
||||
static constexpr Clock::duration NDL_minDccSampling = std::chrono::milliseconds(100);
|
||||
|
||||
|
||||
class State
|
||||
{
|
||||
public:
|
||||
virtual Clock::duration transmission_interval() const = 0;
|
||||
virtual const char* name() const = 0;
|
||||
virtual ~State() {}
|
||||
};
|
||||
|
||||
class Relaxed : public State
|
||||
{
|
||||
public:
|
||||
Clock::duration transmission_interval() const override;
|
||||
const char* name() const override;
|
||||
};
|
||||
|
||||
class Active : public State
|
||||
{
|
||||
public:
|
||||
Active();
|
||||
void update(double min, double max);
|
||||
Clock::duration transmission_interval() const override;
|
||||
const char* name() const override;
|
||||
|
||||
private:
|
||||
static const std::size_t sc_substates;
|
||||
std::size_t m_substate;
|
||||
};
|
||||
|
||||
class Restrictive : public State
|
||||
{
|
||||
public:
|
||||
Clock::duration transmission_interval() const override;
|
||||
const char* name() const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fully meshed TRC state machine as per TS 102 687 v1.1.1
|
||||
*
|
||||
* States are modelled according to C2C-CC DCC Whitepaper / BSP v1.2
|
||||
* State transitions are deferred internally for ramping up and cooling down over time.
|
||||
*/
|
||||
class FullyMeshedStateMachine : public StateMachine
|
||||
{
|
||||
public:
|
||||
FullyMeshedStateMachine();
|
||||
~FullyMeshedStateMachine();
|
||||
|
||||
/**
|
||||
* Notify state machine about current channel load.
|
||||
* This method expects to be called in regular intervals
|
||||
* of NDL_minDccSampling length.
|
||||
*/
|
||||
void update(ChannelLoad channel_load);
|
||||
|
||||
/**
|
||||
* Get currently allowed maximum message rate depending on state
|
||||
* \return messages per second
|
||||
*/
|
||||
double message_rate() const;
|
||||
|
||||
/**
|
||||
* Get advised transmission interval between consecutive messages
|
||||
* \return message transmission interval
|
||||
*/
|
||||
Clock::duration transmission_interval() const;
|
||||
|
||||
/**
|
||||
* Get state machine's active state
|
||||
*/
|
||||
const State& state() const;
|
||||
|
||||
private:
|
||||
double max_channel_load() const;
|
||||
double min_channel_load() const;
|
||||
|
||||
Relaxed m_relaxed;
|
||||
Active m_active;
|
||||
Restrictive m_restrictive;
|
||||
State* m_state;
|
||||
boost::circular_buffer<ChannelLoad> m_channel_loads;
|
||||
};
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* FULLY_MESHED_STATE_MACHINE_HPP_YPE958OH */
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
#include "gradual_state_machine.hpp"
|
||||
#include <boost/format.hpp>
|
||||
#include <iterator>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
GradualStateMachine::GradualStateMachine(const std::set<State>& states) :
|
||||
m_states(states), m_current(m_states.begin())
|
||||
{
|
||||
repair();
|
||||
}
|
||||
|
||||
GradualStateMachine::GradualStateMachine(std::set<State>&& states) :
|
||||
m_states(std::move(states)), m_current(m_states.begin())
|
||||
{
|
||||
repair();
|
||||
}
|
||||
|
||||
void GradualStateMachine::update(ChannelLoad cbr)
|
||||
{
|
||||
static_assert(std::is_base_of<std::bidirectional_iterator_tag,
|
||||
std::iterator_traits<StateContainer::const_iterator>::iterator_category>::value,
|
||||
"State transitions require bidirectional iterators");
|
||||
|
||||
if (cbr < m_current->lower_limit) {
|
||||
if (m_current != m_states.begin()) {
|
||||
std::advance(m_current, -1);
|
||||
}
|
||||
} else {
|
||||
StateContainer::const_iterator up = std::next(m_current);
|
||||
if (up != m_states.end() && cbr >= up->lower_limit) {
|
||||
m_current = up;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Clock::duration GradualStateMachine::transmission_interval() const
|
||||
{
|
||||
return m_current->off_time;
|
||||
}
|
||||
|
||||
std::string GradualStateMachine::state() const
|
||||
{
|
||||
if (m_current == m_states.begin()) {
|
||||
return "Relaxed";
|
||||
} else if (m_current == std::prev(m_states.end())) {
|
||||
return "Restrictive";
|
||||
} else {
|
||||
static const boost::format fmt("Active %1%");
|
||||
return (boost::format(fmt) % std::distance(m_states.begin(), m_current)).str();
|
||||
}
|
||||
}
|
||||
|
||||
void GradualStateMachine::repair()
|
||||
{
|
||||
if (m_states.empty()) {
|
||||
m_states.emplace(ChannelLoad(0.0), Clock::duration::zero());
|
||||
m_current = m_states.begin();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,77 @@
|
||||
#ifndef GRADUAL_STATE_MACHINE_HPP_CGPVG4CS
|
||||
#define GRADUAL_STATE_MACHINE_HPP_CGPVG4CS
|
||||
|
||||
#include <vanetza/common/clock.hpp>
|
||||
#include <vanetza/dcc/channel_load.hpp>
|
||||
#include <vanetza/dcc/state_machine.hpp>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
/**
|
||||
* Reactive Transmit Rate Control (TRC) based on a state machine.
|
||||
*
|
||||
* This implementation complies with ETSI TS 102 687 v1.2.1,
|
||||
* i.e. transitions can only happen gradually between neighbouring states.
|
||||
* No ramping up or cooling down timing behaviour exists (not specified anymore).
|
||||
*/
|
||||
class GradualStateMachine : public StateMachine
|
||||
{
|
||||
public:
|
||||
struct State
|
||||
{
|
||||
constexpr State(ChannelLoad limit, Clock::duration off_time) :
|
||||
lower_limit(limit), off_time(off_time) {}
|
||||
|
||||
ChannelLoad lower_limit;
|
||||
Clock::duration off_time;
|
||||
|
||||
bool operator<(const State& other) const { return lower_limit < other.lower_limit; }
|
||||
};
|
||||
using StateContainer = std::set<State>;
|
||||
|
||||
GradualStateMachine(const StateContainer&);
|
||||
GradualStateMachine(StateContainer&&);
|
||||
|
||||
void update(ChannelLoad) override;
|
||||
Clock::duration transmission_interval() const override;
|
||||
std::string state() const;
|
||||
|
||||
private:
|
||||
void repair();
|
||||
|
||||
StateContainer m_states;
|
||||
StateContainer::const_iterator m_current;
|
||||
};
|
||||
|
||||
/**
|
||||
* CBR mapping as per TS 102 687 V1.2.1 Table A.1 (max T_on = 1ms)
|
||||
*/
|
||||
static const GradualStateMachine::StateContainer etsiStates1ms = {
|
||||
{ ChannelLoad(0.00), std::chrono::milliseconds(100) },
|
||||
{ ChannelLoad(0.30), std::chrono::milliseconds(200) },
|
||||
{ ChannelLoad(0.40), std::chrono::milliseconds(400) },
|
||||
{ ChannelLoad(0.50), std::chrono::milliseconds(500) },
|
||||
{ ChannelLoad(0.60), std::chrono::milliseconds(1000) }
|
||||
};
|
||||
|
||||
/**
|
||||
* CBR mapping as per TS 102 686 V1.2.1 Table A.2 (max T_on = 500 us)
|
||||
*/
|
||||
static const GradualStateMachine::StateContainer etsiStates500us = {
|
||||
{ ChannelLoad(0.00), std::chrono::milliseconds(50) },
|
||||
{ ChannelLoad(0.30), std::chrono::milliseconds(100) },
|
||||
{ ChannelLoad(0.40), std::chrono::milliseconds(200) },
|
||||
{ ChannelLoad(0.50), std::chrono::milliseconds(250) },
|
||||
{ ChannelLoad(0.65), std::chrono::milliseconds(1000) }
|
||||
};
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* GRADUAL_STATE_MACHINE_HPP_CGPVG4CS */
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
#include "hooked_channel_probe_processor.hpp"
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
HookedChannelProbeProcessor::HookedChannelProbeProcessor() :
|
||||
on_indication(m_indication_hook)
|
||||
{
|
||||
}
|
||||
|
||||
void HookedChannelProbeProcessor::indicate(ChannelLoad cl)
|
||||
{
|
||||
m_indication_hook(cl);
|
||||
}
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
#ifndef HOOKED_CHANNEL_PROBE_PROCESSOR_HPP_M1O7VHKS
|
||||
#define HOOKED_CHANNEL_PROBE_PROCESSOR_HPP_M1O7VHKS
|
||||
|
||||
#include <vanetza/common/hook.hpp>
|
||||
#include <vanetza/dcc/channel_probe_processor.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
/**
|
||||
* Implementation of ChannelProbeProcessor invoking hook on indication
|
||||
*/
|
||||
class HookedChannelProbeProcessor : public ChannelProbeProcessor
|
||||
{
|
||||
public:
|
||||
HookedChannelProbeProcessor();
|
||||
void indicate(ChannelLoad) override;
|
||||
|
||||
HookRegistry<ChannelLoad> on_indication;
|
||||
|
||||
private:
|
||||
Hook<ChannelLoad> m_indication_hook;
|
||||
};
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* HOOKED_CHANNEL_PROBE_PROCESSOR_HPP_M1O7VHKS */
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
#ifndef INTERFACE_HPP_4SUUTA6X
|
||||
#define INTERFACE_HPP_4SUUTA6X
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
|
||||
// forward declarations
|
||||
class ChunkPacket;
|
||||
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
// forward declarations
|
||||
struct DataRequest;
|
||||
|
||||
/**
|
||||
* DCC_access interface for data request from upper layers
|
||||
*/
|
||||
class RequestInterface
|
||||
{
|
||||
public:
|
||||
virtual void request(const DataRequest&, std::unique_ptr<ChunkPacket>) = 0;
|
||||
virtual ~RequestInterface() = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Null implemenation of DCC data request interface
|
||||
*/
|
||||
class NullRequestInterface : public RequestInterface
|
||||
{
|
||||
public:
|
||||
void request(const DataRequest&, std::unique_ptr<ChunkPacket>) override {}
|
||||
};
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* INTERFACE_HPP_4SUUTA6X */
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
#include "limeric.hpp"
|
||||
#include <vanetza/common/runtime.hpp>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <numeric>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
static const Limeric::Parameters limericDefaultParams;
|
||||
|
||||
Limeric::Limeric(Runtime& rt) : Limeric(rt, limericDefaultParams)
|
||||
{
|
||||
}
|
||||
|
||||
Limeric::Limeric(Runtime& rt, const Parameters& params) :
|
||||
on_duty_cycle_change(m_duty_cycle_change), m_runtime(rt), m_params(params),
|
||||
m_duty_cycle(mean(params.delta_max, params.delta_min)), m_cbr(2)
|
||||
{
|
||||
assert(m_cbr.empty());
|
||||
schedule();
|
||||
}
|
||||
|
||||
Limeric::~Limeric()
|
||||
{
|
||||
m_runtime.cancel(this);
|
||||
}
|
||||
|
||||
ChannelLoad Limeric::average_cbr() const
|
||||
{
|
||||
if (m_cbr.full()) {
|
||||
return 0.5 * mean(m_cbr.begin(), m_cbr.end()) + 0.5 * m_channel_load;
|
||||
} else {
|
||||
return m_channel_load;
|
||||
}
|
||||
}
|
||||
|
||||
void Limeric::update_cbr(ChannelLoad cbr)
|
||||
{
|
||||
const bool full = m_cbr.full();
|
||||
m_cbr.push_back(cbr);
|
||||
if (!full) {
|
||||
m_channel_load = mean(m_cbr.begin(), m_cbr.end());
|
||||
}
|
||||
}
|
||||
|
||||
UnitInterval Limeric::calculate_duty_cycle() const
|
||||
{
|
||||
const double cbr_delta = m_params.cbr_target.value() - m_channel_load.value();
|
||||
double delta_offset = 0.0;
|
||||
if (cbr_delta > 0.0) {
|
||||
delta_offset = std::min(m_params.beta.value() * cbr_delta, m_params.g_plus_max);
|
||||
} else {
|
||||
delta_offset = std::max(m_params.beta.value() * cbr_delta, m_params.g_minus_max);
|
||||
}
|
||||
UnitInterval delta = m_params.alpha.complement() * m_duty_cycle + delta_offset;
|
||||
delta = std::min(std::max(delta, m_params.delta_min), m_params.delta_max);
|
||||
|
||||
if (m_dual_alpha) {
|
||||
if (m_duty_cycle - delta > m_dual_alpha->threshold) {
|
||||
delta = m_dual_alpha->alternate_alpha.complement() * m_duty_cycle + delta_offset;
|
||||
delta = std::min(std::max(delta, m_params.delta_min), m_params.delta_max);
|
||||
}
|
||||
}
|
||||
return delta;
|
||||
}
|
||||
|
||||
void Limeric::calculate(Clock::time_point tp)
|
||||
{
|
||||
m_channel_load = average_cbr();
|
||||
m_duty_cycle = calculate_duty_cycle(); // uses m_channel_load
|
||||
m_duty_cycle_change(this, tp);
|
||||
schedule();
|
||||
}
|
||||
|
||||
void Limeric::schedule()
|
||||
{
|
||||
// schedule for next possible modulo 2 * cbr_interval (usually 200ms) time point
|
||||
const Clock::duration scheduling_interval = 2 * m_params.cbr_interval;
|
||||
Clock::time_point tp = m_runtime.now() + scheduling_interval;
|
||||
const Clock::duration scheduling_bias = tp.time_since_epoch() % scheduling_interval;
|
||||
if (scheduling_bias > m_params.cbr_interval) {
|
||||
tp += scheduling_interval - scheduling_bias;
|
||||
} else if (scheduling_bias > Clock::duration::zero()) {
|
||||
tp -= scheduling_bias;
|
||||
}
|
||||
m_runtime.schedule(tp, [this](Clock::time_point tp) {
|
||||
this->calculate(tp);
|
||||
});
|
||||
}
|
||||
|
||||
void Limeric::configure_dual_alpha(const boost::optional<DualAlphaParameters>& params)
|
||||
{
|
||||
m_dual_alpha = params;
|
||||
}
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,110 @@
|
||||
#ifndef LIMERIC_HPP_OPCJEHBN
|
||||
#define LIMERIC_HPP_OPCJEHBN
|
||||
|
||||
#include <vanetza/common/clock.hpp>
|
||||
#include <vanetza/common/hook.hpp>
|
||||
#include <vanetza/common/unit_interval.hpp>
|
||||
#include <vanetza/dcc/channel_load.hpp>
|
||||
#include <vanetza/dcc/duty_cycle_permit.hpp>
|
||||
#include <boost/circular_buffer.hpp>
|
||||
#include <boost/optional/optional.hpp>
|
||||
#include <chrono>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
|
||||
// forward declaration
|
||||
class Runtime;
|
||||
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
/**
|
||||
* LIMERIC adapted to ETSI ITS
|
||||
*
|
||||
* This implementation follows TS 102 687 v1.2.1 section 5.4
|
||||
* Optionally, the dual-alpha convergence proposed in [1] can be enabled.
|
||||
*
|
||||
* [1] Ignacio Soto, Oscar Amador, Manuel Uruena, Maria Calderon
|
||||
* "Strengths and Weaknesses of the ETSI Adaptive DCC Algorithm: A Proposal for Improvement"
|
||||
* DOI: 10.1109/LCOMM.2019.2906178
|
||||
*/
|
||||
class Limeric : public DutyCyclePermit
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Limeric paremeters as given by TS 102 687 v1.2.1, Table 3
|
||||
*/
|
||||
struct Parameters
|
||||
{
|
||||
UnitInterval alpha { 0.016 }; /*< also named alpha_low if dual-alpha is enabled */
|
||||
UnitInterval beta { 0.0012 };
|
||||
UnitInterval delta_max { 0.03 }; /*< upper bound permitted duty cycle */
|
||||
UnitInterval delta_min { 0.0006 }; /*< lower bound permitted duty cycle */
|
||||
double g_plus_max = 0.0005;
|
||||
double g_minus_max = -0.00025;
|
||||
ChannelLoad cbr_target { 0.68 };
|
||||
Clock::duration cbr_interval = std::chrono::milliseconds(100); /*< algorithm is scheduled every second interval */
|
||||
};
|
||||
|
||||
struct DualAlphaParameters
|
||||
{
|
||||
UnitInterval alternate_alpha { 0.1 }; /*< called alpha_high in [1] */
|
||||
UnitInterval threshold { 0.00001 }; /* heuristically determined threshold for switching between alphas */
|
||||
};
|
||||
|
||||
Limeric(Runtime&);
|
||||
Limeric(Runtime&, const Parameters&);
|
||||
~Limeric();
|
||||
|
||||
/**
|
||||
* Report new channel load measurement
|
||||
* \param cl channel load measurement
|
||||
*/
|
||||
void update_cbr(ChannelLoad);
|
||||
|
||||
/**
|
||||
* Get current averaged CBR.
|
||||
* \note The result incorporates previous measurements as well as the averaged CBR during last periodic update.
|
||||
* \return averaged CBR
|
||||
*/
|
||||
ChannelLoad average_cbr() const;
|
||||
|
||||
/**
|
||||
* Get permitted duty cycle as calculated by the last periodic update
|
||||
* \return permitted duty cycle
|
||||
*/
|
||||
UnitInterval permitted_duty_cycle() const override { return m_duty_cycle; }
|
||||
|
||||
/**
|
||||
* Called every time the permitted duty cycle is updated
|
||||
* \param this instance itself
|
||||
* \param time point for which algorithm update has been scheduled
|
||||
*/
|
||||
HookRegistry<const Limeric*, Clock::time_point> on_duty_cycle_change;
|
||||
|
||||
/**
|
||||
* Configure dual-alpha convergence
|
||||
* \param params dual-alpha parameters, boost::none disables dual-alpha convergence
|
||||
*/
|
||||
void configure_dual_alpha(const boost::optional<DualAlphaParameters>& params);
|
||||
|
||||
private:
|
||||
void calculate(Clock::time_point);
|
||||
void schedule();
|
||||
UnitInterval calculate_duty_cycle() const;
|
||||
|
||||
Runtime& m_runtime;
|
||||
Parameters m_params;
|
||||
boost::optional<DualAlphaParameters> m_dual_alpha;
|
||||
ChannelLoad m_channel_load; /*< moving average channel load */
|
||||
UnitInterval m_duty_cycle;
|
||||
boost::circular_buffer<ChannelLoad> m_cbr;
|
||||
Hook<const Limeric*, Clock::time_point> m_duty_cycle_change;
|
||||
};
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* LIMERIC_HPP_OPCJEHBN */
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
#include "duty_cycle_permit.hpp"
|
||||
#include "limeric_budget.hpp"
|
||||
#include <vanetza/common/runtime.hpp>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr Clock::duration min_interval = std::chrono::milliseconds(25);
|
||||
constexpr Clock::duration max_interval = std::chrono::seconds(1);
|
||||
} // namespace
|
||||
|
||||
LimericBudget::LimericBudget(const DutyCyclePermit& dcp, const Runtime& rt) :
|
||||
m_duty_cycle_permit(dcp), m_runtime(rt),
|
||||
m_interval(min_interval), m_tx_start(Clock::time_point::min()),
|
||||
m_tx_on(Clock::duration::zero())
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
Clock::duration LimericBudget::delay()
|
||||
{
|
||||
Clock::duration delay = Clock::duration::max();
|
||||
if (m_runtime.now() >= m_tx_start + m_interval) {
|
||||
delay = Clock::duration::zero();
|
||||
} else {
|
||||
delay = m_tx_start + m_interval - m_runtime.now();
|
||||
}
|
||||
return delay;
|
||||
}
|
||||
|
||||
Clock::duration LimericBudget::interval()
|
||||
{
|
||||
return m_interval;
|
||||
}
|
||||
|
||||
void LimericBudget::notify(Clock::duration tx_on)
|
||||
{
|
||||
m_tx_start = m_runtime.now();
|
||||
m_tx_on = tx_on;
|
||||
|
||||
using std::chrono::duration_cast;
|
||||
const auto duty_cycle = m_duty_cycle_permit.permitted_duty_cycle();
|
||||
const auto interval = duration_cast<Clock::duration>(tx_on / duty_cycle.value());
|
||||
m_interval = clamp_interval(interval);
|
||||
}
|
||||
|
||||
void LimericBudget::update()
|
||||
{
|
||||
using std::chrono::duration_cast;
|
||||
using FloatingPointDuration = std::chrono::duration<double, Clock::period>;
|
||||
const FloatingPointDuration delay = m_tx_start + m_interval - m_runtime.now();
|
||||
const double duty_cycle = m_duty_cycle_permit.permitted_duty_cycle().value();
|
||||
|
||||
if (duty_cycle > 0.0) {
|
||||
if (delay.count() > 0.0) {
|
||||
// Apply equation B.2 of TS 102 687 v1.2.1 if gate is closed at the moment
|
||||
const FloatingPointDuration interval = (m_tx_on / duty_cycle) * (delay / m_interval);
|
||||
m_interval = clamp_interval(duration_cast<Clock::duration>(interval) + m_runtime.now() - m_tx_start);
|
||||
} else {
|
||||
// use equation B.1 otherwise
|
||||
const FloatingPointDuration interval = m_tx_on / duty_cycle;
|
||||
m_interval = clamp_interval(duration_cast<Clock::duration>(interval));
|
||||
}
|
||||
} else {
|
||||
// bail out with maximum interval if duty cycle is not positive
|
||||
m_interval = max_interval;
|
||||
}
|
||||
}
|
||||
|
||||
Clock::duration LimericBudget::clamp_interval(Clock::duration interval) const
|
||||
{
|
||||
return std::min(std::max(interval, min_interval), max_interval);
|
||||
}
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,67 @@
|
||||
#ifndef LIMERIC_BUDGET_HPP_RQYL16AG
|
||||
#define LIMERIC_BUDGET_HPP_RQYL16AG
|
||||
|
||||
#include <vanetza/common/clock.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
|
||||
// forward declaration
|
||||
class Runtime;
|
||||
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
// forward declaration
|
||||
class DutyCyclePermit;
|
||||
|
||||
/**
|
||||
* LimericBudget models Annex B of TS 102 687 v1.2.1, i.e.
|
||||
* packet handling to meet the channel occupancy limit
|
||||
*/
|
||||
class LimericBudget
|
||||
{
|
||||
public:
|
||||
LimericBudget(const DutyCyclePermit&, const Runtime&);
|
||||
|
||||
/**
|
||||
* Get delay until next transmission is allowed
|
||||
* \return remaining transmission delay
|
||||
*/
|
||||
Clock::duration delay();
|
||||
|
||||
/**
|
||||
* Get current interval between transmissions
|
||||
* \return transmission interval
|
||||
*/
|
||||
Clock::duration interval();
|
||||
|
||||
/**
|
||||
* Notify budget about transmission activity
|
||||
* \param tx_on over-the-air duration of transmission
|
||||
*/
|
||||
void notify(Clock::duration tx_on);
|
||||
|
||||
/**
|
||||
* Recalculate current transmission interval.
|
||||
*
|
||||
* Transmission interval is derived from Limeric's current permitted duty cycle.
|
||||
* Hence, this method should be called whenever Limeric changes its duty cycle.
|
||||
*/
|
||||
void update();
|
||||
|
||||
private:
|
||||
Clock::duration clamp_interval(Clock::duration) const;
|
||||
|
||||
const DutyCyclePermit& m_duty_cycle_permit;
|
||||
const Runtime& m_runtime;
|
||||
Clock::duration m_interval;
|
||||
Clock::time_point m_tx_start;
|
||||
Clock::duration m_tx_on;
|
||||
};
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* LIMERIC_BUDGET_HPP_RQYL16AG */
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
#include "limeric_transmit_rate_control.hpp"
|
||||
#include <vanetza/common/runtime.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
LimericTransmitRateControl::LimericTransmitRateControl(const Runtime& rt, const Limeric& limeric) :
|
||||
m_budget(limeric, rt)
|
||||
{
|
||||
}
|
||||
|
||||
Clock::duration LimericTransmitRateControl::delay(const Transmission&)
|
||||
{
|
||||
return m_budget.delay();
|
||||
}
|
||||
|
||||
Clock::duration LimericTransmitRateControl::interval(const Transmission&)
|
||||
{
|
||||
return m_budget.interval();
|
||||
}
|
||||
|
||||
void LimericTransmitRateControl::notify(const Transmission& transmission)
|
||||
{
|
||||
m_budget.notify(transmission.channel_occupancy());
|
||||
}
|
||||
|
||||
void LimericTransmitRateControl::update()
|
||||
{
|
||||
m_budget.update();
|
||||
}
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
#ifndef LIMERIC_TRANSMIT_RATE_CONTROL_HPP_RY1TIBMJ
|
||||
#define LIMERIC_TRANSMIT_RATE_CONTROL_HPP_RY1TIBMJ
|
||||
|
||||
#include <vanetza/dcc/limeric.hpp>
|
||||
#include <vanetza/dcc/limeric_budget.hpp>
|
||||
#include <vanetza/dcc/transmit_rate_control.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
|
||||
// forward declaration
|
||||
class Runtime;
|
||||
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
/**
|
||||
* Transmit Rate Control implementation based on Limeric algorithm
|
||||
*/
|
||||
class LimericTransmitRateControl : public TransmitRateControl
|
||||
{
|
||||
public:
|
||||
LimericTransmitRateControl(const Runtime&, const Limeric&);
|
||||
|
||||
Clock::duration delay(const Transmission&) override;
|
||||
Clock::duration interval(const Transmission&) override;
|
||||
void notify(const Transmission&) override;
|
||||
|
||||
/**
|
||||
* Update TRC limits.
|
||||
* Call this method whenever Limeric updates its duty cycle.
|
||||
*/
|
||||
void update();
|
||||
|
||||
private:
|
||||
LimericBudget m_budget;
|
||||
};
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* LIMERIC_TRANSMIT_RATE_CONTROL_HPP_RY1TIBMJ */
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
#include "mapping.hpp"
|
||||
#include <stdexcept>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
access::AccessCategory map_profile_onto_ac(Profile dp_id)
|
||||
{
|
||||
access::AccessCategory ac = access::AccessCategory::BE;
|
||||
|
||||
switch (dp_id)
|
||||
{
|
||||
case Profile::DP0:
|
||||
ac = access::AccessCategory::VO;
|
||||
break;
|
||||
case Profile::DP1:
|
||||
ac = access::AccessCategory::VI;
|
||||
break;
|
||||
case Profile::DP2:
|
||||
ac = access::AccessCategory::BE;
|
||||
break;
|
||||
case Profile::DP3:
|
||||
ac = access::AccessCategory::BK;
|
||||
break;
|
||||
default:
|
||||
throw std::invalid_argument("Invalid DCC Profile ID");
|
||||
break;
|
||||
}
|
||||
|
||||
return ac;
|
||||
}
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef MAPPING_HPP_MZ2RU7VX
|
||||
#define MAPPING_HPP_MZ2RU7VX
|
||||
|
||||
#include <vanetza/access/access_category.hpp>
|
||||
#include <vanetza/dcc/profile.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
/**
|
||||
* Map DCC Profile to EDCA access category
|
||||
* \param profile DCC Profile ID
|
||||
* \return mapped access category
|
||||
*/
|
||||
access::AccessCategory map_profile_onto_ac(Profile);
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* MAPPING_HPP_MZ2RU7VX */
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef PROFILE_HPP_V9RMCGL2
|
||||
#define PROFILE_HPP_V9RMCGL2
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
enum class Profile : unsigned
|
||||
{
|
||||
DP0,
|
||||
DP1,
|
||||
DP2,
|
||||
DP3
|
||||
};
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* PROFILE_HPP_V9RMCGL2 */
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
#include "single_reactive_transmit_rate_control.hpp"
|
||||
#include "state_machine.hpp"
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
SingleReactiveTransmitRateControl::SingleReactiveTransmitRateControl(const StateMachine& fsm, const Runtime& rt) :
|
||||
m_fsm(fsm), m_fsm_budget(fsm, rt)
|
||||
{
|
||||
}
|
||||
|
||||
Clock::duration SingleReactiveTransmitRateControl::interval(const Transmission&)
|
||||
{
|
||||
return m_fsm.transmission_interval();
|
||||
}
|
||||
|
||||
Clock::duration SingleReactiveTransmitRateControl::delay(const Transmission&)
|
||||
{
|
||||
return m_fsm_budget.delay();
|
||||
}
|
||||
|
||||
void SingleReactiveTransmitRateControl::notify(const Transmission&)
|
||||
{
|
||||
m_fsm_budget.notify();
|
||||
}
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
#ifndef SINGLE_REACTIVE_TRANSMIT_RATE_CONTROL_HPP_2GPPO79B
|
||||
#define SINGLE_REACTIVE_TRANSMIT_RATE_CONTROL_HPP_2GPPO79B
|
||||
|
||||
#include <vanetza/dcc/state_machine_budget.hpp>
|
||||
#include <vanetza/dcc/transmit_rate_control.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
|
||||
// forward declarations
|
||||
class Runtime;
|
||||
namespace dcc { class StateMachine; }
|
||||
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
/**
|
||||
* Transmit Rate Control using a single reactive state machine for all messages
|
||||
*/
|
||||
class SingleReactiveTransmitRateControl : public TransmitRateControl
|
||||
{
|
||||
public:
|
||||
SingleReactiveTransmitRateControl(const StateMachine&, const Runtime&);
|
||||
|
||||
Clock::duration delay(const Transmission&) override;
|
||||
Clock::duration interval(const Transmission&) override;
|
||||
void notify(const Transmission&) override;
|
||||
|
||||
private:
|
||||
const StateMachine& m_fsm;
|
||||
StateMachineBudget m_fsm_budget;
|
||||
};
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* SINGLE_REACTIVE_TRANSMIT_RATE_CONTROL_HPP_2GPPO79B */
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
#include "smoothing_channel_probe_processor.hpp"
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
SmoothingChannelProbeProcessor::SmoothingChannelProbeProcessor(UnitInterval alpha) :
|
||||
m_alpha(alpha)
|
||||
{
|
||||
}
|
||||
|
||||
void SmoothingChannelProbeProcessor::indicate(ChannelLoad cl)
|
||||
{
|
||||
m_channel_load = m_alpha * cl + m_alpha.complement() * m_channel_load;
|
||||
HookedChannelProbeProcessor::indicate(m_channel_load);
|
||||
}
|
||||
|
||||
ChannelLoad SmoothingChannelProbeProcessor::channel_load() const
|
||||
{
|
||||
return m_channel_load;
|
||||
}
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
#ifndef SMOOTHING_CHANNEL_PROBE_PROCESSOR_HPP_EIP1WUDK
|
||||
#define SMOOTHING_CHANNEL_PROBE_PROCESSOR_HPP_EIP1WUDK
|
||||
|
||||
#include <vanetza/common/unit_interval.hpp>
|
||||
#include <vanetza/dcc/channel_load.hpp>
|
||||
#include <vanetza/dcc/hooked_channel_probe_processor.hpp>
|
||||
#include <functional>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
/**
|
||||
* Smooth local channel load measurements as per
|
||||
* C2C-CC Whitepaper on DCC for Day One (Version 1.0 from 2013)
|
||||
* and Basic System Profile (RS_BSP_240 in Version 1.3)
|
||||
*/
|
||||
class SmoothingChannelProbeProcessor : public HookedChannelProbeProcessor
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Initialize ChannelProbeProcessor with smoothing behaviour.
|
||||
* \param alpha smoothing factor (influence of new raw measurement)
|
||||
*/
|
||||
SmoothingChannelProbeProcessor(UnitInterval alpha = UnitInterval(0.5));
|
||||
|
||||
/**
|
||||
* Feed new local channel load measurement into smoothing algorithm.
|
||||
* Side effect: Update function is called afterwards with smoothed channel load.
|
||||
* \param cl raw local channel load
|
||||
*/
|
||||
void indicate(ChannelLoad cl) override;
|
||||
|
||||
/**
|
||||
* Get smoothed channel load value
|
||||
*/
|
||||
ChannelLoad channel_load() const;
|
||||
|
||||
private:
|
||||
UnitInterval m_alpha;
|
||||
ChannelLoad m_channel_load;
|
||||
};
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* SMOOTH_CHANNEL_PROBE_PROCESSOR_HPP_EIP1WUDK */
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
#ifndef STATE_MACHINE_HPP_0MHYOQU7
|
||||
#define STATE_MACHINE_HPP_0MHYOQU7
|
||||
|
||||
#include <vanetza/dcc/channel_load.hpp>
|
||||
#include <vanetza/common/clock.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
/**
|
||||
* State machine interface used for Transmit Rate Control
|
||||
*/
|
||||
class StateMachine
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Trigger state transition by updated channel load
|
||||
* \param cl new channel load measurement
|
||||
*/
|
||||
virtual void update(ChannelLoad cl) = 0;
|
||||
|
||||
/**
|
||||
* Get current transmission interval
|
||||
* \return duration between two transmissions
|
||||
*/
|
||||
virtual Clock::duration transmission_interval() const = 0;
|
||||
|
||||
virtual ~StateMachine() = default;
|
||||
};
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* STATE_MACHINE_HPP_0MHYOQU7 */
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
#include <vanetza/common/runtime.hpp>
|
||||
#include <vanetza/dcc/state_machine.hpp>
|
||||
#include <vanetza/dcc/state_machine_budget.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
StateMachineBudget::StateMachineBudget(const StateMachine& fsm, const Runtime& rt) :
|
||||
m_fsm(fsm), m_runtime(rt)
|
||||
{
|
||||
}
|
||||
|
||||
Clock::duration StateMachineBudget::delay()
|
||||
{
|
||||
Clock::duration delay = Clock::duration::max();
|
||||
|
||||
if (m_last_tx) {
|
||||
const auto last_tx = m_last_tx.get();
|
||||
const auto tx_interval = m_fsm.transmission_interval();
|
||||
if (last_tx + tx_interval < m_runtime.now()) {
|
||||
delay = Clock::duration::zero();
|
||||
} else {
|
||||
delay = last_tx + tx_interval - m_runtime.now();
|
||||
}
|
||||
} else {
|
||||
delay = Clock::duration::zero();
|
||||
}
|
||||
|
||||
return delay;
|
||||
}
|
||||
|
||||
void StateMachineBudget::notify()
|
||||
{
|
||||
m_last_tx = m_runtime.now();
|
||||
}
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,37 @@
|
||||
#ifndef STATE_MACHINE_BUDGET_HPP_9AL8A2JG
|
||||
#define STATE_MACHINE_BUDGET_HPP_9AL8A2JG
|
||||
|
||||
#include <vanetza/common/clock.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
|
||||
// forward declarations
|
||||
class Runtime;
|
||||
namespace dcc { class StateMachine; }
|
||||
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
/**
|
||||
* StateMachineBudget: TRC restrictions as determined by a state machine
|
||||
*/
|
||||
class StateMachineBudget
|
||||
{
|
||||
public:
|
||||
StateMachineBudget(const StateMachine&, const Runtime&);
|
||||
Clock::duration delay();
|
||||
void notify();
|
||||
|
||||
private:
|
||||
const StateMachine& m_fsm;
|
||||
const Runtime& m_runtime;
|
||||
boost::optional<Clock::time_point> m_last_tx;
|
||||
};
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* STATE_MACHINE_BUDGET_HPP_9AL8A2JG */
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
+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);
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#include "transmission.hpp"
|
||||
#include <chrono>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
Clock::duration Transmission::channel_occupancy() const
|
||||
{
|
||||
using namespace std::chrono;
|
||||
|
||||
// assume 6 Mbps as default data rate
|
||||
const access::DataRateG5* rate = data_rate() ? data_rate() : &access::G5_6Mbps;
|
||||
|
||||
// PHY
|
||||
static const auto phy_preamble = microseconds(32);
|
||||
static const auto phy_signal = microseconds(8);
|
||||
|
||||
// MAC
|
||||
static const std::size_t bytes_epd = 2; // EtherType Protocol Discrimination (no LLC!)
|
||||
static const std::size_t bytes_mac = 34; // 802.11 MAC header
|
||||
|
||||
const std::size_t bytes = rate->data_length(body_length() + bytes_epd + bytes_mac);
|
||||
const double seconds_per_byte = 1.0 / (rate->bytes_per_second());
|
||||
const duration<double> data_duration { bytes * seconds_per_byte };
|
||||
|
||||
return phy_preamble + phy_signal + duration_cast<microseconds>(data_duration);
|
||||
}
|
||||
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,41 @@
|
||||
#ifndef TRANSMISSION_HPP_SDC4RMQE
|
||||
#define TRANSMISSION_HPP_SDC4RMQE
|
||||
|
||||
#include <vanetza/access/data_rates.hpp>
|
||||
#include <vanetza/common/clock.hpp>
|
||||
#include <vanetza/dcc/profile.hpp>
|
||||
#include <cstddef>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
class Transmission
|
||||
{
|
||||
public:
|
||||
virtual Profile profile() const = 0;
|
||||
virtual const access::DataRateG5* data_rate() const = 0;
|
||||
virtual std::size_t body_length() const = 0;
|
||||
virtual Clock::duration channel_occupancy() const;
|
||||
virtual ~Transmission() = default;
|
||||
};
|
||||
|
||||
struct TransmissionLite : public Transmission
|
||||
{
|
||||
constexpr TransmissionLite(Profile dp, std::size_t len) : m_profile(dp), m_length(len) {}
|
||||
|
||||
Profile m_profile;
|
||||
std::size_t m_length = 0; /*< length in bytes of MAC frame body */
|
||||
const access::DataRateG5* m_data_rate = nullptr;
|
||||
|
||||
Profile profile() const override { return m_profile; }
|
||||
const access::DataRateG5* data_rate() const override { return m_data_rate; }
|
||||
std::size_t body_length() const override { return m_length; }
|
||||
};
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* TRANSMISSION_HPP_SDC4RMQE */
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
#ifndef TRANSMIT_RATE_CONTROL_HPP_NOPDFSY6
|
||||
#define TRANSMIT_RATE_CONTROL_HPP_NOPDFSY6
|
||||
|
||||
#include <vanetza/common/clock.hpp>
|
||||
#include <vanetza/dcc/transmission.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace dcc
|
||||
{
|
||||
|
||||
class TransmitRateThrottle
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Duration until next transmission has to be delayed
|
||||
* \param tx transmission
|
||||
* \return waiting time until next transmission is allowed
|
||||
*/
|
||||
virtual Clock::duration delay(const Transmission& tx) = 0;
|
||||
|
||||
/**
|
||||
* Current interval between packets
|
||||
* \param tx transmission
|
||||
* \return interval enforced by DCC_access
|
||||
*/
|
||||
virtual Clock::duration interval(const Transmission& tx) = 0;
|
||||
|
||||
virtual ~TransmitRateThrottle() = default;
|
||||
};
|
||||
|
||||
class TransmitRateFeedback
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Notify about an actual transmission at link layer
|
||||
* \param tx transmission
|
||||
*/
|
||||
virtual void notify(const Transmission& tx) = 0;
|
||||
|
||||
virtual ~TransmitRateFeedback() = default;
|
||||
};
|
||||
|
||||
class TransmitRateControl : public TransmitRateThrottle, public TransmitRateFeedback
|
||||
{
|
||||
};
|
||||
|
||||
} // namespace dcc
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* TRANSMIT_RATE_CONTROL_HPP_NOPDFSY6 */
|
||||
|
||||
Reference in New Issue
Block a user