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

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

The ESP32-C5 is still flashed from obu-firmware/, which only takes
vanetza-idf from microbu-esp32c5/, so the two stay separate folders.
FLASHING.md says how to take a newer version of the colleague's tree (copy
it over the folder, rebuild, test, commit).
This commit is contained in:
Ashin Walpola
2026-09-23 17:46:40 +02:00
parent 2f60623e18
commit 0e9525162d
9881 changed files with 1582523 additions and 17 deletions
@@ -0,0 +1,48 @@
set(CXX_SOURCES
address.cpp
areas.cpp
basic_header.cpp
beacon_header.cpp
cbf_packet_buffer.cpp
cbf_counter.cpp
cbf_packet_identifier.cpp
cbr_aggregator.cpp
common_header.cpp
data_confirm.cpp
data_request.cpp
dcc_field.cpp
dcc_information_sharing.cpp
dcc_mco_field.cpp
duplicate_packet_list.cpp
gbc_gac_header.cpp
gbc_header.cpp
gbc_memory.cpp
header_type.cpp
header_variant.cpp
indication_context.cpp
mib.cpp
sequence_number.cpp
lifetime.cpp
location_table.cpp
next_hop.cpp
packet_buffer.cpp
parser.cpp
pdu.cpp
pdu_conversion.cpp
position_updater.cpp
position_vector.cpp
repeater.cpp
router.cpp
secured_pdu.cpp
shb_header.cpp
timestamp.cpp
traffic_class.cpp
tsb_header.cpp
variant_pdu.cpp
)
add_vanetza_component(geonet ${CXX_SOURCES})
target_link_libraries(geonet PUBLIC Boost::date_time)
target_link_libraries(geonet PUBLIC dcc geodesy net security)
add_test_subdirectory(tests)
@@ -0,0 +1,88 @@
#include "address.hpp"
#include "serialization.hpp"
#include <boost/functional/hash.hpp>
namespace vanetza
{
namespace geonet
{
constexpr std::size_t Address::length_bytes;
namespace // anonymous namespace for local constants
{
constexpr uint16_t manually_configured_mask = 0x8000;
constexpr uint16_t station_type_mask = 0x7c00;
constexpr uint16_t country_code_mask = 0x03ff;
constexpr unsigned station_type_shift = 10;
} // namespace
Address::Address() :
m_manually_configured(false),
m_station_type(StationType::Unknown),
m_country_code(0)
{
}
Address::Address(const MacAddress& addr) :
m_manually_configured(false),
m_station_type(StationType::Unknown),
m_country_code(0),
m_mid(addr)
{
}
bool Address::operator==(const Address& other) const
{
return (this->m_manually_configured == other.m_manually_configured &&
this->m_station_type == other.m_station_type &&
this->m_country_code == other.m_country_code &&
this->m_mid == other.m_mid);
}
bool Address::operator!=(const Address& other) const
{
return !(*this == other);
}
void serialize(const Address& addr, OutputArchive& ar)
{
uint16_t manuallyConfiguredAndTypeAndCountryCode = addr.country_code().raw();
manuallyConfiguredAndTypeAndCountryCode |=
(static_cast<uint16_t>(addr.station_type()) << station_type_shift) & station_type_mask;
manuallyConfiguredAndTypeAndCountryCode |=
addr.is_manually_configured() ? manually_configured_mask : 0x0000;
serialize(host_cast(manuallyConfiguredAndTypeAndCountryCode), ar);
serialize(ar, addr.mid());
}
void deserialize(Address& addr, InputArchive& ar)
{
uint16_t tmp;
deserialize(tmp, ar);
addr.is_manually_configured((tmp & manually_configured_mask) != 0);
addr.country_code(tmp & country_code_mask);
addr.station_type(static_cast<StationType>((tmp & station_type_mask) >> station_type_shift));
MacAddress mid;
deserialize(ar, mid);
addr.mid(mid);
}
} // namespace geonet
} // namespace vanetza
namespace std
{
namespace gn = vanetza::geonet;
size_t hash<gn::Address>::operator()(const gn::Address& addr) const
{
size_t seed = 0;
boost::hash_combine(seed, addr.is_manually_configured());
boost::hash_combine(seed, addr.station_type());
boost::hash_combine(seed, addr.country_code().raw());
boost::hash_range(seed, addr.mid().octets.begin(), addr.mid().octets.end());
return seed;
}
} // namespace std
@@ -0,0 +1,60 @@
#ifndef GEONET_ADDRESS_HPP_MB8J1IVQ
#define GEONET_ADDRESS_HPP_MB8J1IVQ
#include <vanetza/common/bit_number.hpp>
#include <vanetza/net/mac_address.hpp>
#include <vanetza/geonet/serialization.hpp>
#include <vanetza/geonet/station_type.hpp>
#include <functional>
#include <type_traits>
namespace vanetza
{
namespace geonet
{
class Address
{
public:
static constexpr std::size_t length_bytes = 8;
Address();
explicit Address(const MacAddress&);
bool is_manually_configured() const { return m_manually_configured; }
void is_manually_configured(bool flag) { m_manually_configured = flag; }
StationType station_type() const { return m_station_type; }
void station_type(StationType type) { m_station_type = type; }
BitNumber<unsigned, 10> country_code() const { return m_country_code; }
void country_code(BitNumber<unsigned, 10> country) { m_country_code = country; }
const MacAddress& mid() const { return m_mid; }
void mid(const MacAddress& mid) { m_mid = mid; }
bool operator==(const Address& other) const;
bool operator!=(const Address& other) const;
private:
bool m_manually_configured; // 1 bit
StationType m_station_type; // 5 bit
BitNumber<unsigned, 10> m_country_code; // 10 bit (deprecated since 1.3.1)
MacAddress m_mid; // 48 bit
};
void serialize(const Address&, OutputArchive&);
void deserialize(Address&, InputArchive&);
} // namespace geonet
} // namespace vanetza
namespace std {
template<>
struct hash<vanetza::geonet::Address>
{
size_t operator()(const vanetza::geonet::Address& addr) const;
};
} // namespace std
#endif /* GEONET_ADDRESS_HPP_MB8J1IVQ */
@@ -0,0 +1,115 @@
#include "areas.hpp"
#include <boost/math/constants/constants.hpp>
#include <boost/units/cmath.hpp>
#include <algorithm>
#include <cassert>
#include <limits>
namespace vanetza
{
namespace geonet
{
double geometric_function(const Circle& c, const CartesianPosition& p)
{
if (c.r.value() != 0.0) {
const double x_over_r = p.x / c.r;
const double y_over_r = p.y / c.r;
return 1.0 - (x_over_r * x_over_r) - (y_over_r * y_over_r);
} else {
return -std::numeric_limits<double>::infinity();
};
}
double geometric_function(const Rectangle& r, const CartesianPosition& p)
{
if (r.a.value() != 0.0 && r.b.value() != 0.0) {
const double x_over_a = p.x / r.a;
const double y_over_b = p.y / r.b;
return std::min(1.0 - x_over_a * x_over_a, 1.0 - y_over_b * y_over_b);
} else {
return -std::numeric_limits<double>::infinity();
}
}
double geometric_function(const Ellipse& e, const CartesianPosition& p)
{
if (e.a.value() != 0.0 && e.b.value() != 0.0) {
const double x_over_a = p.x / e.a;
const double y_over_b = p.y / e.b;
return 1.0 - (x_over_a * x_over_a) - (y_over_b * y_over_b);
} else {
return -std::numeric_limits<double>::infinity();
}
}
struct geometric_function_visitor : public boost::static_visitor<double>
{
geometric_function_visitor(const CartesianPosition& p) : point(p) {}
template<class SHAPE>
double operator()(const SHAPE& s) const
{
return geometric_function(s, point);
}
const CartesianPosition& point;
};
double geometric_function(const decltype(Area::shape)& shape, const CartesianPosition& p)
{
geometric_function_visitor visitor(p);
return boost::apply_visitor(visitor, shape);
}
CartesianPosition canonicalize(const CartesianPosition& point, units::Angle azimuth)
{
using namespace boost::math::double_constants;
// area.angle is azimuth angle of EN 302 931 V1.1.1
const units::Angle zenith = half_pi * units::si::radian - azimuth;
const double sin_z = sin(zenith);
const double cos_z = cos(zenith);
// rotate canonical point around origin clockwise: zenith = 90 deg - azimuth
// other interpretation: rotate shape's long side onto abscissa
CartesianPosition canonical;
canonical.x = cos_z * point.x + sin_z * point.y;
canonical.y = -sin_z * point.x + cos_z * point.y;
return canonical;
}
struct area_size_visitor : public boost::static_visitor<units::Area>
{
units::Area operator()(const Circle& circle) const
{
using namespace boost::math::double_constants;
return pi * circle.r * circle.r;
}
units::Area operator()(const Rectangle& rectangle) const
{
using namespace boost::math::double_constants;
return 4.0 * rectangle.a * rectangle.b;
}
units::Area operator()(const Ellipse& ellipse) const
{
using namespace boost::math::double_constants;
return pi * ellipse.a * ellipse.b;
}
};
units::Area area_size(const Area& area)
{
return boost::apply_visitor(area_size_visitor(), area.shape);
}
bool inside_or_at_border(const Area& area, const GeodeticPosition& geo_position)
{
const CartesianPosition local = local_cartesian(area.position, geo_position);
const CartesianPosition canonical = canonicalize(local, area.angle);
return !outside_shape(area.shape, canonical);
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,101 @@
#ifndef AREAS_HPP_CVK1NIAI
#define AREAS_HPP_CVK1NIAI
#include <vanetza/geodesy/geodesy.hpp>
#include <vanetza/units/area.hpp>
#include <boost/variant.hpp>
namespace vanetza
{
namespace geonet
{
using geodesy::CartesianPosition;
using geodesy::GeodeticPosition;
using geodesy::distance;
using geodesy::local_cartesian;
struct Circle
{
Circle() : r(1.0 * units::si::meters) {}
units::Length r; // radius
};
struct Rectangle
{
Rectangle() : a(1.0 * units::si::meters), b(1.0 * units::si::meters) {}
units::Length a; // center to long side
units::Length b; // center to short side
};
struct Ellipse
{
Ellipse() : a(1.0 * units::si::meters), b(1.0 * units::si::meters) {}
units::Length a; // long semi-axis
units::Length b; // short semi-axis
};
struct Area
{
boost::variant<Rectangle, Ellipse, Circle> shape;
GeodeticPosition position;
units::Angle angle;
};
double geometric_function(const Circle&, const CartesianPosition&);
double geometric_function(const Rectangle&, const CartesianPosition&);
double geometric_function(const Ellipse&, const CartesianPosition&);
double geometric_function(const decltype(Area::shape)&, const CartesianPosition&);
/**
* Canonicalize a point in a shape's coordinate system w.r.t. its azimuth angle
* \param point Point to canonicalize
* \param azimuth Azimuth angle of shape's long side
* \return canonical position (suitable for geometric_function)
*/
CartesianPosition canonicalize(const CartesianPosition& point, units::Angle azimuth);
/**
* Check if positon is within or at border of area
* \param area with shape, dimensions, azimuth and center point position
* \param position Geodetic position to check against area
* \return true if position is inside or at border
*/
bool inside_or_at_border(const Area&, const GeodeticPosition&);
/**
* Calculate area size in square km.
* \param area Area object
* \return area size
*/
units::Area area_size(const Area&);
template<class SHAPE>
bool inside_shape(const SHAPE& shape, const CartesianPosition& p)
{
return geometric_function(shape, p) > 0.0;
}
template<class SHAPE>
bool outside_shape(const SHAPE& shape, const CartesianPosition& p)
{
return geometric_function(shape, p) < 0.0;
}
template<class SHAPE>
bool at_shape_border(const SHAPE& shape, const CartesianPosition& p)
{
return geometric_function(shape, p) == 0.0;
}
template<class SHAPE>
bool at_center_point(const SHAPE& shape, const CartesianPosition& p)
{
return geometric_function(shape, p) == 1.0;
}
} // namespace geonet
} // namespace vanetza
#endif /* AREAS_HPP_CVK1NIAI */
@@ -0,0 +1,71 @@
#include <vanetza/geonet/basic_header.hpp>
#include <vanetza/geonet/data_request.hpp>
#include <vanetza/geonet/serialization.hpp>
namespace vanetza
{
namespace geonet
{
constexpr std::size_t BasicHeader::length_bytes;
BasicHeader::BasicHeader() :
version(0),
next_header(NextHeaderBasic::Any),
reserved(0),
hop_limit(0)
{
}
BasicHeader::BasicHeader(const MIB& mib) :
version(mib.itsGnProtocolVersion),
next_header(NextHeaderBasic::Any),
reserved(0),
lifetime(mib.itsGnDefaultPacketLifetime),
hop_limit(mib.itsGnDefaultHopLimit)
{
}
BasicHeader::BasicHeader(const DataRequest& request, const MIB& mib) :
BasicHeader(mib)
{
if (mib.itsGnSecurity) {
next_header = NextHeaderBasic::Secured;
} else {
next_header = NextHeaderBasic::Common;
}
lifetime = request.maximum_lifetime;
hop_limit = request.max_hop_limit;
}
BasicHeader::BasicHeader(const ShbDataRequest& request, const MIB& mib) :
BasicHeader(static_cast<const DataRequest&>(request), mib)
{
hop_limit = 1;
}
void serialize(const BasicHeader& hdr, OutputArchive& ar)
{
uint8_t versionAndNextHeader = hdr.version.raw();
versionAndNextHeader <<= 4;
versionAndNextHeader |= static_cast<uint8_t>(hdr.next_header) & 0x0f;
serialize(host_cast(versionAndNextHeader), ar);
serialize(host_cast(hdr.reserved), ar);
serialize(hdr.lifetime, ar);
serialize(host_cast(hdr.hop_limit), ar);
}
void deserialize(BasicHeader& hdr, InputArchive& ar)
{
uint8_t versionAndNextHeader;
deserialize(versionAndNextHeader, ar);
hdr.version = versionAndNextHeader >> 4;
hdr.next_header = static_cast<NextHeaderBasic>(versionAndNextHeader & 0x0f);
deserialize(hdr.reserved, ar);
deserialize(hdr.lifetime, ar);
deserialize(hdr.hop_limit, ar);
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,59 @@
#ifndef BASIC_HEADER_HPP_8QS7WLG3
#define BASIC_HEADER_HPP_8QS7WLG3
#include <vanetza/common/bit_number.hpp>
#include <vanetza/geonet/lifetime.hpp>
#include <vanetza/geonet/mib.hpp>
#include <vanetza/geonet/serialization.hpp>
namespace vanetza
{
namespace geonet
{
struct DataRequest;
struct ShbDataRequest;
/// NextHeaderBasic specified in ETSI EN 302 636-4-1 v1.2.1, section 8.6.3
enum class NextHeaderBasic : uint8_t
{
Any = 0,
Common = 1,
Secured = 2,
};
/// BasicHeader specified in ETSI EN 302 636-4-1 v1.2.1, section 8.6
struct BasicHeader
{
BasicHeader();
BasicHeader(const MIB&);
BasicHeader(const DataRequest&, const MIB&);
BasicHeader(const ShbDataRequest&, const MIB&);
static constexpr std::size_t length_bytes = 3 + sizeof(Lifetime);
BitNumber<unsigned, 4> version;
NextHeaderBasic next_header; // 4 bit
uint8_t reserved;
Lifetime lifetime;
uint8_t hop_limit;
};
/**
* \brief Serializes a BasicHeader into a binary archive
* \param basic to serialize
* \param ar to serialize in
*/
void serialize(const BasicHeader&, OutputArchive&);
/**
* \brief Deserializes a BasicHeader from a binary archive
* \param basic to deserialize
* \param ar with a serialized BasicHeader at the beginning
*/
void deserialize(BasicHeader&, InputArchive&);
} // namespace geonet
} // namespace vanetza
#endif /* BASIC_HEADER_HPP_8QS7WLG3 */
@@ -0,0 +1,23 @@
#include "beacon_header.hpp"
#include "serialization.hpp"
namespace vanetza
{
namespace geonet
{
constexpr std::size_t BeaconHeader::length_bytes;
void serialize(const BeaconHeader& hdr, OutputArchive& ar)
{
serialize(hdr.source_position, ar);
}
void deserialize(BeaconHeader& hdr, InputArchive& ar)
{
deserialize(hdr.source_position, ar);
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,27 @@
#ifndef BEACON_HEADER_HPP_1NRWPHXO
#define BEACON_HEADER_HPP_1NRWPHXO
#include <vanetza/geonet/position_vector.hpp>
#include <vanetza/geonet/serialization.hpp>
namespace vanetza
{
namespace geonet
{
struct BeaconHeader
{
public:
static constexpr std::size_t length_bytes = LongPositionVector::length_bytes;
LongPositionVector source_position;
};
void serialize(const BeaconHeader&, OutputArchive&);
void deserialize(BeaconHeader&, InputArchive&);
} // namespace geonet
} // namespace vanetza
#endif /* BEACON_HEADER_HPP_1NRWPHXO */
@@ -0,0 +1,63 @@
#include <vanetza/geonet/cbf_counter.hpp>
#include <cassert>
namespace vanetza
{
namespace geonet
{
void CbfCounterImmortal::add(const id_type& id)
{
++m_counters[id];
}
void CbfCounterImmortal::increment(const id_type& id)
{
++m_counters[id];
}
auto CbfCounterImmortal::counter(const id_type& id) const -> counter_type
{
counter_type count = 0;
auto found = m_counters.find(id);
if (found != m_counters.end()) {
count = found->second;
}
return count;
}
void CbfCounterContending::add(const id_type& id)
{
m_counters[id] = 1;
}
void CbfCounterContending::remove(const id_type& id)
{
m_counters.erase(id);
}
CbfCounterFading::CbfCounterFading(Runtime& rt, Clock::duration lifetime) :
m_counters(rt)
{
m_counters.set_lifetime(lifetime);
}
void CbfCounterFading::add(const id_type& id)
{
++m_counters.refresh(id);
m_counters.drop_expired();
}
void CbfCounterFading::increment(const id_type& id)
{
++m_counters.get_value(id);
}
auto CbfCounterFading::counter(const id_type& id) const -> counter_type
{
auto* count = m_counters.get_value_ptr(id);
return count ? *count : 0;
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,113 @@
#ifndef CBF_COUNTER_HPP_QTMUOGJS
#define CBF_COUNTER_HPP_QTMUOGJS
#include <vanetza/common/clock.hpp>
#include <vanetza/geonet/cbf_packet_identifier.hpp>
#include <vanetza/geonet/soft_state_map.hpp>
#include <cstddef>
#include <unordered_map>
namespace vanetza
{
// forward declaration
class Runtime;
namespace geonet
{
/**
* Interface for duplicate packet counters.
* This is used by the Contention Based Forwarding (CBF) packet buffer.
*/
class CbfCounter
{
public:
using id_type = CbfPacketIdentifier;
using counter_type = std::size_t;
/**
* Packet has been added to buffer
* \param id packet identifier
*/
virtual void add(const id_type& id) = 0;
/**
* Packet has been removed from buffer.
* \param id packet identifier
*/
virtual void remove(const id_type& id) = 0;
/**
* Increment packet counter by one
* \param id packet identifier
*/
virtual void increment(const id_type& id) = 0;
/**
* Retrieve counter value
* \param id packet identifier
*/
virtual counter_type counter(const id_type& id) const = 0;
virtual ~CbfCounter() = default;
};
/**
* Immortal CBF counters, i.e. they never expire.
* \note Be aware, memory consumption is constantly growing of this implemenation!
*/
class CbfCounterImmortal : public virtual CbfCounter
{
public:
void add(const id_type&) override;
void remove(const id_type&) override {}
void increment(const id_type&) override;
counter_type counter(const id_type&) const override;
protected:
std::unordered_map<id_type, counter_type> m_counters;
};
/**
* Remembers only counter values for packets currently contending, i.e. stored in CBF buffer.
* \note This is the ADVANCED routing behaviour of EN 302 636-4-1 v1.2.1
*/
class CbfCounterContending : public virtual CbfCounter, private CbfCounterImmortal
{
public:
void add(const id_type&) override;
void remove(const id_type&) override;
};
/**
* Fading CBF counters
*
* Counters are removed from the internal table only after expiry, i.e. they are soft-state.
* This fixes some serious GN flooding due to ADVANCED routing, see CbfCounterContending.
*/
class CbfCounterFading : public virtual CbfCounter
{
public:
/*
* Initialize fading counters
* \param rt runtime used for soft-state behaviour
* \param lifetime newly added counters are initialized with this lifetime
*/
CbfCounterFading(Runtime&, Clock::duration lifetime);
void add(const id_type&) override;
void remove(const id_type&) override {}
void increment(const id_type&) override;
counter_type counter(const id_type&) const override;
private:
SoftStateMap<id_type, counter_type> m_counters;
};
} // namespace geonet
} // namespace vanetza
#endif /* CBF_COUNTER_HPP_QTMUOGJS */
@@ -0,0 +1,226 @@
#include <vanetza/common/runtime.hpp>
#include <vanetza/geonet/address.hpp>
#include <vanetza/geonet/cbf_counter.hpp>
#include <vanetza/geonet/cbf_packet_buffer.hpp>
#include <vanetza/geonet/pdu.hpp>
#include <vanetza/units/time.hpp>
#include <cassert>
#include <iterator>
namespace vanetza
{
namespace geonet
{
CbfPacket::CbfPacket(PendingPacket<GbcPdu>&& packet, const MacAddress& sender) :
m_packet(std::move(packet)), m_sender(sender)
{
}
CbfPacket::CbfPacket(PendingPacket<GbcPdu, const MacAddress&>&& packet, const MacAddress& sender) :
m_packet(PendingPacket<GbcPdu>(std::move(packet), cBroadcastMacAddress)), m_sender(sender)
{
}
const MacAddress& CbfPacket::sender() const
{
return m_sender;
}
const Address& CbfPacket::source() const
{
return m_packet.pdu().extended().source_position.gn_addr;
}
SequenceNumber CbfPacket::sequence_number() const
{
return m_packet.pdu().extended().sequence_number;
}
Clock::duration CbfPacket::reduce_lifetime(Clock::duration d)
{
return m_packet.reduce_lifetime(d);
}
std::size_t CbfPacket::length() const
{
return m_packet.length();
}
CbfPacketBuffer::CbfPacketBuffer(Runtime& rt, TimerCallback cb, std::unique_ptr<CbfCounter> cnt, std::size_t bytes) :
m_runtime(rt), m_counter(std::move(cnt)),
m_capacity_bytes(bytes), m_stored_bytes(0),
m_timer_callback(cb)
{
}
CbfPacketBuffer::~CbfPacketBuffer()
{
m_runtime.cancel(this);
}
bool CbfPacketBuffer::remove(const Identifier& id)
{
bool packet_dropped = false;
auto& id_map = m_timers.right;
auto found = id_map.find(id);
if (found != id_map.end()) {
auto& packet = found->info;
m_stored_bytes -= packet->length();
m_counter->remove(id);
m_packets.erase(packet);
remove_timer(m_timers.project_left(found));
packet_dropped = true;
}
assert(m_packets.size() == m_timers.size());
return packet_dropped;
}
void CbfPacketBuffer::remove_timer(typename timer_bimap::left_map::iterator timer_it)
{
auto& timer_map = m_timers.left;
auto successor = timer_map.erase(timer_it);
if (successor == timer_map.begin() && !timer_map.empty()) {
// erased timer was scheduled one, reschedule timer trigger
schedule_timer();
}
}
void CbfPacketBuffer::add(CbfPacket&& packet, Clock::duration timeout)
{
if(timeout <= Clock::duration::zero()) return;
m_stored_bytes += packet.length();
const auto first_timer = m_timers.left.begin();
// do head drop if necessary
while (m_stored_bytes > m_capacity_bytes && !m_packets.empty()) {
m_stored_bytes -= m_packets.front().length();
const auto id = identifier(m_packets.front());
m_timers.right.erase(id);
m_counter->remove(id);
m_packets.pop_front();
}
Timer timer = { m_runtime, timeout };
const Identifier id = identifier(packet);
m_packets.emplace_back(std::move(packet));
using timer_value = timer_bimap::value_type;
auto insertion = m_timers.insert(timer_value { timer, id, std::prev(m_packets.end()) });
if (!insertion.second) {
m_stored_bytes -= m_packets.back().length();
m_packets.pop_back();
} else {
m_counter->add(id);
}
// first expirying timer has changed (head drop or added packet)
if (m_timers.left.begin() != first_timer) {
schedule_timer();
}
assert(m_packets.size() == m_timers.size());
}
void CbfPacketBuffer::update(const Identifier& id, Clock::duration timeout)
{
auto& id_map = m_timers.right;
auto found = id_map.find(id);
if (found != id_map.end()) {
const Timer& timer = found->second;
CbfPacket& cbf_packet = *found->info;
reduce_lifetime(timer, cbf_packet);
id_map.replace_data(found, Timer { m_runtime, timeout});
m_counter->increment(id);
}
}
boost::optional<CbfPacket> CbfPacketBuffer::fetch(const Identifier& id)
{
boost::optional<CbfPacket> packet;
auto& id_map = m_timers.right;
auto found = id_map.find(id);
if (found != id_map.end()) {
const Timer& timer = found->second;
CbfPacket& cbf_packet = *found->info;
bool valid_packet = reduce_lifetime(timer, cbf_packet);
m_stored_bytes -= cbf_packet.length();
if (valid_packet) {
packet.emplace(std::move(cbf_packet));
}
m_counter->remove(id);
m_packets.erase(found->info);
remove_timer(m_timers.project_left(found));
}
return packet;
}
const CbfPacket* CbfPacketBuffer::find(const Identifier& id) const
{
const auto& id_map = m_timers.right;
auto found = id_map.find(id);
return found != id_map.end() ? &(*found->info) : nullptr;
}
std::size_t CbfPacketBuffer::counter(const Identifier& id) const
{
return m_counter->counter(id);
}
void CbfPacketBuffer::flush()
{
// fetch all expired timers
const Timer now { m_runtime, std::chrono::seconds(0) };
auto end = m_timers.left.upper_bound(now);
for (auto it = m_timers.left.begin(); it != end;) {
// reduce LT by queuing time
const Timer& timer = it->first;
CbfPacket& packet = *it->info;
bool valid_packet = reduce_lifetime(timer, packet);
m_stored_bytes -= packet.length();
if (valid_packet) {
m_timer_callback(std::move(packet).packet());
}
m_counter->remove(it->second);
m_packets.erase(it->info);
it = m_timers.left.erase(it);
}
// schedule timer if not empty
if (!m_timers.empty()) {
schedule_timer();
}
}
bool CbfPacketBuffer::reduce_lifetime(const Timer& timer, CbfPacket& packet) const
{
const auto queuing_time = m_runtime.now() - timer.start;
return packet.reduce_lifetime(queuing_time) > Clock::duration::zero();
}
void CbfPacketBuffer::schedule_timer()
{
assert(!m_timers.empty());
m_runtime.cancel(this);
Runtime::Callback cb = [this](Clock::time_point) { flush(); };
m_runtime.schedule(m_timers.left.begin()->first.expiry, cb, this);
}
CbfPacketBuffer::Timer::Timer(const Runtime& rt, Clock::duration timeout) :
expiry(rt.now() + timeout), start(rt.now())
{
}
bool CbfPacketBuffer::Timer::operator<(const Timer& other) const
{
return this->expiry < other.expiry;
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,202 @@
#ifndef CBF_PACKET_BUFFER_HPP_MU3RK5V1
#define CBF_PACKET_BUFFER_HPP_MU3RK5V1
#include <vanetza/common/clock.hpp>
#include <vanetza/geonet/cbf_packet_identifier.hpp>
#include <vanetza/geonet/packet.hpp>
#include <vanetza/geonet/pending_packet.hpp>
#include <vanetza/geonet/pdu_variant.hpp>
#include <boost/bimap/bimap.hpp>
#include <boost/bimap/multiset_of.hpp>
#include <boost/bimap/unordered_set_of.hpp>
#include <boost/optional/optional.hpp>
#include <cstddef>
#include <list>
#include <memory>
// forward declarations
namespace vanetza
{
class Runtime;
namespace geonet
{
class Address;
class CbfCounter;
class CbfPacket;
class Lifetime;
/**
* CbfPacket enables handling of conventional packets in a CBF packet buffer.
* It contains a GeoBroadcast PDU and the network layer payload.
*/
class CbfPacket
{
public:
CbfPacket(PendingPacket<GbcPdu>&&, const MacAddress& sender);
CbfPacket(PendingPacket<GbcPdu, const MacAddress&>&&, const MacAddress& sender);
CbfPacket(CbfPacket&&) = default;
CbfPacket& operator=(CbfPacket&&) = default;
/**
* Get sender of buffered packet
* \return sender's link-layer address
*/
const MacAddress& sender() const;
/**
* Get source address of buffered packet
* \return source address
*/
const Address& source() const;
/**
* Get sequence number of buffered packet
* \return sequence number
*/
SequenceNumber sequence_number() const;
/**
* Reduce lifetime of buffered packet
* \param d reduce lifetime by this duration
* \return remaining lifetime
*/
Clock::duration reduce_lifetime(Clock::duration d);
/**
* Length of packet data in bytes (PDU including payload)
* \return size of packet on wire
*/
std::size_t length() const;
PendingPacket<GbcPdu> packet() && { return std::move(m_packet); }
private:
PendingPacket<GbcPdu> m_packet;
MacAddress m_sender;
};
/**
* CbfPacketBuffer facilitates implementation of contention based forwarding
*/
class CbfPacketBuffer
{
public:
using TimerCallback = std::function<void(PendingPacket<GbcPdu>&&)>;
using Identifier = CbfPacketIdentifier;
/**
* Create CBF packet buffer with bounded capacity
* \param rt Runtime instance used for internal timers
* \param cb Callback invoked for each packet on expiry
* \param cnt CBF counter implementation
* \param bytes Buffer can hold at most this number of bytes
*/
CbfPacketBuffer(Runtime& rt, TimerCallback cb, std::unique_ptr<CbfCounter> cnt, std::size_t bytes);
~CbfPacketBuffer();
/**
* Enqueue a packet and start an associated timer expiring after timeout
* \param packet Buffer this packet
* \param timeout CBF timer expiration for this packet
*/
void add(CbfPacket&& packet, Clock::duration timeout);
/**
* Try to remove a packet from buffer.
* \param id packet identification
* \return true if packet existed before removal
*/
bool remove(const Identifier& id);
/**
* Update associated packet timer
* \param id packet identification
* \param timeout CBF timer expiration
*/
void update(const Identifier& id, Clock::duration timeout);
/**
* Fetch a packet from buffer.
*
* Associated timer is automatically stopped and packet removed from buffer.
* Packet lifetime is reduced by queueing time at return.
*
* \param id packet identification
* \return packet if found in buffer
*/
boost::optional<CbfPacket> fetch(const Identifier& id);
/**
* Find packet in buffer.
* \param id packet identification
* \return read-only pointer to packet, nullptr if not found
*/
const CbfPacket* find(const Identifier& id) const;
/**
* Get counter associated with given packet
* \note packet counter is incremented at each timer update
* \param id packet identification
* \return 0 if packet has never been buffered before
*/
std::size_t counter(const Identifier& packet) const;
private:
struct Timer
{
Timer(const Runtime&, Clock::duration timeout);
Timer(const Timer&) = default;
Timer& operator=(const Timer&) = default;
bool operator<(const Timer&) const;
Clock::time_point expiry;
Clock::time_point start;
};
using timer_bimap = boost::bimaps::bimap<
boost::bimaps::multiset_of<Timer>,
boost::bimaps::unordered_set_of<Identifier, std::hash<Identifier>>,
boost::bimaps::with_info<std::list<CbfPacket>::iterator>
>;
/**
* Flush all expired packets
*/
void flush();
/**
* Remove timer from map and reschedule timer event if necessary
*/
void remove_timer(typename timer_bimap::left_map::iterator);
/**
* Schedule next timer event at runtime
*/
void schedule_timer();
/**
* Reduce packet lifetime by queueing time
* \param timer contains queueing start time
* \param packet associated packet
* \return true if packet remains valid, false if end of lifetime is reached
*/
bool reduce_lifetime(const Timer&, CbfPacket&) const;
std::list<CbfPacket> m_packets;
timer_bimap m_timers;
Runtime& m_runtime;
std::unique_ptr<CbfCounter> m_counter;
const std::size_t m_capacity_bytes;
std::size_t m_stored_bytes;
TimerCallback m_timer_callback;
};
} // namespace geonet
} // namespace vanetza
#endif /* CBF_PACKET_BUFFER_HPP_MU3RK5V1 */
@@ -0,0 +1,39 @@
#include <vanetza/geonet/cbf_packet_identifier.hpp>
#include <vanetza/geonet/cbf_packet_buffer.hpp>
namespace vanetza
{
namespace geonet
{
CbfPacketIdentifier identifier(const CbfPacket& packet)
{
return identifier(packet.source(), packet.sequence_number());
}
CbfPacketIdentifier identifier(const Address& source, SequenceNumber sn)
{
return std::make_tuple(source, sn);
}
} // namespace geonet
} // namespace vanetza
namespace std
{
size_t hash<vanetza::geonet::CbfPacketIdentifier>::operator()(const vanetza::geonet::CbfPacketIdentifier& id) const
{
using vanetza::geonet::Address;
using vanetza::geonet::SequenceNumber;
static_assert(tuple_size<vanetza::geonet::CbfPacketIdentifier>::value == 2, "Unexpected identifier tuple");
std::size_t seed = 0;
const Address& source = get<0>(id);
boost::hash_combine(seed, std::hash<Address>()(source));
const SequenceNumber& sn = get<1>(id);
boost::hash_combine(seed, static_cast<SequenceNumber::value_type>(sn));
return seed;
}
} // namespace std
@@ -0,0 +1,33 @@
#ifndef CBF_PACKET_IDENTIFIER_HPP_HC6PLCML
#define CBF_PACKET_IDENTIFIER_HPP_HC6PLCML
#include <vanetza/geonet/address.hpp>
#include <vanetza/geonet/sequence_number.hpp>
#include <functional>
#include <tuple>
namespace vanetza
{
namespace geonet
{
class CbfPacket;
using CbfPacketIdentifier = std::tuple<Address, SequenceNumber>;
CbfPacketIdentifier identifier(const CbfPacket&);
CbfPacketIdentifier identifier(const Address&, SequenceNumber);
} // namespace geonet
} // namespace vanetza
namespace std
{
/// std::hash specialization for CbfPacketIdentifier
template<> struct hash<vanetza::geonet::CbfPacketIdentifier>
{
size_t operator()(const vanetza::geonet::CbfPacketIdentifier&) const;
};
} // namespace std
#endif /* CBF_PACKET_IDENTIFIER_HPP_HC6PLCML */
@@ -0,0 +1,74 @@
#include <vanetza/geonet/cbr_aggregator.hpp>
#include <vanetza/geonet/location_table.hpp>
#include <vanetza/geonet/loctex_g5.hpp>
#include <algorithm>
#include <array>
namespace vanetza
{
namespace geonet
{
struct CbrAggregatorUnit
{
CbrAggregatorUnit() : n(0) {}
void operator+=(dcc::ChannelLoad cbr)
{
// calculate average as double so numerator can be larger than 1.0 temporarily
average = dcc::ChannelLoad {(average.value() * n + cbr.value()) / (n + 1)};
++n;
// >= comparison: second largest value might equal largest value
if (cbr >= maximum[0]) {
maximum[1] = maximum[0];
maximum[0] = cbr;
} else if (cbr > maximum[1]) {
maximum[1] = cbr;
}
}
dcc::ChannelLoad operator()(const dcc::ChannelLoad target) const
{
if (average > target) {
return maximum[0];
} else {
return maximum[1];
}
}
unsigned n;
dcc::ChannelLoad average;
std::array<dcc::ChannelLoad, 2> maximum;
};
CbrAggregator::CbrAggregator() :
m_one_hop_cbr(0.0), m_two_hop_cbr(0.0)
{
}
void CbrAggregator::aggregate(ChannelLoad local, const LocationTable& lt, Timestamp lifetime, ChannelLoad target)
{
m_local_cbr[1] = m_local_cbr[0];
m_local_cbr[0] = local;
CbrAggregatorUnit one_hop;
CbrAggregatorUnit two_hop;
LocationTable::entry_visitor entry_visitor =
[&](const MacAddress&, const LocationTableEntry& entry) {
const LocTEX_G5* loctex = entry.extensions.find<LocTEX_G5>();
if (loctex && loctex->local_update >= lifetime) {
one_hop += loctex->dcc_mco.local_cbr();
two_hop += loctex->dcc_mco.neighbour_cbr();
}
};
lt.visit(entry_visitor);
m_one_hop_cbr = one_hop(target);
m_two_hop_cbr = two_hop(target);
m_global_cbr = std::max({ m_local_cbr[1], m_one_hop_cbr, m_two_hop_cbr});
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,75 @@
#ifndef CBR_AGGREGATOR_HPP_VUGJW6BW
#define CBR_AGGREGATOR_HPP_VUGJW6BW
#include <vanetza/dcc/channel_load.hpp>
#include <vanetza/geonet/timestamp.hpp>
#include <array>
namespace vanetza
{
namespace geonet
{
class LocationTable;
/**
* CbrAggregator realises the CBR_G aggregation, originally specified by TS 102 636-4-2 V1.1.1
* section 5.2.2 and carried forward by TS 103 836-4-2 V2.1.1 clause 5.3 (Release 2; the
* five-step algorithm here, including using the previous cycle's local CBR in the final
* max(), matches the current text exactly).
*
* Since this algorithm relies mainly on location table entries it is placed in the geonet module.
*/
class CbrAggregator
{
public:
using ChannelLoad = dcc::ChannelLoad;
CbrAggregator();
/**
* Get local channel busy ratio, i.e. CBR_L_0_Hop
* \return CBR
*/
ChannelLoad get_local_cbr() const { return m_local_cbr[0]; }
/**
* Get one-hop channel busy ratio, i.e. CBR_L_1_Hop
* \return CBR
*/
ChannelLoad get_one_hop_cbr() const { return m_one_hop_cbr; }
/**
* Get two-hop channel busy ratio, i.e. CBR_L_2_Hop
* \return CBR
*/
ChannelLoad get_two_hop_cbr() const { return m_two_hop_cbr; }
/**
* Get global channel busy ratio
* \return CBR
*/
ChannelLoad get_global_cbr() const { return m_global_cbr; }
/**
* Aggregate {1,2}-hop CBRs from received CBR values stored in location table
* \parame local most recent local CBR measurement CBR_L_0_Hop
* \param lt location table containing LocTEX_G5 entries
* \param cbr_lifetime reject entries older than T_cbr
* \param cbr_target reference value
*/
void aggregate(ChannelLoad cbr_local, const LocationTable& lt, Timestamp cbr_lifetime, ChannelLoad cbr_target);
private:
std::array<ChannelLoad, 2> m_local_cbr;
ChannelLoad m_one_hop_cbr;
ChannelLoad m_two_hop_cbr;
ChannelLoad m_global_cbr;
};
} // namespace geonet
} // namespace vanetza
#endif /* CBR_AGGREGATOR_HPP_VUGJW6BW */
@@ -0,0 +1,102 @@
#include "common_header.hpp"
#include "data_request.hpp"
#include "serialization.hpp"
#include <stdexcept>
namespace vanetza
{
namespace geonet
{
constexpr std::size_t CommonHeader::length_bytes;
CommonHeader::CommonHeader() :
next_header(NextHeaderCommon::Any),
reserved1(0),
header_type(HeaderType::Any),
flags(0),
payload(0),
maximum_hop_limit(0),
reserved2(0)
{
}
CommonHeader::CommonHeader(const MIB& mib) :
next_header(NextHeaderCommon::Any),
reserved1(0),
header_type(HeaderType::Any),
traffic_class(mib.itsGnDefaultTrafficClass),
flags(mib.itsGnIsMobile ? 0x80 : 0x00),
payload(0),
maximum_hop_limit(mib.itsGnDefaultHopLimit),
reserved2(0)
{
}
CommonHeader::CommonHeader(const DataRequest& request, const MIB& mib) :
CommonHeader(mib)
{
switch (request.upper_protocol) {
case UpperProtocol::BTP_A:
next_header = NextHeaderCommon::BTP_A;
break;
case UpperProtocol::BTP_B:
next_header = NextHeaderCommon::BTP_B;
break;
case UpperProtocol::IPv6:
next_header = NextHeaderCommon::IPv6;
break;
case UpperProtocol::Unknown:
// No upper-layer header: e.g. a raw GN-DATA.request SDU (TS 103 836-4-1
// clause 9.3 N-SAP), which carries no BTP/IPv6 framing to declare.
next_header = NextHeaderCommon::Any;
break;
default:
throw std::runtime_error("Unhandled upper protocol");
break;
}
traffic_class = request.traffic_class;
maximum_hop_limit = request.max_hop_limit;
}
CommonHeader::CommonHeader(const ShbDataRequest& request, const MIB& mib) :
CommonHeader(static_cast<const DataRequest&>(request), mib)
{
header_type = HeaderType::TSB_Single_Hop;
maximum_hop_limit = 1;
}
void serialize(const CommonHeader& hdr, OutputArchive& ar)
{
uint8_t nextHeaderAndReserved = static_cast<uint8_t>(hdr.next_header);
nextHeaderAndReserved <<= 4;
nextHeaderAndReserved |= hdr.reserved1.raw();
serialize(host_cast(nextHeaderAndReserved), ar);
serialize(host_cast(static_cast<std::underlying_type<HeaderType>::type>(hdr.header_type)), ar);
serialize(hdr.traffic_class, ar);
serialize(host_cast(hdr.flags), ar);
serialize(host_cast(hdr.payload), ar);
serialize(host_cast(hdr.maximum_hop_limit), ar);
serialize(host_cast(hdr.reserved2), ar);
}
void deserialize(CommonHeader& hdr, InputArchive& ar)
{
uint8_t nextHeaderAndReserved;
deserialize(nextHeaderAndReserved, ar);
hdr.next_header = static_cast<NextHeaderCommon>(nextHeaderAndReserved >> 4);
hdr.reserved1 = nextHeaderAndReserved & 0x0f;
typename std::underlying_type<HeaderType>::type headerType;
deserialize(headerType, ar);
hdr.header_type = static_cast<HeaderType>(headerType);
deserialize(hdr.traffic_class, ar);
deserialize(hdr.flags, ar);
deserialize(hdr.payload, ar);
deserialize(hdr.maximum_hop_limit, ar);
deserialize(hdr.reserved2, ar);
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,54 @@
#ifndef COMMON_HEADER_HPP_SEFIWCT4
#define COMMON_HEADER_HPP_SEFIWCT4
#include <vanetza/common/bit_number.hpp>
#include <vanetza/geonet/header_type.hpp>
#include <vanetza/geonet/mib.hpp>
#include <vanetza/geonet/position_vector.hpp>
#include <vanetza/geonet/serialization.hpp>
#include <vanetza/geonet/traffic_class.hpp>
#include <cstdint>
namespace vanetza
{
namespace geonet
{
struct Area;
struct DataRequest;
struct ShbDataRequest;
class LocationTable;
enum class NextHeaderCommon
{
Any = 0, BTP_A = 1, BTP_B = 2, IPv6 = 3
};
struct CommonHeader
{
public:
CommonHeader();
CommonHeader(const MIB&);
CommonHeader(const DataRequest&, const MIB&);
CommonHeader(const ShbDataRequest&, const MIB&);
static constexpr std::size_t length_bytes = 8;
NextHeaderCommon next_header; // 4 bit
BitNumber<unsigned, 4> reserved1;
HeaderType header_type;
TrafficClass traffic_class;
uint8_t flags; // Bit 0: itsGnIsMobile
uint16_t payload; // number of octets following whole GeoNet header
uint8_t maximum_hop_limit;
uint8_t reserved2;
};
void serialize(const CommonHeader&, OutputArchive&);
void deserialize(CommonHeader&, InputArchive&);
} // namespace geonet
} // namespace vanetza
#endif /* COMMON_HEADER_HPP_SEFIWCT4 */
@@ -0,0 +1,59 @@
#include "data_confirm.hpp"
#include "data_request.hpp"
namespace vanetza
{
namespace geonet
{
DataConfirm& operator ^=(DataConfirm& lhs, DataConfirm::ResultCode rhs)
{
if (rhs != DataConfirm::ResultCode::Accepted) {
lhs.result_code = rhs;
}
return lhs;
}
DataConfirm::ResultCode validate_data_request(const DataRequest& req, const MIB& mib)
{
DataConfirm::ResultCode result = DataConfirm::ResultCode::Rejected_Unspecified;
// TODO: traffic class validation
if (req.maximum_lifetime > mib.itsGnMaxPacketLifetime) {
result = DataConfirm::ResultCode::Rejected_Max_Lifetime;
} else if (req.repetition && req.repetition->interval < mib.itsGnMinPacketRepetitionInterval) {
result = DataConfirm::ResultCode::Rejected_Min_Repetition_Interval;
} else {
result = DataConfirm::ResultCode::Accepted;
}
return result;
}
DataConfirm::ResultCode validate_data_request(const DataRequestWithArea& req, const MIB& mib)
{
if (area_size(req.destination) > mib.itsGnMaxGeoAreaSize) {
return DataConfirm::ResultCode::Rejected_Max_Geo_Area_Size;
} else {
return validate_data_request(static_cast<const DataRequest&>(req), mib);
}
}
DataConfirm::ResultCode validate_payload(const std::unique_ptr<DownPacket>& payload, const MIB& mib)
{
DataConfirm::ResultCode result = DataConfirm::ResultCode::Rejected_Unspecified;
if (!payload) {
// leave code to unspecified
} else if (payload->size() > mib.itsGnMaxSduSize) {
result = DataConfirm::ResultCode::Rejected_Max_SDU_Size;
} else {
result = DataConfirm::ResultCode::Accepted;
}
return result;
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,52 @@
#ifndef DATA_CONFIRM_HPP_Z1WCMN8T
#define DATA_CONFIRM_HPP_Z1WCMN8T
#include <vanetza/geonet/mib.hpp>
#include <vanetza/geonet/packet.hpp>
#include <memory>
namespace vanetza
{
namespace geonet
{
struct DataRequest;
struct DataRequestWithArea;
struct DataConfirm
{
enum class ResultCode {
Accepted,
Rejected_Max_SDU_Size,
Rejected_Max_Lifetime,
Rejected_Min_Repetition_Interval,
Rejected_Unsupported_Traffic_Class,
Rejected_Max_Geo_Area_Size,
Rejected_Unspecified
};
DataConfirm() : result_code(ResultCode::Accepted) {}
DataConfirm(ResultCode code) : result_code(code) {}
bool accepted() const { return result_code == ResultCode::Accepted; }
bool rejected() const { return !accepted(); }
ResultCode result_code;
};
/**
* XOR result code with DataConfirm's result code.
* Replaces result code of DataConfirm only if new code is an error code.
* \param lhs Operate on this DataConfirm
* \param rhs XOR this ResultCode with lhs
* \return reference to modified DataConfirm
*/
DataConfirm& operator ^=(DataConfirm& lhs, DataConfirm::ResultCode rhs);
DataConfirm::ResultCode validate_data_request(const DataRequest&, const MIB&);
DataConfirm::ResultCode validate_data_request(const DataRequestWithArea&, const MIB&);
DataConfirm::ResultCode validate_payload(const std::unique_ptr<DownPacket>&, const MIB&);
} // namespace geonet
} // namespace vanetza
#endif /* DATA_CONFIRM_HPP_Z1WCMN8T */
@@ -0,0 +1,41 @@
#ifndef DATA_INDICATION_HPP_DOJK9Q8T
#define DATA_INDICATION_HPP_DOJK9Q8T
#include <vanetza/common/byte_buffer.hpp>
#include <vanetza/common/its_aid.hpp>
#include <vanetza/geonet/destination_variant.hpp>
#include <vanetza/geonet/interface.hpp>
#include <vanetza/geonet/position_vector.hpp>
#include <vanetza/security/decap_service.hpp>
#include <boost/optional.hpp>
namespace vanetza
{
namespace geonet
{
/**
* \brief GN-DATA.indication
* \see EN 302 636-4-1 V1.4.1, section J.4
*/
struct DataIndication
{
UpperProtocol upper_protocol;
TransportType transport_type;
DestinationVariant destination;
ShortPositionVector source_position;
security::DecapReport security_report;
boost::optional<ItsAid> its_aid;
boost::optional<ByteBuffer> permissions;
// TS 102 723-8 V2.0.0 clause 5 incorporates V1.1.1 Table 27:
// preserve the optional SN-DECAP certificate_id through NF-SAP.
boost::optional<security::HashedId8> certificate_id;
TrafficClass traffic_class;
boost::optional<Lifetime> remaining_packet_lifetime;
boost::optional<unsigned> remaining_hop_limit;
};
} // namespace geonet
} // namespace vanetza
#endif /* DATA_INDICATION_HPP_DOJK9Q8T */
@@ -0,0 +1,93 @@
#include "data_request.hpp"
#include <vanetza/btp/data_request.hpp>
#include <vanetza/units/time.hpp>
#include <boost/units/cmath.hpp>
#include <stdexcept>
namespace vanetza
{
namespace geonet
{
void decrement_by_one(DataRequest::Repetition& repetition)
{
const auto zero = 0.0 * units::si::seconds;
if (repetition.maximum > zero && repetition.interval > zero
&& repetition.maximum > repetition.interval) {
repetition.maximum = repetition.maximum - repetition.interval;
} else {
repetition.maximum = zero;
}
}
bool has_further_repetition(const DataRequest& request)
{
bool repeat = false;
if (request.repetition) {
repeat = has_further_repetition(request.repetition.get());
}
return repeat;
}
bool has_further_repetition(const DataRequest::Repetition& repetition)
{
const auto zero = 0.0 * units::si::seconds;
return repetition.maximum > zero && repetition.interval > zero &&
repetition.maximum >= repetition.interval;
}
struct access_request_visitor : public boost::static_visitor<DataRequest&>
{
template<typename REQUEST>
DataRequest& operator()(REQUEST& request)
{
return request;
}
};
DataRequest& access_request(DataRequestVariant& variant)
{
access_request_visitor visitor;
return boost::apply_visitor(visitor, variant);
}
void copy_request_parameters(const btp::DataRequestB& btp, DataRequest& gn)
{
gn.upper_protocol = geonet::UpperProtocol::BTP_B;
gn.communication_profile = btp.gn.communication_profile;
gn.its_aid = btp.gn.its_aid;
if (btp.gn.maximum_lifetime) {
gn.maximum_lifetime = *btp.gn.maximum_lifetime;
}
gn.repetition = btp.gn.repetition;
if (btp.gn.maximum_hop_limit) {
gn.max_hop_limit = *btp.gn.maximum_hop_limit;
}
gn.traffic_class = btp.gn.traffic_class;
}
void copy_request_parameters(const btp::DataRequestB& btp, DataRequestWithAddress& gn)
{
copy_request_parameters(btp, static_cast<DataRequest&>(gn));
const Address* address = boost::get<Address>(&btp.gn.destination);
if (address) {
gn.destination = *address;
} else {
throw std::runtime_error("BTP-B data request lacks destination address");
}
}
void copy_request_parameters(const btp::DataRequestB& btp, DataRequestWithArea& gn)
{
copy_request_parameters(btp, static_cast<DataRequest&>(gn));
const Area* area = boost::get<Area>(&btp.gn.destination);
if (area) {
gn.destination = *area;
} else {
throw std::runtime_error("BTP-B data request lacks destination area");
}
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,139 @@
#ifndef DATA_REQUEST_HPP_3JYISVXB
#define DATA_REQUEST_HPP_3JYISVXB
#include <vanetza/common/byte_buffer.hpp>
#include <vanetza/common/its_aid.hpp>
#include <vanetza/geonet/address.hpp>
#include <vanetza/geonet/areas.hpp>
#include <vanetza/geonet/interface.hpp>
#include <vanetza/geonet/lifetime.hpp>
#include <vanetza/geonet/mib.hpp>
#include <vanetza/geonet/traffic_class.hpp>
#include <vanetza/units/time.hpp>
#include <boost/optional.hpp>
#include <boost/variant.hpp>
namespace vanetza
{
// forward declaration
namespace btp { struct DataRequestB; }
namespace geonet
{
struct DataRequest
{
DataRequest(const MIB& mib, ItsAid its_aid = 0) :
upper_protocol(UpperProtocol::BTP_A),
communication_profile(CommunicationProfile::Unspecified),
its_aid(its_aid),
maximum_lifetime(mib.itsGnDefaultPacketLifetime),
max_hop_limit(mib.itsGnDefaultHopLimit),
traffic_class(mib.itsGnDefaultTrafficClass)
{}
struct Repetition
{
units::Duration interval;
units::Duration maximum;
};
UpperProtocol upper_protocol;
CommunicationProfile communication_profile;
ItsAid its_aid;
ByteBuffer permissions;
// Security context information (TS 103 836-4-1 V2.2.1 Annex J.2), passed to SN-ENCAP.request
ByteBuffer security_context;
Lifetime maximum_lifetime;
boost::optional<Repetition> repetition;
unsigned max_hop_limit;
TrafficClass traffic_class;
};
/**
* \brief Decrement maximum repetition by one interval
* \param repetition Repetition data structure
*/
void decrement_by_one(DataRequest::Repetition& repetition);
/**
* \brief Test if request has to be repeated at least once more
* \param request DataRequest
* \return true if there is at least one repetition left
*/
bool has_further_repetition(const DataRequest&);
/**
* \brief Test if at least one repetition is outstanding
* \param repetition
* \return true if there is at least one repetition left
*/
bool has_further_repetition(const DataRequest::Repetition&);
struct DataRequestWithAddress : public DataRequest
{
using DataRequest::DataRequest;
Address destination;
};
struct DataRequestWithArea : public DataRequest
{
using DataRequest::DataRequest;
Area destination;
};
struct GucDataRequest : public DataRequestWithAddress
{
using DataRequestWithAddress::DataRequestWithAddress;
};
struct GbcDataRequest : public DataRequestWithArea
{
using DataRequestWithArea::DataRequestWithArea;
};
struct GacDataRequest : public DataRequestWithArea
{
using DataRequestWithArea::DataRequestWithArea;
};
struct ShbDataRequest : public DataRequest
{
using DataRequest::DataRequest;
};
struct TsbDataRequest : public DataRequest
{
using DataRequest::DataRequest;
};
using DataRequestVariant =
boost::variant<
GucDataRequest,
GbcDataRequest,
GacDataRequest,
ShbDataRequest,
TsbDataRequest
>;
/**
* Get access to common base data request class of all variants
* \param variant DataRequestVariant object
* \return reference to underlying DataRequest
*/
DataRequest& access_request(DataRequestVariant&);
/**
* Copy request parameters from BTP-B request
* \param btp BTP-B data request
* \param gn GeoNet destination request
*/
void copy_request_parameters(const btp::DataRequestB& btp, DataRequest& gn);
void copy_request_parameters(const btp::DataRequestB& btp, DataRequestWithAddress& gn);
void copy_request_parameters(const btp::DataRequestB& btp, DataRequestWithArea& gn);
} // namespace geonet
} // namespace vanetza
#endif /* DATA_REQUEST_HPP_3JYISVXB */
@@ -0,0 +1,56 @@
#include <vanetza/geonet/dcc_field.hpp>
#include <boost/variant/apply_visitor.hpp>
#include <boost/variant/static_visitor.hpp>
namespace vanetza
{
namespace geonet
{
struct dcc_mco_extractor : boost::static_visitor< boost::optional<DccMcoField> >
{
using return_type = boost::optional<DccMcoField>;
return_type operator()(const DccMcoField& mco) const
{
return mco;
}
return_type operator()(uint32_t raw) const
{
return DccMcoField { raw };
}
template<typename T>
return_type operator()(const T&)
{
return boost::none;
}
};
boost::optional<DccMcoField> get_dcc_mco(const DccField& field)
{
return boost::apply_visitor(dcc_mco_extractor(), field);
}
void serialize(const DccField& field, OutputArchive& ar)
{
struct serialize_visitor : boost::static_visitor<uint32_t>
{
uint32_t operator()(const DccMcoField& mco) const { return static_cast<uint32_t>(mco); }
uint32_t operator()(const uint32_t& raw) const { return raw; }
};
uint32_t raw = boost::apply_visitor(serialize_visitor(), field);
serialize(host_cast(raw), ar);
}
void deserialize(DccField& field, InputArchive& ar)
{
uint32_t raw;
deserialize(raw, ar);
field = raw;
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,32 @@
#ifndef DCC_FIELD_HPP_EPTBYHAU
#define DCC_FIELD_HPP_EPTBYHAU
#include <vanetza/geonet/dcc_mco_field.hpp>
#include <vanetza/geonet/serialization.hpp>
#include <boost/optional/optional.hpp>
#include <boost/variant/variant.hpp>
#include <cstdint>
namespace vanetza
{
namespace geonet
{
/**
* DccField represents the supported variants for the DCC field in SHB headers.
*
* \note Enclosed types are using "host byte order".
* Byte order conversion is handled by serialize and deserialize functions.
*/
using DccField = boost::variant<DccMcoField, uint32_t>;
boost::optional<DccMcoField> get_dcc_mco(const DccField&);
void serialize(const DccField&, OutputArchive&);
void deserialize(DccField&, InputArchive&);
} // namespace geonet
} // namespace vanetza
#endif /* DCC_FIELD_HPP_EPTBYHAU */
@@ -0,0 +1,29 @@
#ifndef DCC_FIELD_GENERATOR_HPP_FVGALNWN
#define DCC_FIELD_GENERATOR_HPP_FVGALNWN
#include <vanetza/geonet/dcc_field.hpp>
namespace vanetza
{
namespace geonet
{
class DccFieldGenerator
{
public:
virtual DccField generate_dcc_field() = 0;
virtual ~DccFieldGenerator() = default;
};
class NullDccFieldGenerator : public DccFieldGenerator
{
public:
DccField generate_dcc_field() override { return static_cast<uint32_t>(0); }
};
} // namespace geonet
} // namespace vanetza
#endif /* DCC_DCC_FIELD_GENERATOR_HPP_FVGALNWN */
@@ -0,0 +1,54 @@
#include <vanetza/common/runtime.hpp>
#include <vanetza/geonet/dcc_information_sharing.hpp>
#include <vanetza/geonet/location_table.hpp>
namespace vanetza
{
namespace geonet
{
DccInformationSharing::DccInformationSharing(Runtime& rt, const LocationTable& lt, dcc::ChannelLoad target, UnitInterval delay) :
m_runtime(rt), m_location_table(lt), m_cbr_target(target), m_tx_power(0),
m_trigger_interval(std::chrono::milliseconds(100)),
m_last_aggregation(m_runtime.now()),
on_global_cbr_update(m_update_hook)
{
Clock::duration initial = m_trigger_interval;
initial *= delay.value();
m_runtime.schedule(initial, [this](const Clock::time_point&) { trigger(); });
}
DccInformationSharing::DccInformationSharing(Runtime& rt, const LocationTable& lt, dcc::ChannelLoad target) :
DccInformationSharing(rt, lt, target, UnitInterval { 0.0 })
{
}
DccField DccInformationSharing::generate_dcc_field()
{
DccMcoField dcc_mco;
dcc_mco.local_cbr(m_aggregator.get_local_cbr());
dcc_mco.neighbour_cbr(m_aggregator.get_one_hop_cbr());
dcc_mco.output_power(m_tx_power);
return dcc_mco;
}
void DccInformationSharing::update_local_cbr(dcc::ChannelLoad local_cbr)
{
m_cbr_local = local_cbr;
}
void DccInformationSharing::set_tx_power(unsigned tx_power)
{
m_tx_power = tx_power;
}
void DccInformationSharing::trigger()
{
m_aggregator.aggregate(m_cbr_local, m_location_table, m_last_aggregation, m_cbr_target);
m_last_aggregation = m_runtime.now();
m_update_hook(static_cast<const CbrAggregator&>(m_aggregator));
m_runtime.schedule(m_trigger_interval, [this](const Clock::time_point&) { trigger(); });
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,90 @@
#ifndef DCC_INFORMATION_SHARING_HPP_GZCSHZLD
#define DCC_INFORMATION_SHARING_HPP_GZCSHZLD
#include <vanetza/common/clock.hpp>
#include <vanetza/common/hook.hpp>
#include <vanetza/common/unit_interval.hpp>
#include <vanetza/geonet/cbr_aggregator.hpp>
#include <vanetza/geonet/dcc_field_generator.hpp>
namespace vanetza
{
class Runtime;
namespace geonet
{
class LocationTable;
/**
* DccInformationSharing realises the DCC_NET behaviour for ITS-G5, originally specified by
* TS 102 636-4-2 V1.1.1 and carried forward by TS 103 836-4-2 V2.1.1 clauses 5.3-5.4
* (Release 2; the CBR_G algorithm and trigger cadence implemented here match the current
* text step-for-step). CBR_target corresponds to the Release-2 protocol constant
* itsGNCBRTarget (TS 103 836-4-2 V2.1.1 Annex A: 0,62), which supersedes the value this
* class's original author had to guess at from TS 102 687's undefined NDL_maxChannelUse.
*/
class DccInformationSharing : public DccFieldGenerator
{
public:
/**
* Create DCC_net instance
*
* \param rt Runtime for scheduling periodic update cycles
* \param lt Location Table with LocTEX_G5 entries
* \param target CBR_target value (usually NDL_maxChannelLoad)
* \param delay Delaying first update cycle randomly
*
* \note Set random delay interval when multiple stations are created at the same time!
* Values shall be distributed uniformly across full integer range.
*/
DccInformationSharing(Runtime& rt, const LocationTable& lt, dcc::ChannelLoad target, UnitInterval delay);
DccInformationSharing(Runtime& rt, const LocationTable& lt, dcc::ChannelLoad target);
DccField generate_dcc_field() override;
/**
* Update local CBR measurement
*
* Local measurement rate is decoupled from processing in DCC_net,
* i.e. DccInformationSharing buffers the given value and the latest
* measurement value when its internal update cycle runs.
*
* \param cbr local CBR measurement
*/
void update_local_cbr(dcc::ChannelLoad cbr);
/**
* Set packet TX power
*
* \param power Packet transmission output power in dBm
*/
void set_tx_power(unsigned power);
private:
void trigger();
Runtime& m_runtime;
const LocationTable& m_location_table;
const dcc::ChannelLoad m_cbr_target;
dcc::ChannelLoad m_cbr_local;
unsigned m_tx_power;
CbrAggregator m_aggregator;
Clock::duration m_trigger_interval;
Timestamp m_last_aggregation;
Hook<const CbrAggregator&> m_update_hook;
public:
/**
* on_global_cbr_update is called at each update cycle,
* i.e. when a new global CBR has been calculated
*/
HookRegistry<const CbrAggregator&> on_global_cbr_update;
};
} // namespace geonet
} // namespace vanetza
#endif /* DCC_INFORMATION_SHARING_HPP_GZCSHZLD */
@@ -0,0 +1,67 @@
#include "dcc_mco_field.hpp"
#include <algorithm>
#include <cmath>
namespace vanetza
{
namespace geonet
{
DccMcoField::DccMcoField() :
m_cbr_l0_hop(0), m_cbr_l1_hop(0), m_output_power(0)
{
}
DccMcoField::DccMcoField(uint32_t field)
{
m_cbr_l0_hop = field >> 24;
m_cbr_l1_hop = field >> 16;
m_output_power = field >> 11;
}
DccMcoField::operator uint32_t() const
{
uint32_t field = m_cbr_l0_hop;
field <<= 8;
field |= m_cbr_l1_hop;
field <<= 5;
field |= m_output_power.raw();
field <<= 11;
return field;
}
void DccMcoField::local_cbr(const ChannelLoad& cbr)
{
m_cbr_l0_hop = std::floor(cbr.value() * 255.0);
}
dcc::ChannelLoad DccMcoField::local_cbr() const
{
return ChannelLoad(m_cbr_l0_hop / 255.0);
}
void DccMcoField::neighbour_cbr(const ChannelLoad& cbr)
{
m_cbr_l1_hop = std::floor(cbr.value() * 255.0);
}
dcc::ChannelLoad DccMcoField::neighbour_cbr() const
{
return ChannelLoad(m_cbr_l1_hop / 255.0);
}
void DccMcoField::output_power(unsigned dbm)
{
m_output_power = std::min(dbm, 31u);
}
unsigned DccMcoField::output_power() const
{
return m_output_power.raw();
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,63 @@
#ifndef DCC_MCO_FIELD_HPP_RLZ4PQMF
#define DCC_MCO_FIELD_HPP_RLZ4PQMF
#include <vanetza/common/bit_number.hpp>
#include <vanetza/dcc/channel_load.hpp>
#include <cstdint>
namespace vanetza
{
namespace geonet
{
/**
* DCC-MCO (Multi Channel Operations) was introduced by TS 102 636-4-2 V1.1.1 and is carried
* forward unchanged by TS 103 836-4-2 V2.1.1 clause 6.3.3 Table 3 (Release 2): Octet 40
* CBR_L_0_Hop = floor(CBR_L_0_Hop x 255), Octet 41 CBR_L_1_Hop likewise, Octet 42 bits 0-4 TX
* power [0;31] dBm, Octet 43 reserved. This field layout was cross-checked bit-for-bit against
* the current V2.1.1 text; only this docstring's citation was stale.
* DccMcoField implements the SHB header field extension.
*/
class DccMcoField
{
public:
using ChannelLoad = dcc::ChannelLoad;
DccMcoField();
// copy operations
DccMcoField(const DccMcoField&) = default;
DccMcoField& operator=(const DccMcoField&) = default;
// conversion from/to 4 bytes (host byte order)
explicit DccMcoField(uint32_t);
DccMcoField& operator=(uint32_t);
explicit operator uint32_t() const;
void local_cbr(const ChannelLoad&);
ChannelLoad local_cbr() const;
void neighbour_cbr(const ChannelLoad&);
ChannelLoad neighbour_cbr() const;
/**
* Output power of packet transmission
* \return [0; 31] dBm (values are cramped at limits)
*/
void output_power(unsigned dbm);
unsigned output_power() const;
private:
using cbr_type = uint8_t;
using power_type = BitNumber<unsigned, 5>;
cbr_type m_cbr_l0_hop; /*< local CBR measurement */
cbr_type m_cbr_l1_hop; /*< maximum CBR measurement from 1-hop neighbours */
power_type m_output_power; /*< output power of packet transmission */
};
} // namespace geonet
} // namespace vanetza
#endif /* DCC_MCO_FIELD_HPP_RLZ4PQMF */
@@ -0,0 +1,19 @@
#ifndef DESTINATION_VARIANT_HPP_Y4TEVUXO
#define DESTINATION_VARIANT_HPP_Y4TEVUXO
#include <vanetza/geonet/areas.hpp>
#include <vanetza/geonet/address.hpp>
#include <boost/variant.hpp>
namespace vanetza
{
namespace geonet
{
typedef boost::variant<Address, Area, std::nullptr_t> DestinationVariant;
} // namespace geonet
} // namespace vanetza
#endif /* DESTINATION_VARIANT_HPP_Y4TEVUXO */
@@ -0,0 +1,53 @@
#include <vanetza/geonet/duplicate_packet_list.hpp>
#include <cassert>
namespace vanetza
{
namespace geonet
{
DuplicatePacketList::DuplicatePacketList(unsigned elements) :
m_elements(elements)
{
assert(m_elements.size() == 0);
}
bool DuplicatePacketList::check(SequenceNumber sn)
{
ListElement* element = find(sn);
if (element) {
++element->counter;
return true;
} else {
m_elements.push_back(ListElement { sn });
return false;
}
}
unsigned DuplicatePacketList::counter(SequenceNumber sn) const
{
for (auto& element : m_elements) {
if (element.sequence_number == sn) {
return element.counter;
}
}
return 0;
}
DuplicatePacketList::ListElement* DuplicatePacketList::find(SequenceNumber sn)
{
for (auto& element : m_elements) {
if (element.sequence_number == sn) {
return &element;
}
}
return nullptr;
}
DuplicatePacketList::ListElement::ListElement(SequenceNumber sn) :
sequence_number(sn), counter(1)
{
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,57 @@
#ifndef DUPLICATE_PACKET_LIST_HPP_T8JWKCKG
#define DUPLICATE_PACKET_LIST_HPP_T8JWKCKG
#include <vanetza/geonet/sequence_number.hpp>
#include <boost/circular_buffer.hpp>
namespace vanetza
{
namespace geonet
{
/**
* Duplicate Packet List for a single source SO.
* Those objects area meant as extension to LocationTableEntry.
*
* \see EN 302 636-4-1 v1.3.1 Annex A.2
*/
class DuplicatePacketList
{
public:
DuplicatePacketList(unsigned elements);
/**
* Duplicate packet detection based on sequence number.
*
* Sequence number will be included in list afterwards.
* \param sn sequence number
* \return true if its a duplicate
*/
bool check(SequenceNumber);
/**
* Retrieve duplicate packet counter
* \param sn sequence number
* \return number of duplicates seen for given sequence number
*/
unsigned counter(SequenceNumber) const;
private:
struct ListElement
{
ListElement(SequenceNumber);
SequenceNumber sequence_number;
unsigned counter;
};
ListElement* find(SequenceNumber);
boost::circular_buffer<ListElement> m_elements;
};
} // namespace geonet
} // namespace vanetza
#endif /* DUPLICATE_PACKET_LIST_HPP_T8JWKCKG */
@@ -0,0 +1,132 @@
#ifndef EXTENDED_PDU_HPP_TL2WFH9W
#define EXTENDED_PDU_HPP_TL2WFH9W
#include <vanetza/geonet/basic_header.hpp>
#include <vanetza/geonet/common_header.hpp>
#include <vanetza/geonet/pdu.hpp>
#include <vanetza/geonet/serialization.hpp>
#include <vanetza/common/byte_buffer.hpp>
#include <vanetza/common/byte_buffer_sink.hpp>
#include <vanetza/security/secured_message.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/optional/optional.hpp>
#include <memory>
namespace vanetza
{
namespace geonet
{
template<class HEADER> class ExtendedPduConstRefs;
template<class HEADER>
class ExtendedPdu : public Pdu
{
public:
using SecuredMessage = security::SecuredMessage;
using ExtendedHeader = HEADER;
ExtendedPdu() = default;
ExtendedPdu(const ExtendedPdu& pdu) :
m_basic(pdu.m_basic), m_common(pdu.m_common), m_extended(pdu.m_extended),
m_secured(pdu.m_secured) {}
ExtendedPdu& operator=(const ExtendedPdu&) = default;
ExtendedPdu(const MIB& mib) : m_basic(mib), m_common(mib) {}
ExtendedPdu(const DataRequest& request, const MIB& mib) :
m_basic(request, mib), m_common(request, mib) {}
ExtendedPdu(const BasicHeader& basic, const CommonHeader& common, const HEADER& extended) :
m_basic(basic), m_common(common), m_extended(extended) {}
ExtendedPdu(const BasicHeader& basic, const CommonHeader& common, const HEADER& extended,
const SecuredMessage& secured) :
m_basic(basic), m_common(common), m_extended(extended), m_secured(secured) {}
ExtendedPdu(const ExtendedPduConstRefs<HEADER>& pdu) :
m_basic(pdu.basic()), m_common(pdu.common()), m_extended(pdu.extended()),
m_secured(pdu.secured() ? boost::make_optional(*pdu.secured()) : boost::none) {}
BasicHeader& basic() override { return m_basic; }
const BasicHeader& basic() const override { return m_basic; }
CommonHeader& common() override { return m_common; }
const CommonHeader& common() const override { return m_common; }
HeaderConstRefVariant extended_variant() const override { return m_extended; }
HEADER& extended() { return m_extended; }
const HEADER& extended() const { return m_extended; }
SecuredMessage* secured() override { return m_secured.get_ptr(); }
const SecuredMessage* secured() const override { return m_secured.get_ptr(); }
void secured(const SecuredMessage& smsg) override
{
m_secured = smsg;
}
std::unique_ptr<Pdu> clone() const override
{
return std::unique_ptr<ExtendedPdu> { new ExtendedPdu(*this) };
}
private:
BasicHeader m_basic;
CommonHeader m_common;
HEADER m_extended;
boost::optional<SecuredMessage> m_secured;
};
template<class HEADER>
class ExtendedPduConstRefs : public ConstAccessiblePdu
{
public:
using SecuredMessage = security::SecuredMessage;
using ExtendedHeader = HEADER;
ExtendedPduConstRefs(const BasicHeader& basic, const CommonHeader& common, const HEADER& extended) :
mr_basic(basic), mr_common(common), mr_extended(extended), mp_secured(nullptr) {}
ExtendedPduConstRefs(const BasicHeader& basic, const CommonHeader& common, const HEADER& extended,
const SecuredMessage* secured) :
mr_basic(basic), mr_common(common), mr_extended(extended), mp_secured(secured) {}
const BasicHeader& basic() const override { return mr_basic; }
const CommonHeader& common() const override { return mr_common; }
HeaderConstRefVariant extended_variant() const override { return mr_extended; }
const HEADER& extended() const { return mr_extended; }
const SecuredMessage* secured() const override { return mp_secured; }
std::unique_ptr<Pdu> clone() const override
{
return std::unique_ptr<ExtendedPdu<HEADER>> {
mp_secured ?
new ExtendedPdu<HEADER>(mr_basic, mr_common, mr_extended, *mp_secured) :
new ExtendedPdu<HEADER>(mr_basic, mr_common, mr_extended)
};
}
private:
const BasicHeader& mr_basic;
const CommonHeader& mr_common;
const HEADER& mr_extended;
const SecuredMessage* mp_secured;
};
/**
* \brief Serialize relevant header parts for signing
* Uses common and extended headers.
* \param pdu containing GN headers
* \return binary form of relevant header parts
*/
template<class HEADER>
ByteBuffer convert_for_signing(const ExtendedPdu<HEADER>& pdu)
{
ByteBuffer buf;
byte_buffer_sink sink(buf);
boost::iostreams::stream_buffer<byte_buffer_sink> stream(sink);
OutputArchive ar(stream);
serialize(pdu.common(), ar);
serialize(pdu.extended(), ar);
stream.close();
return buf;
}
} // namespace geonet
} // namespace vanetza
#endif /* EXTENDED_PDU_HPP_TL2WFH9W */
@@ -0,0 +1,129 @@
#include "gbc_gac_header.hpp"
#include "areas.hpp"
#include <cmath>
namespace vanetza
{
namespace geonet
{
namespace detail
{
class set_distance_visitor : public boost::static_visitor<>
{
public:
set_distance_visitor(GbcGacHeader& hdr) : m_header(hdr) {}
void operator()(const Circle& circle)
{
m_header.distance_a = circle.r;
m_header.distance_b = 0;
}
void operator()(const Rectangle& rect)
{
m_header.distance_a = rect.a;
m_header.distance_b = rect.b;
}
void operator()(const Ellipse& elip)
{
m_header.distance_a = elip.a;
m_header.distance_b = elip.b;
}
private:
GbcGacHeader& m_header;
};
class get_distance_visitor : public boost::static_visitor<>
{
public:
get_distance_visitor(const GbcGacHeader& hdr) : m_header(hdr) {}
void operator()(Circle& circle)
{
circle.r = m_header.distance_a;
}
void operator()(Rectangle& rect)
{
rect.a = m_header.distance_a;
rect.b = m_header.distance_b;
}
void operator()(Ellipse& elip)
{
elip.a = m_header.distance_a;
elip.b = m_header.distance_b;
}
private:
const GbcGacHeader& m_header;
};
constexpr std::size_t GbcGacHeader::length_bytes;
void GbcGacHeader::destination(const Area& area)
{
this->position(area.position);
angle = static_cast<angle_u16t>(area.angle);
set_distance_visitor visitor(*this);
boost::apply_visitor(visitor, area.shape);
}
Area GbcGacHeader::destination(const decltype(Area::shape)& shape) const
{
Area area;
area.shape = shape;
area.position = this->position();
area.angle = static_cast<units::Angle>(angle);
get_distance_visitor visitor(*this);
boost::apply_visitor(visitor, area.shape);
return area;
}
void GbcGacHeader::position(const GeodeticPosition& position)
{
geo_area_pos_latitude = geo_angle_i32t::from_value(std::round(position.latitude.value() * 1e7));
geo_area_pos_longitude = geo_angle_i32t::from_value(std::round(position.longitude.value() * 1e7));
}
GeodeticPosition GbcGacHeader::position() const
{
return GeodeticPosition(
static_cast<units::GeoAngle>(geo_area_pos_latitude),
static_cast<units::GeoAngle>(geo_area_pos_longitude)
);
}
void serialize(const GbcGacHeader& header, OutputArchive& ar)
{
geonet::serialize(header.sequence_number, ar);
geonet::serialize(host_cast(header.reserved1), ar);
geonet::serialize(header.source_position, ar);
geonet::serialize(header.geo_area_pos_latitude, ar);
geonet::serialize(header.geo_area_pos_longitude, ar);
geonet::serialize(header.distance_a, ar);
geonet::serialize(header.distance_b, ar);
geonet::serialize(header.angle, ar);
geonet::serialize(host_cast(header.reserved2), ar);
}
void deserialize(GbcGacHeader& header, InputArchive& ar)
{
geonet::deserialize(header.sequence_number, ar);
geonet::deserialize(header.reserved1, ar);
geonet::deserialize(header.source_position, ar);
geonet::deserialize(header.geo_area_pos_latitude, ar);
geonet::deserialize(header.geo_area_pos_longitude, ar);
geonet::deserialize(header.distance_a, ar);
geonet::deserialize(header.distance_b, ar);
geonet::deserialize(header.angle, ar);
geonet::deserialize(header.reserved2, ar);
}
} // namespace detail
} // namespace geonet
} // namepsace vanetza
@@ -0,0 +1,52 @@
#ifndef GBC_GAC_HEADER_HPP_FS5DH20M
#define GBC_GAC_HEADER_HPP_FS5DH20M
#include <vanetza/geonet/areas.hpp>
#include <vanetza/geonet/position_vector.hpp>
#include <vanetza/geonet/sequence_number.hpp>
#include <vanetza/geonet/serialization.hpp>
#include <vanetza/geonet/units.hpp>
#include <cstddef>
namespace vanetza
{
namespace geonet
{
namespace detail
{
/**
* GeoBroadcast GUC and GeoAnycast GAC have identical header layout.
* We use this class as common base class to reduce duplicate code.
*/
struct GbcGacHeader
{
static constexpr std::size_t length_bytes = 20 + LongPositionVector::length_bytes;
SequenceNumber sequence_number;
uint16_t reserved1 = 0;
LongPositionVector source_position;
geo_angle_i32t geo_area_pos_latitude;
geo_angle_i32t geo_area_pos_longitude;
distance_u16t distance_a;
distance_u16t distance_b;
angle_u16t angle;
uint16_t reserved2 = 0;
void destination(const Area&);
GeodeticPosition position() const;
void position(const GeodeticPosition&);
protected:
Area destination(const decltype(Area::shape)&) const;
};
void serialize(const GbcGacHeader&, OutputArchive&);
void deserialize(GbcGacHeader&, InputArchive&);
} // namespace detail
} // namespace geonet
} // namespace vanetza
#endif /* GBC_GAC_HEADER_HPP_FS5DH20M */
@@ -0,0 +1,42 @@
#include "gbc_header.hpp"
#include "areas.hpp"
#include <stdexcept>
namespace vanetza
{
namespace geonet
{
Area GeoBroadcastHeader::destination(HeaderType ht) const
{
decltype(Area::shape) shape;
switch (ht) {
case HeaderType::GeoBroadcast_Circle:
shape = Circle();
break;
case HeaderType::GeoBroadcast_Rect:
shape = Rectangle();
break;
case HeaderType::GeoBroadcast_Elip:
shape = Ellipse();
break;
default:
throw std::runtime_error("Invalid GBC header type");
break;
}
return detail::GbcGacHeader::destination(shape);
}
void serialize(const GeoBroadcastHeader& header, OutputArchive& ar)
{
detail::serialize(header, ar);
}
void deserialize(GeoBroadcastHeader& header, InputArchive& ar)
{
detail::deserialize(header, ar);
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,27 @@
#ifndef GBC_HEADER_HPP_AWYN1M7E
#define GBC_HEADER_HPP_AWYN1M7E
#include <vanetza/geonet/gbc_gac_header.hpp>
#include <vanetza/geonet/header_type.hpp>
#include <vanetza/geonet/serialization.hpp>
namespace vanetza
{
namespace geonet
{
struct GeoBroadcastHeader : public detail::GbcGacHeader
{
public:
using detail::GbcGacHeader::destination;
Area destination(HeaderType) const;
};
void serialize(const GeoBroadcastHeader&, OutputArchive&);
void deserialize(GeoBroadcastHeader&, InputArchive&);
} // namespace geonet
} // namespace vanetza
#endif /* GBC_HEADER_HPP_AWYN1M7E */
@@ -0,0 +1,52 @@
#include <vanetza/geonet/gbc_memory.hpp>
namespace vanetza
{
namespace geonet
{
void GbcMemory::capacity(std::size_t num)
{
m_capacity = num < 1 ? 1 : num;
// remove excessive identifiers
auto& by_queue_index = m_identifiers.get<by_queue>();
while (by_queue_index.size() > m_capacity) {
by_queue_index.pop_front();
}
}
std::size_t GbcMemory::size() const
{
return m_identifiers.size();
}
bool GbcMemory::remember(const PacketIdentifier& id)
{
auto& by_packet_index = m_identifiers.get<by_packet>();
auto found_packet = by_packet_index.find(id);
if (found_packet == by_packet_index.end()) {
// make space for one identifier
auto& by_queue_index = m_identifiers.get<by_queue>();
while (!by_queue_index.empty() && by_queue_index.size() >= m_capacity) {
by_queue_index.pop_front();
}
by_queue_index.push_back(id);
return false;
} else {
// packet is already known, just move it to end of queue
auto found_queue = m_identifiers.project<by_queue>(found_packet);
auto& by_queue_index = m_identifiers.get<by_queue>();
by_queue_index.relocate(by_queue_index.end(), found_queue);
return true;
}
}
bool GbcMemory::knows(const PacketIdentifier& id) const
{
return m_identifiers.get<by_packet>().find(id) != m_identifiers.get<by_packet>().end();
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,72 @@
#ifndef GBC_MEMORY_HPP_032N8PRJ
#define GBC_MEMORY_HPP_032N8PRJ
#include <vanetza/geonet/cbf_packet_identifier.hpp>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/sequenced_index.hpp>
namespace vanetza
{
namespace geonet
{
/**
* GbcMemory remembers previously seens GBC packets.
*
* GBC packets are identified by their (GN addr, sequence number) tuple.
* The size of GbcMemory is bounded, i.e. it will forget old packets in favour of recent ones.
*/
class GbcMemory
{
public:
using PacketIdentifier = CbfPacketIdentifier;
/**
* Forget a packet if memory exceeds upper limit of stored identifiers
* \param num upper limit of remembered packets
*/
void capacity(std::size_t num);
/**
* Number of currently known packets
* \return number of packets
*/
std::size_t size() const;
/**
* Remember a particular packet
* \param id packet identifier
* \return true if packet is already known
*/
bool remember(const PacketIdentifier& id);
/**
* Check if a particular packet is known
* \param id packet identifier
* \return true if packet is known
*/
bool knows(const PacketIdentifier& id) const;
private:
std::size_t m_capacity = 1;
struct by_packet {};
using packet_index = boost::multi_index::hashed_unique<
boost::multi_index::tag<by_packet>,
boost::multi_index::identity<PacketIdentifier>,
std::hash<PacketIdentifier>
>;
struct by_queue {};
using queue_index = boost::multi_index::sequenced<boost::multi_index::tag<by_queue>>;
using container_type = boost::multi_index_container<PacketIdentifier,
boost::multi_index::indexed_by<queue_index, packet_index>>;
container_type m_identifiers;
};
} // namespace geonet
} // namespace vanetza
#endif /* GBC_MEMORY_HPP_032N8PRJ */
@@ -0,0 +1,29 @@
#ifndef GUC_HEADER_HPP_UEGE3IFW
#define GUC_HEADER_HPP_UEGE3IFW
#include <vanetza/geonet/position_vector.hpp>
#include <vanetza/geonet/sequence_number.hpp>
#include <cstddef>
namespace vanetza
{
namespace geonet
{
struct GeoUnicastHeader
{
static constexpr std::size_t length_bytes = 4 +
LongPositionVector::length_bytes +
ShortPositionVector::length_bytes;
SequenceNumber sequence_number;
uint16_t reserved;
LongPositionVector source_position;
ShortPositionVector destination_position;
};
} // namespace geonet
} // namespace vanetza
#endif /* GUC_HEADER_HPP_UEGE3IFW */
@@ -0,0 +1,59 @@
#include "header_type.hpp"
#include "areas.hpp"
#include <boost/variant/apply_visitor.hpp>
#include <boost/variant/static_visitor.hpp>
namespace vanetza
{
namespace geonet
{
struct gbc_header_type_visitor : public boost::static_visitor<HeaderType>
{
HeaderType operator()(const Circle&) const
{
return HeaderType::GeoBroadcast_Circle;
}
HeaderType operator()(const Rectangle&) const
{
return HeaderType::GeoBroadcast_Rect;
}
HeaderType operator()(const Ellipse&) const
{
return HeaderType::GeoBroadcast_Elip;
}
};
HeaderType gbc_header_type(const Area& area)
{
return boost::apply_visitor(gbc_header_type_visitor(), area.shape);
}
struct gac_header_type_visitor : public boost::static_visitor<HeaderType>
{
HeaderType operator()(const Circle&) const
{
return HeaderType::GeoAnycast_Circle;
}
HeaderType operator()(const Rectangle&) const
{
return HeaderType::GeoAnycast_Rect;
}
HeaderType operator()(const Ellipse&) const
{
return HeaderType::GeoAnycast_Elip;
}
};
HeaderType gac_header_type(const Area& area)
{
return boost::apply_visitor(gac_header_type_visitor(), area.shape);
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,38 @@
#ifndef HEADER_TYPE_HPP_U5FGWR9N
#define HEADER_TYPE_HPP_U5FGWR9N
#include <cstdint>
namespace vanetza
{
namespace geonet
{
// forward declaration
struct Area;
enum class HeaderType : uint8_t
{
Any = 0x00,
Beacon = 0x10,
GeoUnicast = 0x20,
GeoAnycast_Circle = 0x30,
GeoAnycast_Rect = 0x31,
GeoAnycast_Elip = 0x32,
GeoBroadcast_Circle = 0x40,
GeoBroadcast_Rect = 0x41,
GeoBroadcast_Elip = 0x42,
TSB_Single_Hop = 0x50,
TSB_Multi_Hop = 0x51,
LS_Request = 0x60,
LS_Reply = 0x61
};
HeaderType gbc_header_type(const Area&);
HeaderType gac_header_type(const Area&);
} // namespace geonet
} // namespace vanetza
#endif /* HEADER_TYPE_HPP_U5FGWR9N */
@@ -0,0 +1,81 @@
#include <vanetza/geonet/header_variant.hpp>
namespace boost
{
using namespace vanetza::geonet;
class HeaderVariantVisitor : public boost::static_visitor<>
{
public:
HeaderVariantVisitor(OutputArchive& ar) :
m_archive(ar)
{
}
template<typename T>
void operator()(const T& header)
{
serialize(header, m_archive);
}
private:
OutputArchive& m_archive;
};
void serialize(const HeaderVariant& header, OutputArchive& ar)
{
HeaderVariantVisitor visit(ar);
boost::apply_visitor(visit, header);
}
void serialize(const HeaderRefVariant& header, OutputArchive& ar)
{
HeaderVariantVisitor visitor(ar);
boost::apply_visitor(visitor, header);
}
void serialize(const HeaderConstRefVariant& header, OutputArchive& ar)
{
HeaderVariantVisitor visitor(ar);
boost::apply_visitor(visitor, header);
}
} // namespace boost
namespace vanetza
{
namespace geonet
{
class HeaderVariantLengthVisitor : public boost::static_visitor<std::size_t>
{
public:
template<typename T>
std::size_t operator()(const T&)
{
return T::length_bytes;
}
};
std::size_t get_length(const HeaderVariant& header)
{
HeaderVariantLengthVisitor visit;
return boost::apply_visitor(visit, header);
}
std::size_t get_length(const HeaderRefVariant& header)
{
HeaderVariantLengthVisitor visitor;
return boost::apply_visitor(visitor, header);
}
std::size_t get_length(const HeaderConstRefVariant& header)
{
HeaderVariantLengthVisitor visitor;
return boost::apply_visitor(visitor, header);
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,48 @@
#ifndef HEADER_VARIANT_HPP
#define HEADER_VARIANT_HPP
#include <vanetza/geonet/serialization.hpp>
#include <vanetza/geonet/beacon_header.hpp>
#include <vanetza/geonet/gbc_header.hpp>
#include <vanetza/geonet/shb_header.hpp>
#include <vanetza/geonet/tsb_header.hpp>
#include <boost/variant/variant.hpp>
namespace vanetza
{
namespace geonet
{
typedef boost::variant<BeaconHeader, GeoBroadcastHeader, ShbHeader, TsbHeader> HeaderVariant;
typedef boost::variant<BeaconHeader&, GeoBroadcastHeader&, ShbHeader&, TsbHeader&> HeaderRefVariant;
typedef boost::variant<const BeaconHeader&, const GeoBroadcastHeader&, const ShbHeader&, const TsbHeader&> HeaderConstRefVariant;
/** \brief get the length of the underlying header type
*
* \param header the header to get the length from
* \return std::size_t
*
*/
std::size_t get_length(const HeaderVariant& header);
std::size_t get_length(const HeaderRefVariant& header);
std::size_t get_length(const HeaderConstRefVariant& header);
} // namespace geonet
} // namespace vanetza
namespace boost
{
/** \brief serialize the underlying header type to an OutputArchive
*
* \param header the header to serialize
* \param ar the archive to write to
*
*/
void serialize(const vanetza::geonet::HeaderVariant& header, vanetza::OutputArchive& ar);
void serialize(const vanetza::geonet::HeaderRefVariant& header, vanetza::OutputArchive& ar);
void serialize(const vanetza::geonet::HeaderConstRefVariant& header, vanetza::OutputArchive& ar);
} // namespace boost
#endif // HEADER_VARIANT_HPP
@@ -0,0 +1,177 @@
#include <vanetza/geonet/indication_context.hpp>
#include <vanetza/geonet/pdu_conversion.hpp>
#include <vanetza/geonet/secured_pdu.hpp>
namespace vanetza
{
namespace geonet
{
IndicationContextDeserialize::IndicationContextDeserialize(UpPacketPtr packet, CohesivePacket& cohesive, const LinkLayer& ll) :
IndicationContextBasic(ll),
m_packet(std::move(packet)), m_cohesive_packet(cohesive),
m_parser(cohesive[OsiLayer::Network])
{
}
const BasicHeader* IndicationContextDeserialize::parse_basic()
{
auto bytes = m_parser.parse_basic(pdu().basic());
return bytes > 0 ? &pdu().basic() : nullptr;
}
const CommonHeader* IndicationContextDeserialize::parse_common()
{
auto bytes = m_parser.parse_common(pdu().common());
return bytes > 0 ? &pdu().common() : nullptr;
}
const IndicationContext::SecuredMessage* IndicationContextDeserialize::parse_secured()
{
IndicationContext::SecuredMessage tmp;
auto bytes = m_parser.parse_secured(tmp);
if (bytes > 0) {
pdu().secured(std::move(tmp));
return pdu().secured();
} else {
return nullptr;
}
}
boost::optional<HeaderConstRefVariant> IndicationContextDeserialize::parse_extended(HeaderType ht)
{
auto bytes = m_parser.parse_extended(pdu().extended_variant(), ht);
return boost::optional<HeaderConstRefVariant>(bytes > 0, pdu().extended_variant());
}
IndicationContext::UpPacketPtr IndicationContextDeserialize::finish()
{
m_cohesive_packet.set_boundary(OsiLayer::Network, m_parser.parsed_bytes());
m_cohesive_packet.trim(OsiLayer::Transport, pdu().common().payload);
return std::move(m_packet);
}
IndicationContextCast::IndicationContextCast(UpPacketPtr packet, ChunkPacket& chunk, const LinkLayer& ll) :
IndicationContextBasic(ll), m_packet(std::move(packet))
{
Pdu* casted_pdu = pdu_cast(chunk.layer(OsiLayer::Network));
if (casted_pdu) {
pdu() = *casted_pdu;
} else {
throw std::runtime_error("Casting to Pdu failed");
}
}
const BasicHeader* IndicationContextCast::parse_basic()
{
return &pdu().basic();
}
const CommonHeader* IndicationContextCast::parse_common()
{
return &pdu().common();
}
const IndicationContext::SecuredMessage* IndicationContextCast::parse_secured()
{
return pdu().secured();
}
boost::optional<HeaderConstRefVariant> IndicationContextCast::parse_extended(HeaderType)
{
return boost::optional<HeaderConstRefVariant> { pdu().extended_variant() };
}
IndicationContext::UpPacketPtr IndicationContextCast::finish()
{
// payload should be already in place (if any)
return std::move(m_packet);
}
IndicationContextSecuredDeserialize::IndicationContextSecuredDeserialize(IndicationContextBasic& parent, CohesivePacket& payload) :
IndicationContextSecured(parent),
m_packet(payload),
m_parser(payload[OsiLayer::Network])
{
}
const CommonHeader* IndicationContextSecuredDeserialize::parse_common()
{
auto bytes = m_parser.parse_common(pdu().common());
return bytes > 0 ? &pdu().common() : nullptr;
}
boost::optional<HeaderConstRefVariant> IndicationContextSecuredDeserialize::parse_extended(HeaderType ht)
{
auto bytes = m_parser.parse_extended(pdu().extended_variant(), ht);
return boost::optional<HeaderConstRefVariant>(bytes > 0, pdu().extended_variant());
}
IndicationContext::UpPacketPtr IndicationContextSecuredDeserialize::finish()
{
m_packet.set_boundary(OsiLayer::Network, m_parser.parsed_bytes());
auto packet = m_parent.finish();
(*packet) = m_packet;
return packet;
}
IndicationContextSecuredCast::IndicationContextSecuredCast(IndicationContextBasic& parent, ChunkPacket& packet) :
IndicationContextSecured(parent),
m_packet(parent.finish())
{
SecuredPdu* secured_pdu = secured_pdu_cast(packet.layer(OsiLayer::Network));
if (secured_pdu) {
pdu().common() = secured_pdu->common;
pdu().extended_variant() = secured_pdu->extended;
} else {
throw std::runtime_error("Casting to SecuredPdu failed");
}
struct parent_packet_visitor : public boost::static_visitor<>
{
parent_packet_visitor(ChunkPacket& _secured_payload) : secured_payload(_secured_payload) {}
void operator()(ChunkPacket& packet)
{
packet.merge(secured_payload, OsiLayer::Transport, max_osi_layer());
}
void operator()(CohesivePacket& packet)
{
// CohesivePacket and casting PDUs will probably never happen...
ByteBuffer buffer(secured_payload.size());
for (auto layer : osi_layer_range(OsiLayer::Transport, max_osi_layer())) {
ByteBuffer layer_buffer;
secured_payload.layer(layer).convert(layer_buffer);
buffer.insert(buffer.end(), layer_buffer.begin(), layer_buffer.end());
}
packet = CohesivePacket(std::move(buffer), OsiLayer::Transport);
}
ChunkPacket& secured_payload;
};
parent_packet_visitor visitor(packet);
boost::apply_visitor(visitor, *m_packet);
}
const CommonHeader* IndicationContextSecuredCast::parse_common()
{
return &pdu().common();
}
boost::optional<HeaderConstRefVariant> IndicationContextSecuredCast::parse_extended(HeaderType)
{
return boost::optional<HeaderConstRefVariant> { pdu().extended_variant() };
}
IndicationContext::UpPacketPtr IndicationContextSecuredCast::finish()
{
return std::move(m_packet);
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,149 @@
#ifndef INDICATION_CONTEXT_HPP_UWOD2BSQ
#define INDICATION_CONTEXT_HPP_UWOD2BSQ
#include <vanetza/geonet/data_indication.hpp>
#include <vanetza/geonet/link_layer.hpp>
#include <vanetza/geonet/packet.hpp>
#include <vanetza/geonet/parser.hpp>
#include <vanetza/geonet/pdu.hpp>
#include <vanetza/geonet/variant_pdu.hpp>
#include <vanetza/security/secured_message.hpp>
#include <boost/optional/optional.hpp>
namespace vanetza
{
namespace geonet
{
/**
* IndicationContext is used for parsing incoming packets.
* For each indication (packet reception) a new context should be created.
*/
class IndicationContext
{
public:
using UpPacketPtr = std::unique_ptr<UpPacket>;
using SecuredMessage = security::SecuredMessage;
using LinkLayer = geonet::LinkLayer;
// parser commands
virtual const BasicHeader* parse_basic() = 0;
virtual const CommonHeader* parse_common() = 0;
virtual const SecuredMessage* parse_secured() = 0;
virtual boost::optional<HeaderConstRefVariant> parse_extended(HeaderType) = 0;
// access to data structures related to indication
virtual const VariantPdu& pdu() const = 0;
virtual VariantPdu& pdu() = 0;
virtual const LinkLayer& link_layer() const = 0;
virtual DataIndication& service_primitive() = 0;
/**
* Finish usage of IndicationContext and release owned packet
* \return owned packet
*/
virtual UpPacketPtr finish() = 0;
virtual ~IndicationContext() = default;
};
/**
* IndicationContextBasic represents the first phase of packet reception,
* i.e. it is the context for unsecured headers
*/
class IndicationContextBasic : public IndicationContext
{
public:
IndicationContextBasic(const LinkLayer& ll) : m_link_layer(ll) {}
const LinkLayer& link_layer() const override { return m_link_layer; }
DataIndication& service_primitive() override { return m_service_primitive; }
VariantPdu& pdu() override { return m_pdu; }
const VariantPdu& pdu() const override { return m_pdu; }
protected:
LinkLayer m_link_layer;
DataIndication m_service_primitive;
VariantPdu m_pdu;
};
class IndicationContextDeserialize : public IndicationContextBasic
{
public:
IndicationContextDeserialize(UpPacketPtr, CohesivePacket&, const LinkLayer&);
const BasicHeader* parse_basic() override;
const CommonHeader* parse_common() override;
const SecuredMessage* parse_secured() override;
boost::optional<HeaderConstRefVariant> parse_extended(HeaderType) override;
UpPacketPtr finish() override;
private:
UpPacketPtr m_packet;
CohesivePacket& m_cohesive_packet;
Parser m_parser;
};
class IndicationContextCast : public IndicationContextBasic
{
public:
IndicationContextCast(UpPacketPtr, ChunkPacket&, const LinkLayer&);
const BasicHeader* parse_basic() override;
const CommonHeader* parse_common() override;
const SecuredMessage* parse_secured() override;
boost::optional<HeaderConstRefVariant> parse_extended(HeaderType) override;
UpPacketPtr finish() override;
private:
UpPacketPtr m_packet;
};
/**
* IndicationContextSecured is used for the (optional) second phase of packet reception,
* i.e. handling the payload contained in a secured message's payload
*/
class IndicationContextSecured : public IndicationContext
{
public:
IndicationContextSecured(IndicationContextBasic& parent) : m_parent(parent) {}
const LinkLayer& link_layer() const override { return m_parent.link_layer(); }
DataIndication& service_primitive() override { return m_parent.service_primitive(); }
VariantPdu& pdu() override { return m_parent.pdu(); }
const VariantPdu& pdu() const override { return m_parent.pdu(); }
const BasicHeader* parse_basic() override { return nullptr; }
const SecuredMessage* parse_secured() override { return nullptr; }
protected:
IndicationContextBasic& m_parent;
};
class IndicationContextSecuredDeserialize : public IndicationContextSecured
{
public:
IndicationContextSecuredDeserialize(IndicationContextBasic&, CohesivePacket&);
const CommonHeader* parse_common() override;
boost::optional<HeaderConstRefVariant> parse_extended(HeaderType) override;
UpPacketPtr finish() override;
private:
CohesivePacket& m_packet;
Parser m_parser;
};
class IndicationContextSecuredCast : public IndicationContextSecured
{
public:
IndicationContextSecuredCast(IndicationContextBasic&, ChunkPacket&);
const CommonHeader* parse_common() override;
boost::optional<HeaderConstRefVariant> parse_extended(HeaderType) override;
UpPacketPtr finish() override;
private:
UpPacketPtr m_packet;
};
} // namespace geonet
} // namespace vanetza
#endif /* INDICATION_CONTEXT_HPP_UWOD2BSQ */
@@ -0,0 +1,17 @@
#ifndef INTERFACE_HPP_KN7EDWOX
#define INTERFACE_HPP_KN7EDWOX
#include <vanetza/geonet/mib.hpp>
namespace vanetza
{
namespace geonet
{
enum class UpperProtocol { Unknown, BTP_A, BTP_B, IPv6 };
enum class TransportType { GUC, GAC, GBC, TSB, SHB };
typedef InterfaceType CommunicationProfile;
} // namespace geonet
} // namespace vanetza
#endif /* INTERFACE_HPP_KN7EDWOX */
@@ -0,0 +1,102 @@
#include "lifetime.hpp"
#include "serialization.hpp"
#include <vanetza/common/byte_order.hpp>
#include <boost/units/cmath.hpp>
#include <stdexcept>
namespace vanetza
{
namespace geonet
{
const Lifetime Lifetime::zero()
{
return Lifetime();
}
Lifetime::Lifetime()
{
set(Base::Fifty_Milliseconds, 0);
}
Lifetime::Lifetime(Base base, BitNumber<uint8_t, 6> multiplier)
{
set(base, multiplier);
}
void Lifetime::set(Base base, BitNumber<uint8_t, 6> multiplier)
{
m_lifetime = multiplier.raw() << 2 | (static_cast<uint8_t>(base) & base_mask);
}
bool Lifetime::operator<(const Lifetime& other) const
{
return this->decode() < other.decode();
}
bool Lifetime::operator==(const Lifetime& other) const
{
const units::Duration diff = this->decode() - other.decode();
// 50 ms is the smallest non-zero value Lifetime can represent
const auto min_value = 0.050 * units::si::seconds;
return abs(diff) < min_value;
}
void Lifetime::encode(units::Duration duration)
{
double seconds = duration / boost::units::si::seconds;
if (seconds >= 630.0) {
set(Base::Hundred_Seconds, std::lround(seconds / 100.0));
} else if (seconds >= 63.0) {
set(Base::Ten_Seconds, std::lround(seconds / 10.0));
} else if (seconds >= 3.15) {
set(Base::One_Second, std::lround(seconds));
} else {
set(Base::Fifty_Milliseconds, std::lround(seconds / 0.050));
}
}
units::Duration Lifetime::decode() const
{
using vanetza::units::si::seconds;
Base base = static_cast<Base>(m_lifetime & base_mask);
const double multiplier = (m_lifetime & multiplier_mask) >> 2;
units::Duration unit;
switch (base) {
case Base::Fifty_Milliseconds:
unit = 0.050 * seconds;
break;
case Base::One_Second:
unit = 1.0 * seconds;
// already done
break;
case Base::Ten_Seconds:
unit = 10.0 * seconds;
break;
case Base::Hundred_Seconds:
unit = 100.0 * seconds;
break;
default:
throw std::runtime_error("Decoding of Lifetime::Base failed");
break;
};
return multiplier * unit;
}
void serialize(const Lifetime& lifetime, OutputArchive& ar)
{
serialize(host_cast(lifetime.raw()), ar);
}
void deserialize(Lifetime& lifetime, InputArchive& ar)
{
uint8_t raw;
deserialize(raw, ar);
lifetime.raw(raw);
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,63 @@
#ifndef LIFETIME_HPP_XYTSNW3J
#define LIFETIME_HPP_XYTSNW3J
#include <vanetza/common/bit_number.hpp>
#include <vanetza/geonet/serialization.hpp>
#include <vanetza/units/time.hpp>
#include <boost/operators.hpp>
#include <cstdint>
namespace vanetza
{
namespace geonet
{
class Lifetime : public boost::totally_ordered<Lifetime>
{
public:
static constexpr uint8_t multiplier_mask = 0xFC;
static constexpr uint8_t base_mask = 0x03;
enum class Base {
Fifty_Milliseconds = 0,
One_Second = 1,
Ten_Seconds = 2,
Hundred_Seconds = 3
};
static const Lifetime zero();
Lifetime();
Lifetime(Base base, BitNumber<uint8_t, 6> multiplier);
void set(Base base, BitNumber<uint8_t, 6> multiplier);
uint8_t raw() const { return m_lifetime; }
void raw(uint8_t raw) { m_lifetime = raw; }
bool operator<(const Lifetime&) const;
bool operator==(const Lifetime&) const;
/**
* Decodes stored lifetime
* \return lifetime as duration quantity
*/
units::Duration decode() const;
/**
* Encode duration in lifetime object
* \note Precision loss might occur
* \param duration Lifetime duration
*/
void encode(units::Duration);
private:
uint8_t m_lifetime;
};
void serialize(const Lifetime&, OutputArchive&);
void deserialize(Lifetime&, InputArchive&);
} // namespace geonet
} // namespace vanetza
#endif /* LIFETIME_HPP_XYTSNW3J */
@@ -0,0 +1,21 @@
#ifndef LINK_LAYER_HPP_F2JBRUTL
#define LINK_LAYER_HPP_F2JBRUTL
#include <vanetza/net/mac_address.hpp>
namespace vanetza
{
namespace geonet
{
struct LinkLayer
{
MacAddress sender;
MacAddress destination;
};
} // namespace geonet
} // namespace vanetza
#endif /* LINK_LAYER_HPP_F2JBRUTL */
@@ -0,0 +1,42 @@
#ifndef LOCATION_SERVICE_HEADER_HPP_0BWHQP7J
#define LOCATION_SERVICE_HEADER_HPP_0BWHQP7J
#include <vanetza/geonet/address.hpp>
#include <vanetza/geonet/position_vector.hpp>
#include <vanetza/geonet/sequence_number.hpp>
namespace vanetza
{
namespace geonet
{
struct LsRequestHeader
{
static constexpr std::size_t length_bytes = 4 +
LongPositionVector::length_bytes +
Address::length_bytes;
SequenceNumber sequence_number;
uint16_t reserved;
LongPositionVector source_position;
Address request_addr;
};
struct LsReplyHeader
{
static constexpr std::size_t length_bytes = 4 +
LongPositionVector::length_bytes +
ShortPositionVector::length_bytes;
SequenceNumber sequence_number;
uint16_t reserved;
LongPositionVector source_position;
ShortPositionVector destination_position;
};
} // namespace geonet
} // namespace vanetza
#endif /* LOCATION_SERVICE_HEADER_HPP_0BWHQP7J */
@@ -0,0 +1,195 @@
#include "location_table.hpp"
#include <chrono>
#include <limits>
namespace vanetza
{
namespace geonet
{
static_assert(std::numeric_limits<double>::has_quiet_NaN, "quiet NaN value unavailable");
LocationTableEntry::LocationTableEntry(const Runtime& rt) :
m_runtime(rt), m_is_neighbour(Clock::time_point::min()), m_has_position_vector(false),
m_pdr(std::numeric_limits<double>::quiet_NaN()), m_pdr_update(rt.now())
{
}
StationType LocationTableEntry::station_type() const
{
return geonet_address().station_type();
}
const Address& LocationTableEntry::geonet_address() const
{
return m_position_vector.gn_addr;
}
const MacAddress& LocationTableEntry::link_layer_address() const
{
return geonet_address().mid();
}
bool LocationTableEntry::is_neighbour() const
{
return m_is_neighbour > m_runtime.now();
}
void LocationTableEntry::update_pdr(std::size_t packet_size, double beta)
{
using namespace vanetza::units;
if (std::isnan(m_pdr)) {
m_pdr = 0.0;
m_pdr_update = m_runtime.now();
} else if (beta > 0.0 && beta < 1.0) {
const std::chrono::duration<double> time_period = m_runtime.now() - m_pdr_update;
if (time_period.count() > 0.0) {
double instant_pdr = packet_size / time_period.count();
m_pdr *= beta;
m_pdr += (1.0 - beta) * instant_pdr;
m_pdr_update = m_runtime.now();
}
}
}
bool LocationTableEntry::set_position_vector(const LongPositionVector& pv)
{
if (is_valid(pv)) {
m_has_position_vector = true;
m_position_vector = pv;
return true;
} else {
return false;
}
}
bool LocationTableEntry::update_position_vector(const LongPositionVector& lpv)
{
if (has_position_vector()) {
if (get_position_vector().timestamp < lpv.timestamp) {
return set_position_vector(lpv);
}
} else {
return set_position_vector(lpv);
}
return false;
}
void LocationTableEntry::set_neighbour(bool flag)
{
m_is_neighbour = flag ? Clock::time_point::max() : Clock::time_point::min();
}
void LocationTableEntry::set_neighbour(bool flag, Clock::duration expiry)
{
if (flag && expiry > Clock::duration::zero()) {
m_is_neighbour = m_runtime.now() + expiry;
} else {
set_neighbour(flag);
}
}
LocationTable::LocationTable(const MIB& mib, Runtime& rt) :
m_table(rt, LocationTableEntryCreator(rt))
{
m_table.set_lifetime(std::chrono::seconds(mib.itsGnLifetimeLocTE / units::si::seconds));
}
bool LocationTable::has_entry(const Address& addr) const
{
return m_table.has_value(addr.mid());
}
bool LocationTable::has_neighbours() const
{
bool found_neighbour = false;
for (const auto& entry : m_table.map()) {
if (entry.second.is_neighbour()) {
found_neighbour = true;
break;
}
}
return found_neighbour;
}
auto LocationTable::neighbours() const -> neighbour_range
{
const entry_predicate neighbour_predicate =
[](const MacAddress&, const LocationTableEntry& entry) {
return entry.is_neighbour();
};
return filter(neighbour_predicate);
}
auto LocationTable::filter(const entry_predicate& predicate) const -> entry_range
{
using namespace boost::adaptors;
std::function<bool(const typename table_type::value_type&)> filter_fn =
[predicate](const typename table_type::value_type& v) {
return predicate(v.first, v.second);
};
return m_table.map() | filtered(filter_fn) | map_values;
}
void LocationTable::visit(const entry_visitor& visitor) const
{
for (const auto& entry : m_table.map()) {
visitor(entry.first, entry.second);
}
}
LocationTableEntry& LocationTable::update(const LongPositionVector& lpv)
{
LocationTableEntry* entry = m_table.get_value_ptr(lpv.gn_addr.mid());
if (entry && entry->has_position_vector()) {
if (entry->update_position_vector(lpv)) {
m_table.refresh(lpv.gn_addr.mid());
}
} else {
entry = &m_table.refresh(lpv.gn_addr.mid());
entry->update_position_vector(lpv);
}
return *entry;
}
LocationTableEntry& LocationTable::get_or_create_entry(const Address& addr)
{
return m_table.get_value(addr.mid());
}
LocationTableEntry& LocationTable::get_or_create_entry(const MacAddress& mac)
{
return m_table.get_value(mac);
}
const LocationTableEntry* LocationTable::get_entry(const Address& addr) const
{
return m_table.get_value_ptr(addr.mid());
}
const LocationTableEntry* LocationTable::get_entry(const MacAddress& mac) const
{
return m_table.get_value_ptr(mac);
}
const LongPositionVector* LocationTable::get_position(const Address& addr) const
{
return get_position(addr.mid());
}
const LongPositionVector* LocationTable::get_position(const MacAddress& mac) const
{
const LongPositionVector* position = nullptr;
auto* entry = m_table.get_value_ptr(mac);
if (entry && entry->has_position_vector()) {
position = &entry->get_position_vector();
}
return position;
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,147 @@
#ifndef LOCATION_TABLE_HPP_EMPVZSHQ
#define LOCATION_TABLE_HPP_EMPVZSHQ
#include <vanetza/common/object_container.hpp>
#include <vanetza/geonet/address.hpp>
#include <vanetza/geonet/mib.hpp>
#include <vanetza/geonet/position_vector.hpp>
#include <vanetza/geonet/soft_state_map.hpp>
#include <vanetza/geonet/station_type.hpp>
#include <boost/range/adaptor/filtered.hpp>
#include <boost/range/adaptor/map.hpp>
namespace vanetza
{
namespace geonet
{
class LocationTableEntry
{
public:
LocationTableEntry(const Runtime& rt);
const Address& geonet_address() const;
const MacAddress& link_layer_address() const;
StationType station_type() const;
/**
* Get packed data rate (PDR) of corresponding source.
* \return PDR in bytes per second, might be not-a-number
*/
double get_pdr() const { return m_pdr; }
/**
* Update packet data rate.
* See Annex B of EN 302 636-4-1 for details.
* \param packet_size received number of bytes
* \param beta weight factor for exponential moving average ]0; 1[
*/
void update_pdr(std::size_t packet_size, double beta = 0.5);
/**
* Check if position vector has been set before
* \return false after entry initialization
* true after set_position_vector invocations
*/
bool has_position_vector() const { return m_has_position_vector; }
/**
* Get stored position vector
* \return position vector (empty until set_position_vector invocation)
*/
const LongPositionVector& get_position_vector() const { return m_position_vector; }
/**
* Update stored position vector (only after time stamp check)
* \param pv source position vector
* \return true if position vector passed time stamp check
*/
bool update_position_vector(const LongPositionVector& pv);
/**
* Check if this entry belongs to a direct neighbour
* \return true if direct neighbour
*/
bool is_neighbour() const;
/**
* Set neighbour relation
* \param flag true if entry represents a direct neighbour
*/
void set_neighbour(bool flag);
/**
* Set neighbour relation with expiry
* \param flag true if entry represents a direct neighbour
* \param expiry reset neighbour relation to false after expiry
*/
void set_neighbour(bool flag, Clock::duration expiry);
ObjectContainer extensions;
private:
/**
* Set stored position vector (without timestamp check)
* \param pv source position vector
*/
bool set_position_vector(const LongPositionVector& pv);
const Runtime& m_runtime;
Clock::time_point m_is_neighbour;
bool m_has_position_vector;
LongPositionVector m_position_vector;
double m_pdr; /*< packet data rate in bytes per second */
Clock::time_point m_pdr_update;
};
class LocationTableEntryCreator
{
public:
LocationTableEntryCreator(const Runtime& rt) : m_runtime(rt) {}
LocationTableEntry operator()() { return LocationTableEntry(m_runtime); }
private:
const Runtime& m_runtime;
};
/**
* GeoNetworking LocationTable
* See section 7.1 of EN 302 636-4-1 for details.
*/
class LocationTable
{
public:
using table_type = SoftStateMap<MacAddress, LocationTableEntry, LocationTableEntryCreator>;
using entry_visitor = std::function<void(const MacAddress&, const LocationTableEntry&)>;
using entry_predicate = std::function<bool(const MacAddress&, const LocationTableEntry&)>;
using entry_range =
boost::select_second_const_range<
boost::filtered_range<
std::function<bool(const typename table_type::value_type&)>,
const typename table_type::map_range>>;
using neighbour_range = entry_range;
LocationTable(const MIB&, Runtime&);
bool has_entry(const Address&) const;
LocationTableEntry& update(const LongPositionVector&);
LocationTableEntry& get_or_create_entry(const Address&);
LocationTableEntry& get_or_create_entry(const MacAddress&);
const LocationTableEntry* get_entry(const Address&) const;
const LocationTableEntry* get_entry(const MacAddress&) const;
const LongPositionVector* get_position(const Address&) const;
const LongPositionVector* get_position(const MacAddress&) const;
bool has_neighbours() const;
neighbour_range neighbours() const;
entry_range filter(const entry_predicate&) const;
void visit(const entry_visitor&) const;
void drop_expired() { m_table.drop_expired(); }
private:
table_type m_table;
};
} // namespace geonet
} // namespace vanetza
#endif /* LOCATION_TABLE_HPP_EMPVZSHQ */
@@ -0,0 +1,26 @@
#ifndef LOCTEX_G5_HPP_BVRKEPHW
#define LOCTEX_G5_HPP_BVRKEPHW
#include <vanetza/geonet/dcc_mco_field.hpp>
#include <vanetza/geonet/timestamp.hpp>
namespace vanetza
{
namespace geonet
{
/**
* Media-dependent extension to the Location Table Entry (LocTE) for ITS-G5
*/
struct LocTEX_G5
{
Timestamp local_update; /*< TST_G5: last update time (local time stamp) */
Timestamp source_update; /*< TST_SO_PV_G5: SO PV timestamp from SHB header */
DccMcoField dcc_mco;
};
} // namespace geonet
} // namespace vanetza
#endif /* LOCTEX_G5_HPP_BVRKEPHW */
@@ -0,0 +1,66 @@
#include "mib.hpp"
#include <boost/units/systems/si/prefixes.hpp>
namespace vanetza
{
namespace geonet
{
using namespace vanetza::units::si;
using vanetza::units::degrees;
using boost::units::si::kilo;
using boost::units::si::milli;
const auto milliseconds = milli * seconds;
ManagementInformationBase::ManagementInformationBase() :
itsGnLocalAddrConfMethod(AddrConfMethod::Managed),
itsGnProtocolVersion(1),
itsGnIsMobile(true),
itsGnIfType(InterfaceType::ITS_G5),
itsGnMinimumUpdateFrequencyEPV(1.0 / (1000.0 * milliseconds)),
itsGnPaiInterval(80 * meters),
itsGnMaxSduSize(1398),
itsGnMaxGeoNetworkingHeaderSize(88),
itsGnLifetimeLocTE(20 * seconds),
itsGnSecurity(false),
itsGnSnDecapResultHandling(SecurityDecapHandling::Strict),
itsGnLocationServiceMaxRetrans(10),
itsGnLocationServiceRetransmitTimer(1 * seconds),
itsGnLocationServicePacketBufferSize(1024),
itsGnBeaconServiceRetransmitTimer(3 * seconds),
itsGnBeaconServiceMaxJitter(itsGnBeaconServiceRetransmitTimer / 4.0),
itsGnDefaultHopLimit(10),
itsGnDPLLength(8),
itsGnMaxPacketLifetime(Lifetime::Base::Hundred_Seconds, 6),
itsGnDefaultPacketLifetime(Lifetime::Base::Ten_Seconds, 6),
itsGnMaxPacketDataRate(100),
itsGnMaxPacketDataRateEmaBeta(0.9),
itsGnMaxGeoAreaSize(10 * kilo * kilo * square_meters),
itsGnMinPacketRepetitionInterval(100 * milliseconds),
itsGnNonAreaForwardingAlgorithm(UnicastForwarding::Greedy),
itsGnAreaForwardingAlgorithm(BroadcastForwarding::CBF),
itsGnCbfMinTime(1 * milliseconds),
itsGnCbfMaxTime(100 * milliseconds),
itsGnDefaultMaxCommunicationRange(1000 * meters),
itsGnBroadcastCBFDefSectorAngle(30 * degrees),
itsGnUcForwardingPacketBufferSize(256),
itsGnBcForwardingPacketBufferSize(1024),
itsGnCbfPacketBufferSize(256),
itsGnDefaultTrafficClass(false, false, 0),
vanetzaDefaultSeed(0xc0114c2c),
vanetzaCbfMaxCounter(1),
vanetzaDeferInitialBeacon(Clock::duration::zero()),
vanetzaDisableBeaconing(false),
vanetzaMultiHopDuplicateAddressDetection(false),
vanetzaFadingCbfCounter(false),
vanetzaFadingCbfCounterLifetime(4.0 * itsGnCbfMaxTime),
vanetzaNeighbourFlagExpiry(Clock::duration::zero()),
vanetzaGbcMemoryCapacity(0),
vanetzaGbcPassUpOutsideDestination(false)
{
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,115 @@
#ifndef MIB_HPP_U3WJ4WES
#define MIB_HPP_U3WJ4WES
#include <vanetza/common/clock.hpp>
#include <vanetza/geonet/address.hpp>
#include <vanetza/geonet/lifetime.hpp>
#include <vanetza/geonet/station_type.hpp>
#include <vanetza/geonet/traffic_class.hpp>
#include <vanetza/units/angle.hpp>
#include <vanetza/units/area.hpp>
#include <vanetza/units/frequency.hpp>
#include <vanetza/units/length.hpp>
#include <vanetza/units/time.hpp>
#include <cstdint>
#include <string>
namespace vanetza
{
namespace geonet
{
enum class UnicastForwarding {
Unspecified = 0,
Greedy = 1,
CBF = 2
};
enum class BroadcastForwarding {
Unspecified = 0,
SIMPLE = 1,
CBF = 2,
Advanced = 3
};
enum class AddrConfMethod {
Auto = 0,
Managed = 1,
Anonymous = 2
};
enum class InterfaceType {
Unspecified = 0,
ITS_G5 = 1,
LTE_V2X = 2
};
enum class SecurityDecapHandling {
Strict = 0,
Non_Strict = 1
};
/**
* The Management Information Base (MIB) defines the GeoNetworking protocol constants.
*
* \see EN 302 636-4-1 v1.3.1 Annex H
*/
struct ManagementInformationBase
{
ManagementInformationBase();
Address itsGnLocalGnAddr;
AddrConfMethod itsGnLocalAddrConfMethod;
unsigned itsGnProtocolVersion;
bool itsGnIsMobile;
InterfaceType itsGnIfType;
units::Frequency itsGnMinimumUpdateFrequencyEPV;
units::Length itsGnPaiInterval;
unsigned itsGnMaxSduSize;
unsigned itsGnMaxGeoNetworkingHeaderSize;
units::Duration itsGnLifetimeLocTE;
bool itsGnSecurity;
SecurityDecapHandling itsGnSnDecapResultHandling;
unsigned itsGnLocationServiceMaxRetrans;
units::Duration itsGnLocationServiceRetransmitTimer;
unsigned itsGnLocationServicePacketBufferSize; // byte
units::Duration itsGnBeaconServiceRetransmitTimer;
units::Duration itsGnBeaconServiceMaxJitter;
unsigned itsGnDefaultHopLimit;
unsigned itsGnDPLLength;
Lifetime itsGnMaxPacketLifetime;
Lifetime itsGnDefaultPacketLifetime;
unsigned itsGnMaxPacketDataRate; // kbyte/s
double itsGnMaxPacketDataRateEmaBeta; // percentage ]0; 1[
units::Area itsGnMaxGeoAreaSize;
units::Duration itsGnMinPacketRepetitionInterval;
UnicastForwarding itsGnNonAreaForwardingAlgorithm;
BroadcastForwarding itsGnAreaForwardingAlgorithm;
units::Duration itsGnCbfMinTime;
units::Duration itsGnCbfMaxTime;
units::Length itsGnDefaultMaxCommunicationRange;
units::Angle itsGnBroadcastCBFDefSectorAngle;
unsigned itsGnUcForwardingPacketBufferSize; // kbyte
unsigned itsGnBcForwardingPacketBufferSize; // kbyte
unsigned itsGnCbfPacketBufferSize; // kbyte
TrafficClass itsGnDefaultTrafficClass;
std::uint32_t vanetzaDefaultSeed; /*< default seed for internal random number generator */
std::size_t vanetzaCbfMaxCounter; /*< maximum counter value used for Advanced routing */
Clock::duration vanetzaDeferInitialBeacon; /*< defer first beacon by given duration */
bool vanetzaDisableBeaconing; /*< disable transmission of beacons entirely */
bool vanetzaMultiHopDuplicateAddressDetection; /*< execute DAD for multi-hop packets */
bool vanetzaFadingCbfCounter; /*< use fading counters for CBF packet buffer */
units::Duration vanetzaFadingCbfCounterLifetime; /*< lifetime until counter vanishes */
Clock::duration vanetzaNeighbourFlagExpiry; /*< reset LocTE neighbour state without explicit updates */
std::size_t vanetzaGbcMemoryCapacity; /*< do not pass up duplicate GBC packets (0 to disable this filter) */
bool vanetzaGbcPassUpOutsideDestination; /*< pass up received GBC packets even when outside of destination area */
};
// This name is too clumsy to write it out every time
typedef ManagementInformationBase MIB;
} // namespace geonet
} // namespace vanetza
#endif /* MIB_HPP_U3WJ4WES */
@@ -0,0 +1,62 @@
#include "next_hop.hpp"
namespace vanetza
{
namespace geonet
{
NextHop::NextHop() : m_state(State::Discarded)
{
}
bool NextHop::discarded() const
{
return m_state == State::Discarded;
}
bool NextHop::buffered() const
{
return m_state == State::Buffered;
}
bool NextHop::valid() const
{
return m_state == State::Valid;
}
const MacAddress& NextHop::mac() const
{
return m_destination;
}
bool NextHop::process() &&
{
if (valid()) {
PendingPacket<GbcPdu>(std::move(m_packet), m_destination).process();
m_state = State::Discarded;
return true;
} else {
return false;
}
}
void NextHop::transmit(Packet&& packet, const MacAddress& destination)
{
m_state = NextHop::State::Valid;
m_packet = std::move(packet);
m_destination = destination;
}
void NextHop::discard()
{
m_state = NextHop::State::Discarded;
}
void NextHop::buffer()
{
m_state = NextHop::State::Buffered;
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,92 @@
#ifndef NEXT_HOP_HPP_ON0AKMBY
#define NEXT_HOP_HPP_ON0AKMBY
#include <vanetza/geonet/packet.hpp>
#include <vanetza/geonet/pending_packet.hpp>
#include <vanetza/geonet/pdu_variant.hpp>
#include <vanetza/net/mac_address.hpp>
namespace vanetza
{
namespace geonet
{
/**
* NextHop is the result of GeoNet forwarding algorithms.
* It may convey a destination link-layer address or one of the states "discarded" and "buffered".
*/
class NextHop
{
public:
using Packet = PendingPacket<GbcPdu, const MacAddress&>;
NextHop();
NextHop(NextHop&&) = default;
NextHop& operator=(NextHop&&) = default;
/**
* Test if forwarding decision is to discard the packet.
* \return true if discarded
*/
bool discarded() const;
/**
* Test if forwarding decided to buffer the packet.
* \return true if packet got buffered
*/
bool buffered() const;
/**
* Test if stored packet and link-layer address are valid.
* \return if valid
*/
bool valid() const;
/**
* Access stored link-layer address.
*
* The returned address is only meaningful if NextHop is valid.
* \return link-layer address
*/
const MacAddress& mac() const;
/**
* Prepare for immediate packet transmission (not discarded, not buffered).
*
* valid() will return true after invocation of this method.
* \param packet the packet to be transmitted
* \param destination link-layer destination address
*/
void transmit(Packet&& packet, const MacAddress& destination);
/**
* Set the NextHop state to discarded.
*/
void discard();
/**
* Set the NextHop state to buffered.
*/
void buffer();
/**
* Invoke further packet processing.
*
* It is safe to call this method though packet has been discarded or buffered.
* \return true if a valid packet (after previous transmit()) has been processed
*/
bool process() &&;
private:
enum class State { Valid, Discarded, Buffered };
State m_state;
MacAddress m_destination;
Packet m_packet;
};
} // namespace geonet
} // namespace vanetza
#endif /* NEXT_HOP_HPP_ON0AKMBY */
@@ -0,0 +1,21 @@
#ifndef PACKET_HPP_LFURGMBS
#define PACKET_HPP_LFURGMBS
#include <vanetza/net/packet.hpp>
namespace vanetza
{
namespace geonet
{
// This header is only provided for compatibility reasons
// New code should rely on net/packet.hpp only
using vanetza::DownPacket;
using vanetza::UpPacket;
} // namespace geonet
} // namespace vanetza
#endif /* PACKET_HPP_LFURGMBS */
@@ -0,0 +1,94 @@
#include "packet_buffer.hpp"
#include "basic_header.hpp"
namespace vanetza
{
namespace geonet
{
namespace packet_buffer
{
Expiry::Expiry(Clock::time_point now, Clock::duration lifetime) :
m_buffered_since(now), m_expires_at(now)
{
m_expires_at += lifetime;
}
bool Expiry::is_expired(Clock::time_point now) const
{
return (m_expires_at < now);
}
} // namespace packet_buffer
PacketBuffer::PacketBuffer(std::size_t capacity) :
m_capacity(capacity), m_stored(0)
{
}
bool PacketBuffer::push(data_ptr data, Clock::time_point now)
{
assert(data);
bool pushed = false;
drop_expired(now);
if (drop(data->length())) {
m_stored += data->length();
m_nodes.push_back(std::make_tuple(
expiry_type(now, data->reduce_lifetime(Clock::duration::zero())),
std::move(data)
));
pushed = true;
}
return pushed;
}
void PacketBuffer::flush(Clock::time_point now)
{
decltype(m_nodes) nodes;
std::swap(m_nodes, nodes);
m_stored = 0;
for (auto& node : nodes) {
const auto& expiry = std::get<0>(node);
if (!expiry.is_expired(now)) {
auto& data = std::get<1>(node);
const auto queuing_time = now - expiry.buffered_since();
data->reduce_lifetime(queuing_time);
data->flush();
}
}
}
bool PacketBuffer::drop_head()
{
bool dropped = !m_nodes.empty();
if (dropped) {
m_stored -= std::get<1>(m_nodes.front())->length();
m_nodes.pop_front();
}
return dropped;
}
void PacketBuffer::drop_expired(Clock::time_point now)
{
std::list<decltype(m_nodes)::iterator> expired;
for (auto it = m_nodes.begin(), end = m_nodes.end(); it != end;) {
if (std::get<0>(*it).is_expired(now)) {
m_stored -= std::get<1>(*it)->length();
it = m_nodes.erase(it);
} else {
++it;
}
}
}
bool PacketBuffer::drop(std::size_t bytes)
{
while (free() < bytes && drop_head()) {}
return free() >= bytes;
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,134 @@
#ifndef PACKET_BUFFER_HPP_U97KIBQC
#define PACKET_BUFFER_HPP_U97KIBQC
#include <vanetza/common/clock.hpp>
#include <vanetza/geonet/packet.hpp>
#include <vanetza/geonet/pdu.hpp>
#include <vanetza/geonet/timestamp.hpp>
#include <vanetza/net/mac_address.hpp>
#include <functional>
#include <list>
#include <memory>
namespace vanetza
{
namespace geonet
{
namespace packet_buffer
{
class Expiry
{
public:
Expiry(Clock::time_point now, Clock::duration lifetime);
bool is_expired(Clock::time_point now) const;
Clock::time_point buffered_since() const { return m_buffered_since; }
Clock::time_point expires_at() const { return m_expires_at; }
private:
Clock::time_point m_buffered_since;
Clock::time_point m_expires_at;
};
class Data
{
public:
/**
* Length of packet data
* \return length in bytes
*/
virtual std::size_t length() const = 0;
/**
* Reduce lifetime associated with data
* \param d reduce lifetime by given duration
* \return remaining lifetime (never negative)
*/
virtual Clock::duration reduce_lifetime(Clock::duration d) = 0;
/**
* Flush data
* \note length() and lifetime() should not be called afterwards!
*/
virtual void flush() = 0;
virtual ~Data() {}
};
} // namespace packet_buffer
/**
* PacketBuffer with bounded capacity, packet expiry and head-drop
*/
class PacketBuffer
{
public:
typedef std::unique_ptr<packet_buffer::Data> data_ptr;
/**
* Create PacketBuffer with given capacity
* \param capacity Buffer can store this many bytes
*/
PacketBuffer(std::size_t capacity);
/**
* Push one packet into buffer
* \param packet Packet data
* \param t Current time
* \return true if packet has been pushed successfully
*/
bool push(data_ptr packet, Clock::time_point t);
/**
* Flush packets from buffer. Expired packets are dropped.
* \note Some packets might remain in buffer (re-added during flushing)
* \param t Current time
*/
void flush(Clock::time_point t);
private:
typedef packet_buffer::Expiry expiry_type;
typedef std::tuple<expiry_type, data_ptr> node_type;
std::size_t free() const { return m_capacity - m_stored; }
std::size_t capacity() const { return m_capacity; }
/**
* Push one new element into buffer list
* \note capacity is not checked by this method, has to be done manually
* \param expiry Expiry data
* \param packet Packet data
*/
void push(expiry_type&& expiry, data_ptr packet);
/**
* Drop current head element
* \return true if head element was dropped
*/
bool drop_head();
/**
* Drop all packets with expired timestamp
* \param t current time
*/
void drop_expired(Clock::time_point t);
/**
* Drop as many packets as required to store given number of bytes.
* Packets at the head of the list are dropped first.
* \param bytes require #bytes free capacity
* \return true if there is enough capacity left for #bytes
*/
bool drop(std::size_t bytes);
std::list<node_type> m_nodes;
std::size_t m_capacity;
std::size_t m_stored;
};
} // namespace geonet
} // namespace vanetza
#endif /* PACKET_BUFFER_HPP_U97KIBQC */
@@ -0,0 +1,129 @@
#include "parser.hpp"
#include "basic_header.hpp"
#include "common_header.hpp"
#include <vanetza/security/exception.hpp>
#include <vanetza/security/secured_message.hpp>
namespace vanetza
{
namespace geonet
{
Parser::Parser(ByteBuffer::const_iterator begin, ByteBuffer::const_iterator end) :
m_byte_buffer_source(begin, end),
m_stream(m_byte_buffer_source),
m_archive(m_stream),
m_read_bytes(0)
{
}
Parser::Parser(boost::iterator_range<ByteBuffer::const_iterator> range) :
Parser(range.begin(), range.end())
{
}
std::size_t Parser::parse_basic(BasicHeader& basic)
{
std::size_t bytes = 0;
try {
deserialize(basic, m_archive);
bytes = BasicHeader::length_bytes;
} catch (InputArchive::Exception&) {
}
m_read_bytes += bytes;
return bytes;
}
std::size_t Parser::parse_common(CommonHeader& common)
{
std::size_t bytes = 0;
try {
deserialize(common, m_archive);
bytes = CommonHeader::length_bytes;
} catch (InputArchive::Exception&) {
}
m_read_bytes += bytes;
return bytes;
}
std::size_t Parser::parse_secured(security::SecuredMessage& secured)
{
std::size_t bytes = 0;
if (m_archive.is_good()) {
try {
std::uint8_t sec_first_byte = m_archive.peek_byte();
if (sec_first_byte < 3) {
security::v2::SecuredMessage msg;
bytes = security::v2::deserialize(m_archive, msg);
secured = std::move(msg);
} else if (sec_first_byte == 3) {
security::v3::SecuredMessage msg;
bytes = security::v3::deserialize(m_archive, msg);
secured = std::move(msg);
}
} catch (...) {
}
m_read_bytes += bytes;
}
return bytes;
}
template<typename EXTENDED>
std::size_t deserialize_extended(InputArchive& archive, HeaderVariant& extended)
{
EXTENDED header;
deserialize(header, archive);
extended = std::move(header);
return EXTENDED::length_bytes;
}
std::size_t Parser::parse_extended(HeaderVariant& extended, HeaderType ht)
{
std::size_t bytes = 0;
try {
switch (ht) {
case HeaderType::TSB_Single_Hop:
bytes = deserialize_extended<ShbHeader>(m_archive, extended);
break;
case HeaderType::TSB_Multi_Hop:
bytes = deserialize_extended<TsbHeader>(m_archive, extended);
break;
case HeaderType::GeoBroadcast_Circle:
case HeaderType::GeoBroadcast_Rect:
case HeaderType::GeoBroadcast_Elip:
bytes = deserialize_extended<GeoBroadcastHeader>(m_archive, extended);
break;
case HeaderType::Beacon:
bytes = deserialize_extended<BeaconHeader>(m_archive, extended);
break;
case HeaderType::Any:
case HeaderType::GeoUnicast:
case HeaderType::GeoAnycast_Circle:
case HeaderType::GeoAnycast_Rect:
case HeaderType::GeoAnycast_Elip:
case HeaderType::LS_Request:
case HeaderType::LS_Reply:
// unimplemented types
break;
default:
// invalid types
break;
}
} catch (InputArchive::Exception&) {
}
m_read_bytes += bytes;
return bytes;
}
std::size_t Parser::parsed_bytes() const
{
return m_read_bytes;
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,45 @@
#ifndef PARSER_HPP_IBDRMPKB
#define PARSER_HPP_IBDRMPKB
#include <vanetza/common/byte_buffer.hpp>
#include <vanetza/common/byte_buffer_source.hpp>
#include <vanetza/geonet/header_type.hpp>
#include <vanetza/geonet/header_variant.hpp>
#include <vanetza/security/secured_message.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/range/iterator_range.hpp>
#include <cstdint>
namespace vanetza
{
namespace geonet
{
// forward declarations
struct BasicHeader;
struct CommonHeader;
class Parser
{
public:
Parser(ByteBuffer::const_iterator begin, ByteBuffer::const_iterator end);
Parser(boost::iterator_range<ByteBuffer::const_iterator> range);
std::size_t parse_basic(BasicHeader&);
std::size_t parse_common(CommonHeader&);
std::size_t parse_secured(security::SecuredMessage&);
std::size_t parse_extended(HeaderVariant&, HeaderType);
std::size_t parsed_bytes() const;
private:
byte_buffer_source m_byte_buffer_source;
boost::iostreams::stream_buffer<byte_buffer_source> m_stream;
InputArchive m_archive;
std::size_t m_read_bytes;
};
} // namespace geonet
} // namespace vanetza
#endif /* PARSER_HPP_IBDRMPKB */
@@ -0,0 +1,36 @@
#include <vanetza/geonet/basic_header.hpp>
#include <vanetza/geonet/common_header.hpp>
#include <vanetza/geonet/header_variant.hpp>
#include <vanetza/geonet/pdu.hpp>
#include <vanetza/security/secured_message.hpp>
namespace vanetza
{
namespace geonet
{
void serialize(const ConstAccessiblePdu& pdu, OutputArchive& ar)
{
serialize(pdu.basic(), ar);
if (pdu.secured()) {
security::serialize(ar, *pdu.secured());
} else {
geonet::serialize(pdu.common(), ar);
boost::serialize(pdu.extended_variant(), ar);
}
}
std::size_t get_length(const ConstAccessiblePdu& pdu)
{
std::size_t length = BasicHeader::length_bytes;
if (pdu.secured()) {
length += security::get_size(*pdu.secured());
} else {
length += CommonHeader::length_bytes;
length += get_length(pdu.extended_variant());
}
return length;
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,58 @@
#ifndef PDU_HPP_PQEC9PDO
#define PDU_HPP_PQEC9PDO
#include <vanetza/geonet/header_variant.hpp>
#include <vanetza/geonet/serialization.hpp>
#include <vanetza/security/secured_message.hpp>
#include <cstddef>
#include <memory>
namespace vanetza
{
namespace geonet
{
struct BasicHeader;
struct CommonHeader;
class Pdu;
class ConstAccessiblePdu
{
public:
using SecuredMessage = security::SecuredMessage;
virtual const BasicHeader& basic() const = 0;
virtual const CommonHeader& common() const = 0;
virtual const SecuredMessage* secured() const = 0;
virtual HeaderConstRefVariant extended_variant() const = 0;
virtual std::unique_ptr<Pdu> clone() const = 0;
virtual ~ConstAccessiblePdu() = default;
};
class Pdu : public ConstAccessiblePdu
{
public:
using ConstAccessiblePdu::basic;
using ConstAccessiblePdu::common;
using ConstAccessiblePdu::secured;
virtual BasicHeader& basic() = 0;
virtual CommonHeader& common() = 0;
virtual SecuredMessage* secured() = 0;
virtual void secured(const SecuredMessage&) = 0;
};
void serialize(const ConstAccessiblePdu&, OutputArchive&);
inline void serialize(const Pdu& pdu, OutputArchive& ar)
{
serialize(static_cast<const ConstAccessiblePdu&>(pdu), ar);
}
std::size_t get_length(const ConstAccessiblePdu&);
} // namespace geonet
} // namespace vanetza
#endif /* PDU_HPP_PQEC9PDO */
@@ -0,0 +1,49 @@
#include "pdu_conversion.hpp"
#include "serialization_buffer.hpp"
#include <cassert>
namespace vanetza
{
namespace convertible
{
typedef std::unique_ptr<vanetza::geonet::Pdu> PduPtr;
void byte_buffer_impl<PduPtr>::convert(ByteBuffer& dest) const
{
assert(m_pdu);
dest.clear();
geonet::serialize_into_buffer(*m_pdu, dest);
}
std::size_t byte_buffer_impl<PduPtr>::size() const
{
assert(m_pdu);
return get_length(*m_pdu);
}
std::unique_ptr<byte_buffer> byte_buffer_impl<PduPtr>::duplicate() const
{
assert(m_pdu);
PduPtr duplicate { m_pdu->clone() };
std::unique_ptr<byte_buffer> result {
new byte_buffer_impl<PduPtr>(std::move(duplicate))
};
return result;
}
} // namespace convertible
namespace geonet
{
Pdu* pdu_cast(ByteBufferConvertible& convertible)
{
using convertible_pdu_t = convertible::byte_buffer_impl<std::unique_ptr<Pdu>>;
auto convertible_pdu = dynamic_cast<convertible_pdu_t*>(convertible.ptr());
return convertible_pdu ? convertible_pdu->m_pdu.get() : nullptr;
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,43 @@
#ifndef PDU_CONVERSION_HPP_XLCSI42E
#define PDU_CONVERSION_HPP_XLCSI42E
#include <vanetza/common/byte_buffer.hpp>
#include <vanetza/common/byte_buffer_convertible.hpp>
#include <vanetza/geonet/pdu.hpp>
#include <memory>
namespace vanetza
{
namespace convertible
{
template<>
struct byte_buffer_impl<std::unique_ptr<vanetza::geonet::Pdu>> : public byte_buffer
{
byte_buffer_impl(std::unique_ptr<vanetza::geonet::Pdu> pdu) :
m_pdu(std::move(pdu)) {}
void convert(ByteBuffer& dest) const override;
std::size_t size() const override;
std::unique_ptr<byte_buffer> duplicate() const override;
const std::unique_ptr<vanetza::geonet::Pdu> m_pdu;
};
} // namespace convertible
namespace geonet
{
/**
* Fetch PDU from byte buffer convertible
* \param conv source convertible
* \return PDU pointer or nullptr if cast failed
* */
Pdu* pdu_cast(ByteBufferConvertible& conv);
} // namespace geonet
} // namespace vanetza
#endif /* PDU_CONVERSION_HPP_XLCSI42E */
@@ -0,0 +1,27 @@
#ifndef PDU_VARIANT_HPP_AAEVKD5M
#define PDU_VARIANT_HPP_AAEVKD5M
#include <vanetza/geonet/extended_pdu.hpp>
#include <vanetza/geonet/beacon_header.hpp>
#include <vanetza/geonet/gbc_header.hpp>
#include <vanetza/geonet/shb_header.hpp>
#include <vanetza/geonet/tsb_header.hpp>
#include <boost/variant.hpp>
namespace vanetza
{
namespace geonet
{
typedef ExtendedPdu<ShbHeader> ShbPdu;
typedef ExtendedPdu<TsbHeader> TsbPdu;
typedef ExtendedPdu<BeaconHeader> BeaconPdu;
typedef ExtendedPdu<GeoBroadcastHeader> GbcPdu;
typedef boost::variant<BeaconPdu, GbcPdu, ShbPdu> PduVariant;
} // namespace geonet
} // namespace vanetza
#endif /* PDU_VARIANT_HPP_AAEVKD5M */
@@ -0,0 +1,127 @@
#ifndef PENDING_PACKET_HPP_ZHSCP1UI
#define PENDING_PACKET_HPP_ZHSCP1UI
#include <vanetza/common/clock.hpp>
#include <vanetza/geonet/lifetime.hpp>
#include <vanetza/geonet/packet.hpp>
#include <vanetza/units/time.hpp>
#include <functional>
#include <memory>
#include <tuple>
namespace vanetza
{
namespace geonet
{
/**
* PendingPacket combines PDU, payload and custom action code for pending processing steps.
* This makes it easy to resume packet forwarding steps after buffering, for example.
*/
template<typename PDU, typename... Args>
class PendingPacket
{
public:
using Packet = std::tuple<std::unique_ptr<PDU>, std::unique_ptr<DownPacket>>;
using Function = std::function<void(Packet&&, Args&&...)>;
PendingPacket() = default;
PendingPacket(Packet&& packet, const Function& fn) :
m_packet(std::move(packet)), m_function(fn) {}
template<typename... OtherArgs>
PendingPacket(PendingPacket<PDU, OtherArgs...>&& other, std::function<void(PendingPacket<PDU, OtherArgs...>&&, Args&&...)> fn) :
m_packet(std::move(other).packet())
{
auto other_fn = other.action();
m_function = [fn, other_fn](Packet&& packet, Args&&... args) {
fn(PendingPacket<PDU, OtherArgs...> { std::move(packet), other_fn }, std::forward<Args>(args)...);
};
}
template<typename... OtherArgs, typename... T>
PendingPacket(PendingPacket<PDU, OtherArgs...>&& other, T&&... ts) :
m_packet(std::move(other).packet())
{
typename PendingPacket<PDU, OtherArgs...>::Function other_action = other.action();
std::function<void(Packet&&)> bound = std::bind(other_action, std::placeholders::_1, std::forward<T>(ts)...);
m_function = [bound](Packet&& packet, Args&&... args) {
bound(std::move(packet), std::forward<Args>(args)...);
};
}
void process(Args&&... args)
{
if (std::get<0>(m_packet) && std::get<1>(m_packet)) {
m_function(std::move(m_packet), std::forward<Args>(args)...);
}
}
std::size_t length() const
{
const PDU* pdu = std::get<0>(m_packet).get();
const DownPacket* payload = std::get<1>(m_packet).get();
return (pdu ? get_length(*pdu) : 0) + (payload ? payload->size(OsiLayer::Transport, max_osi_layer()) : 0);
}
Clock::duration reduce_lifetime(Clock::duration queuing_time)
{
Clock::duration remaining = Clock::duration::zero();
PDU* pdu_ptr = std::get<0>(m_packet).get();
if (pdu_ptr) {
using vanetza::units::clock_cast;
Clock::duration packet_lifetime = clock_cast(pdu_ptr->basic().lifetime.decode());
if (queuing_time <= Clock::duration::zero()) {
remaining = packet_lifetime;
} else if (queuing_time < packet_lifetime) {
remaining = packet_lifetime - queuing_time;
pdu_ptr->basic().lifetime.encode(clock_cast(remaining));
} else {
pdu_ptr->basic().lifetime = Lifetime::zero();
}
}
return remaining;
}
const PDU& pdu() const
{
const PDU* ptr = std::get<0>(m_packet).get();
assert(ptr);
return *ptr;
}
const DownPacket& payload() const
{
const DownPacket* ptr = std::get<1>(m_packet).get();
assert(ptr);
return *ptr;
}
Function action() const { return m_function; }
Packet packet() && { return std::move(m_packet); }
PendingPacket duplicate()
{
const PDU* pdu_ptr = std::get<0>(m_packet).get();
std::unique_ptr<PDU> pdu_dup { pdu_ptr ? new PDU(*pdu_ptr) : nullptr };
std::unique_ptr<DownPacket> payload_dup;
if (!pdu_ptr || pdu_ptr->secured()) {
payload_dup.reset(new DownPacket());
} else if (const DownPacket* payload_ptr = std::get<1>(m_packet).get()) {
payload_dup = vanetza::duplicate(*payload_ptr);
}
return PendingPacket(std::make_tuple(std::move(pdu_dup), std::move(payload_dup)), m_function);
}
private:
Packet m_packet;
Function m_function;
};
} // namespace geonet
} // namespace vanetza
#endif /* PENDING_PACKET_HPP_ZHSCP1UI */
@@ -0,0 +1,53 @@
#include <vanetza/common/position_provider.hpp>
#include <vanetza/common/runtime.hpp>
#include <vanetza/geonet/position_updater.hpp>
#include <vanetza/geonet/router.hpp>
#include <vanetza/units/time.hpp>
#include <chrono>
namespace vanetza
{
namespace geonet
{
PositionUpdater::PositionUpdater(Runtime& rt, PositionProvider& position, Router& router) :
m_runtime(rt), m_positioning(position), m_router(router)
{
update_rate(m_router.get_mib().itsGnMinimumUpdateFrequencyEPV);
}
PositionUpdater::~PositionUpdater()
{
m_runtime.cancel(this);
}
void PositionUpdater::schedule()
{
if (m_interval > Clock::duration::zero()) {
m_runtime.schedule(m_interval, [this](Clock::time_point) {
m_router.update_position(m_positioning.position_fix());
schedule();
}, this);
}
}
void PositionUpdater::update_rate(Clock::duration interval)
{
m_interval = interval;
m_runtime.cancel(this); /*< cancel previously scheduled callback */
schedule();
}
void PositionUpdater::update_rate(units::Frequency rate)
{
if (rate > units::Frequency::from_value(0.0)) {
using namespace std::chrono;
const duration<double> interval { 1.0 / rate / units::si::second };
update_rate(duration_cast<Clock::duration>(interval));
} else {
update_rate(Clock::duration::zero());
}
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,56 @@
#ifndef POSITION_UPDATER_HPP_KMWXTJRO
#define POSITION_UPDATER_HPP_KMWXTJRO
#include <vanetza/common/clock.hpp>
#include <vanetza/units/frequency.hpp>
namespace vanetza
{
// forward declarations
class Runtime;
class PositionProvider;
namespace geonet { class Router; }
namespace geonet
{
/**
* PositionUpdater helps updating a Router's position vector periodically
*/
class PositionUpdater
{
public:
/**
* Create PositionUpdater scheduling position updates read from position provider
* \note the default update rate is derived from router's MIB.
* \param runtime where updater schedules its callback
* \param pos position updates are read from this source
* \param router position sink
*/
PositionUpdater(Runtime& runtime, PositionProvider& pos, Router& router);
~PositionUpdater();
/**
* Change rate at which update is looking up new positions
* \note MIB setting is not affected
* \note an update interval smaller than zero disables updates
* \param interval update interval
*/
void update_rate(Clock::duration interval);
void update_rate(units::Frequency);
private:
void schedule();
Runtime& m_runtime;
PositionProvider& m_positioning;
Router& m_router;
Clock::duration m_interval;
};
} // namespace geonet
} // namespace vanetza
#endif /* POSITION_UPDATER_HPP_KMWXTJRO */
@@ -0,0 +1,111 @@
#include "areas.hpp"
#include "position_vector.hpp"
#include "serialization.hpp"
namespace vanetza
{
namespace geonet
{
constexpr std::size_t LongPositionVector::length_bytes;
constexpr std::size_t ShortPositionVector::length_bytes;
LongPositionVector::LongPositionVector() : position_accuracy_indicator(false)
{
}
GeodeticPosition LongPositionVector::position() const
{
return GeodeticPosition {
static_cast<units::GeoAngle>(latitude),
static_cast<units::GeoAngle>(longitude)
};
}
ShortPositionVector::ShortPositionVector(const LongPositionVector& lpv) :
gn_addr(lpv.gn_addr), timestamp(lpv.timestamp),
latitude(lpv.latitude), longitude(lpv.longitude)
{
}
bool operator==(const LongPositionVector& lhs, const LongPositionVector& rhs)
{
return lhs.gn_addr == rhs.gn_addr
&& lhs.timestamp == rhs.timestamp
&& lhs.latitude == rhs.latitude
&& lhs.longitude == rhs.longitude
&& lhs.speed == rhs.speed
&& lhs.heading == rhs.heading
&& lhs.position_accuracy_indicator == rhs.position_accuracy_indicator;
}
bool operator!=(const LongPositionVector& lhs, const LongPositionVector& rhs)
{
return !(lhs == rhs);
}
bool operator==(const ShortPositionVector& lhs, const ShortPositionVector& rhs)
{
return lhs.gn_addr == rhs.gn_addr
&& lhs.timestamp == rhs.timestamp
&& lhs.latitude == rhs.latitude
&& lhs.longitude == rhs.longitude;
}
bool operator!=(const ShortPositionVector& lhs, const ShortPositionVector& rhs)
{
return !(lhs == rhs);
}
bool is_empty(const LongPositionVector& pv)
{
static const LongPositionVector zero;
return pv == zero;
}
bool is_valid(const LongPositionVector& pv)
{
static const geo_angle_i32t limit_lat { 90.0 * units::degree };
static const geo_angle_i32t limit_lon { 180.0 * units::degree };
static const heading_u16t limit_hdg { 360.0 * units::degree };
if (is_empty(pv)) {
return false;
} else if (pv.latitude < -limit_lat || pv.latitude > limit_lat) {
return false;
} else if (pv.longitude < -limit_lon || pv.longitude > limit_lon) {
return false;
} else if (pv.heading > limit_hdg) {
return false;
}
return true;
}
void serialize(const LongPositionVector& lpv, OutputArchive& ar)
{
serialize(lpv.gn_addr, ar);
serialize(lpv.timestamp, ar);
serialize(lpv.latitude, ar);
serialize(lpv.longitude, ar);
uint16_t paiAndSpeed = lpv.speed.value().raw();
paiAndSpeed |= lpv.position_accuracy_indicator ? 0x8000 : 0x0000;
serialize(host_cast(paiAndSpeed), ar);
serialize(lpv.heading, ar);
}
void deserialize(LongPositionVector& lpv, InputArchive& ar)
{
deserialize(lpv.gn_addr, ar);
deserialize(lpv.timestamp, ar);
deserialize(lpv.latitude, ar);
deserialize(lpv.longitude, ar);
uint16_t paiAndSpeed = 0;
deserialize(paiAndSpeed, ar);
lpv.position_accuracy_indicator = ((paiAndSpeed & 0x8000) != 0);
lpv.speed = LongPositionVector::speed_u15t::from_value(paiAndSpeed);
deserialize(lpv.heading, ar);
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,84 @@
#ifndef POSITION_VECTOR_HPP_WJAGEOCS
#define POSITION_VECTOR_HPP_WJAGEOCS
#include <vanetza/common/bit_number.hpp>
#include <vanetza/geodesy/position.hpp>
#include <vanetza/geonet/address.hpp>
#include <vanetza/geonet/serialization.hpp>
#include <vanetza/geonet/timestamp.hpp>
#include <vanetza/geonet/units.hpp>
#include <boost/units/quantity.hpp>
#include <boost/units/systems/si/velocity.hpp>
#include <cstdint>
namespace vanetza
{
namespace geonet
{
using geodesy::GeodeticPosition;
class LongPositionVector
{
public:
static constexpr std::size_t length_bytes = 24;
typedef boost::units::quantity<boost::units::make_scaled_unit<
boost::units::si::velocity,
boost::units::scale<10, boost::units::static_rational<-2>>
>::type, BitNumber<uint_fast16_t, 15>> speed_u15t; // 1/100 m/s
Address gn_addr;
Timestamp timestamp; // ms since 2004-01-01 00:00:00.000
geo_angle_i32t latitude;
geo_angle_i32t longitude;
bool position_accuracy_indicator;
speed_u15t speed;
heading_u16t heading;
LongPositionVector();
GeodeticPosition position() const;
};
bool operator==(const LongPositionVector& lhs, const LongPositionVector& rhs);
bool operator!=(const LongPositionVector& lhs, const LongPositionVector& rhs);
/**
* Check if position vector is empty (default)
* \return true if all data fields are zero
*/
bool is_empty(const LongPositionVector&);
/**
* Check if position vector contains valid data,
* i.e. not empty and latitude, longitude, and heading values within range
* \return true if all data fields are valid
*/
bool is_valid(const LongPositionVector&);
void serialize(const LongPositionVector&, OutputArchive&);
void deserialize(LongPositionVector&, InputArchive&);
class ShortPositionVector
{
public:
static constexpr std::size_t length_bytes = 20;
ShortPositionVector() = default;
ShortPositionVector(const ShortPositionVector&) = default;
ShortPositionVector& operator=(const ShortPositionVector&) = default;
explicit ShortPositionVector(const LongPositionVector&);
Address gn_addr;
Timestamp timestamp;
geo_angle_i32t latitude;
geo_angle_i32t longitude;
};
bool operator==(const ShortPositionVector& lhs, const ShortPositionVector& rhs);
bool operator!=(const ShortPositionVector& lhs, const ShortPositionVector& rhs);
} // namespace geonet
} // namespace vanetza
#endif /* POSITION_VECTOR_HPP_WJAGEOCS */
@@ -0,0 +1,60 @@
#include "repeater.hpp"
#include "data_request.hpp"
#include <vanetza/common/runtime.hpp>
#include <cassert>
namespace vanetza
{
namespace geonet
{
Repeater::Repetition::Repetition(const DataRequestVariant& request, const DownPacket& payload) :
m_request(request), m_payload(new DownPacket(payload))
{
}
Repeater::Repeater(Runtime& rt, const Callback& cb) : m_repeat_fn(cb), m_runtime(rt)
{
assert(m_repeat_fn);
}
Repeater::~Repeater()
{
m_runtime.cancel(this);
}
void Repeater::add(const DataRequestVariant& request,
const DataRequest::Repetition& repetition, const DownPacket& payload)
{
if (has_further_repetition(repetition)) {
const auto next_repetition = m_runtime.now() + units::clock_cast(repetition.interval);
m_repetitions.emplace_front(request, payload);
auto added = m_repetitions.begin();
auto then = std::placeholders::_1;
m_runtime.schedule(next_repetition, std::bind<void>(&Repeater::trigger, this, added, then), this);
}
}
void Repeater::trigger(std::list<Repetition>::iterator rep, Clock::time_point invocation)
{
Repetition& repetition = *rep;
DataRequest& request = access_request(repetition.m_request);
assert(request.repetition);
// reduce remaining interval by one step and occurred triggering delay
decrement_by_one(*request.repetition);
auto delayed = m_runtime.now() - invocation;
request.repetition->maximum -= units::clock_cast(delayed);
// reset repetition data if this is the last repetition
if (!has_further_repetition(request)) {
request.repetition.reset();
}
m_repeat_fn(repetition.m_request, std::move(repetition.m_payload));
m_repetitions.erase(rep);
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,77 @@
#ifndef REPEATER_HPP_AH49FXB1
#define REPEATER_HPP_AH49FXB1
#include <vanetza/common/clock.hpp>
#include <vanetza/geonet/data_request.hpp>
#include <vanetza/geonet/packet.hpp>
#include <functional>
#include <list>
#include <memory>
namespace vanetza
{
// forward declaration
class Runtime;
namespace geonet
{
/**
* Repeater eases handling of packet repetitions.
* Packet repetitions are repeated requests with same payload.
*/
class Repeater
{
public:
using Callback = std::function<void(const DataRequestVariant&, std::unique_ptr<DownPacket>)>;
/**
* Create a Repeater instance
* \param rt runtime used for scheduling repetitions
* \param cb callback to be invoked when repetition is due
*/
Repeater(Runtime& rt, const Callback& cb);
~Repeater();
/**
* Add another repetition
* \param request any kind of GeoNet data request
* \param payload request's payload
*/
template<class REQUEST>
void add(const REQUEST& request, const DownPacket& payload)
{
if (request.repetition) {
add(request, *request.repetition, payload);
}
}
private:
struct Repetition
{
Repetition(const DataRequestVariant&, const DownPacket&);
DataRequestVariant m_request;
std::unique_ptr<DownPacket> m_payload;
};
/**
* Add packet repetition to internal book keeping
*/
void add(const DataRequestVariant&, const DataRequest::Repetition&, const DownPacket&);
/**
* Triggered when repetition is due according to runtime
*/
void trigger(std::list<Repetition>::iterator, Clock::time_point);
std::list<Repetition> m_repetitions;
Callback m_repeat_fn;
Runtime& m_runtime;
};
} // namespace geonet
} // namespace vanetza
#endif /* REPEATER_HPP_AH49FXB1 */
@@ -0,0 +1,34 @@
#ifndef REPETITION_DISPATCHER_HPP_GO4BAEVL
#define REPETITION_DISPATCHER_HPP_GO4BAEVL
#include <vanetza/geonet/router.hpp>
#include <boost/variant/static_visitor.hpp>
namespace vanetza
{
namespace geonet
{
class RepetitionDispatcher : public boost::static_visitor<void>
{
public:
RepetitionDispatcher(Router& _router, std::unique_ptr<DownPacket> _payload)
: router(_router), payload(std::move(_payload))
{}
template<class REQUEST>
void operator()(const REQUEST& request)
{
router.request(request, std::move(payload));
}
private:
Router& router;
std::unique_ptr<DownPacket> payload;
};
} // namespace geonet
} // namespace vanetza
#endif /* REPETITION_DISPATCHER_HPP_GO4BAEVL */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,582 @@
#ifndef ROUTER_HPP_UKYYCAR0
#define ROUTER_HPP_UKYYCAR0
#include <vanetza/common/byte_order.hpp>
#include <vanetza/common/hook.hpp>
#include <vanetza/common/its_aid.hpp>
#include <vanetza/access/ethertype.hpp>
#include <vanetza/geonet/beacon_header.hpp>
#include <vanetza/geonet/cbf_packet_buffer.hpp>
#include <vanetza/geonet/common_header.hpp>
#include <vanetza/geonet/extended_pdu.hpp>
#include <vanetza/geonet/gbc_header.hpp>
#include <vanetza/geonet/gbc_memory.hpp>
#include <vanetza/geonet/interface.hpp>
#include <vanetza/geonet/location_table.hpp>
#include <vanetza/geonet/mib.hpp>
#include <vanetza/geonet/packet.hpp>
#include <vanetza/geonet/packet_buffer.hpp>
#include <vanetza/geonet/pending_packet.hpp>
#include <vanetza/geonet/pdu.hpp>
#include <vanetza/geonet/pdu_variant.hpp>
#include <vanetza/geonet/repeater.hpp>
#include <vanetza/geonet/sequence_number.hpp>
#include <vanetza/geonet/shb_header.hpp>
#include <vanetza/geonet/timestamp.hpp>
#include <vanetza/units/length.hpp>
#include <vanetza/units/time.hpp>
#include <vanetza/security/security_entity.hpp>
#include <boost/variant.hpp>
#include <cstdint>
#include <memory>
#include <random>
#include <map>
namespace vanetza
{
// forward declarations
class MacAddress;
struct PositionFix;
class Runtime;
namespace dcc
{
struct DataRequest;
class RequestInterface;
} // namespace dcc
namespace geonet
{
extern const access::EtherType ether_type;
class DccFieldGenerator;
class IndicationContext;
class IndicationContextBasic;
class NextHop;
class TransportInterface;
struct ShbDataRequest;
struct GbcDataRequest;
struct DataConfirm;
struct DataIndication;
struct LinkLayer;
/**
* Router is the central entity for GeoNet communication
*
* Incoming and outgoing GeoNet packets are handled by the router.
* It may even dispatch own packets (beacons) if necessary.
*
* This implementation follows EN 302 636-4-1 v1.3.1
*/
class Router
{
public:
typedef std::unique_ptr<DataRequest> DataRequestPtr;
typedef std::unique_ptr<Pdu> PduPtr;
typedef std::unique_ptr<DownPacket> DownPacketPtr;
typedef std::unique_ptr<UpPacket> UpPacketPtr;
using PendingPacketForwarding = PendingPacket<GbcPdu, const MacAddress&>;
/// Reason for packet drop used by drop hook
enum class PacketDropReason
{
Parse_Basic_Header,
Parse_Common_Header,
Parse_Secured_Header,
Parse_Extended_Header,
ITS_Protocol_Version,
Decap_Unsuccessful_Non_Strict,
Decap_Unsuccessful_Strict,
Hop_Limit,
Payload_Size,
Security_Entity_Missing,
Packet_Size,
Internal_Error,
};
// Reason for stopping packet forwarding
enum class ForwardingStopReason
{
Hop_Limit,
Source_PDR,
Sender_PDR,
Outside_Destination_Area
};
Router(Runtime&, const MIB&);
~Router();
/**
* \brief Request to send payload per single hop broadcast (SHB).
* If security is enabled, the message gets encapsulated in a security envelope.
* Returns whether data was valid to be sent.
*
* \param request
* \param payload from upper layers
* \return result code if packet has been accepted
*/
DataConfirm request(const ShbDataRequest&, DownPacketPtr);
/**
* \brief Request to send payload per GeoBroadcast (GBC).
* If security is enabled, the message gets encapsulated in a security envelope.
* Returns whether data was valid to be sent.
*
* \param request
* \param payload from upper layers
* \return result code if packet has been accepted
*/
DataConfirm request(const GbcDataRequest&, DownPacketPtr);
// These three requests are not supported yet
DataConfirm request(const GucDataRequest&, DownPacketPtr);
DataConfirm request(const GacDataRequest&, DownPacketPtr);
DataConfirm request(const TsbDataRequest&, DownPacketPtr);
/**
* \brief Handle the received packet on network layer.
* Packet handling involves these steps:
* - header processing
* - packet forwarding
* - passing to transport layer
* - security decapsulation
*
* \param packet received packet from access layer
* \param sender MAC address of sender
* \param destination MAC address of destination (might be broadcast)
*/
void indicate(UpPacketPtr, const MacAddress& sender, const MacAddress& destination);
/**
* \brief When a packet is dropped, this Hook is invoked
* \tparam PacketDropReason why Router decided to drop packet
*/
Hook<PacketDropReason> packet_dropped;
/**
* \brief When packet forwarding is stopped, this Hook is invoked
* \tparam ForwardingStopReason why Router decided not to forward packet
*/
Hook<ForwardingStopReason> forwarding_stopped;
/**
* \brief Update router's local position vector
*
* \param fix current position fix
*/
void update_position(const PositionFix&);
/**
* \brief Register a transport protocol handler.
*
* \param proto register handler for this upper protocol
* \param ifc use this interface or disable handling if nullptr
*/
void set_transport_handler(UpperProtocol proto, TransportInterface* ifc);
/**
* \brief Register security entity used when itsGnSecurity is enabled
*
* \param entity security entity
*/
void set_security_entity(security::SecurityEntity* entity);
/**
* \brief Register access layer interface
*
* \param ifc interface used for passing packets down to access layer
*/
void set_access_interface(dcc::RequestInterface* ifc);
/**
* \brief Register generator for DCC-MCO fields
*
* \param dcc DCC-MCO field generator or nullptr for disabling feature
*/
void set_dcc_field_generator(DccFieldGenerator* dcc);
/**
* \brief Set Router's own GeoNetworking address
*
* \param addr
*/
void set_address(const Address&);
/**
* \brief Get Management Information Base (MIB)
* \return read-only reference to MIB
*/
const MIB& get_mib() const { return m_mib; }
/**
* \brief Get the Contention-Based-Forwarding buffer
*
* \return read-only reference to CBF packet buffer
*/
const CbfPacketBuffer& get_cbf_buffer() const { return m_cbf_buffer; }
/**
* \brief Get the LocationTable.
* The table holds information about neighbouring ITS-Routers.
*
* \return read-only reference to LocationTable
*/
const LocationTable& get_location_table() const { return m_location_table; }
/**
* \brief Get the local position vector.
* This vector describes the current position of the router.
*
* \return read-only reference to LongPositionVector
*/
const LongPositionVector& get_local_position_vector() const { return m_local_position_vector; }
/**
* rief Flush the broadcast and unicast forwarding buffers now.
* TS 102 723-8 V1.1.1 clause 6.3.1.3: between an identifier change PREPARE and COMMIT
* "caches shall be flushed" so no packet with the old identifier stays queued.
*/
void flush_forwarding_buffers();
/**
* \brief Check if router is outside the sectorial contention area
* See TS 102 636-4-1 v1.2.3 section E.4 and figure E.2 for details.
*
* \param sender
* \param forwarder
* \return bool true if either sender or forwarder is outside
*/
bool outside_sectorial_contention_area(const MacAddress& sender, const MacAddress& forwarder) const;
/**
* \brief Set seed for internal random number generator (RNG)
* RNG is used e.g. for random Beacon jitter
*
* \param seed reset RNG's state to this seed
*/
void set_random_seed(std::uint_fast32_t seed);
/**
* Forwarding algorithm selection procedure as given by Annex D
* \param pdu GeoNetworking PDU
* \param payload packet payload
* \param ll link-layer control info (unavailable for source operations)
* \return routing decision (next hop's address, buffered, or discarded)
*/
NextHop forwarding_algorithm_selection(PendingPacketForwarding&&, const LinkLayer* ll = nullptr);
private:
typedef std::map<UpperProtocol, TransportInterface*> transport_map_t;
/**
* \brief Send Beacon packet to all neighbours with updated position vector.
* Only to be called when the beacon timer expires.
*/
void on_beacon_timer_expired();
/**
* \brief Reschedule timer for next Beacon transmission
* Timer will be scheduled according to MIB's Beacon timer settings.
*/
void reset_beacon_timer();
/**
* \brief Reschedule timer for next Beacon transmission
* \param next Duration until next transmission
*/
void reset_beacon_timer(Clock::duration next);
/**
* \brief Process BasicHeader at packet indication.
* \param ctx Context holding data for further parsing
*/
void indicate_basic(IndicationContextBasic&);
/**
* \brief Process CommonHeader at packet indication.
* \param ctx Context holding data for further parsing
* \param basic Previously decoded BasicHeader
*/
void indicate_common(IndicationContext&, const BasicHeader&);
/**
* \brief Process ExtendedHeader at packet indication.
* \param ctx Context holding data for further parsing
* \param common Previously decoded CommonHeader
*/
void indicate_extended(IndicationContext&, const CommonHeader&);
/**
* \brief Process SecuredMessage at packet indication.
* \param ctx Context holding data for further parsing
* \param basic Previously decoded BasicHeader
*/
void indicate_secured(IndicationContextBasic&, const BasicHeader&);
/**
* \brief Process ExtendedHeader information.
* Update router's LocationTable and neighbour relationship.
*
* \param pdu containing the ExtendedHeader
* \param packet received packet
* \param ll link-layer control info
* \return pass up decision (always false for Beacons)
*/
bool process_extended(const ExtendedPduConstRefs<BeaconHeader>&, const UpPacket&, const LinkLayer& ll);
/**
* \brief Process ExtendedHeader information.
* Update router's LocationTable and neighbour relationship.
* Pass packet up to transport layer for further processing.
*
* \param pdu containing the ExtendedHeader
* \param packet received packet
* \param ll link-layer control info
* \return pass up decision (true for all non-duplicate SHBs)
*/
bool process_extended(const ExtendedPduConstRefs<ShbHeader>&, const UpPacket&, const LinkLayer& ll);
/**
* \brief packet handling of received TSB packet
*
* \param pdu PDU with TSB header
* \param packet received packet
* \param ll link-layer control info
* \return pass up decision (true for all non-duplicate TSBs)
*/
bool process_extended(const ExtendedPduConstRefs<TsbHeader>&, const UpPacket&, const LinkLayer& ll);
/**
* \brief Process ExtendedHeader information.
* Update router's LocationTable and neighbour relationship.
* Pass packet up to transport layer for further processing.
* Forward packets.
*
* \param pdu containing the ExtendedHeader
* \param packet received packet
* \param ll link-layer control info
* \return pass up decision (depends on addressed area and router position)
*/
bool process_extended(const ExtendedPduConstRefs<GeoBroadcastHeader>&, const UpPacket&, const LinkLayer& ll);
/**
* \brief Send all packets in the broadcast forwarding buffer with expired waiting time.
*/
void flush_broadcast_forwarding_buffer();
/**
* \brief Send all matching packets in the unicast forwarding buffer with expired waiting time.
* \param addr unicast packets for this address
*/
void flush_unicast_forwarding_buffer(const Address& addr);
/**
* \brief Executes media specific functionalities
* Details are described in TS 102 636-4-2.
*
* \param profile e.g. ITS-G5
*/
void execute_media_procedures(CommunicationProfile);
/**
* \brief Executes ITS-G5 media specific procedures
* Details are described in TS 102 636-4-2.
*/
void execute_itsg5_procedures();
/**
* \brief Pass down the packet to the access layer.
*
* \param addr MAC address of destination
* \param pdu header information
* \param payload Packet payload
*/
void pass_down(const MacAddress&, PduPtr, DownPacketPtr);
/**
* \brief Send packet using the information in the DataRequest.
* The packet is formed using the data in PDU and payload.
*
* \param request containing transmission parameters
* \param pdu header information
* \param payload Packet payload
*/
void pass_down(const dcc::DataRequest&, PduPtr, DownPacketPtr);
/**
* \brief Pass packet up to the transport layer.
*
* \param ind containing network information
* \param packet payload to be passed up to the next layer
*/
void pass_up(const DataIndication&, UpPacketPtr);
/**
* \brief Decide if GBC packet shall be passed up to transport layer.
*
* \param within_destination is router located within destination area
* \param gbc GeoBroadcast header
*
* \return true if packet shall be passed up
*/
bool decide_pass_up(bool within_destination, const GeoBroadcastHeader& gbc);
/**
* \brief Helper method to handle duplicate addresses.
* If own address collides with the address of a received packet
* Router's address is set to a new random address.
* \note Behaviour depends on MIB's itsGnLocalAddrConfMethod.
*
* \param source address of source (from packet header)
* \param sender address of sender (link layer)
*/
void detect_duplicate_address(const Address& source, const MacAddress& sender);
/**
* \brief Detect duplicate packets
* See EN 302 636-4-1 v1.3.1 Annex A.2
*
* \param source source address
* \param sn sequence number
* \return true if packet is detected as a duplicate
*/
bool detect_duplicate_packet(const Address& source, SequenceNumber sn);
/**
* \brief Determine next hop for greedy forwarding.
* See EN 302 636-4-1 v1.3.1 Annex E.2
*
* \param pdu
* \param payload
* \return next hop
*/
NextHop greedy_forwarding(PendingPacketForwarding&&);
/**
* \brief Determine next hop for non-area contention-based forwarding
* See EN 302 636-4-1 v1.3.1 Annex E.3
*
* \param pdu
* \param payload
* \param sender optional sender MAC address (if not first hop)
* \return next hop
*/
NextHop non_area_contention_based_forwarding(PendingPacketForwarding&&, const MacAddress* sender);
/**
* \brief Determine next hop for area contention-based forwarding
* See EN 302 636-4-1 v1.3.1 Annex F.3
*
* \param pdu
* \param payload
* \param sender optional sender MAC address (if not first hop)
* \return next hop
*/
NextHop area_contention_based_forwarding(PendingPacketForwarding&&, const MacAddress* sender);
/**
* \brief Determine CBF buffering time for a packet.
* Complies to EN 302 636-4-1 v1.3.1 Annex E.3 (non-area CBF, eq. E.1) and F.3 (area CBF, eq. F.1)
*
* \param dist distance or progress (interpretation depends on non-area vs. area CBF)
* \return CBF time-out
*/
units::Duration timeout_cbf(units::Length distance) const;
/**
* \brief Determine (area) CBF buffering time for a packet from a sender
*
* This is a shortcut for a re-curring pattern in Annex F.3 and F.4:
* 1) sender position is looked up in location table
* 2) position accuracy of sender is validated (if it is found)
* 3) progress is then distance between sender and local router
*
* \param sender MAC address of sender
* \return CBF time-out
*/
units::Duration timeout_cbf(const MacAddress& sender) const;
/**
* \brief Determine next hop for area advanced forwarding
* See EN 302 636-4-1 v1.3.1 Annex F.4
*
* \param pdu
* \param payload
* \param ll optional link-layer control info (if not source operations)
* \return next hop
*/
NextHop area_advanced_forwarding(PendingPacketForwarding&&, const LinkLayer* sender);
/**
* \brief Callback function for dispatching a packet repetition.
* Invoked by Repeater when a scheduled repetition is due.
*
* \param request
* \param payload
*/
void dispatch_repetition(const DataRequestVariant&, DownPacketPtr);
/**
* \brief Encaspulate a packet according to security profile
*
* \param aid ITS-AID
* \param ssp Service Specific Permissions
* \param pdu PDU
* \param packet Packet with payload
*/
DownPacketPtr encap_packet(ItsAid aid, ByteBuffer ssp, ByteBuffer context, Pdu& pdu, DownPacketPtr packet);
/**
* \brief Create an initialized Single-Hop-Broadcast PDU
*
* \param request
* \return PDU object
*/
std::unique_ptr<ShbPdu> create_shb_pdu(const ShbDataRequest&);
/**
* \brief Create an initialzed Beacon PDU
*
* \return PDU object
*/
std::unique_ptr<BeaconPdu> create_beacon_pdu();
/**
* \brief Create an initialized GeoBroadcast PDU
*
* \param request
* \return PDU object
*/
std::unique_ptr<GbcPdu> create_gbc_pdu(const GbcDataRequest&);
const MIB& m_mib;
Runtime& m_runtime;
dcc::RequestInterface* m_request_interface;
DccFieldGenerator* m_dcc_field_generator;
security::SecurityEntity* m_security_entity;
transport_map_t m_transport_ifcs;
LocationTable m_location_table;
PacketBuffer m_bc_forward_buffer;
PacketBuffer m_uc_forward_buffer;
CbfPacketBuffer m_cbf_buffer;
LongPositionVector m_local_position_vector;
SequenceNumber m_local_sequence_number;
Repeater m_repeater;
std::mt19937 m_random_gen;
GbcMemory m_gbc_memory;
};
/**
* Get string representation of packet drop reason
* \param pdr packet drop reason code
* \return string representation
*/
std::string stringify(Router::PacketDropReason pdr);
} // namespace geonet
} // namespace vanetza
#endif /* ROUTER_HPP_UKYYCAR0 */
@@ -0,0 +1,54 @@
#include <vanetza/geonet/secured_pdu.hpp>
#include <vanetza/geonet/serialization_buffer.hpp>
namespace vanetza
{
namespace geonet
{
SecuredPdu::SecuredPdu(const Pdu& pdu) :
common(pdu.common()), extended(pdu.extended_variant())
{
}
void serialize(const SecuredPdu& pdu, OutputArchive& ar)
{
serialize(pdu.common, ar);
serialize(pdu.extended, ar);
}
SecuredPdu* secured_pdu_cast(ByteBufferConvertible& convertible)
{
using convertible_pdu_t = convertible::byte_buffer_impl<SecuredPdu>;
auto convertible_pdu = dynamic_cast<convertible_pdu_t*>(convertible.ptr());
return convertible_pdu ? &convertible_pdu->pdu : nullptr;
}
} // namespace geonet
namespace convertible
{
byte_buffer_impl<geonet::SecuredPdu>::byte_buffer_impl(const geonet::SecuredPdu& _pdu) :
pdu(_pdu)
{
}
void byte_buffer_impl<geonet::SecuredPdu>::convert(ByteBuffer& buffer) const
{
geonet::serialize_into_buffer(pdu, buffer);
}
std::size_t byte_buffer_impl<geonet::SecuredPdu>::size() const
{
using namespace geonet;
return CommonHeader::length_bytes + get_length(pdu.extended);
}
std::unique_ptr<byte_buffer> byte_buffer_impl<geonet::SecuredPdu>::duplicate() const
{
return std::unique_ptr<byte_buffer_impl> { new byte_buffer_impl(*this) };
}
} // namespace convertible
} // namespace vanetza
@@ -0,0 +1,63 @@
#ifndef SECURED_PDU_HPP_TVERYI91
#define SECURED_PDU_HPP_TVERYI91
#include <vanetza/common/byte_buffer_convertible.hpp>
#include <vanetza/geonet/common_header.hpp>
#include <vanetza/geonet/extended_pdu.hpp>
#include <vanetza/geonet/header_variant.hpp>
#include <vanetza/geonet/serialization.hpp>
namespace vanetza
{
namespace geonet
{
/**
* Secured PDU consists of GeoNetworking headers belonging to a secured message's payload
*/
class SecuredPdu
{
public:
template<typename EXT>
SecuredPdu(const ExtendedPdu<EXT>& pdu) :
common(pdu.common()), extended(pdu.extended())
{
}
SecuredPdu(const Pdu&);
CommonHeader common;
HeaderVariant extended;
};
void serialize(const SecuredPdu&, OutputArchive&);
/**
* Cast secured PDU from byte buffer convertible
* \param conv source convertible
* \return secured PDU pointer or nullptr if cast failed
*/
SecuredPdu* secured_pdu_cast(ByteBufferConvertible&);
} // namespace geonet
namespace convertible
{
template<>
struct byte_buffer_impl<geonet::SecuredPdu> : public byte_buffer
{
byte_buffer_impl(const geonet::SecuredPdu&);
void convert(ByteBuffer&) const override;
std::size_t size() const override;
std::unique_ptr<byte_buffer> duplicate() const override;
geonet::SecuredPdu pdu;
};
} // namespace convertible
} // namespace vanetza
#endif /* SECURED_PDU_HPP_TVERYI91 */
@@ -0,0 +1,35 @@
#include "sequence_number.hpp"
namespace vanetza
{
namespace geonet
{
bool SequenceNumber::operator<(SequenceNumber other) const
{
return ((other.m_number > m_number && other.m_number - m_number <= SequenceNumber::max/2) ||
(m_number > other.m_number && m_number - other.m_number > SequenceNumber::max/2));
}
SequenceNumber SequenceNumber::operator++(int)
{
SequenceNumber tmp = *this;
++m_number;
return tmp;
}
void serialize(const SequenceNumber& sn, OutputArchive& ar)
{
serialize(host_cast(static_cast<SequenceNumber::value_type>(sn)), ar);
}
void deserialize(SequenceNumber& sn, InputArchive& ar)
{
SequenceNumber::value_type tmp = 0;
deserialize(tmp, ar);
sn = SequenceNumber(tmp);
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,42 @@
#ifndef SEQUENCE_NUMBER_HPP_HEO4A3XC
#define SEQUENCE_NUMBER_HPP_HEO4A3XC
#include <vanetza/geonet/serialization.hpp>
#include <boost/operators.hpp>
#include <cstdint>
#include <limits>
namespace vanetza
{
namespace geonet
{
class SequenceNumber :
public boost::totally_ordered<SequenceNumber>,
public boost::additive<SequenceNumber>
{
public:
using value_type = uint16_t;
static constexpr value_type max = std::numeric_limits<value_type>::max();
SequenceNumber() : m_number(0) {}
explicit SequenceNumber(value_type number) : m_number(number) {}
explicit operator value_type() const { return m_number; }
bool operator<(SequenceNumber other) const;
bool operator==(SequenceNumber other) const { return m_number == other.m_number; }
void operator+=(SequenceNumber other) { m_number += other.m_number; }
void operator-=(SequenceNumber other) { m_number -= other.m_number; }
SequenceNumber operator++(int);
private:
value_type m_number;
};
void serialize(const SequenceNumber&, OutputArchive&);
void deserialize(SequenceNumber&, InputArchive&);
} // namespace geonet
} // namespace vanetza
#endif /* SEQUENCE_NUMBER_HPP_HEO4A3XC */
@@ -0,0 +1,32 @@
#ifndef SERIALIZATION_HPP_XBJELAF0
#define SERIALIZATION_HPP_XBJELAF0
#include <vanetza/common/serialization.hpp>
namespace vanetza
{
namespace geonet
{
using vanetza::InputArchive;
using vanetza::OutputArchive;
template<typename T>
void serialize(T&& t, OutputArchive& oa)
{
using vanetza::serialize;
serialize(oa, std::forward<T>(t));
}
template<typename T>
void deserialize(T&& t, InputArchive& ia)
{
using vanetza::deserialize;
deserialize(ia, std::forward<T>(t));
}
} // namespace geonet
} // namespace vanetza
#endif /* SERIALIZATION_HPP_XBJELAF0 */
@@ -0,0 +1,47 @@
#ifndef SERIALIZATION_BUFFER_HPP_8G2XAHRG
#define SERIALIZATION_BUFFER_HPP_8G2XAHRG
#include <vanetza/common/byte_buffer.hpp>
#include <vanetza/common/byte_buffer_sink.hpp>
#include <vanetza/common/byte_buffer_source.hpp>
#include <vanetza/geonet/serialization.hpp>
#include <boost/iostreams/stream_buffer.hpp>
namespace vanetza
{
namespace geonet
{
/**
* This function is deprecated.
* It will be removed as soon as geonet::serialize signatures are
* compatible with common::serialize_into_buffer
*/
template<typename T>
void serialize_into_buffer(const T& t, ByteBuffer& buf)
{
byte_buffer_sink sink(buf);
boost::iostreams::stream_buffer<byte_buffer_sink> stream(sink);
OutputArchive ar(stream);
serialize(t, ar);
}
/**
* This function is deprecated.
* It will be removed as soon as geonet::deserialize signatures are
* compatible with common::deserialize_into_buffer
*/
template<typename T>
void deserialize_from_buffer(T& t, const ByteBuffer& buf)
{
byte_buffer_source source(buf);
boost::iostreams::stream_buffer<byte_buffer_source> stream(source);
InputArchive ar(stream);
deserialize(t, ar);
}
} // namespace geonet
} // namespace vanetza
#endif /* SERIALIZATION_BUFFER_HPP_8G2XAHRG */
@@ -0,0 +1,29 @@
#include "shb_header.hpp"
#include "serialization.hpp"
namespace vanetza
{
namespace geonet
{
constexpr std::size_t ShbHeader::length_bytes;
ShbHeader::ShbHeader() : dcc(0u)
{
}
void serialize(const ShbHeader& hdr, OutputArchive& ar)
{
serialize(hdr.source_position, ar);
serialize(hdr.dcc, ar);
}
void deserialize(ShbHeader& hdr, InputArchive& ar)
{
deserialize(hdr.source_position, ar);
deserialize(hdr.dcc, ar);
}
} // namespace geonet
} // namespace vanetza
@@ -0,0 +1,30 @@
#ifndef SHB_HEADER_HPP_MRLDRPNK
#define SHB_HEADER_HPP_MRLDRPNK
#include <vanetza/geonet/dcc_field.hpp>
#include <vanetza/geonet/position_vector.hpp>
#include <cstdint>
namespace vanetza
{
namespace geonet
{
struct ShbHeader
{
ShbHeader();
static constexpr std::size_t length_bytes = 4 + LongPositionVector::length_bytes;
LongPositionVector source_position;
DccField dcc; /* < this field is "reserved" in 102 636-4-1 */
};
void serialize(const ShbHeader&, OutputArchive&);
void deserialize(ShbHeader&, InputArchive&);
} // namespace geonet
} // namespace vanetza
#endif /* SHB_HEADER_HPP_MRLDRPNK */
@@ -0,0 +1,247 @@
#ifndef SOFT_STATE_MAP_HPP_B0MARRWZ
#define SOFT_STATE_MAP_HPP_B0MARRWZ
#include <vanetza/common/clock.hpp>
#include <vanetza/common/runtime.hpp>
#include <boost/heap/binomial_heap.hpp>
#include <boost/range/iterator_range_core.hpp>
#include <boost/range/adaptor/filtered.hpp>
#include <boost/range/adaptor/transformed.hpp>
#include <cassert>
#include <functional>
#include <type_traits>
#include <unordered_map>
namespace vanetza
{
namespace geonet
{
template<typename VALUE>
struct SoftStateDefaultCreator
{
VALUE operator()() { return VALUE(); }
};
/**
* SoftStateMap is a map data structure with expiring entries
* \tparam KEY key type
* \tparam VALUE mapped type
* \tparam CTOR optional creator of values
*/
template<typename KEY, typename VALUE, typename CTOR = SoftStateDefaultCreator<VALUE>>
class SoftStateMap
{
public:
using key_type = KEY;
using mapped_type = VALUE;
using value_type = std::pair<const key_type&, mapped_type&>;
using creator_type = CTOR;
private:
class ExpiryWithKey : public Clock::time_point
{
public:
ExpiryWithKey() = default;
ExpiryWithKey(const key_type& key, Clock::time_point expiry) :
Clock::time_point(expiry), m_key(key) {}
const key_type& key() const { return m_key; }
private:
key_type m_key;
};
using heap_type = boost::heap::binomial_heap<ExpiryWithKey, boost::heap::compare<std::greater<ExpiryWithKey>>>;
struct ValueWithHandle
{
ValueWithHandle(mapped_type&& v) : value(std::move(v)) {}
typename heap_type::handle_type handle;
mapped_type value;
mapped_type& operator*() { return value; }
const mapped_type& operator*() const { return value; }
};
using map_type = std::unordered_map<key_type, ValueWithHandle>;
using data_range = boost::iterator_range<typename map_type::iterator>;
using data_filter = std::function<bool(const typename map_type::value_type&)>;
using data_transform = std::function<value_type(typename map_type::value_type&)>;
public:
/**
* Construct SoftStateMap
* \param rt runtime object
* \note This constructor is only available if CTOR is default constructible
*/
template<typename T = CTOR>
SoftStateMap(const Runtime& rt, typename std::enable_if<std::is_default_constructible<T>::value>::type* = nullptr) :
m_runtime(rt)
{
}
/**
* Construct SoftStateMap
* \param rt runtime object
* \param ctor value creator
*/
SoftStateMap(const Runtime& rt, creator_type&& ctor) :
m_runtime(rt), m_creator(std::move(ctor))
{
}
/**
* Set lifetime duration used for new and refreshed entries
* \param lifetime entry lieftime
*/
void set_lifetime(Clock::duration lifetime)
{
m_lifetime = lifetime;
}
/**
* Get value mapped to key
* \param key
* \return existing value entry or just created entry
*/
mapped_type& get_value(const key_type& key)
{
return get_data(key).value;
}
/**
* Get non-expired value pointer mapped to key
* \param key
* \return pointer to value or nullptr if not existing
*/
mapped_type* get_value_ptr(const key_type& key)
{
auto* data = get_data_ptr(key);
return data && !is_expired(*data->handle) ? &data->value : nullptr;
}
/**
* Get non-expired value pointer mapped to key
* \param key
* \return pointer to value or nullptr if not existing
*/
const mapped_type* get_value_ptr(const key_type& key) const
{
auto* data = get_data_ptr(key);
return data && !is_expired(*data->handle) ? &data->value : nullptr;
}
/**
* Check if non-expired value for given key exists
* \param key
* \return true if entry exists
*/
bool has_value(const key_type& key) const
{
return get_value_ptr(key) != nullptr;
}
/**
* Refresh lifetime of entry associated with given key
* \param key
* \return associated value (might have been created)
*/
mapped_type& refresh(const key_type& key)
{
auto* data = get_data_ptr(key);
if (data) {
refresh(data->handle);
} else {
data = &get_data(key);
assert(data != nullptr);
}
return data->value;
}
/**
* Drop all entries with expired lifetime.
* Expired but still stored entries are only hided at retrieval until calling this method.
*/
void drop_expired()
{
while (!m_heap.empty() && is_expired(m_heap.top())) {
m_map.erase(m_heap.top().key());
m_heap.pop();
}
}
using map_range = boost::transformed_range<data_transform, const boost::filtered_range<data_filter, data_range>>;
/**
* Create a range of all non-expired entries mimicking STL's map interface
*/
map_range map()
{
data_filter filter_fn = [this](const typename map_type::value_type& v) {
return !this->is_expired(*v.second.handle);
};
data_transform transform_fn = [](typename map_type::value_type& v) {
return value_type { v.first, v.second.value };
};
using namespace boost::adaptors;
data_range range_all = boost::make_iterator_range(m_map.begin(), m_map.end());
return range_all | filtered(filter_fn) | transformed(transform_fn);
}
const map_range map() const
{
return const_cast<SoftStateMap*>(this)->map();
}
private:
ValueWithHandle& get_data(const key_type& key)
{
auto* data = get_data_ptr(key);
if (!data) {
auto insertion = m_map.emplace(std::piecewise_construct,
std::forward_as_tuple(key), std::forward_as_tuple(m_creator()));
data = &insertion.first->second;
data->handle = m_heap.push(ExpiryWithKey {key, m_runtime.now() + m_lifetime});
} else if (is_expired(*data->handle)) {
// resurrect this data element, i.e. pretend it has just been created
refresh(data->handle);
}
return *data;
}
ValueWithHandle* get_data_ptr(const key_type& key)
{
auto it = m_map.find(key);
return it != m_map.end() ? &it->second : nullptr;
}
const ValueWithHandle* get_data_ptr(const key_type& key) const
{
auto it = m_map.find(key);
return it != m_map.end() ? &it->second : nullptr;
}
bool is_expired(const ExpiryWithKey& expiry) const
{
return m_runtime.now() > expiry;
}
void refresh(typename heap_type::handle_type& handle)
{
ExpiryWithKey& expiry = *handle;
static_cast<Clock::time_point&>(expiry) = m_runtime.now() + m_lifetime;
m_heap.update(handle);
}
const Runtime& m_runtime;
Clock::duration m_lifetime;
creator_type m_creator;
heap_type m_heap;
map_type m_map;
};
} // namespace geonet
} // namespace vanetza
#endif /* SOFT_STATE_MAP_HPP_B0MARRWZ */
@@ -0,0 +1,29 @@
#ifndef STATION_TYPE_HPP_BARYX6ET
#define STATION_TYPE_HPP_BARYX6ET
namespace vanetza
{
namespace geonet
{
enum class StationType {
Unknown = 0,
Pedestrian = 1,
Cyclist = 2,
Moped = 3,
Motorcycle = 4,
Passenger_Car = 5,
Bus = 6,
Light_Truck = 7,
Heavy_Truck = 8,
Trailer = 9,
Special_Vehicle = 10,
Tram = 11,
RSU = 15
};
} // namespace geonet
} // namespace vanetza
#endif /* STATION_TYPE_HPP_BARYX6ET */
@@ -0,0 +1,31 @@
include(UseGTest)
configure_gtest_directory(LINK_LIBRARIES geonet)
add_gtest(Address address.cpp)
add_gtest(Areas areas.cpp)
add_gtest(BasicHeader basic_header.cpp)
add_gtest(CbfPacketBuffer cbf_packet_buffer.cpp)
add_gtest(CbrAggregator cbr_aggregator.cpp)
add_gtest(CommonHeader common_header.cpp)
add_gtest(DataConfirm data_confirm.cpp)
add_gtest(DataRequest data_request.cpp)
add_gtest(DccMcoField dcc_mco_field.cpp)
add_gtest(DuplicatePacketList duplicate_packet_list.cpp)
add_gtest(GbcGacHeader gbc_gac_header.cpp)
add_gtest(GbcMemory
SOURCES gbc_memory.cpp network_topology.cpp)
add_gtest(Lifetime lifetime.cpp)
add_gtest(LocationTable location_table.cpp)
add_gtest(PacketBuffer packet_buffer.cpp)
add_gtest(PositionUpdater position_updater.cpp)
add_gtest(PositionVector position_vector.cpp)
add_gtest(Repeater repeater.cpp)
add_gtest(Router router.cpp)
add_gtest(RouterIndicate router_indicate.cpp
INCLUDE_DIRECTORIES ${PROJECT_SOURCE_DIR}/vanetza/security/tests)
add_gtest(SequenceNumber sequence_number.cpp)
add_gtest(Timestamp timestamp.cpp)
add_gtest(TrafficClass traffic_class.cpp)
add_gtest(Routing
SOURCES network_topology.cpp routing.cpp)
add_gtest(RouterRequest router_request.cpp)
@@ -0,0 +1,51 @@
#include <gtest/gtest.h>
#include <vanetza/geonet/address.hpp>
#include <vanetza/geonet/serialization_buffer.hpp>
#include <vanetza/net/mac_address.hpp>
using namespace vanetza;
using namespace vanetza::geonet;
TEST(Address, ctor) {
Address a;
EXPECT_FALSE(a.is_manually_configured());
EXPECT_EQ(a.mid(), MacAddress());
EXPECT_EQ(a.station_type(), StationType::Unknown);
EXPECT_EQ(a.country_code(), 0);
}
TEST(Address, equality) {
Address a;
Address b({0x01, 0x02, 0x03, 0x04, 0x05, 0x06});
EXPECT_NE(a, b);
Address c = b;
EXPECT_EQ(b, c);
c.is_manually_configured(true);
EXPECT_NE(b, c);
Address d = c;
EXPECT_EQ(c, d);
d.station_type(StationType::Passenger_Car);
EXPECT_NE(c, d);
Address e = d;
EXPECT_EQ(d, e);
e.country_code(8);
EXPECT_NE(d, e);
a.mid(b.mid());
EXPECT_EQ(a, b);
}
TEST(Address, serialization) {
Address a({1, 2, 3, 4, 5, 6});
a.is_manually_configured(true);
a.station_type(StationType::Tram);
a.country_code(0x0333);
ByteBuffer buffer;
serialize_into_buffer(a, buffer);
EXPECT_EQ(Address::length_bytes, buffer.size());
Address b;
deserialize_from_buffer(b, buffer);
EXPECT_EQ(a, b);
}
@@ -0,0 +1,114 @@
#include <gtest/gtest.h>
#include <vanetza/geonet/areas.hpp>
#include <vanetza/units/length.hpp>
using namespace vanetza::geonet;
namespace units = vanetza::units;
using units::si::meter;
using units::degree;
using units::si::square_meter;
TEST(Areas, cartesian_substraction) {
CartesianPosition a(-3.4 * meter , 8.3 * meter);
CartesianPosition b(34.8 * meter, -14.8 * meter);
CartesianPosition c = a - b;
EXPECT_DOUBLE_EQ(c.x / meter, -38.2);
EXPECT_DOUBLE_EQ(c.y / meter, 23.1);
}
TEST(Areas, geodetic_distance) {
GeodeticPosition a(48.76714 * degree, 11.43263 * degree);
GeodeticPosition b(-25.41272 * degree, -49.24815 * degree);
units::Length d = distance(a, b);
const double expected_m = 10185367.442;
// accept less than 0.5% error for this large distance
EXPECT_NEAR(d / meter, expected_m, 0.005 * expected_m);
EXPECT_DOUBLE_EQ(0.0, distance(a, a).value());
}
TEST(Areas, geometric_function_circle) {
Circle c;
c.r = 38.4 * meter;
CartesianPosition p(0.0 * meter, 0.0 * meter);
EXPECT_TRUE(at_center_point(c, p));
EXPECT_TRUE(inside_shape(c, p));
EXPECT_FALSE(outside_shape(c, p));
EXPECT_FALSE(at_shape_border(c, p));
p.x = 15.0 * meter;
p.y = 36.0 * meter;
EXPECT_TRUE(outside_shape(c, p));
}
TEST(Areas, geometric_function_rectangle) {
Rectangle r;
r.a = 8.5 * meter;
r.b = 3.0 * meter;
CartesianPosition p(-3.5 * meter, 2.9 * meter);
EXPECT_TRUE(inside_shape(r, p));
p.x = -8.6 * meter;
EXPECT_TRUE(outside_shape(r, p));
}
TEST(Areas, geometric_function_ellipse) {
Ellipse e;
e.a = 8.4 * meter;
e.b = 6.5 * meter;
CartesianPosition p(-7.6 * meter, 1.3 * meter);
EXPECT_TRUE(inside_shape(e, p));
p.y = -4.5 * meter;
EXPECT_TRUE(outside_shape(e, p));
}
TEST(Areas, local_cartesian) {
GeodeticPosition origin(48.76714 * degree, 11.43263 * degree); // THI
GeodeticPosition datum(48.7656 * degree, 11.4296 * degree); // ZAF
CartesianPosition pos = local_cartesian(origin, datum);
EXPECT_NEAR(pos.x / meter, -222.74, 1.0);
EXPECT_NEAR(pos.y / meter, -171.25, 1.0);
}
TEST(Areas, canonicalize) {
CartesianPosition point(3.0 * meter, 2.0 * meter);
units::Angle azimuth = units::Angle(30.0 * degree);
CartesianPosition canonical_point = canonicalize(point, azimuth);
EXPECT_NEAR(canonical_point.x / meter, 3.23, 0.01);
EXPECT_NEAR(canonical_point.y / meter, -1.59, 0.01);
}
TEST(Areas, inside_or_at_border) {
Rectangle r;
r.a = 300.0 * meter;
r.b = 170.0 * meter;
Area a;
a.shape = r;
a.angle = units::Angle(90.0 * degree);
a.position = GeodeticPosition(48.7656 * degree, 11.4296 * degree);
GeodeticPosition ego(48.76714 * degree, 11.43263 * degree);
EXPECT_FALSE(inside_or_at_border(a, ego));
a.angle = units::Angle(45.0 * degree);
EXPECT_TRUE(inside_or_at_border(a, ego));
}
TEST(Areas, area_size) {
Circle c;
c.r = 18.3 * meter;
Rectangle r;
r.a = 393.0 * meter;
r.b = 140.8 * meter;
Ellipse e;
e.a = 393.0 * meter;
e.b = 140.8 * meter;
Area a;
a.shape = c;
EXPECT_NEAR(area_size(a) / square_meter, 1052.0880, 0.0001);
a.shape = r;
EXPECT_NEAR(area_size(a) / square_meter, 221337.6000, 0.0001);
a.shape = e;
EXPECT_NEAR(area_size(a) / square_meter, 173838.1445, 0.0001);
}
@@ -0,0 +1,43 @@
#include <gtest/gtest.h>
#include <vanetza/geonet/basic_header.hpp>
#include <vanetza/geonet/data_request.hpp>
#include <vanetza/geonet/tests/serialization.hpp>
using namespace vanetza::geonet;
using vanetza::units::si::seconds;
TEST(BasicHeader, ctor) {
MIB mib;
BasicHeader a(mib);
EXPECT_EQ(a.lifetime, mib.itsGnDefaultPacketLifetime);
EXPECT_EQ(a.hop_limit, mib.itsGnDefaultHopLimit);
DataRequest req(mib);
req.maximum_lifetime.encode(31.0 * seconds);
req.max_hop_limit = 4;
BasicHeader b(req, mib);
EXPECT_EQ(b.lifetime.decode(), 31.0 * seconds);
EXPECT_EQ(b.hop_limit, 4);
ShbDataRequest shb(mib);
BasicHeader c(shb, mib);
EXPECT_EQ(c.hop_limit, 1);
}
TEST(BasicHeader, serialization) {
BasicHeader a;
a.version = 2;
a.next_header = NextHeaderBasic::Secured;
a.reserved = 0xC3;
a.lifetime.raw(0x89);
a.hop_limit = 218;
BasicHeader b = serialize_roundtrip(a);
EXPECT_EQ(a.version, b.version);
EXPECT_EQ(a.next_header, b.next_header);
EXPECT_EQ(a.reserved, b.reserved);
EXPECT_EQ(a.lifetime, b.lifetime);
EXPECT_EQ(a.hop_limit, b.hop_limit);
EXPECT_EQ(BasicHeader::length_bytes, serialize_length(a));
}
@@ -0,0 +1,325 @@
#include <gtest/gtest.h>
#include <vanetza/common/manual_runtime.hpp>
#include <vanetza/geonet/cbf_counter.hpp>
#include <vanetza/geonet/cbf_packet_buffer.hpp>
#include <vanetza/geonet/mib.hpp>
#include <functional>
using namespace std::chrono;
using namespace vanetza;
using namespace vanetza::geonet;
static const size_t GbcPduLength = BasicHeader::length_bytes +
CommonHeader::length_bytes + GeoBroadcastHeader::length_bytes;
class CbfPacketBufferTest : public ::testing::Test
{
protected:
using PduPtr = std::unique_ptr<GbcPdu>;
using PayloadPtr = std::unique_ptr<DownPacket>;
using PendingPacketCbf = PendingPacket<GbcPdu>;
void SetUp() override
{
// lifetime of 3 seconds can be stored with 50 ms accuracy
mib.itsGnDefaultPacketLifetime.encode(3.0 * units::si::seconds);
runtime.reset(Clock::time_point { hours(42) });
calls = 0;
last_call_length = 0;
}
CbfPacket create_packet(const MacAddress&, SequenceNumber::value_type, std::size_t length = GbcPduLength) const;
CbfPacket create_packet(std::size_t length = GbcPduLength) const;
CbfPacketBuffer::TimerCallback callback();
std::unique_ptr<CbfCounter> counter();
MIB mib;
ManualRuntime runtime;
unsigned calls;
unsigned last_call_length;
};
CbfPacket CbfPacketBufferTest::create_packet(const MacAddress& mac, SequenceNumber::value_type sn, std::size_t size) const
{
PduPtr pdu { new CbfPacketBufferTest::PduPtr::element_type(mib) };
pdu->extended().source_position.gn_addr.mid(mac);
pdu->extended().sequence_number = SequenceNumber { sn };
PayloadPtr payload { new CbfPacketBufferTest::PayloadPtr::element_type() };
assert(get_length(*pdu) <= size);
const std::size_t payload_size = size - get_length(*pdu);
payload->layer(OsiLayer::Application) = ByteBuffer(payload_size);
PendingPacketCbf pending { std::make_tuple(std::move(pdu), std::move(payload)), [](PendingPacketCbf::Packet&&) {} };
return CbfPacket(std::move(pending), cBroadcastMacAddress);
}
CbfPacket CbfPacketBufferTest::create_packet(std::size_t length) const
{
static unsigned counter = 0;
return create_packet({0, 0, 0, 0, 0, 0}, ++counter, length);
}
CbfPacketBuffer::TimerCallback CbfPacketBufferTest::callback()
{
return [this](PendingPacketCbf&& data) {
++calls;
last_call_length = data.length();
};
}
std::unique_ptr<CbfCounter> CbfPacketBufferTest::counter()
{
return std::unique_ptr<CbfCounter> { new CbfCounterImmortal() };
}
TEST_F(CbfPacketBufferTest, identifier_hash)
{
std::hash<CbfPacketIdentifier> hasher;
CbfPacketIdentifier id1 { Address {{ 1, 2, 3, 4, 5, 6}}, SequenceNumber(2) };
CbfPacketIdentifier id2 { Address {{ 1, 2, 3, 4, 5, 6}}, SequenceNumber(3) };
CbfPacketIdentifier id3 { Address {{ 1, 2, 3, 4, 5, 6}}, SequenceNumber(2) };
CbfPacketIdentifier id4 { Address {{ 1, 2, 3, 4, 5, 7}}, SequenceNumber(3) };
EXPECT_EQ(id1, id3);
EXPECT_EQ(hasher(id1), hasher(id3));
EXPECT_NE(id1, id2);
EXPECT_NE(hasher(id1), hasher(id2));
EXPECT_NE(id2, id4);
EXPECT_NE(hasher(id2), hasher(id4));
}
TEST_F(CbfPacketBufferTest, packet_identifier)
{
const MacAddress mac { 1, 3, 5, 7, 9, 11 };
CbfPacket packet = create_packet(mac, 8);
EXPECT_EQ(mac, packet.source().mid());
EXPECT_EQ(SequenceNumber { 8 }, packet.sequence_number());
}
TEST_F(CbfPacketBufferTest, packet_lifetime)
{
CbfPacket packet = create_packet({}, 1);
// check initialization
using vanetza::units::clock_cast;
EXPECT_EQ(clock_cast(mib.itsGnDefaultPacketLifetime.decode()), packet.reduce_lifetime(Clock::duration::zero()));
// lifetime has to be modifiable
Clock::duration lifetime = packet.reduce_lifetime(Clock::duration::zero());
EXPECT_EQ(lifetime - milliseconds(50), packet.reduce_lifetime(milliseconds(50)));
// but negative reductions have no effect
EXPECT_EQ(lifetime - milliseconds(50), packet.reduce_lifetime(milliseconds(-100)));
// and lifetime does not go below zero
EXPECT_EQ(Clock::duration::zero(), packet.reduce_lifetime(milliseconds(6000)));
}
TEST_F(CbfPacketBufferTest, packet_length)
{
CbfPacket packet1 = create_packet({0, 1, 2, 3, 4, 5}, 3);
EXPECT_EQ(GbcPduLength, packet1.length());
CbfPacket packet2 = create_packet({0, 1, 2, 3, 4, 5}, 3, 64);
EXPECT_EQ(64, packet2.length());
}
TEST_F(CbfPacketBufferTest, find)
{
CbfPacketBuffer buffer(runtime, callback(), counter(), 8192);
auto found1 = buffer.find(identifier(Address {{1, 2, 3, 4, 5, 6}}, SequenceNumber(3)));
EXPECT_FALSE(found1);
auto packet1 = create_packet({1, 2, 3, 4, 5, 6}, 3);
buffer.add(std::move(packet1), seconds(5));
auto found2 = buffer.find(identifier(Address {{1, 2, 3, 4, 5, 6}}, SequenceNumber(4)));
EXPECT_FALSE(found2);
auto found3 = buffer.find(identifier(Address {{1, 2, 3, 4, 5, 6}}, SequenceNumber(3)));
ASSERT_TRUE(found3);
EXPECT_EQ(1, buffer.counter(identifier(*found3)));
EXPECT_EQ((MacAddress {1, 2, 3, 4, 5, 6}), found3->source().mid());
}
TEST_F(CbfPacketBufferTest, counter)
{
CbfPacket packet1 = create_packet({3, 8, 3, 8, 3, 8}, 10);
CbfPacketIdentifier id1 = identifier(packet1);
CbfPacketBuffer buffer(runtime, callback(), counter(), 8192);
EXPECT_EQ(0, buffer.counter(id1));
buffer.add(std::move(packet1), milliseconds(30));
EXPECT_EQ(1, buffer.counter(id1));
buffer.remove(id1);
EXPECT_EQ(1, buffer.counter(id1));
CbfPacket packet2 = create_packet({3, 8, 3, 8, 3, 8}, 11);
CbfPacketIdentifier id2 = identifier(packet2);
buffer.update(id2, milliseconds(30));
EXPECT_EQ(0, buffer.counter(id2));
buffer.add(std::move(packet2), milliseconds(30));
EXPECT_EQ(1, buffer.counter(id2));
buffer.update(id2, milliseconds(30));
EXPECT_EQ(2, buffer.counter(id2));
EXPECT_EQ(1, buffer.counter(id1));
}
TEST_F(CbfPacketBufferTest, fetch)
{
CbfPacketBuffer buffer(runtime, callback(), counter(), 8192);
auto found1 = buffer.fetch(identifier(Address {{1, 2, 3, 4, 5, 6}}, SequenceNumber(3)));
EXPECT_FALSE(!!found1);
auto packet1 = create_packet({1, 2, 3, 4, 5, 6}, 3);
buffer.add(std::move(packet1), milliseconds(500));
auto found2 = buffer.fetch(identifier(Address {{1, 2, 3, 4, 5, 6}}, SequenceNumber(4)));
EXPECT_FALSE(!!found2);
auto found3 = buffer.fetch(identifier(Address {{1, 2, 3, 4, 5, 6}}, SequenceNumber(3)));
ASSERT_TRUE(!!found3);
EXPECT_EQ((MacAddress {1, 2, 3, 4, 5, 6}), found3->source().mid());
auto found4 = buffer.fetch(identifier(Address {{1, 2, 3, 4, 5, 6}}, SequenceNumber(3)));
EXPECT_FALSE(!!found4);
}
TEST_F(CbfPacketBufferTest, fetch_reduce_lifetime)
{
CbfPacketBuffer buffer(runtime, callback(), counter(), 8192);
auto packet = create_packet({1, 2, 3, 4, 5, 6}, 1);
buffer.add(std::move(packet), milliseconds(500));
runtime.trigger(milliseconds(200));
auto found = buffer.fetch(identifier(Address {{1, 2, 3, 4, 5, 6}}, SequenceNumber(1)));
ASSERT_TRUE(!!found);
EXPECT_EQ(milliseconds(2800), found->reduce_lifetime(Clock::duration::zero()));
}
TEST_F(CbfPacketBufferTest, next_timer_expiry)
{
const Address addr {{1, 2, 3, 4, 5, 6}};
CbfPacketBuffer buffer(runtime, callback(), counter(), 8192);
EXPECT_EQ(Clock::time_point::max(), runtime.next());
buffer.add(create_packet(), seconds(3));
EXPECT_EQ(seconds(3), runtime.next() - runtime.now());
runtime.trigger(seconds(1));
buffer.add(create_packet(addr.mid(), 3), seconds(1));
EXPECT_EQ(seconds(1), runtime.next() - runtime.now());
buffer.add(create_packet(addr.mid(), 2), milliseconds(200));
EXPECT_EQ(milliseconds(200), runtime.next() - runtime.now());
runtime.trigger(milliseconds(100));
auto fetch = buffer.fetch(identifier(addr, SequenceNumber(2)));
EXPECT_TRUE(!!fetch);
EXPECT_EQ(milliseconds(900), runtime.next() - runtime.now());
bool dropped = buffer.remove(identifier(addr, SequenceNumber(3)));
EXPECT_TRUE(dropped);
EXPECT_EQ(milliseconds(1900), runtime.next() - runtime.now());
}
TEST_F(CbfPacketBufferTest, remove_sequence_number)
{
CbfPacketBuffer buffer(runtime, callback(), counter(), 8192);
const auto addr = Address {{1, 1, 1, 1, 1, 1}};
EXPECT_FALSE(buffer.remove(identifier(addr, SequenceNumber(3))));
auto packet = create_packet(addr.mid(), 8);
buffer.add(std::move(packet), milliseconds(400));
EXPECT_FALSE(buffer.remove(identifier(addr, SequenceNumber(7))));
EXPECT_FALSE(buffer.remove(identifier(addr, SequenceNumber(9))));
EXPECT_TRUE(buffer.remove(identifier(addr, SequenceNumber(8))));
EXPECT_FALSE(buffer.remove(identifier(addr, SequenceNumber(8))));
}
TEST_F(CbfPacketBufferTest, remove_drop_addr)
{
CbfPacketBuffer buffer(runtime, callback(), counter(), 8192);
const auto addr1 = Address {{1, 1, 1, 1, 1, 1}};
const auto addr2 = Address {{ 2, 2, 2, 2, 2, 2}};
auto packet = create_packet(addr1.mid(), 8);
buffer.add(std::move(packet), milliseconds(400));
EXPECT_FALSE(buffer.remove(identifier(addr2, SequenceNumber(8))));
EXPECT_TRUE(buffer.remove(identifier(addr1, SequenceNumber(8))));
EXPECT_FALSE(buffer.remove(identifier(addr1, SequenceNumber(8))));
}
TEST_F(CbfPacketBufferTest, remove_multiple_packets)
{
CbfPacketBuffer buffer(runtime, callback(), counter(), 8192);
const auto addr = Address {{1, 1, 1, 1, 1, 1}};
const auto timeout = milliseconds(400);
auto packet1 = create_packet(addr.mid(), 8);
buffer.add(std::move(packet1), timeout);
auto packet2 = create_packet(addr.mid(), 10);
buffer.add(std::move(packet2), timeout);
EXPECT_FALSE(buffer.remove(identifier(addr, SequenceNumber(9))));
EXPECT_TRUE(buffer.remove(identifier(addr, SequenceNumber(10))));
EXPECT_TRUE(buffer.remove(identifier(addr, SequenceNumber(8))));
}
TEST_F(CbfPacketBufferTest, capacity)
{
CbfPacketBuffer buffer(runtime, callback(), counter(), 256);
buffer.add(create_packet(128), seconds(1));
buffer.add(create_packet(128), seconds(1));
runtime.trigger(milliseconds(1010));
EXPECT_EQ(2, calls);
buffer.add(create_packet(157), seconds(1));
buffer.add(create_packet(100), seconds(1));
runtime.trigger(seconds(2));
EXPECT_EQ(3, calls);
EXPECT_EQ(100, last_call_length);
}
TEST_F(CbfPacketBufferTest, packets_to_send)
{
std::vector<PendingPacketCbf> packets;
auto cb = [&packets](PendingPacketCbf&& data) { packets.emplace_back(std::move(data)); };
CbfPacketBuffer buffer(runtime, cb, counter(), 8192);
runtime.trigger(minutes(42));
EXPECT_EQ(0, packets.size());
buffer.add(create_packet(110), milliseconds(2500));
runtime.trigger(seconds(1));
EXPECT_EQ(0, packets.size());
buffer.add(create_packet(120), seconds(1));
runtime.trigger(seconds(1));
ASSERT_EQ(1, packets.size());
EXPECT_EQ(120, packets[0].length());
runtime.trigger(milliseconds(500));
EXPECT_EQ(2, packets.size());
buffer.add(create_packet(130), seconds(1));
buffer.add(create_packet(140), milliseconds(1500));
runtime.trigger(seconds(2));
ASSERT_EQ(4, packets.size());
EXPECT_EQ(130, packets[2].length());
EXPECT_EQ(140, packets[3].length());
// check if lifetime is reduced by queuing time
auto packet = create_packet(150);
EXPECT_EQ(milliseconds(1000), packet.reduce_lifetime(milliseconds(2000)));
buffer.add(std::move(packet), milliseconds(72));
runtime.trigger(runtime.next());
ASSERT_EQ(5, packets.size());
// Lifetime can only be encoded in 50ms steps (in best case): 950 ms remaining lifetime
EXPECT_EQ((Lifetime {Lifetime::Base::Fifty_Milliseconds, 19}), packets[4].pdu().basic().lifetime);
}
@@ -0,0 +1,106 @@
#include <gtest/gtest.h>
#include <vanetza/common/manual_runtime.hpp>
#include <vanetza/geonet/cbr_aggregator.hpp>
#include <vanetza/geonet/location_table.hpp>
#include <vanetza/geonet/loctex_g5.hpp>
#include <vanetza/geonet/mib.hpp>
#include <vanetza/net/mac_address.hpp>
using namespace vanetza;
using namespace vanetza::geonet;
using vanetza::dcc::ChannelLoad;
using std::chrono::seconds;
class CbrAggregatorTest : public ::testing::Test
{
protected:
void SetUp() override
{
mib.reset(new MIB());
runtime.reset(new ManualRuntime(Clock::at("2017-05-20 17:36:00")));
location_table.reset(new LocationTable(*mib, *runtime));
}
void TearDown() override
{
location_table.reset();
runtime.reset();
mib.reset();
}
Timestamp timestamp_earlier(std::chrono::milliseconds ms)
{
Timestamp ts { runtime->now() };
ts -= Timestamp::duration_type { ms.count() * Timestamp::millisecond() };
return ts;
}
Address address(unsigned i)
{
Address addr;
addr.mid(create_mac_address(i));
return addr;
}
std::unique_ptr<LocTEX_G5> loctex_g5(Clock::duration age, double local, double one_hop)
{
std::unique_ptr<LocTEX_G5> entry { new LocTEX_G5() };
entry->local_update = Timestamp { runtime->now() - age };
entry->dcc_mco.local_cbr(ChannelLoad (local));
entry->dcc_mco.neighbour_cbr(ChannelLoad (one_hop));
return entry;
}
std::unique_ptr<Runtime> runtime;
std::unique_ptr<MIB> mib;
std::unique_ptr<LocationTable> location_table;
};
TEST_F(CbrAggregatorTest, init)
{
CbrAggregator cbra;
EXPECT_EQ(ChannelLoad(0.0), cbra.get_local_cbr());
EXPECT_EQ(ChannelLoad(0.0), cbra.get_one_hop_cbr());
EXPECT_EQ(ChannelLoad(0.0), cbra.get_two_hop_cbr());
EXPECT_EQ(ChannelLoad(0.0), cbra.get_global_cbr());
}
TEST_F(CbrAggregatorTest, local_cbr)
{
CbrAggregator cbra;
ChannelLoad cbr_target(0.6);
cbra.aggregate(ChannelLoad(0.1), *location_table, timestamp_earlier(seconds(5)), cbr_target);
EXPECT_EQ(ChannelLoad(0.1), cbra.get_local_cbr());
cbra.aggregate(ChannelLoad(0.2), *location_table, timestamp_earlier(seconds(5)), cbr_target);
EXPECT_EQ(ChannelLoad(0.2), cbra.get_local_cbr());
// no one-hop and two-hop values have been provided: previous local measurement should be maximum
EXPECT_EQ(ChannelLoad(0.1), cbra.get_global_cbr());
}
TEST_F(CbrAggregatorTest, shared_cbr)
{
location_table->get_or_create_entry(address(1)).extensions.insert(loctex_g5(seconds(3), 0.45, 0.5));
location_table->get_or_create_entry(address(2)).extensions.insert(loctex_g5(seconds(2), 0.4, 0.2));
location_table->get_or_create_entry(address(3)).extensions.insert(loctex_g5(seconds(4), 0.4, 0.45));
location_table->get_or_create_entry(address(4)).extensions.insert(loctex_g5(seconds(2), 0.35, 0.25));
CbrAggregator cbra;
cbra.aggregate(ChannelLoad(0.32), *location_table, timestamp_earlier(seconds(10)), ChannelLoad(0.6));
EXPECT_DOUBLE_EQ(0.32, cbra.get_local_cbr().value());
EXPECT_NEAR(0.4, cbra.get_one_hop_cbr().value(), 0.005); // second largest (one hop average = 0.4 < target = 0.6)
EXPECT_NEAR(0.45, cbra.get_two_hop_cbr().value(), 0.005); // second largest (two hop average = 0.35 < target = 0.6)
EXPECT_NEAR(0.45, cbra.get_global_cbr().value(), 0.005);
cbra.aggregate(ChannelLoad(0.34), *location_table, timestamp_earlier(seconds(10)), ChannelLoad(0.3));
EXPECT_DOUBLE_EQ(0.34, cbra.get_local_cbr().value());
EXPECT_NEAR(0.45, cbra.get_one_hop_cbr().value(), 0.005); // largest (two hop average above target)
EXPECT_NEAR(0.5, cbra.get_two_hop_cbr().value(), 0.005); // largest (two hop average above target)
EXPECT_NEAR(0.5, cbra.get_global_cbr().value(), 0.005);
cbra.aggregate(ChannelLoad(0.3), *location_table, timestamp_earlier(seconds(2)), ChannelLoad(0.3));
EXPECT_DOUBLE_EQ(0.3, cbra.get_local_cbr().value());
EXPECT_NEAR(0.4, cbra.get_one_hop_cbr().value(), 0.005); // average (0.375) above target: largest
EXPECT_NEAR(0.2, cbra.get_two_hop_cbr().value(), 0.005); // average (0.225) below target: second largest
EXPECT_NEAR(0.4, cbra.get_global_cbr().value(), 0.005);
}
@@ -0,0 +1,52 @@
#include <gtest/gtest.h>
#include <vanetza/geonet/common_header.hpp>
#include <vanetza/geonet/data_request.hpp>
#include <vanetza/geonet/tests/serialization.hpp>
using namespace vanetza::geonet;
TEST(CommonHeader, ctor) {
MIB mib;
CommonHeader a(mib);
EXPECT_EQ(a.traffic_class.raw(), mib.itsGnDefaultTrafficClass.raw());
EXPECT_EQ(a.maximum_hop_limit, mib.itsGnDefaultHopLimit);
EXPECT_EQ(a.payload, 0);
DataRequest req(mib);
req.upper_protocol = UpperProtocol::BTP_B;
req.max_hop_limit = 3;
req.traffic_class.store_carry_forward(true);
CommonHeader b(req, mib);
EXPECT_EQ(b.next_header, NextHeaderCommon::BTP_B);
EXPECT_EQ(b.maximum_hop_limit, 3);
EXPECT_TRUE(b.traffic_class.store_carry_forward());
ShbDataRequest shb(mib);
CommonHeader c(shb, mib);
EXPECT_EQ(c.header_type, HeaderType::TSB_Single_Hop);
EXPECT_EQ(c.maximum_hop_limit, 1);
}
TEST(CommonHeader, serialization) {
CommonHeader a;
a.next_header = NextHeaderCommon::IPv6;
a.reserved1 = 12;
a.header_type = HeaderType::GeoAnycast_Elip;
a.traffic_class = TrafficClass(0xAB);
a.flags = 0x18;
a.payload = 0x1234;
a.maximum_hop_limit = 0x78;
a.reserved2 = 0x56;
CommonHeader b = serialize_roundtrip(a);
EXPECT_EQ(a.next_header, b.next_header);
EXPECT_EQ(a.reserved1, b.reserved1);
EXPECT_EQ(a.header_type, b.header_type);
EXPECT_EQ(a.traffic_class.raw(), b.traffic_class.raw());
EXPECT_EQ(a.flags, b.flags);
EXPECT_EQ(a.payload, b.payload);
EXPECT_EQ(a.maximum_hop_limit, b.maximum_hop_limit);
EXPECT_EQ(a.reserved2, b.reserved2);
EXPECT_EQ(CommonHeader::length_bytes, serialize_length(a));
}
@@ -0,0 +1,89 @@
#include <gtest/gtest.h>
#include <vanetza/common/byte_buffer.hpp>
#include <vanetza/geonet/data_confirm.hpp>
#include <vanetza/geonet/data_request.hpp>
#include <vanetza/geonet/packet.hpp>
#include <algorithm>
using namespace vanetza::geonet;
using vanetza::units::si::seconds;
using vanetza::units::si::meter;
TEST(DataConfirm, ctor) {
DataConfirm a;
EXPECT_EQ(a.result_code, DataConfirm::ResultCode::Accepted);
DataConfirm b(DataConfirm::ResultCode::Rejected_Unspecified);
EXPECT_EQ(b.result_code, DataConfirm::ResultCode::Rejected_Unspecified);
}
TEST(DataConfirm, accepted_rejected) {
DataConfirm a(DataConfirm::ResultCode::Rejected_Max_Lifetime);
EXPECT_TRUE(a.rejected());
EXPECT_FALSE(a.accepted());
a.result_code = DataConfirm::ResultCode::Accepted;
EXPECT_FALSE(a.rejected());
EXPECT_TRUE(a.accepted());
}
TEST(DataConfirm, validate_data_request) {
MIB mib;
DataRequest req(mib);
EXPECT_EQ(validate_data_request(req, mib),
DataConfirm::ResultCode::Accepted);
DataRequest req_lt(req);
req_lt.maximum_lifetime.encode(mib.itsGnMaxPacketLifetime.decode() + 10.0 * seconds);
EXPECT_EQ(validate_data_request(req_lt, mib),
DataConfirm::ResultCode::Rejected_Max_Lifetime);
DataRequest req_rep(req);
req_rep.repetition = DataRequest::Repetition();
req_rep.repetition->interval = mib.itsGnMinPacketRepetitionInterval - 1 * seconds;
EXPECT_EQ(validate_data_request(req_rep, mib),
DataConfirm::ResultCode::Rejected_Min_Repetition_Interval);
}
TEST(DataConfirm, validate_data_request_with_area) {
MIB mib;
DataRequestWithArea req(mib);
EXPECT_EQ(validate_data_request(req, mib),
DataConfirm::ResultCode::Accepted);
Circle c;
// radius = magnitude of max area size -> circle area is much larger
c.r = vanetza::units::Length(mib.itsGnMaxGeoAreaSize / meter); // hack!
req.destination.shape = c;
EXPECT_EQ(validate_data_request(req, mib),
DataConfirm::ResultCode::Rejected_Max_Geo_Area_Size);
}
TEST(DataConfirm, validate_payload) {
MIB mib;
std::unique_ptr<DownPacket> no_payload;
std::unique_ptr<DownPacket> giant_payload(new DownPacket());
{
vanetza::ByteBuffer giant_buffer;
std::fill_n(std::back_inserter(giant_buffer), 2048, 0x0f);
(*giant_payload)[vanetza::OsiLayer::Link] = std::move(giant_buffer);
}
std::unique_ptr<DownPacket> ok_payload(new DownPacket());
EXPECT_EQ(validate_payload(no_payload, mib),
DataConfirm::ResultCode::Rejected_Unspecified);
EXPECT_EQ(validate_payload(giant_payload, mib),
DataConfirm::ResultCode::Rejected_Max_SDU_Size);
EXPECT_EQ(validate_payload(ok_payload, mib),
DataConfirm::ResultCode::Accepted);
}
TEST(DataConfirm, xor_op) {
DataConfirm a;
EXPECT_EQ(a.result_code, DataConfirm::ResultCode::Accepted);
a ^= DataConfirm::ResultCode::Rejected_Max_Lifetime;
EXPECT_EQ(a.result_code, DataConfirm::ResultCode::Rejected_Max_Lifetime);
a ^= DataConfirm::ResultCode::Accepted;
EXPECT_EQ(a.result_code, DataConfirm::ResultCode::Rejected_Max_Lifetime);
a ^= DataConfirm::ResultCode::Rejected_Unspecified;
EXPECT_EQ(a.result_code, DataConfirm::ResultCode::Rejected_Unspecified);
}
@@ -0,0 +1,69 @@
#include <gtest/gtest.h>
#include <vanetza/geonet/data_request.hpp>
#include <vanetza/units/time.hpp>
using namespace vanetza::geonet;
using vanetza::units::si::seconds;
TEST(DataRequest, repetition) {
MIB mib;
DataRequest r(mib);
EXPECT_FALSE(!!r.repetition);
r.repetition = DataRequest::Repetition();
EXPECT_TRUE(!!r.repetition);
}
TEST(DataRequest, has_further_repetition) {
DataRequest::Repetition repetition;
repetition.interval = 10.0 * seconds;
repetition.maximum = 0.0 * seconds;
EXPECT_FALSE(has_further_repetition(repetition));
repetition.interval = 0.0 * seconds;
EXPECT_FALSE(has_further_repetition(repetition));
repetition.maximum = -10.0 * seconds;
repetition.interval = -15.0 * seconds;
EXPECT_FALSE(has_further_repetition(repetition));
repetition.maximum = 30.0 * seconds;
repetition.interval = 10.0 * seconds;
EXPECT_TRUE(has_further_repetition(repetition));
repetition.interval = 0.0 * seconds;
EXPECT_FALSE(has_further_repetition(repetition));
}
TEST(DataRequest, decrement_by_one) {
DataRequest::Repetition repetition;
repetition.maximum = 30.0 * seconds;
repetition.interval = 10.0 * seconds;
decrement_by_one(repetition);
EXPECT_DOUBLE_EQ(repetition.interval / seconds, 10.0);
EXPECT_DOUBLE_EQ(repetition.maximum / seconds, 20.0);
repetition.interval = 5.0 * seconds;
decrement_by_one(repetition);
EXPECT_DOUBLE_EQ(repetition.maximum / seconds, 15.0);
repetition.interval = 20.0 * seconds;
decrement_by_one(repetition);
EXPECT_DOUBLE_EQ(repetition.maximum / seconds, 0.0);
EXPECT_DOUBLE_EQ(repetition.interval / seconds, 20.0);
repetition.maximum = -30.0 * seconds;
repetition.interval = -60.0 * seconds;
decrement_by_one(repetition);
EXPECT_DOUBLE_EQ(repetition.maximum / seconds, 0.0);
repetition.maximum = -30.0 * seconds;
repetition.interval = -10.0 * seconds;
decrement_by_one(repetition);
EXPECT_DOUBLE_EQ(repetition.maximum / seconds, 0.0);
repetition.maximum = 30.0 * seconds;
repetition.interval = -10.0 * seconds;
decrement_by_one(repetition);
EXPECT_DOUBLE_EQ(repetition.maximum / seconds, 0.0);
}
@@ -0,0 +1,46 @@
#include <gtest/gtest.h>
#include <vanetza/geonet/dcc_mco_field.hpp>
using namespace vanetza::geonet;
using vanetza::dcc::ChannelLoad;
TEST(DccMcoField, ctor)
{
DccMcoField mco;
EXPECT_EQ(ChannelLoad(0.0), mco.local_cbr());
EXPECT_EQ(ChannelLoad(0.0), mco.neighbour_cbr());
EXPECT_EQ(0, mco.output_power());
}
TEST(DccMcoField, uint32_view)
{
DccMcoField mco;
EXPECT_EQ(0, static_cast<uint32_t>(mco));
// last 11 bits are reserved for future use: masked zero
mco = DccMcoField(0x12345678);
EXPECT_EQ(0x12345000, static_cast<uint32_t>(mco));
}
TEST(DccMcoField, channel_load)
{
DccMcoField mco;
mco.local_cbr(ChannelLoad(0.5));
EXPECT_NEAR(0.5, mco.local_cbr().value(), 1.0 / 255.0);
mco.neighbour_cbr(ChannelLoad(0.25));
EXPECT_NEAR(0.25, mco.neighbour_cbr().value(), 1.0 / 255.0);
}
TEST(DccMcoField, output_power)
{
DccMcoField mco;
mco.output_power(10);
EXPECT_EQ(10, mco.output_power());
mco.output_power(31);
EXPECT_EQ(31, mco.output_power());
mco.output_power(32);
EXPECT_EQ(31, mco.output_power());
}

Some files were not shown because too many files have changed in this diff Show More