Keep vanetza-idf in obu-firmware, so a plain clone builds the firmware

obu-firmware builds against the vanetza-idf C-ITS library, which until now
came from the colleague's microbu-esp32c5 tree beside the repository and was
not tracked here, so a clone of this repository could not build the firmware
it ships. The library alone is now part of obu-firmware, as
obu-firmware/external/vanetza-idf: their external/vanetza-idf at commit
cf4b99f, unchanged (9775 files; see its PROVENANCE.md). CMake takes it from
there by default; -DVANETZA_IDF_DIR still points the build elsewhere.

The rest of the colleague's tree (their own VAM firmware, PKI tooling,
station-link Python tools, the V2X2MAP bridge) stays out of this repository
and gitignored; nothing is pushed to their repository. NOTES.md, docs/06,
TODO.md and the pcap verifier's usage line point at the new location.
This commit is contained in:
Ashin Walpola
2026-09-24 10:56:05 +02:00
parent 2f60623e18
commit d107534eb2
9781 changed files with 1560475 additions and 17 deletions
@@ -0,0 +1,110 @@
if(NOT TARGET Boost::program_options)
message(STATUS "Skip build of socktap because of missing Boost::program_options dependency")
return()
endif()
find_package(Threads REQUIRED)
add_executable(socktap
application.cpp
benchmark_application.cpp
cam_application.cpp
dcc_passthrough.cpp
ethernet_device.cpp
hello_application.cpp
link_layer.cpp
main.cpp
positioning.cpp
raw_socket_link.cpp
router_context.cpp
security.cpp
tcp_link.cpp
time_trigger.cpp
udp_link.cpp
certificate_validation_v3.cpp
)
target_link_libraries(socktap PUBLIC Boost::program_options Threads::Threads vanetza)
if(VANETZA_WITH_PQC)
target_sources(socktap PRIVATE certificate_validation_v3_pqc.cpp)
else()
target_sources(socktap PRIVATE certificate_validation_v3_strict.cpp)
endif()
if (TARGET rpc)
set(SOCKTAP_WITH_RPC_DEFAULT ON)
else()
set(SOCKTAP_WITH_RPC_DEFAULT OFF)
endif()
option(SOCKTAP_WITH_RPC "Enable RPC support for socktap" ${SOCKTAP_WITH_RPC_DEFAULT})
if (SOCKTAP_WITH_RPC)
target_sources(socktap PRIVATE rpc_link.cpp)
target_link_libraries(socktap PUBLIC rpc)
target_compile_definitions(socktap PUBLIC SOCKTAP_WITH_RPC)
endif()
# cube evk board from nfiniity
option(SOCKTAP_WITH_CUBE_EVK "Use cube evk for socktap" OFF)
if (SOCKTAP_WITH_CUBE_EVK)
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE)
find_package(Protobuf REQUIRED)
protobuf_generate(TARGET socktap PROTOS nfiniity_cube_radio.proto)
target_compile_definitions(socktap PUBLIC "SOCKTAP_WITH_CUBE_EVK")
target_sources(socktap PRIVATE nfiniity_cube_evk_link.cpp nfiniity_cube_evk.cpp)
target_include_directories(socktap PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(socktap PUBLIC protobuf::libprotobuf)
if (Protobuf_VERSION VERSION_GREATER_EQUAL "22.0")
set_property(TARGET socktap PROPERTY CXX_STANDARD 17)
endif()
endif()
option(SOCKTAP_WITH_AUTOTALKS "Use Autotalks API for socktap" OFF) # Both Secton and Craton devices
if (SOCKTAP_WITH_AUTOTALKS)
find_package(Autotalks MODULE REQUIRED)
target_compile_definitions(socktap PUBLIC "SOCKTAP_WITH_AUTOTALKS")
if(NOT AUTOTALKS_CRATON)
target_compile_definitions(socktap PUBLIC "SECTON")
target_link_libraries(socktap PUBLIC Autotalks::AtlkRemote)
else ()
target_compile_definitions(socktap PUBLIC "CRATON_2")
target_link_libraries(socktap PUBLIC Autotalks::AtlkLocal)
endif()
target_include_directories(socktap PUBLIC ${AUTOTALKS_INCLUDE_DIRS})
target_sources(socktap PRIVATE autotalks.cpp autotalks_link.cpp)
endif()
set_target_properties(socktap PROPERTIES INSTALL_RPATH $ORIGIN/../${CMAKE_INSTALL_LIBDIR})
install(TARGETS socktap DESTINATION ${CMAKE_INSTALL_BINDIR})
find_file(COHDA_LLC_API_HEADER llc-api.h
HINTS "/home/duser"
PATH_SUFFIXES "cohda/kernel/include/linux/cohda/llc"
CMAKE_FIND_ROOT_PATH_BOTH
DOC "Cohda LLC API header")
mark_as_advanced(COHDA_LLC_API_HEADER)
if(COHDA_LLC_API_HEADER)
set(COHDA_LLC_API_FOUND ON)
else()
set(COHDA_LLC_API_FOUND OFF)
endif()
option(SOCKTAP_WITH_COHDA_LLC "Enable Cohda LLC link layer for socktap" ${COHDA_LLC_API_FOUND})
if(SOCKTAP_WITH_COHDA_LLC)
if(NOT COHDA_LLC_API_HEADER)
message(SEND_ERROR "Cohda LLC API header [llc-api.h] is missing")
endif()
get_filename_component(COHDA_LLC_INCLUDE_DIR ${COHDA_LLC_API_HEADER} DIRECTORY)
target_compile_definitions(socktap PUBLIC "SOCKTAP_WITH_COHDA_LLC")
target_include_directories(socktap PUBLIC ${COHDA_LLC_INCLUDE_DIR})
target_sources(socktap PRIVATE cohda.cpp cohda_link.cpp)
endif()
find_package(GPS QUIET)
option(SOCKTAP_WITH_GPSD "Enable gpsd positioning for socktap" ${GPS_FOUND})
if(SOCKTAP_WITH_GPSD)
find_package(GPS REQUIRED)
target_compile_definitions(socktap PUBLIC "SOCKTAP_WITH_GPSD")
target_link_libraries(socktap PUBLIC GPS::GPS)
target_sources(socktap PRIVATE gps_position_provider.cpp)
endif()
@@ -0,0 +1 @@
Content of this readme file has been moved to our [documentation](https://www.vanetza.org/tools/socktap).
@@ -0,0 +1,65 @@
#include "application.hpp"
#include <vanetza/btp/header.hpp>
#include <vanetza/btp/header_conversion.hpp>
#include <cassert>
using namespace vanetza;
Application::DataConfirm Application::request(const DataRequest& request, DownPacketPtr packet)
{
DataConfirm confirm(DataConfirm::ResultCode::Rejected_Unspecified);
if (router_ && packet) {
btp::HeaderB btp_header;
btp_header.destination_port = this->port();
btp_header.destination_port_info = host_cast<uint16_t>(0);
packet->layer(OsiLayer::Transport) = btp_header;
switch (request.transport_type) {
case geonet::TransportType::SHB:
confirm = router_->request(request_shb(request), std::move(packet));
break;
case geonet::TransportType::GBC:
confirm = router_->request(request_gbc(request), std::move(packet));
break;
default:
// TODO remaining transport types are not implemented
break;
}
}
return confirm;
}
void initialize_request(const Application::DataRequest& generic, geonet::DataRequest& geonet)
{
geonet.upper_protocol = geonet::UpperProtocol::BTP_B;
geonet.communication_profile = generic.communication_profile;
geonet.its_aid = generic.its_aid;
if (generic.maximum_lifetime) {
geonet.maximum_lifetime = generic.maximum_lifetime.get();
}
geonet.repetition = generic.repetition;
geonet.traffic_class = generic.traffic_class;
}
geonet::GbcDataRequest Application::request_gbc(const DataRequest& generic)
{
assert(router_);
geonet::GbcDataRequest gbc(router_->get_mib());
initialize_request(generic, gbc);
gbc.destination = boost::get<geonet::Area>(generic.destination);
return gbc;
}
geonet::ShbDataRequest Application::request_shb(const DataRequest& generic)
{
assert(router_);
geonet::ShbDataRequest shb(router_->get_mib());
initialize_request(generic, shb);
return shb;
}
Application::PromiscuousHook* Application::promiscuous_hook()
{
return nullptr;
}
@@ -0,0 +1,41 @@
#ifndef APPLICATION_HPP_PSIGPUTG
#define APPLICATION_HPP_PSIGPUTG
#include <vanetza/btp/data_interface.hpp>
#include <vanetza/btp/data_indication.hpp>
#include <vanetza/btp/data_request.hpp>
#include <vanetza/btp/port_dispatcher.hpp>
#include <vanetza/geonet/data_confirm.hpp>
#include <vanetza/geonet/router.hpp>
class Application : public vanetza::btp::IndicationInterface
{
public:
using DataConfirm = vanetza::geonet::DataConfirm;
using DataIndication = vanetza::btp::DataIndication;
using DataRequest = vanetza::btp::DataRequestGeoNetParams;
using DownPacketPtr = vanetza::geonet::Router::DownPacketPtr;
using PortType = vanetza::btp::port_type;
using PromiscuousHook = vanetza::btp::PortDispatcher::PromiscuousHook;
using UpPacketPtr = vanetza::geonet::Router::UpPacketPtr;
Application() = default;
Application(const Application&) = delete;
Application& operator=(const Application&) = delete;
virtual ~Application() = default;
virtual PortType port() = 0;
virtual PromiscuousHook* promiscuous_hook();
protected:
DataConfirm request(const DataRequest&, DownPacketPtr);
private:
friend class RouterContext;
vanetza::geonet::GbcDataRequest request_gbc(const DataRequest&);
vanetza::geonet::ShbDataRequest request_shb(const DataRequest&);
vanetza::geonet::Router* router_;
};
#endif /* APPLICATION_HPP_PSIGPUTG */
@@ -0,0 +1,251 @@
#include "autotalks.hpp"
#include <vanetza/access/g5_link_layer.hpp>
#include <vanetza/common/serialization_buffer.hpp>
#include <vanetza/access/ethertype.hpp>
#include <pthread.h>
#include <iostream>
#include <atlk/sdk.h>
#include <atlk/v2x.h>
#include <atlk/v2x_service.h>
#include <atlk/ddm_service.h>
#include <atlk/wdm.h>
#include <atlk/dsm.h>
#include <atlk/wdm_service.h>
#include <atlk/log_service.h>
#ifdef __cplusplus
extern "C" {
#endif
#include <extern/ref_sys.h>
#include <extern/target_type.h>
#include <extern/time_sync.h>
#ifdef __cplusplus
}
#endif
#include "autotalks_link.hpp"
// Update this if needed
#define SECTON_NET_NAME "enx0002ccf00006"
namespace vanetza
{
namespace autotalks
{
v2x_socket_t* v2x_socket_ptr;
bool endRxThread = false;
static pthread_t v2x_rx_thread;
static uint8_t v2x_rx_buffer[2048];
static void* v2x_rx_thread_entry(void * arg);
int autotalks_device_init(void)
{
atlk_rc_t rc;
const char* arg[] = {NULL, SECTON_NET_NAME};
/** Reference system initialization */
#if defined(CRATON_2)
rc = ref_sys_init_ex(1, (char**) arg);
#elif defined(SECTON)
rc = ref_sys_init_ex(2, (char**) arg);
#else
#error Wrong configuration selected.
#endif
// TODO //
// TODO: Take initialization from Autotalks basic example's main function //
// TODO //
#error "Autotalks device initialization code is missing"
v2x_socket_t *v2x_if[IF_INDEX_MAX];
// Assign the socket to the global parameter
v2x_socket_ptr = v2x_if[0];
return EXIT_SUCCESS;
}
int autotalks_device_deinit(void)
{
endRxThread = true;
if (v2x_socket_ptr)
v2x_socket_delete(v2x_socket_ptr);
atlk_rc_t ret;
ret = time_sync_deinit();
if (atlk_error(ret))
fprintf(stderr, "Fail of time_sync_deinit(). Error: %s\n", atlk_rc_to_str(ret));
ret = ref_sys_deinit();
if (atlk_error(ret))
fprintf(stderr, "Fail of ref_sys_deinit(). Error: %s\n", atlk_rc_to_str(ret));
return EXIT_SUCCESS;
}
atlk_rc_t autotalks_send(const void *data_ptr, size_t data_size,
const v2x_send_params_t *params_ptr, const atlk_wait_t *wait_ptr)
{
atlk_rc_t ret = 0;
if (v2x_socket_ptr == NULL)
fprintf(stderr, "Invalid socket.\n");
else if (v2x_socket_ptr != NULL)
ret = v2x_send(v2x_socket_ptr, data_ptr, data_size, params_ptr, wait_ptr);
// Check the return value
if (atlk_error(ret)) {
fprintf(stderr, "v2x_send failed: %d\n", ret);
}
return ret;
}
atlk_rc_t autotalks_receive(void *data_ptr, size_t *data_size_ptr,
v2x_receive_params_t *params_ptr, const atlk_wait_t *wait_ptr)
{
return v2x_receive(v2x_socket_ptr, data_ptr, data_size_ptr, params_ptr, wait_ptr);
}
vanetza::MacAddress num_to_mac(eui48_t addr)
{
return vanetza::MacAddress({addr.octets[0], addr.octets[1], addr.octets[2], addr.octets[3], addr.octets[4], addr.octets[5]});
}
eui48_t mac_to_num(vanetza::MacAddress addr)
{
eui48_t ret;
ret.octets[0] = addr.octets[0];
ret.octets[1] = addr.octets[1];
ret.octets[2] = addr.octets[2];
ret.octets[3] = addr.octets[3];
ret.octets[4] = addr.octets[4];
ret.octets[5] = addr.octets[5];
return ret;
}
void insert_autotalks_header_transmit(const vanetza::access::DataRequest& request, std::unique_ptr<vanetza::ChunkPacket>& packet, uint8_t* pData, uint16_t length)
{
// There cannot be an assignment as three dots (...) are gcc extension that does not work in g++
// => initialize the structure manually
//v2x_send_params_t send_params = V2X_SEND_PARAMS_INIT;
v2x_send_params_t send_params;
send_params.source_address = EUI48_ZERO_INIT;
send_params.dest_address = EUI48_BCAST_INIT;
send_params.user_priority = USER_PRIORITY_NA;
send_params.channel_id = V2X_CHANNEL_ID_INIT;
send_params.datarate = DATARATE_NA;
send_params.power_dbm8 = POWER_DBM8_NA;
send_params.transmit_diversity_power_dbm8 = POWER_DBM8_NA;
send_params.expiry_time_ms = V2X_EXPIRY_TIME_MS_NA;
for (uint8_t i = 0; i < RF_INDEX_MAX; i++)
send_params.comp_data[i] = COMPENSATOR_DATA_INIT;
vanetza::access::G5LinkLayer link_layer;
vanetza::access::ieee802::dot11::QosDataHeader& mac_header = link_layer.mac_header;
mac_header.destination = request.destination_addr;
mac_header.source = request.source_addr;
mac_header.qos_control.user_priority(request.access_category);
send_params.dest_address = mac_to_num(request.destination_addr);
send_params.source_address = mac_to_num(request.source_addr);
//mac_header.qos_control.user_priority(request.access_category);
//send_params.user_priority = mac_header.qos_control.raw; TODO later
/* Set TX power to -10 dB */
send_params.power_dbm8 = -80;
/* Set user priority */
send_params.user_priority = 0;
/* Set default data rate */
send_params.datarate = DATARATE_DEFAULT_VALUE;
vanetza::ByteBuffer link_layer_buffer;
vanetza::serialize_into_buffer(link_layer, link_layer_buffer);
vanetza::ByteBuffer buffer;
packet->layer(vanetza::OsiLayer::Physical).convert(buffer);
autotalks_send(pData, length, &send_params, NULL);
}
boost::optional<vanetza::EthernetHeader> strip_autotalks_rx_header(vanetza::CohesivePacket& packet, v2x_receive_params_t rx_params)
{
vanetza::access::G5LinkLayer link_layer;
vanetza::ByteBuffer link_layer_buffer;
link_layer.mac_header.destination = num_to_mac(rx_params.dest_address);
link_layer.mac_header.source = num_to_mac(rx_params.source_address);
link_layer.llc_snap_header.protocol_id = vanetza::access::ethertype::GeoNetworking;
vanetza::serialize_into_buffer(link_layer, link_layer_buffer);
assert(link_layer_buffer.size() == vanetza::access::G5LinkLayer::length_bytes);
const vanetza::ByteBuffer& data_buffer = packet.buffer();
vanetza::ByteBuffer final;
for (auto i : link_layer_buffer)
final.push_back(i);
for (auto i : data_buffer)
final.push_back(i);
vanetza::CohesivePacket finalPkt(final, vanetza::OsiLayer::Physical);
finalPkt.set_boundary(vanetza::OsiLayer::Physical, 0);
finalPkt.set_boundary(vanetza::OsiLayer::Link, vanetza::access::G5LinkLayer::length_bytes);
finalPkt.set_boundary(vanetza::OsiLayer::Network, packet.size());
packet.set_boundary(vanetza::OsiLayer::Physical, 0);
packet.set_boundary(vanetza::OsiLayer::Link, vanetza::access::G5LinkLayer::length_bytes);
packet = finalPkt;
vanetza::EthernetHeader eth;
eth.destination = num_to_mac(rx_params.dest_address);
eth.source = num_to_mac(rx_params.source_address);
eth.type = vanetza::access::ethertype::GeoNetworking; // This is set in the Autotalks API initialization
return eth;
}
static void* v2x_rx_thread_entry(void * arg)
{
AutotalksLink* link = (AutotalksLink*) arg;
atlk_rc_t rc;
(void) arg;
v2x_receive_params_t rx_params;
size_t rx_buffer_size;
while (!endRxThread) {
rx_buffer_size = sizeof(v2x_rx_buffer);
atlk_wait_t wait = {ATLK_WAIT_TYPE_INTERVAL, 100000};
rc = autotalks_receive(v2x_rx_buffer, &rx_buffer_size, &rx_params, &wait);
if (rc == ATLK_E_TIMEOUT)
continue;
else if (atlk_error(rc)) {
std::cerr << "Receive failed: " << rc << ", " << atlk_rc_to_str(rc) << ", RX thread ends." << std::endl;
break;
}
else {
std::cout << "Autotalks receive successful" << std::endl;
if (nullptr != link)
link->data_received(v2x_rx_buffer, rx_buffer_size, rx_params);
}
usleep(1000);
}
return NULL;
}
void init_rx(AutotalksLink* link_layer)
{
// Create new thread as Autotalks API does not have asynchronous callbacks
int rv = pthread_create(&v2x_rx_thread, NULL, v2x_rx_thread_entry, link_layer);
if (0 != rv) {
fprintf(stderr, "pthread_create failed with %s\n", strerror(rv));
}
printf("pthread_create success!\n");
}
} // namespace autotalks
} // namespace vanetza
@@ -0,0 +1,69 @@
#ifndef AUTOTALKS_HPP_
#define AUTOTALKS_HPP_
#include <cstddef>
#include <memory>
#include <vanetza/net/mac_address.hpp>
#include <vanetza/access/data_request.hpp>
#include <vanetza/net/chunk_packet.hpp>
#include <vanetza/net/ethernet_header.hpp>
#include <vanetza/net/cohesive_packet.hpp>
#include "atlk/sdk.h"
#include "atlk/v2x_service.h"
#include "autotalks_link.hpp"
namespace vanetza
{
namespace autotalks
{
/*
* Device initialization.
*/
int autotalks_device_init(void);
/*
* Device deinitialization.
*/
int autotalks_device_deinit(void);
/*
* Request sending in the API.
*/
atlk_rc_t autotalks_send(const void*, size_t, const v2x_send_params_t*, const atlk_wait_t*);
/*
* Request reception in the API.
*/
atlk_rc_t autotalks_receive(void*, size_t*, v2x_receive_params_t*, const atlk_wait_t*);
/*
* Convert MAC address to the Vanetza format
*/
vanetza::MacAddress num_to_mac(eui48_t);
/*
* Convert Vanetza MAC format to the array
*/
eui48_t mac_to_num(vanetza::MacAddress);
/*
* Create autotalks header and send the packet.
*/
void insert_autotalks_header_transmit(const vanetza::access::DataRequest&, std::unique_ptr<vanetza::ChunkPacket>&, uint8_t*, uint16_t);
/*
* Parse information from received Autotalks packet.
*/
boost::optional<vanetza::EthernetHeader> strip_autotalks_rx_header(vanetza::CohesivePacket&, v2x_receive_params_t);
/*
* Create a new thread for receiving data.
*/
void init_rx(AutotalksLink*);
} // namespace autotalks
} // namespace vanetza
#endif /* AUTOTALKS_HPP_ */
@@ -0,0 +1,57 @@
#include "autotalks_link.hpp"
#include "autotalks.hpp"
#include <vanetza/net/osi_layer.hpp>
const unsigned SendBufferSize = 2000;
AutotalksLink::AutotalksLink(boost::asio::io_context& io) : io_(io)
{
vanetza::autotalks::autotalks_device_init();
vanetza::autotalks::init_rx(this);
}
AutotalksLink::~AutotalksLink(void)
{
vanetza::autotalks::autotalks_device_deinit();
}
void AutotalksLink::request(const vanetza::access::DataRequest& request, std::unique_ptr<vanetza::ChunkPacket> packet)
{
uint8_t toSend[SendBufferSize];
size_t j = 0;
constexpr std::size_t layers = num_osi_layers(vanetza::OsiLayer::Physical, vanetza::OsiLayer::Application);
std::array<boost::asio::const_buffer, layers> const_buffers;
for (auto& layer : vanetza::osi_layer_range<vanetza::OsiLayer::Physical, vanetza::OsiLayer::Application>()) {
const auto index = distance(vanetza::OsiLayer::Physical, layer);
packet->layer(layer).convert(buffers_[index]);
const_buffers[index] = boost::asio::buffer(buffers_[index]);
for (size_t i = 0; i < buffers_[index].size() && j < SendBufferSize; i++)
toSend[j++] = buffers_[index][i];
}
uint16_t datalen = j;
vanetza::autotalks::insert_autotalks_header_transmit(request, packet, (uint8_t*) toSend, datalen);
}
void AutotalksLink::data_received(uint8_t* pBuf, uint16_t size, v2x_receive_params_t rx_params)
{
vanetza::ByteBuffer buffer(size);
for (uint16_t i = 0; i < size; i++)
buffer[i] = pBuf[i];
vanetza::CohesivePacket packet(std::move(buffer), vanetza::OsiLayer::Physical);
boost::optional<vanetza::EthernetHeader> eth = vanetza::autotalks::strip_autotalks_rx_header(packet, rx_params);
if (callback_ && eth)
{
boost::asio::post(io_, [this, packet = std::move(packet), eth]() mutable
{
callback_(std::move(packet), *eth);
});
}
}
void AutotalksLink::indicate(IndicationCallback cb)
{
callback_ = cb;
}
@@ -0,0 +1,34 @@
#ifndef AUTOTALKS_LINK_HPP_
#define AUTOTALKS_LINK_HPP_
#include "raw_socket_link.hpp"
#include "atlk/v2x_service.h"
#include <iostream>
class AutotalksLink : public LinkLayer
{
public:
/*
* Constructor used for device and thread initialization.
*/
AutotalksLink(boost::asio::io_context&);
/*
* Destructor used for deinitialization.
*/
~AutotalksLink(void);
void request(const vanetza::access::DataRequest&, std::unique_ptr<vanetza::ChunkPacket>) override;
void indicate(IndicationCallback callback) override;
void data_received(uint8_t*, uint16_t, v2x_receive_params_t);
private:
static constexpr std::size_t layers_ = num_osi_layers(vanetza::OsiLayer::Physical, vanetza::OsiLayer::Application);
IndicationCallback callback_;
std::array<vanetza::ByteBuffer, layers_> buffers_;
boost::asio::io_context& io_;
};
#endif /* AUTOTALKS_LINK_HPP_ */
@@ -0,0 +1,52 @@
#include "benchmark_application.hpp"
#include <chrono>
#include <iostream>
// Benchmark application counts all incoming messages and calculates the message rate.
using namespace std::chrono;
using namespace vanetza;
BenchmarkApplication::BenchmarkApplication(boost::asio::io_context& io) :
m_timer(io), m_interval(std::chrono::seconds(1))
{
schedule_timer();
}
BenchmarkApplication::PortType BenchmarkApplication::port()
{
return host_cast<uint16_t>(0);
}
Application::PromiscuousHook* BenchmarkApplication::promiscuous_hook()
{
return this;
}
void BenchmarkApplication::tap_packet(const DataIndication&, const UpPacket&)
{
++m_received_messages;
}
void BenchmarkApplication::indicate(const DataIndication&, UpPacketPtr)
{
// do nothing here
}
void BenchmarkApplication::schedule_timer()
{
m_timer.expires_after(m_interval);
m_timer.async_wait(std::bind(&BenchmarkApplication::on_timer, this, std::placeholders::_1));
}
void BenchmarkApplication::on_timer(const boost::system::error_code& ec)
{
if (ec == boost::asio::error::operation_aborted) {
return;
}
std::cout << "Received " << m_received_messages << " messages/second" << std::endl;
m_received_messages = 0;
schedule_timer();
}
@@ -0,0 +1,27 @@
#ifndef BENCHMARK_APPLICATION_HPP_EUIC2VFR
#define BENCHMARK_APPLICATION_HPP_EUIC2VFR
#include "application.hpp"
#include <boost/asio/io_context.hpp>
#include <boost/asio/steady_timer.hpp>
#include <chrono>
class BenchmarkApplication : public Application, private Application::PromiscuousHook
{
public:
BenchmarkApplication(boost::asio::io_context&);
PortType port() override;
void indicate(const DataIndication&, UpPacketPtr) override;
Application::PromiscuousHook* promiscuous_hook() override;
private:
void schedule_timer();
void on_timer(const boost::system::error_code& ec);
void tap_packet(const DataIndication&, const vanetza::UpPacket&) override;
boost::asio::steady_timer m_timer;
std::chrono::milliseconds m_interval;
unsigned m_received_messages;
};
#endif /* BENCHMARK_APPLICATION_HPP_EUIC2VFR */
@@ -0,0 +1,137 @@
#include "cam_application.hpp"
#include <vanetza/btp/ports.hpp>
#include <vanetza/asn1/cam.hpp>
#include <vanetza/asn1/packet_visitor.hpp>
#include <vanetza/facilities/cam_functions.hpp>
#include <chrono>
#include <functional>
#include <iostream>
#include <stdexcept>
// This is a very simple CA application sending CAMs at a fixed rate.
using namespace vanetza;
using namespace vanetza::facilities;
using namespace std::chrono;
CamApplication::CamApplication(PositionProvider& positioning, Runtime& rt) :
positioning_(positioning), runtime_(rt), cam_interval_(seconds(1))
{
schedule_timer();
}
void CamApplication::set_interval(Clock::duration interval)
{
cam_interval_ = interval;
runtime_.cancel(this);
schedule_timer();
}
void CamApplication::set_station_id(std::uint32_t station_id)
{
station_id_ = station_id;
}
void CamApplication::print_generated_message(bool flag)
{
print_tx_msg_ = flag;
}
void CamApplication::print_received_message(bool flag)
{
print_rx_msg_ = flag;
}
CamApplication::PortType CamApplication::port()
{
return btp::ports::CAM;
}
void CamApplication::indicate(const DataIndication&, UpPacketPtr packet)
{
asn1::PacketVisitor<asn1::Cam> visitor;
std::shared_ptr<const asn1::Cam> cam = boost::apply_visitor(visitor, *packet);
std::cout << "CAM application received a packet with " << (cam ? "decodable" : "broken") << " content" << std::endl;
if (cam && print_rx_msg_) {
std::cout << "Received CAM contains\n";
print_indented(std::cout, *cam, " ", 1);
}
}
void CamApplication::schedule_timer()
{
runtime_.schedule(cam_interval_, std::bind(&CamApplication::on_timer, this, std::placeholders::_1), this);
}
void CamApplication::on_timer(Clock::time_point)
{
schedule_timer();
vanetza::asn1::Cam message;
ItsPduHeader_t& header = message->header;
header.protocolVersion = 2;
header.messageID = ItsPduHeader__messageID_cam;
header.stationID = station_id_;
const auto time_now = duration_cast<milliseconds>(runtime_.now().time_since_epoch());
uint16_t gen_delta_time = time_now.count();
CoopAwareness_t& cam = message->cam;
cam.generationDeltaTime = gen_delta_time * GenerationDeltaTime_oneMilliSec;
auto position = positioning_.position_fix();
if (!has_horizontal_position(position)) {
std::cerr << "Skip CAM generation without position fix" << std::endl;
return;
}
BasicContainer_t& basic = cam.camParameters.basicContainer;
basic.stationType = StationType_passengerCar;
copy(position, basic.referencePosition);
cam.camParameters.highFrequencyContainer.present = HighFrequencyContainer_PR_basicVehicleContainerHighFrequency;
BasicVehicleContainerHighFrequency& bvc = cam.camParameters.highFrequencyContainer.choice.basicVehicleContainerHighFrequency;
bvc.heading.headingValue = 0;
bvc.heading.headingConfidence = HeadingConfidence_equalOrWithinOneDegree;
bvc.speed.speedValue = 0;
bvc.speed.speedConfidence = SpeedConfidence_equalOrWithinOneCentimeterPerSec;
bvc.driveDirection = DriveDirection_forward;
bvc.longitudinalAcceleration.longitudinalAccelerationValue = LongitudinalAccelerationValue_unavailable;
bvc.vehicleLength.vehicleLengthValue = VehicleLengthValue_unavailable;
bvc.vehicleLength.vehicleLengthConfidenceIndication = VehicleLengthConfidenceIndication_noTrailerPresent;
bvc.vehicleWidth = VehicleWidth_unavailable;
bvc.curvature.curvatureValue = 0;
bvc.curvature.curvatureConfidence = CurvatureConfidence_unavailable;
bvc.curvatureCalculationMode = CurvatureCalculationMode_yawRateUsed;
bvc.yawRate.yawRateValue = YawRateValue_unavailable;
std::string error;
if (!message.validate(error)) {
throw std::runtime_error("Invalid high frequency CAM: %s" + error);
}
if (print_tx_msg_) {
std::cout << "Generated CAM contains\n";
print_indented(std::cout, message, " ", 1);
}
DownPacketPtr packet { new DownPacket() };
packet->layer(OsiLayer::Application) = std::move(message);
DataRequest request;
request.its_aid = aid::CA;
request.transport_type = geonet::TransportType::SHB;
request.communication_profile = geonet::CommunicationProfile::ITS_G5;
auto confirm = Application::request(request, std::move(packet));
if (!confirm.accepted()) {
throw std::runtime_error("CAM application data request failed");
}
}
@@ -0,0 +1,32 @@
#ifndef CAM_APPLICATION_HPP_EUIC2VFR
#define CAM_APPLICATION_HPP_EUIC2VFR
#include "application.hpp"
#include <vanetza/common/clock.hpp>
#include <vanetza/common/position_provider.hpp>
#include <vanetza/common/runtime.hpp>
class CamApplication : public Application
{
public:
CamApplication(vanetza::PositionProvider& positioning, vanetza::Runtime& rt);
PortType port() override;
void indicate(const DataIndication&, UpPacketPtr) override;
void set_interval(vanetza::Clock::duration);
void set_station_id(std::uint32_t station_id);
void print_received_message(bool flag);
void print_generated_message(bool flag);
private:
void schedule_timer();
void on_timer(vanetza::Clock::time_point);
vanetza::PositionProvider& positioning_;
vanetza::Runtime& runtime_;
vanetza::Clock::duration cam_interval_;
std::uint32_t station_id_ = 1;
bool print_rx_msg_ = false;
bool print_tx_msg_ = false;
};
#endif /* CAM_APPLICATION_HPP_EUIC2VFR */
@@ -0,0 +1,50 @@
#include "certificate_validation_v3.hpp"
#include <vanetza/common/position_provider.hpp>
#include <vanetza/common/runtime.hpp>
#include <vanetza/security/persistence.hpp>
#include <vanetza/security/v3/certificate_validator.hpp>
#include <vanetza/security/v3/location_checker.hpp>
namespace
{
class DefaultCertificateValidationV3 : public CertificateValidationV3
{
public:
DefaultCertificateValidationV3(
const vanetza::Runtime& runtime, vanetza::PositionProvider& positioning,
const vanetza::security::v3::LocationChecker& location_checker)
{
m_validator.use_runtime(&runtime);
m_validator.use_position_provider(&positioning);
m_validator.use_location_checker(&location_checker);
}
vanetza::security::v3::CertificateValidator& validator() override
{
return m_validator;
}
vanetza::security::PrivateKey load_authorization_ticket_key(
const std::string& path) const override
{
return vanetza::security::load_private_key_from_pem_file(path);
}
void add_chain_certificate(const vanetza::security::v3::Certificate&) override
{
}
private:
vanetza::security::v3::DefaultCertificateValidator m_validator;
};
} // namespace
std::unique_ptr<CertificateValidationV3> create_default_certificate_validation_v3(
const vanetza::Runtime& runtime, vanetza::PositionProvider& positioning,
const vanetza::security::v3::LocationChecker& location_checker)
{
return std::make_unique<DefaultCertificateValidationV3>(
runtime, positioning, location_checker);
}
@@ -0,0 +1,64 @@
#pragma once
#include <vanetza/security/private_key.hpp>
#include <memory>
#include <string>
namespace boost
{
namespace program_options
{
class options_description;
class variables_map;
} // namespace program_options
} // namespace boost
namespace vanetza
{
class PositionProvider;
class Runtime;
namespace security
{
class Backend;
namespace v3
{
class Certificate;
class CertificateValidator;
class LocationChecker;
} // namespace v3
} // namespace security
} // namespace vanetza
/**
* Owns the profile-specific services selected for a socktap V3 security context.
*
* Profile-specific implementations may retain issuer certificates and trust
* anchors needed by their validator and select the credential format expected
* by that profile. The strict implementation preserves socktap's established
* behavior.
*/
class CertificateValidationV3
{
public:
virtual ~CertificateValidationV3() = default;
virtual vanetza::security::v3::CertificateValidator& validator() = 0;
virtual vanetza::security::PrivateKey load_authorization_ticket_key(
const std::string&) const = 0;
virtual void add_chain_certificate(const vanetza::security::v3::Certificate&) = 0;
};
std::unique_ptr<CertificateValidationV3> create_default_certificate_validation_v3(
const vanetza::Runtime&, vanetza::PositionProvider&,
const vanetza::security::v3::LocationChecker&);
std::unique_ptr<CertificateValidationV3> create_certificate_validation_v3(
const boost::program_options::variables_map&, const vanetza::Runtime&,
vanetza::PositionProvider&, const vanetza::security::v3::LocationChecker&,
vanetza::security::Backend&);
void add_certificate_validation_v3_options(boost::program_options::options_description&);
@@ -0,0 +1,91 @@
#include "certificate_validation_v3.hpp"
#include <vanetza/security/persistence.hpp>
#include <vanetza/security/pqc/fndsa512.hpp>
#include <vanetza/security/pqc/hybrid_certificate_validator.hpp>
#include <vanetza/security/v3/certificate.hpp>
#include <vanetza/security/v3/issuer_memory_lookup.hpp>
#include <vanetza/security/v3/trust_store.hpp>
#include <boost/program_options.hpp>
#include <stdexcept>
namespace po = boost::program_options;
namespace
{
class HybridCertificateValidationV3 : public CertificateValidationV3
{
public:
HybridCertificateValidationV3(
const vanetza::Runtime& runtime, vanetza::PositionProvider& positioning,
const vanetza::security::v3::LocationChecker& location_checker,
vanetza::security::Backend& ecc_backend) :
m_pqc_backend(vanetza::security::pqc::create_fndsa512_backend())
{
using Policy =
vanetza::security::pqc::HybridCertificateValidator::VerificationPolicy;
m_validator.use_runtime(&runtime);
m_validator.use_position_provider(&positioning);
m_validator.use_location_checker(&location_checker);
m_validator.use_issuer_lookup(&m_issuer_lookup);
m_validator.use_trust_store(&m_trust_store);
m_validator.use_backends(&ecc_backend, m_pqc_backend.get());
m_validator.use_verification_policy(Policy::HybridIfPresent);
}
vanetza::security::v3::CertificateValidator& validator() override
{
return m_validator;
}
vanetza::security::PrivateKey load_authorization_ticket_key(
const std::string& path) const override
{
return vanetza::security::load_private_key_from_der_file(path);
}
void add_chain_certificate(const vanetza::security::v3::Certificate& certificate) override
{
if (!m_issuer_lookup.insert(certificate)) {
throw std::invalid_argument(
"V3 certificate chain contains a certificate that cannot act as an issuer");
}
if (certificate.issuer_is_self()) {
m_trust_store.insert(certificate);
}
}
private:
std::unique_ptr<vanetza::security::pqc::Backend> m_pqc_backend;
vanetza::security::v3::IssuerMemoryLookup m_issuer_lookup;
vanetza::security::v3::TrustStore m_trust_store;
vanetza::security::pqc::HybridCertificateValidator m_validator;
};
} // namespace
std::unique_ptr<CertificateValidationV3> create_certificate_validation_v3(
const po::variables_map& options, const vanetza::Runtime& runtime,
vanetza::PositionProvider& positioning,
const vanetza::security::v3::LocationChecker& location_checker,
vanetza::security::Backend& backend)
{
if (!options["enable-pqc-verification"].as<bool>()) {
return create_default_certificate_validation_v3(runtime, positioning, location_checker);
}
if (!options.count("certificate") || !options.count("certificate-chain")) {
throw std::invalid_argument(
"--enable-pqc-verification requires --certificate, --certificate-key, "
"and a trusted --certificate-chain");
}
return std::make_unique<HybridCertificateValidationV3>(
runtime, positioning, location_checker, backend);
}
void add_certificate_validation_v3_options(po::options_description& options)
{
options.add_options()
("enable-pqc-verification", po::bool_switch()->default_value(false),
"Verify hybrid PQC certificate signatures in an external V3 chain.");
}
@@ -0,0 +1,14 @@
#include "certificate_validation_v3.hpp"
std::unique_ptr<CertificateValidationV3> create_certificate_validation_v3(
const boost::program_options::variables_map&, const vanetza::Runtime& runtime,
vanetza::PositionProvider& positioning,
const vanetza::security::v3::LocationChecker& location_checker,
vanetza::security::Backend&)
{
return create_default_certificate_validation_v3(runtime, positioning, location_checker);
}
void add_certificate_validation_v3_options(boost::program_options::options_description&)
{
}
@@ -0,0 +1,75 @@
#include "cohda.hpp"
#include <vanetza/access/access_category.hpp>
#include <vanetza/access/g5_link_layer.hpp>
#include <vanetza/common/serialization_buffer.hpp>
#include <vanetza/dcc/mapping.hpp>
#include <cassert>
#include <llc-api.h>
namespace vanetza
{
void insert_cohda_tx_header(const access::DataRequest& request, std::unique_ptr<ChunkPacket>& packet)
{
access::G5LinkLayer link_layer;
access::ieee802::dot11::QosDataHeader& mac_header = link_layer.mac_header;
mac_header.destination = request.destination_addr;
mac_header.source = request.source_addr;
mac_header.qos_control.user_priority(request.access_category);
ByteBuffer link_layer_buffer;
serialize_into_buffer(link_layer, link_layer_buffer);
assert(link_layer_buffer.size() == access::G5LinkLayer::length_bytes);
packet->layer(OsiLayer::Link) = std::move(link_layer_buffer);
const std::size_t payload_size = packet->size();
const std::size_t total_size = sizeof(tMKxTxPacket) + payload_size;
tMKxTxPacket phy = { 0 };
phy.Hdr.Type = MKXIF_TXPACKET;
phy.Hdr.Len = total_size;
phy.TxPacketData.TxAntenna = MKX_ANT_DEFAULT;
phy.TxPacketData.TxFrameLength = payload_size;
auto phy_ptr = reinterpret_cast<const uint8_t*>(&phy);
packet->layer(OsiLayer::Physical) = ByteBuffer { phy_ptr, phy_ptr + sizeof(tMKxTxPacket) };
}
boost::optional<EthernetHeader> strip_cohda_rx_header(CohesivePacket& packet)
{
static const std::size_t min_length = sizeof(tMKxRxPacket) + access::G5LinkLayer::length_bytes +
access::ieee802::dot11::fcs_length_bytes;
if (packet.size(OsiLayer::Physical) < min_length) {
return boost::none;
}
packet.set_boundary(OsiLayer::Physical, sizeof(tMKxRxPacket));
auto phy = reinterpret_cast<const tMKxRxPacket*>(&*packet[OsiLayer::Physical].begin());
if (phy->Hdr.Type != MKXIF_RXPACKET) {
return boost::none;
}
// Sanity check that sizes reported by Cohda LLC are correct, since we rely on Cohda's FCS checking
if (phy->Hdr.Len != packet.size() || phy->RxPacketData.RxFrameLength != packet.size() - sizeof(tMKxRxPacket)) {
return boost::none;
}
if (!phy->RxPacketData.FCSPass) {
return boost::none;
}
packet.trim(OsiLayer::Link, packet.size() - access::ieee802::dot11::fcs_length_bytes);
packet.set_boundary(OsiLayer::Link, access::G5LinkLayer::length_bytes);
access::G5LinkLayer link_layer;
deserialize_from_range(link_layer, packet[OsiLayer::Link]);
if (!access::check_fixed_fields(link_layer)) {
return boost::none;
}
EthernetHeader eth;
eth.destination = link_layer.mac_header.destination;
eth.source = link_layer.mac_header.source;
eth.type = link_layer.llc_snap_header.protocol_id;
return eth;
}
} // namespace vanetza
@@ -0,0 +1,31 @@
#ifndef COHDA_HPP_GBENHCVN
#define COHDA_HPP_GBENHCVN
#include <boost/optional/optional.hpp>
#include <vanetza/access/data_request.hpp>
#include <vanetza/common/byte_buffer.hpp>
#include <vanetza/common/clock.hpp>
#include <vanetza/net/chunk_packet.hpp>
#include <vanetza/net/cohesive_packet.hpp>
#include <vanetza/net/ethernet_header.hpp>
namespace vanetza
{
/**
* Add Physical and Link layer headers understood by Cohda V2X API
* \param req access layer request parameters
* \param packet packet to be transmitted
*/
void insert_cohda_tx_header(const access::DataRequest& req, std::unique_ptr<ChunkPacket>& packet);
/**
* Remove packet headers by Cohda V2X API and build Ethernet header from them
* \return equivalent EthernetHeader if successfully received
*/
boost::optional<EthernetHeader> strip_cohda_rx_header(CohesivePacket&);
} // namespace vanetza
#endif /* COHDA_HPP_GBENHCVN */
@@ -0,0 +1,13 @@
#include "cohda_link.hpp"
#include "cohda.hpp"
void CohdaLink::request(const vanetza::access::DataRequest& request, std::unique_ptr<vanetza::ChunkPacket> packet)
{
insert_cohda_tx_header(request, packet);
transmit(std::move(packet));
}
boost::optional<vanetza::EthernetHeader> CohdaLink::parse_ethernet_header(vanetza::CohesivePacket& packet) const
{
return strip_cohda_rx_header(packet);
}
@@ -0,0 +1,18 @@
#ifndef COHDA_LINK_HPP_IXOCQ5RH
#define COHDA_LINK_HPP_IXOCQ5RH
#include "raw_socket_link.hpp"
class CohdaLink : public RawSocketLink
{
public:
using RawSocketLink::RawSocketLink;
void request(const vanetza::access::DataRequest&, std::unique_ptr<vanetza::ChunkPacket>) override;
protected:
boost::optional<vanetza::EthernetHeader> parse_ethernet_header(vanetza::CohesivePacket&) const override;
};
#endif /* COHDA_LINK_HPP_IXOCQ5RH */
@@ -0,0 +1,41 @@
#include "dcc_passthrough.hpp"
#include "time_trigger.hpp"
#include <vanetza/access/data_request.hpp>
#include <vanetza/dcc/data_request.hpp>
#include <vanetza/dcc/interface.hpp>
#include <vanetza/dcc/mapping.hpp>
#include <vanetza/net/chunk_packet.hpp>
#include <iostream>
using namespace vanetza;
DccPassthrough::DccPassthrough(access::Interface& access, TimeTrigger& trigger) :
access_(access), trigger_(trigger) {}
void DccPassthrough::request(const dcc::DataRequest& request, std::unique_ptr<ChunkPacket> packet)
{
if (!allow_packet_flow_) {
std::cout << "ignored request because packet flow is suppressed\n";
return;
}
trigger_.schedule();
access::DataRequest acc_req;
acc_req.ether_type = request.ether_type;
acc_req.source_addr = request.source;
acc_req.destination_addr = request.destination;
acc_req.access_category = dcc::map_profile_onto_ac(request.dcc_profile);
access_.request(acc_req, std::move(packet));
}
void DccPassthrough::allow_packet_flow(bool allow)
{
allow_packet_flow_ = allow;
}
bool DccPassthrough::allow_packet_flow()
{
return allow_packet_flow_;
}
@@ -0,0 +1,26 @@
#ifndef DCC_PASSTHROUGH_HPP_GSDFESAE
#define DCC_PASSTHROUGH_HPP_GSDFESAE
#include "time_trigger.hpp"
#include <vanetza/access/interface.hpp>
#include <vanetza/dcc/data_request.hpp>
#include <vanetza/dcc/interface.hpp>
#include <vanetza/net/cohesive_packet.hpp>
class DccPassthrough : public vanetza::dcc::RequestInterface
{
public:
DccPassthrough(vanetza::access::Interface&, TimeTrigger& trigger);
void request(const vanetza::dcc::DataRequest& request, std::unique_ptr<vanetza::ChunkPacket> packet) override;
void allow_packet_flow(bool allow);
bool allow_packet_flow();
private:
vanetza::access::Interface& access_;
TimeTrigger& trigger_;
bool allow_packet_flow_ = true;
};
#endif /* DCC_PASSTHROUGH_HPP_GSDFESAE */
@@ -0,0 +1,73 @@
#include "ethernet_device.hpp"
#include <boost/asio/ip/address.hpp>
#include <algorithm>
#include <cstring>
#include <system_error>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <ifaddrs.h>
static void initialize(ifreq& request, const char* interface_name)
{
std::memset(&request, 0, sizeof(ifreq));
std::strncpy(request.ifr_name, interface_name, IF_NAMESIZE);
request.ifr_name[IF_NAMESIZE - 1] = '\0';
}
EthernetDevice::EthernetDevice(const char* devname) :
inet_socket_(::socket(AF_INET, SOCK_DGRAM, 0)),
interface_name_(devname)
{
if (!inet_socket_) {
throw std::system_error(errno, std::system_category());
}
}
EthernetDevice::~EthernetDevice()
{
if (inet_socket_ >= 0)
::close(inet_socket_);
}
EthernetDevice::protocol::endpoint EthernetDevice::endpoint(int family) const
{
sockaddr_ll socket_address = {};
socket_address.sll_family = family;
socket_address.sll_protocol = htons(ETH_P_ALL);
socket_address.sll_ifindex = index();
return protocol::endpoint(&socket_address, sizeof(sockaddr_ll));
}
int EthernetDevice::index() const
{
ifreq data;
initialize(data, interface_name_.c_str());
::ioctl(inet_socket_, SIOCGIFINDEX, &data);
return data.ifr_ifindex;
}
vanetza::MacAddress EthernetDevice::address() const
{
ifreq data;
initialize(data, interface_name_.c_str());
::ioctl(inet_socket_, SIOCGIFHWADDR, &data);
vanetza::MacAddress addr;
std::copy_n(data.ifr_hwaddr.sa_data, addr.octets.size(), addr.octets.data());
return addr;
}
boost::asio::ip::address_v4 EthernetDevice::ip() const
{
ifreq data;
initialize(data, interface_name_.c_str());
::ioctl(inet_socket_, SIOCGIFADDR, &data);
char host[NI_MAXHOST] = { 0 };
::getnameinfo(&data.ifr_addr, sizeof(sockaddr), host, NI_MAXHOST, nullptr, 0, NI_NUMERICHOST);
return boost::asio::ip::make_address_v4(host);
}
@@ -0,0 +1,31 @@
#ifndef ETHERNET_DEVICE_HPP_NEVC5DAY
#define ETHERNET_DEVICE_HPP_NEVC5DAY
#include <vanetza/net/mac_address.hpp>
#include <boost/asio/generic/raw_protocol.hpp>
#include <boost/asio/ip/address_v4.hpp>
#include <string>
class EthernetDevice
{
public:
using protocol = boost::asio::generic::raw_protocol;
EthernetDevice(const char* devname);
EthernetDevice(const EthernetDevice&) = delete;
EthernetDevice& operator=(const EthernetDevice&) = delete;
~EthernetDevice();
protocol::endpoint endpoint(int family) const;
vanetza::MacAddress address() const;
boost::asio::ip::address_v4 ip() const;
private:
int index() const;
int inet_socket_;
std::string interface_name_;
};
#endif /* ETHERNET_DEVICE_HPP_NEVC5DAY */
@@ -0,0 +1,216 @@
#include "gps_position_provider.hpp"
#include <vanetza/units/angle.hpp>
#include <vanetza/common/confident_quantity.hpp>
#include <vanetza/units/velocity.hpp>
#include <vanetza/units/length.hpp>
#include <cmath>
static_assert(GPSD_API_MAJOR_VERSION >= 5, "libgps has incompatible API");
#if GPSD_API_MAJOR_VERSION > 16
#warning "Your API version of libgps is not yet checked for compatibility"
#endif
#if GPSD_API_MAJOR_VERSION >= 7
# define GPSD_READ_WITH_MESSAGE
#endif
#if GPSD_API_MAJOR_VERSION < 9
# define GPSD_HAS_TIMESTAMP_T
#endif
#if GPSD_API_MAJOR_VERSION > 9 || (GPSD_API_MAJOR_VERSION == 9 && GPSD_API_MINOR_VERSION >= 1)
# define GPSD_HAS_LEAP_SECONDS
#endif
#if GPSD_API_MAJOR_VERSION >= 9
# define GPSD_HAS_ALTITUDE_HAE
#endif
#if GPSD_API_MAJOR_VERSION >= 15
# define GPSD_HAS_ERROR_ELLIPSE
#endif
namespace
{
static const vanetza::units::TrueNorth north = vanetza::units::TrueNorth::from_value(0.0);
// gpsd claims to report errors as standard deviations (1-sigma). Need to scale them for 95% confidence.
static const double scale_1dof95 = 2.0;
static const double scale_2dof95 = std::sqrt(5.991);
#ifdef GPSD_HAS_TIMESTAMP_T
using gpsd_timestamp = timestamp_t;
#else
using gpsd_timestamp = timespec_t;
#endif
int gpsd_read(gps_data_t& data)
{
#ifdef GPSD_READ_WITH_MESSAGE
return gps_read(&data, nullptr, 0);
#else
return gps_read(&data);
#endif
}
constexpr double gpsd_get_altitude(const gps_data_t& data)
{
#ifdef GPSD_HAS_ALTITUDE_HAE
return data.fix.altHAE;
#else
return data.fix.altitude;
#endif
}
vanetza::Clock::time_point convert_gps_time(const gps_data_t& data)
{
namespace posix = boost::posix_time;
static const boost::gregorian::date posix_epoch(1970, boost::gregorian::Jan, 1);
const auto& gpstime = data.fix.time;
#ifdef GPSD_HAS_TIMESTAMP_T
// gpsd's timestamp_t is UNIX time (UTC) with fractional seconds
const posix::time_duration::fractional_seconds_type posix_ticks(gpstime * posix::time_duration::ticks_per_second());
const posix::ptime posix_time { posix_epoch, posix::time_duration(0, 0, 0, posix_ticks) };
#else
// standard timespec_t is used from gpsd API version 9 on; use microsec for compatibility reasons
const posix::ptime posix_time { posix_epoch, posix::seconds(gpstime.tv_sec) + posix::microsec(gpstime.tv_nsec / 1000) };
#endif
// TAI has some seconds bias compared to UTC
const auto tai_utc_bias = posix::seconds(37); // 37 seconds since 1st January 2017
const auto tai_gps_bias = posix::seconds(19); // TAI offset to GPS time is fixed
#ifdef GPSD_HAS_LEAP_SECONDS
// prefer leap seconds when they are reported, fall back to UTC conversion
if (data.leap_seconds > 0) {
return vanetza::Clock::at(posix_time + tai_gps_bias + posix::seconds(data.leap_seconds));
} else {
return vanetza::Clock::at(posix_time + tai_utc_bias);
}
#else
return vanetza::Clock::at(posix_time + tai_utc_bias);
#endif
}
vanetza::PositionConfidence convert_gps_error_ellipse(const gps_fix_t& fix)
{
using namespace vanetza::units;
vanetza::PositionConfidence confidence;
#ifdef GPSD_HAS_ERROR_ELLIPSE
if (std::isfinite(fix.errEllipseOrient) && std::isfinite(fix.errEllipseMajor) && std::isfinite(fix.errEllipseMinor)) {
confidence.semi_minor = scale_2dof95 * fix.errEllipseMinor * si::meter;
confidence.semi_major = scale_2dof95 * fix.errEllipseMajor * si::meter;
confidence.orientation = north + scale_1dof95 * fix.errEllipseOrient * degree;
} else
#endif
if (std::isfinite(fix.epx) && std::isfinite(fix.epy)) {
if (fix.epx > fix.epy) {
confidence.semi_minor = scale_2dof95 * fix.epy * si::meter;
confidence.semi_major = scale_2dof95 * fix.epx * si::meter;
confidence.orientation = north + 90.0 * degree;
} else {
confidence.semi_minor = scale_2dof95 * fix.epx * si::meter;
confidence.semi_major = scale_2dof95 * fix.epy * si::meter;
confidence.orientation = north;
}
}
return confidence;
}
} // namespace
GpsPositionProvider::GpsPositionProvider(boost::asio::io_context& io) :
GpsPositionProvider(io, gpsd::shared_memory, "")
{
}
GpsPositionProvider::GpsPositionProvider(boost::asio::io_context& io, const std::string& hostname, const std::string& port) :
timer_(io)
{
if (gps_open(hostname.c_str(), port.c_str(), &gps_data_)) {
throw GpsPositioningException(errno);
}
gps_stream(&gps_data_, WATCH_ENABLE | WATCH_JSON, nullptr);
using namespace vanetza::units;
fetched_position_fix_.latitude = GeoAngle::from_value(std::numeric_limits<GeoAngle::value_type>::infinity());
fetched_position_fix_.longitude = GeoAngle::from_value(std::numeric_limits<GeoAngle::value_type>::infinity());
schedule_timer();
}
GpsPositionProvider::~GpsPositionProvider()
{
gps_stream(&gps_data_, WATCH_DISABLE, nullptr);
gps_close(&gps_data_);
}
GpsPositionProvider::GpsPositioningException::GpsPositioningException(int err) :
PositioningException(gps_errstr(err))
{
}
const vanetza::PositionFix& GpsPositionProvider::position_fix()
{
return fetched_position_fix_;
}
void GpsPositionProvider::schedule_timer()
{
timer_.expires_after(std::chrono::milliseconds(500));
timer_.async_wait(std::bind(&GpsPositionProvider::on_timer, this, std::placeholders::_1));
}
void GpsPositionProvider::on_timer(const boost::system::error_code& ec)
{
if (ec == boost::asio::error::operation_aborted) {
return;
}
fetch_position_fix();
schedule_timer();
}
void GpsPositionProvider::fetch_position_fix()
{
while (gps_waiting(&gps_data_, 0)) {
// reading is not expected to block now
int gps_read_rc = gpsd_read(gps_data_);
if (gps_read_rc > 0) {
apply_gps_data(gps_data_);
} else if (gps_read_rc < 0) {
throw GpsPositioningException(errno);
}
}
}
bool GpsPositionProvider::apply_gps_data(const gps_data_t& gps_data)
{
using namespace vanetza::units;
if ((gps_data.set & MODE_SET) != MODE_SET) {
// no mode set at all
return false;
} else if ((gps_data.set & TIME_SET) != TIME_SET) {
// mandatory GPS time is missing (fix.time field)
return false;
} else if (gps_data.fix.mode < MODE_2D) {
// latitude and longitude unavailable
return false;
}
fetched_position_fix_.timestamp = convert_gps_time(gps_data);
fetched_position_fix_.latitude = gps_data.fix.latitude * degree;
fetched_position_fix_.longitude = gps_data.fix.longitude * degree;
fetched_position_fix_.speed.assign(gps_data.fix.speed * si::meter_per_second, scale_1dof95 * gps_data.fix.eps * si::meter_per_second);
fetched_position_fix_.course.assign(north + gps_data.fix.track * degree, north + scale_1dof95 * gps_data.fix.epd * degree);
fetched_position_fix_.confidence = convert_gps_error_ellipse(gps_data.fix);
if (gps_data.fix.mode == MODE_3D) {
fetched_position_fix_.altitude = vanetza::ConfidentQuantity<vanetza::units::Length> {
gpsd_get_altitude(gps_data) * si::meter, scale_1dof95 * gps_data.fix.epv * si::meter };
} else {
fetched_position_fix_.altitude = boost::none;
}
return true;
}
@@ -0,0 +1,47 @@
#ifndef GPS_POSITION_PROVIDER_HPP_GYN3GVQA
#define GPS_POSITION_PROVIDER_HPP_GYN3GVQA
#include "positioning.hpp"
#include <vanetza/common/clock.hpp>
#include <vanetza/common/position_provider.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/steady_timer.hpp>
#include <string>
#include <gps.h>
class GpsPositionProvider : public vanetza::PositionProvider
{
public:
class GpsPositioningException : public PositioningException
{
protected:
GpsPositioningException(int);
friend class GpsPositionProvider;
};
GpsPositionProvider(boost::asio::io_context& io);
GpsPositionProvider(boost::asio::io_context& io, const std::string& hostname, const std::string& port);
~GpsPositionProvider();
const vanetza::PositionFix& position_fix() override;
void fetch_position_fix();
private:
void schedule_timer();
void on_timer(const boost::system::error_code& ec);
bool apply_gps_data(const gps_data_t&);
boost::asio::steady_timer timer_;
gps_data_t gps_data_;
vanetza::PositionFix fetched_position_fix_;
};
namespace gpsd
{
constexpr const char* default_port = DEFAULT_GPSD_PORT;
constexpr const char* shared_memory = GPSD_SHARED_MEMORY;
} // namespace gpsd
#endif /* GPS_POSITION_PROVIDER_HPP_GYN3GVQA */
@@ -0,0 +1,48 @@
#include "hello_application.hpp"
#include <chrono>
#include <functional>
#include <iostream>
// This is a very simple application that sends BTP-B messages with the content 0xc0ffee.
using namespace vanetza;
HelloApplication::HelloApplication(boost::asio::io_context& io, std::chrono::milliseconds interval) :
timer_(io), interval_(interval)
{
schedule_timer();
}
HelloApplication::PortType HelloApplication::port()
{
return host_cast<uint16_t>(42);
}
void HelloApplication::indicate(const DataIndication&, UpPacketPtr)
{
std::cout << "Hello application received a packet" << std::endl;
}
void HelloApplication::schedule_timer()
{
timer_.expires_after(interval_);
timer_.async_wait(std::bind(&HelloApplication::on_timer, this, std::placeholders::_1));
}
void HelloApplication::on_timer(const boost::system::error_code& ec)
{
if (ec != boost::asio::error::operation_aborted) {
DownPacketPtr packet { new DownPacket() };
packet->layer(OsiLayer::Application) = ByteBuffer { 0xC0, 0xFF, 0xEE };
DataRequest request;
request.transport_type = geonet::TransportType::SHB;
request.communication_profile = geonet::CommunicationProfile::ITS_G5;
request.its_aid = aid::CA;
auto confirm = Application::request(request, std::move(packet));
if (!confirm.accepted()) {
throw std::runtime_error("Hello application data request failed");
}
schedule_timer();
}
}
@@ -0,0 +1,23 @@
#ifndef HELLO_APPLICATION_HPP_EUIC2VFR
#define HELLO_APPLICATION_HPP_EUIC2VFR
#include "application.hpp"
#include <boost/asio/io_context.hpp>
#include <boost/asio/steady_timer.hpp>
class HelloApplication : public Application
{
public:
HelloApplication(boost::asio::io_context&, std::chrono::milliseconds interval);
PortType port() override;
void indicate(const DataIndication&, UpPacketPtr) override;
private:
void schedule_timer();
void on_timer(const boost::system::error_code& ec);
boost::asio::steady_timer timer_;
std::chrono::milliseconds interval_;
};
#endif /* HELLO_APPLICATION_HPP_EUIC2VFR */
@@ -0,0 +1,142 @@
#include "link_layer.hpp"
#include "raw_socket_link.hpp"
#include "tcp_link.hpp"
#include "udp_link.hpp"
#include <vanetza/access/ethertype.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/generic/raw_protocol.hpp>
#include <boost/asio/ip/address.hpp>
#include <boost/asio/ip/udp.hpp>
#include <iostream>
#ifdef SOCKTAP_WITH_CUBE_EVK
# include "nfiniity_cube_evk_link.hpp"
#endif
#ifdef SOCKTAP_WITH_COHDA_LLC
# include "cohda_link.hpp"
#endif
#ifdef SOCKTAP_WITH_AUTOTALKS
# include "autotalks_link.hpp"
# include "autotalks.hpp"
#endif
#ifdef SOCKTAP_WITH_RPC
# include "rpc_link.hpp"
#endif
boost::optional<std::pair<boost::asio::ip::address, unsigned short>> parse_ip_port(const std::string& ip_port)
{
using opt_ip_port = boost::optional<std::pair<boost::asio::ip::address, unsigned short>>;
std::size_t ip_len = ip_port.find_last_of(":");
if (ip_len == std::string::npos) {
// error: port not found
std::cerr << "[" << ip_port << "] Missing port." << std::endl;
return opt_ip_port();
}
std::size_t port = std::strtoul(ip_port.substr(ip_len + 1).c_str(), NULL, 10);
if (port < 1 || port > 65535) {
// error: port out of range
std::cerr << "[" << ip_port << "] Port " << port << " out of range (1-65535)." << std::endl;
return opt_ip_port();
}
boost::system::error_code ec;
boost::asio::ip::address ip = boost::asio::ip::make_address(ip_port.substr(0, ip_len), ec);
if (ec) {
// error: IP-address invalid
std::cerr << "[" << ip_port << "] Invalid IP-address: " << ec.message() << std::endl;
return opt_ip_port();
}
return opt_ip_port({ip, port});
}
std::unique_ptr<LinkLayer>
create_link_layer(boost::asio::io_context& io_context, const EthernetDevice& device, const std::string& name, const boost::program_options::variables_map& vm)
{
std::unique_ptr<LinkLayer> link_layer;
if (name == "ethernet" || name == "cohda") {
boost::asio::generic::raw_protocol raw_protocol(AF_PACKET, vanetza::access::ethertype::GeoNetworking.net());
boost::asio::generic::raw_protocol::socket raw_socket(io_context, raw_protocol);
raw_socket.bind(device.endpoint(AF_PACKET));
if (name == "ethernet") {
link_layer.reset(new RawSocketLink { std::move(raw_socket) });
} else if (name == "cohda") {
#ifdef SOCKTAP_WITH_COHDA_LLC
link_layer.reset(new CohdaLink { std::move(raw_socket) });
#endif
}
} else if (name == "udp") {
namespace ip = boost::asio::ip;
ip::udp::endpoint multicast(ip::make_address("239.118.122.97"), 8947);
link_layer.reset(new UdpLink { io_context, multicast, device });
} else if (name == "tcp") {
namespace ip = boost::asio::ip;
TcpLink* tcp = new TcpLink { io_context };
if (vm.count("tcp-connect")) {
for (const std::string& ip_port : vm["tcp-connect"].as<std::vector<std::string>>()) {
auto ip_port_pair = parse_ip_port(ip_port);
if (ip_port_pair) {
tcp->connect(ip::tcp::endpoint(ip_port_pair.value().first, ip_port_pair.value().second));
}
}
}
if (vm.count("tcp-accept")) {
for (const std::string& ip_port : vm["tcp-accept"].as<std::vector<std::string>>()) {
auto ip_port_pair = parse_ip_port(ip_port);
if (ip_port_pair) {
tcp->accept(ip::tcp::endpoint(ip_port_pair.value().first, ip_port_pair.value().second));
}
}
}
link_layer.reset(tcp);
} else if (name == "autotalks") {
#ifdef SOCKTAP_WITH_AUTOTALKS
link_layer.reset(new AutotalksLink { io_context });
#endif
} else if (name == "cube-evk") {
#ifdef SOCKTAP_WITH_CUBE_EVK
link_layer.reset(new CubeEvkLink {
io_context,
boost::asio::ip::make_address(vm["cube-ip"].as<std::string>()),
vm["cube-tx-port"].as<unsigned>(),
vm["cube-rx-port"].as<unsigned>()
});
#endif
} else if (name == "rpc") {
#ifdef SOCKTAP_WITH_RPC
boost::asio::ip::tcp::socket socket(io_context);
boost::asio::ip::tcp::resolver resolver(io_context);
auto rpc_host = vm["rpc-host"].as<std::string>();
auto rpc_port = vm["rpc-port"].as<unsigned>();
auto endpoints = resolver.resolve(rpc_host, std::to_string(rpc_port));
boost::asio::connect(socket, endpoints);
link_layer.reset(new RpcLinkLayer {io_context, std::move(socket)});
if (auto rpc_link_layer = static_cast<RpcLinkLayer*>(link_layer.get())) {
rpc_link_layer->radio_technology(vm["rpc-radio-technology"].as<std::string>());
rpc_link_layer->enable_debug(vm["rpc-debug"].as<bool>());
}
#endif
}
return link_layer;
}
void add_link_layer_options(boost::program_options::options_description& options)
{
options.add_options()
("tcp-connect", boost::program_options::value<std::vector<std::string>>()->multitoken(), "Connect to TCP-Host(s). Comma separated list of [ip]:[port].")
("tcp-accept", boost::program_options::value<std::vector<std::string>>()->multitoken(), "Accept TCP-Connections. Comma separated list of [ip]:[port].")
;
}
@@ -0,0 +1,38 @@
#ifndef LINK_LAYER_HPP_FGEK0QTH
#define LINK_LAYER_HPP_FGEK0QTH
#include "ethernet_device.hpp"
#include <vanetza/access/interface.hpp>
#include <vanetza/net/cohesive_packet.hpp>
#include <vanetza/net/ethernet_header.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/address.hpp>
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp>
#include <memory>
#include <string>
class LinkLayerIndication
{
public:
using IndicationCallback = std::function<void(vanetza::CohesivePacket&&, const vanetza::EthernetHeader&)>;
virtual void indicate(IndicationCallback) = 0;
virtual ~LinkLayerIndication() = default;
};
class LinkLayer : public vanetza::access::Interface, public LinkLayerIndication
{
public:
virtual void set_source_address(const vanetza::MacAddress&) {};
};
boost::optional<std::pair<boost::asio::ip::address, unsigned short>> parse_ip_port(const std::string& ip_port);
std::unique_ptr<LinkLayer>
create_link_layer(boost::asio::io_context&, const EthernetDevice&, const std::string& name, const boost::program_options::variables_map& vm);
void add_link_layer_options(boost::program_options::options_description&);
#endif /* LINK_LAYER_HPP_FGEK0QTH */
+199
View File
@@ -0,0 +1,199 @@
#include "ethernet_device.hpp"
#include "benchmark_application.hpp"
#include "cam_application.hpp"
#include "hello_application.hpp"
#include "link_layer.hpp"
#include "positioning.hpp"
#include "router_context.hpp"
#include "security.hpp"
#include "time_trigger.hpp"
#include <boost/asio/io_context.hpp>
#include <boost/asio/signal_set.hpp>
#include <boost/program_options.hpp>
#include <vanetza/common/annotation.hpp>
#include <iostream>
#ifdef SOCKTAP_WITH_CUBE_EVK
#include "nfiniity_cube_evk.hpp"
#endif
#ifdef SOCKTAP_WITH_RPC
#include "rpc_link.hpp"
#endif
namespace asio = boost::asio;
namespace gn = vanetza::geonet;
namespace po = boost::program_options;
using namespace vanetza;
int main(int argc, const char** argv)
{
po::options_description options("Allowed options");
options.add_options()
("help", "Print out available options.")
("link-layer,l", po::value<std::string>()->default_value("ethernet"), "Link layer type")
("interface,i", po::value<std::string>()->default_value("lo"), "Network interface to use.")
("mac-address", po::value<std::string>(), "Override the network interface's MAC address.")
("require-gnss-fix", "Suppress transmissions while GNSS position fix is missing")
("gn-version", po::value<unsigned>()->default_value(1), "GeoNetworking protocol version to use.")
("cam-interval", po::value<unsigned>()->default_value(1000), "CAM sending interval in milliseconds.")
("station-id", po::value<unsigned>()->default_value(1), "CAM station ID used in generated messages.")
("print-rx-cam", "Print received CAMs")
("print-tx-cam", "Print generated CAMs")
("benchmark", "Enable benchmarking")
("applications,a", po::value<std::vector<std::string>>()->default_value({"ca"}, "ca")->multitoken(), "Run applications [ca,hello,benchmark]")
("non-strict", "Set MIB parameter ItsGnSnDecapResultHandling to NON_STRICT")
;
add_positioning_options(options);
add_security_options(options);
add_link_layer_options(options);
#ifdef SOCKTAP_WITH_CUBE_EVK
nfiniity::add_cube_evk_options(options);
#endif
#ifdef SOCKTAP_WITH_RPC
RpcLinkLayer::add_options(options);
#endif
po::positional_options_description positional_options;
positional_options.add("interface", 1);
po::variables_map vm;
try {
po::store(
po::command_line_parser(argc, argv)
.options(options)
.positional(positional_options)
.run(),
vm
);
po::notify(vm);
} catch (po::error& e) {
std::cerr << "ERROR: " << e.what() << std::endl << std::endl;
std::cerr << options << std::endl;
return 1;
}
if (vm.count("help")) {
std::cout << options << std::endl;
return 1;
}
try {
asio::io_context io_context;
TimeTrigger trigger(io_context);
const char* device_name = vm["interface"].as<std::string>().c_str();
EthernetDevice device(device_name);
vanetza::MacAddress mac_address = device.address();
if (vm.count("mac-address")) {
std::cout << "Using MAC address: " << vm["mac-address"].as<std::string>() << "." << std::endl;
if (!parse_mac_address(vm["mac-address"].as<std::string>().c_str(), mac_address)) {
std::cerr << "The specified MAC address is invalid." << std::endl;
return 1;
}
}
const std::string link_layer_name = vm["link-layer"].as<std::string>();
auto link_layer = create_link_layer(io_context, device, link_layer_name, vm);
if (!link_layer) {
std::cerr << "No link layer '" << link_layer_name << "' found." << std::endl;
return 1;
}
auto signal_handler = [&io_context](const boost::system::error_code& ec, int signal_number) {
mark_unused(signal_number);
if (!ec) {
std::cout << "Termination requested." << std::endl;
io_context.stop();
}
};
asio::signal_set signals(io_context, SIGINT, SIGTERM);
signals.async_wait(signal_handler);
// configure management information base
// TODO: make more MIB options configurable by command line flags
gn::MIB mib;
mib.itsGnLocalGnAddr.mid(mac_address);
mib.itsGnLocalGnAddr.is_manually_configured(true);
mib.itsGnLocalAddrConfMethod = geonet::AddrConfMethod::Managed;
mib.itsGnSecurity = false;
if (vm.count("non-strict")) {
mib.itsGnSnDecapResultHandling = vanetza::geonet::SecurityDecapHandling::Non_Strict;
}
mib.itsGnProtocolVersion = vm["gn-version"].as<unsigned>();
if (mib.itsGnProtocolVersion != 0 && mib.itsGnProtocolVersion != 1) {
throw std::runtime_error("Unsupported GeoNetworking version, only version 0 and 1 are supported.");
}
auto positioning = create_position_provider(io_context, vm, trigger.runtime());
if (!positioning) {
std::cerr << "Requested positioning method is not available\n";
return 1;
}
auto security = create_security_entity(vm, trigger.runtime(), *positioning);
if (security) {
mib.itsGnSecurity = true;
}
RouterContext context(mib, trigger, *positioning, security.get());
context.require_position_fix(vm.count("require-gnss-fix") > 0);
context.set_link_layer(link_layer.get());
std::map<std::string, std::unique_ptr<Application>> apps;
for (const std::string& app_name : vm["applications"].as<std::vector<std::string>>()) {
if (apps.find(app_name) != apps.end()) {
std::cerr << "application '" << app_name << "' requested multiple times, skip\n";
continue;
}
if (app_name == "ca") {
std::unique_ptr<CamApplication> ca {
new CamApplication(*positioning, trigger.runtime())
};
ca->set_interval(std::chrono::milliseconds(vm["cam-interval"].as<unsigned>()));
ca->set_station_id(vm["station-id"].as<unsigned>());
ca->print_received_message(vm.count("print-rx-cam") > 0);
ca->print_generated_message(vm.count("print-tx-cam") > 0);
apps.emplace(app_name, std::move(ca));
} else if (app_name == "hello") {
std::unique_ptr<HelloApplication> hello {
new HelloApplication(io_context, std::chrono::milliseconds(800))
};
apps.emplace(app_name, std::move(hello));
} else if (app_name == "benchmark") {
std::unique_ptr<BenchmarkApplication> benchmark {
new BenchmarkApplication(io_context)
};
apps.emplace(app_name, std::move(benchmark));
} else {
std::cerr << "skip unknown application '" << app_name << "'\n";
}
}
if (apps.empty()) {
std::cerr << "Warning: No applications are configured, only GN beacons will be exchanged\n";
}
for (const auto& app : apps) {
std::cout << "Enable application '" << app.first << "'...\n";
context.enable(app.second.get());
}
io_context.run();
} catch (PositioningException& e) {
std::cerr << "Exit because of positioning error: " << e.what() << std::endl;
return 1;
} catch (std::exception& e) {
std::cerr << "Exit: " << e.what() << std::endl;
return 1;
}
return 0;
}
@@ -0,0 +1,12 @@
#include "nfiniity_cube_evk.hpp"
namespace po = boost::program_options;
void vanetza::nfiniity::add_cube_evk_options(po::options_description& options)
{
options.add_options()
("cube-ip", po::value<std::string>()->default_value("127.0.0.1"), "cube evk's ip address")
("cube-tx-port", po::value<unsigned>()->default_value(33210), "cube evk UDP transmit port")
("cube-rx-port", po::value<unsigned>()->default_value(33211), "cube evk UDP receive port")
;
}
@@ -0,0 +1,16 @@
#ifndef NFINIITY_CUBE_EVK_HPP_
#define NFINIITY_CUBE_EVK_HPP_
#include <boost/program_options.hpp>
namespace vanetza
{
namespace nfiniity
{
void add_cube_evk_options(boost::program_options::options_description& options);
} // namespace nfiniity
} // namespace vanetza
#endif /* NFINIITY_CUBE_EVK_HPP_ */
@@ -0,0 +1,148 @@
#include "nfiniity_cube_evk_link.hpp"
#include "nfiniity_cube_radio.pb.h"
#include <iostream>
#include <vanetza/net/osi_layer.hpp>
#include <vanetza/net/packet_variant.hpp>
#include <vanetza/net/cohesive_packet.hpp>
#include <vanetza/access/data_request.hpp>
#include <vanetza/common/byte_view.hpp>
#include <vanetza/access/ethertype.hpp>
#include <boost/asio/placeholders.hpp>
#include <boost/bind/bind.hpp>
CubeEvkLink::CubeEvkLink(boost::asio::io_context& io, boost::asio::ip::address radio_ip, unsigned int tx_port, unsigned int rx_port)
: io_(io), tx_socket_(io), rx_socket_(io)
{
const boost::asio::ip::udp::endpoint radio_endpoint_tx(radio_ip, tx_port);
tx_socket_.connect(radio_endpoint_tx);
boost::asio::ip::udp::endpoint radio_endpoint_rx(boost::asio::ip::udp::v4(), rx_port);
rx_socket_.open(radio_endpoint_rx.protocol());
rx_socket_.bind(radio_endpoint_rx);
rx_socket_.async_receive_from(
boost::asio::buffer(received_data_), host_endpoint_,
boost::bind(&CubeEvkLink::handle_packet_received, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void CubeEvkLink::handle_packet_received(const boost::system::error_code& ec, size_t bytes)
{
if (!ec)
{
vanetza::ByteBuffer buf(received_data_.begin(), received_data_.begin() + bytes);
GossipMessage gossipMessage;
gossipMessage.ParseFromArray(buf.data(), buf.size());
switch (gossipMessage.kind_case())
{
case GossipMessage::KindCase::kCbr:
{
// got CBR; use this for your DCC
// const ChannelBusyRatio& cbr = gossipMessage.cbr();
// vanetza::dcc::ChannelLoad(cbr.busy(), cbr.total())
break;
}
case GossipMessage::KindCase::kLinklayerRx:
{
pass_message_to_router(std::unique_ptr<LinkLayerReception>{gossipMessage.release_linklayer_rx()});
break;
}
default:
{
std::cerr << "Received GossipMessage of unknown kind " << gossipMessage.kind_case() << std::endl;
}
}
rx_socket_.async_receive_from(
boost::asio::buffer(received_data_), host_endpoint_,
boost::bind(&CubeEvkLink::handle_packet_received, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
else
{
std::cerr << "CubeEvkLink::handle_packet_received went wrong: " << ec << std::endl;
}
}
void CubeEvkLink::request(const vanetza::access::DataRequest& request, std::unique_ptr<vanetza::ChunkPacket> packet)
{
CommandRequest command;
command.set_allocated_linklayer_tx(create_link_layer_tx(request, std::move(packet)).release());
std::string serializedTransmission;
command.SerializeToString(&serializedTransmission);
tx_socket_.send(boost::asio::buffer(serializedTransmission));
}
void CubeEvkLink::indicate(IndicationCallback callback)
{
indicate_to_router_ = callback;
}
void CubeEvkLink::pass_message_to_router(std::unique_ptr<LinkLayerReception> packet)
{
if (packet->source().size() != vanetza::MacAddress::length_bytes)
{
std::cerr << "received packet's source MAC address is invalid" << std::endl;
}
else if (packet->destination().size() != vanetza::MacAddress::length_bytes)
{
std::cerr << "received packet's destination MAC address is invalid" << std::endl;
}
else
{
vanetza::EthernetHeader ethernet_header;
std::copy_n(packet->source().begin(), vanetza::MacAddress::length_bytes, ethernet_header.source.octets.begin());
std::copy_n(packet->destination().begin(), vanetza::MacAddress::length_bytes, ethernet_header.destination.octets.begin());
ethernet_header.type = vanetza::access::ethertype::GeoNetworking;
vanetza::ByteBuffer buffer(packet->payload().begin(), packet->payload().end());
vanetza::CohesivePacket packet(std::move(buffer), vanetza::OsiLayer::Network);
indicate_to_router_(std::move(packet), ethernet_header);
}
}
std::unique_ptr<LinkLayerTransmission> CubeEvkLink::create_link_layer_tx(const vanetza::access::DataRequest& req, std::unique_ptr<vanetza::ChunkPacket> packet)
{
using namespace vanetza;
std::unique_ptr<LinkLayerTransmission> transmission{new LinkLayerTransmission()};
transmission->set_source(req.source_addr.octets.data(), req.source_addr.octets.size());
transmission->set_destination(req.destination_addr.octets.data(), req.destination_addr.octets.size());
LinkLayerPriority prio = LinkLayerPriority::BEST_EFFORT;
switch (req.access_category)
{
case access::AccessCategory::VO:
prio = LinkLayerPriority::VOICE;
break;
case access::AccessCategory::VI:
prio = LinkLayerPriority::VIDEO;
break;
case access::AccessCategory::BE:
prio = LinkLayerPriority::BEST_EFFORT;
break;
case access::AccessCategory::BK:
prio = LinkLayerPriority::BACKGROUND;
break;
default:
std::cerr << "Unknown access category requested, falling back to best effort!" << std::endl;
break;
}
transmission->set_priority(prio);
std::string* payload = transmission->mutable_payload();
for (auto& layer : osi_layer_range<OsiLayer::Network, OsiLayer::Application>())
{
auto byte_view = create_byte_view(packet->layer(layer));
payload->append(byte_view.begin(), byte_view.end());
}
return transmission;
}
@@ -0,0 +1,40 @@
#ifndef NFINIITY_CUBE_EVK_LINK_HPP_
#define NFINIITY_CUBE_EVK_LINK_HPP_
#include "link_layer.hpp"
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/udp.hpp>
#include <vanetza/net/chunk_packet.hpp>
#include <array>
#include <cstdint>
#include <memory>
class LinkLayerReception;
class LinkLayerTransmission;
class CubeEvkLink : public LinkLayer
{
public:
CubeEvkLink(boost::asio::io_context&, boost::asio::ip::address, unsigned int tx_port, unsigned int rx_port);
void handle_packet_received(const boost::system::error_code&, size_t);
void request(const vanetza::access::DataRequest&, std::unique_ptr<vanetza::ChunkPacket>) override;
void indicate(IndicationCallback callback) override;
private:
boost::asio::io_context& io_;
IndicationCallback indicate_to_router_;
boost::asio::ip::udp::socket tx_socket_;
boost::asio::ip::udp::socket rx_socket_;
boost::asio::ip::udp::endpoint host_endpoint_;
std::array<uint8_t, 4096> received_data_;
void pass_message_to_router(std::unique_ptr<LinkLayerReception>);
std::unique_ptr<LinkLayerTransmission> create_link_layer_tx(const vanetza::access::DataRequest&, std::unique_ptr<vanetza::ChunkPacket>);
};
#endif /* NFINIITY_CUBE_EVK_LINK_HPP_ */
@@ -0,0 +1,79 @@
syntax = "proto2";
message CommandRequest {
oneof kind {
LifecycleAction lifecycle = 1;
LinkLayerTransmission linklayer_tx = 2;
RadioConfiguration radio_cfg = 3;
}
}
message CommandResponse {
enum Status {
SUCCESS = 0;
FAILURE = 1;
UNKNOWN = 2;
NOT_IMPLEMENTED = 3;
}
required Status status = 1;
optional string message = 2;
optional CommandResponseData data = 3;
}
message CommandResponseData {
oneof kind {
RadioConfiguration radio_cfg = 1;
}
}
enum LifecycleAction {
SOFT_RESET = 0;
HARD_RESET = 1;
}
enum LinkLayerPriority {
BACKGROUND = 0;
BEST_EFFORT = 1;
VIDEO = 2;
VOICE = 3;
}
message RadioConfiguration {
optional bytes address = 1;
optional uint32 channel_frequency_mhz = 2;
optional bool filter_unicast_destination = 3;
optional sint32 default_tx_power_cbm = 4;
optional uint32 default_tx_datarate_500kbps = 5;
}
message LinkLayerTransmission {
optional bytes source = 1;
required bytes destination = 2;
required LinkLayerPriority priority = 3;
optional uint32 channel = 4;
optional uint32 datarate_500kbps = 5;
optional sint32 power_cbm = 6; // centi Bel mW = 10 * dBm
required bytes payload = 10;
}
message LinkLayerReception {
required bytes source = 1;
required bytes destination = 2;
optional uint32 channel = 4;
optional sint32 power_cbm = 6;
required bytes payload = 10;
}
message ChannelBusyRatio {
required uint32 busy = 1;
required uint32 total = 2;
}
message GossipMessage {
oneof kind {
ChannelBusyRatio cbr = 1;
LinkLayerReception linklayer_rx = 2;
}
}
@@ -0,0 +1,55 @@
#include "positioning.hpp"
#include <vanetza/common/stored_position_provider.hpp>
#ifdef SOCKTAP_WITH_GPSD
# include "gps_position_provider.hpp"
#endif
using namespace vanetza;
namespace po = boost::program_options;
std::unique_ptr<vanetza::PositionProvider>
create_position_provider(boost::asio::io_context& io_context, const po::variables_map& vm, const Runtime& runtime)
{
std::unique_ptr<vanetza::PositionProvider> positioning;
if (vm["positioning"].as<std::string>() == "gpsd") {
#ifdef SOCKTAP_WITH_GPSD
positioning.reset(new GpsPositionProvider {
io_context, vm["gpsd-host"].as<std::string>(), vm["gpsd-port"].as<std::string>()
});
#endif
} else if (vm["positioning"].as<std::string>() == "static") {
std::unique_ptr<StoredPositionProvider> stored { new StoredPositionProvider() };
PositionFix fix;
fix.timestamp = runtime.now();
fix.latitude = vm["latitude"].as<double>() * units::degree;
fix.longitude = vm["longitude"].as<double>() * units::degree;
fix.confidence.semi_major = vm["pos_confidence"].as<double>() * units::si::meter;
fix.confidence.semi_minor = fix.confidence.semi_major;
stored->position_fix(fix);
positioning = std::move(stored);
}
return positioning;
}
void add_positioning_options(po::options_description& options)
{
#ifdef SOCKTAP_WITH_GPSD
const char* default_positioning = "gpsd";
#else
const char* default_positioning = "static";
#endif
options.add_options()
("positioning,p", po::value<std::string>()->default_value(default_positioning), "Select positioning provider")
#ifdef SOCKTAP_WITH_GPSD
("gpsd-host", po::value<std::string>()->default_value("localhost"), "gpsd's server hostname")
("gpsd-port", po::value<std::string>()->default_value(gpsd::default_port), "gpsd's listening port")
#endif
("latitude", po::value<double>()->default_value(48.7668616), "Latitude of static position")
("longitude", po::value<double>()->default_value(11.432068), "Longitude of static position")
("pos_confidence", po::value<double>()->default_value(5.0), "95% circular confidence of static position")
;
}
@@ -0,0 +1,22 @@
#ifndef POSITIONING_HPP_VZRIW7PB
#define POSITIONING_HPP_VZRIW7PB
#include <vanetza/common/position_provider.hpp>
#include <vanetza/common/runtime.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp>
#include <memory>
#include <stdexcept>
class PositioningException : public std::runtime_error
{
using std::runtime_error::runtime_error;
};
std::unique_ptr<vanetza::PositionProvider>
create_position_provider(boost::asio::io_context&, const boost::program_options::variables_map&, const vanetza::Runtime&);
void add_positioning_options(boost::program_options::options_description&);
#endif /* POSITIONING_HPP_VZRIW7PB */
@@ -0,0 +1,71 @@
#include "raw_socket_link.hpp"
#include <vanetza/access/data_request.hpp>
#include <vanetza/net/ethernet_header.hpp>
#include <iostream>
using namespace vanetza;
RawSocketLink::RawSocketLink(boost::asio::generic::raw_protocol::socket&& socket) :
socket_(std::move(socket)), receive_buffer_(2048, 0x00),
receive_endpoint_(socket_.local_endpoint())
{
do_receive();
}
void RawSocketLink::request(const access::DataRequest& request, std::unique_ptr<ChunkPacket> packet)
{
packet->layer(OsiLayer::Link) = create_ethernet_header(request.destination_addr, request.source_addr, request.ether_type);
transmit(std::move(packet));
}
std::size_t RawSocketLink::transmit(std::unique_ptr<ChunkPacket> packet)
{
std::array<boost::asio::const_buffer, layers_> const_buffers;
for (auto& layer : osi_layer_range<OsiLayer::Physical, OsiLayer::Application>()) {
const auto index = distance(OsiLayer::Physical, layer);
packet->layer(layer).convert(buffers_[index]);
const_buffers[index] = boost::asio::buffer(buffers_[index]);
}
return socket_.send(const_buffers);
}
void RawSocketLink::indicate(IndicationCallback callback)
{
callback_ = callback;
}
void RawSocketLink::do_receive()
{
namespace sph = std::placeholders;
socket_.async_receive_from(
boost::asio::buffer(receive_buffer_), receive_endpoint_,
std::bind(&RawSocketLink::on_read, this, sph::_1, sph::_2));
}
void RawSocketLink::on_read(const boost::system::error_code& ec, std::size_t read_bytes)
{
if (!ec) {
ByteBuffer buffer(receive_buffer_.begin(), receive_buffer_.begin() + read_bytes);
CohesivePacket packet(std::move(buffer), OsiLayer::Physical);
boost::optional<EthernetHeader> eth = parse_ethernet_header(packet);
if (callback_ && eth) {
callback_(std::move(packet), *eth);
}
do_receive();
}
}
boost::optional<EthernetHeader> RawSocketLink::parse_ethernet_header(vanetza::CohesivePacket& packet) const
{
packet.set_boundary(OsiLayer::Physical, 0);
if (packet.size(OsiLayer::Link) < EthernetHeader::length_bytes) {
std::cerr << "Router dropped invalid packet (too short for Ethernet header)\n";
} else {
packet.set_boundary(OsiLayer::Link, EthernetHeader::length_bytes);
auto link_range = packet[OsiLayer::Link];
return decode_ethernet_header(link_range.begin(), link_range.end());
}
return boost::none;
}
@@ -0,0 +1,38 @@
#ifndef RAW_SOCKET_LINK_HPP_VUXH507U
#define RAW_SOCKET_LINK_HPP_VUXH507U
#include "link_layer.hpp"
#include <vanetza/access/interface.hpp>
#include <vanetza/net/ethernet_header.hpp>
#include <boost/asio/generic/raw_protocol.hpp>
#include <boost/optional/optional.hpp>
#include <array>
#include <functional>
class RawSocketLink : public LinkLayer
{
public:
RawSocketLink(boost::asio::generic::raw_protocol::socket&&);
void request(const vanetza::access::DataRequest&, std::unique_ptr<vanetza::ChunkPacket>) override;
void indicate(IndicationCallback) override;
protected:
std::size_t transmit(std::unique_ptr<vanetza::ChunkPacket>);
virtual boost::optional<vanetza::EthernetHeader> parse_ethernet_header(vanetza::CohesivePacket&) const;
private:
void do_receive();
void on_read(const boost::system::error_code&, std::size_t);
void pass_up(vanetza::CohesivePacket&&);
static constexpr std::size_t layers_ = num_osi_layers(vanetza::OsiLayer::Physical, vanetza::OsiLayer::Application);
boost::asio::generic::raw_protocol::socket socket_;
std::array<vanetza::ByteBuffer, layers_> buffers_;
IndicationCallback callback_;
vanetza::ByteBuffer receive_buffer_;
boost::asio::generic::raw_protocol::endpoint receive_endpoint_;
};
#endif /* RAW_SOCKET_LINK_HPP_VUXH507U */
@@ -0,0 +1,114 @@
#include "application.hpp"
#include "dcc_passthrough.hpp"
#include "ethernet_device.hpp"
#include "router_context.hpp"
#include "time_trigger.hpp"
#include <vanetza/access/ethertype.hpp>
#include <vanetza/dcc/data_request.hpp>
#include <vanetza/dcc/interface.hpp>
#include <iostream>
#include <vanetza/common/byte_order.hpp>
using namespace vanetza;
RouterContext::RouterContext(const geonet::MIB& mib, TimeTrigger& trigger, vanetza::PositionProvider& positioning, vanetza::security::SecurityEntity* security_entity) :
mib_(mib), router_(trigger.runtime(), mib_),
trigger_(trigger), positioning_(positioning)
{
router_.packet_dropped = std::bind(&RouterContext::log_packet_drop, this, std::placeholders::_1);
router_.set_address(mib_.itsGnLocalGnAddr);
router_.set_transport_handler(geonet::UpperProtocol::BTP_B, &dispatcher_);
router_.set_security_entity(security_entity);
update_position_vector();
trigger_.schedule();
}
RouterContext::~RouterContext()
{
for (auto* app : applications_) {
disable(app);
}
}
void RouterContext::log_packet_drop(geonet::Router::PacketDropReason reason)
{
auto reason_string = stringify(reason);
std::cout << "Router dropped packet because of " << reason_string << " (" << static_cast<int>(reason) << ")\n";
}
void RouterContext::set_link_layer(LinkLayer* link_layer)
{
namespace dummy = std::placeholders;
if (link_layer) {
link_layer->set_source_address(mib_.itsGnLocalGnAddr.mid());
request_interface_.reset(new DccPassthrough { *link_layer, trigger_ });
router_.set_access_interface(request_interface_.get());
link_layer->indicate(std::bind(&RouterContext::indicate, this, dummy::_1, dummy::_2));
update_packet_flow(router_.get_local_position_vector());
} else {
router_.set_access_interface(nullptr);
request_interface_.reset();
}
}
void RouterContext::indicate(CohesivePacket&& packet, const EthernetHeader& hdr)
{
if (hdr.source != mib_.itsGnLocalGnAddr.mid() && hdr.type == access::ethertype::GeoNetworking) {
std::cout << "received packet from " << hdr.source << " (" << packet.size() << " bytes)\n";
std::unique_ptr<PacketVariant> up { new PacketVariant(std::move(packet)) };
trigger_.schedule(); // ensure the clock is up-to-date for the security entity
router_.indicate(std::move(up), hdr.source, hdr.destination);
trigger_.schedule(); // schedule packet forwarding
}
}
void RouterContext::enable(Application* app)
{
app->router_ = &router_;
dispatcher_.add_promiscuous_hook(app->promiscuous_hook());
if (app->port() != btp::port_type(0)) {
dispatcher_.set_non_interactive_handler(app->port(), app);
}
}
void RouterContext::disable(Application* app)
{
if (app->port() != btp::port_type(0)) {
dispatcher_.set_non_interactive_handler(app->port(), nullptr);
}
dispatcher_.remove_promiscuous_hook(app->promiscuous_hook());
app->router_ = nullptr;
}
void RouterContext::require_position_fix(bool flag)
{
require_position_fix_ = flag;
update_packet_flow(router_.get_local_position_vector());
}
void RouterContext::update_position_vector()
{
router_.update_position(positioning_.position_fix());
vanetza::Runtime::Callback callback = [this](vanetza::Clock::time_point) { this->update_position_vector(); };
vanetza::Clock::duration next = std::chrono::seconds(1);
trigger_.runtime().schedule(next, callback);
trigger_.schedule();
update_packet_flow(router_.get_local_position_vector());
}
void RouterContext::update_packet_flow(const geonet::LongPositionVector& lpv)
{
if (request_interface_) {
if (require_position_fix_) {
// Skip all requests until a valid GPS position is available
request_interface_->allow_packet_flow(lpv.position_accuracy_indicator);
} else {
request_interface_->allow_packet_flow(true);
}
}
}
@@ -0,0 +1,50 @@
#ifndef ROUTER_CONTEXT_HPP_KIPUYBY2
#define ROUTER_CONTEXT_HPP_KIPUYBY2
#include "dcc_passthrough.hpp"
#include "link_layer.hpp"
#include <vanetza/btp/port_dispatcher.hpp>
#include <vanetza/common/position_provider.hpp>
#include <vanetza/geonet/mib.hpp>
#include <vanetza/geonet/router.hpp>
#include <array>
#include <list>
#include <memory>
class Application;
class TimeTrigger;
class RouterContext
{
public:
RouterContext(const vanetza::geonet::MIB&, TimeTrigger&, vanetza::PositionProvider&, vanetza::security::SecurityEntity*);
~RouterContext();
void enable(Application*);
void disable(Application*);
/**
* Allow/disallow transmissions without GNSS position fix
*
* \param flag true if transmissions shall be dropped when no GNSS position fix is available
*/
void require_position_fix(bool flag);
void set_link_layer(LinkLayer*);
private:
void indicate(vanetza::CohesivePacket&& packet, const vanetza::EthernetHeader& hdr);
void log_packet_drop(vanetza::geonet::Router::PacketDropReason);
void update_position_vector();
void update_packet_flow(const vanetza::geonet::LongPositionVector&);
vanetza::geonet::MIB mib_;
vanetza::geonet::Router router_;
TimeTrigger& trigger_;
vanetza::PositionProvider& positioning_;
vanetza::btp::PortDispatcher dispatcher_;
std::unique_ptr<DccPassthrough> request_interface_;
std::list<Application*> applications_;
bool require_position_fix_ = false;
};
#endif /* ROUTER_CONTEXT_HPP_KIPUYBY2 */
@@ -0,0 +1,112 @@
#include "rpc_link.hpp"
#include "vanetza/rpc/link_layer_client.hpp"
#include "vanetza/rpc/logger.hpp"
#include <vanetza/access/ethertype.hpp>
#include <iostream>
namespace {
class RpcLinkLayerLogger : public vanetza::rpc::Logger
{
public:
void error(const char* module, const char* message) override
{
std::cerr << "RPC error at " << module << ": " << message << "\n";
}
void debug(const char* module, const char* message) override
{
if (print_debug_) {
std::cout << "RPC debug(" << module << "): " << message << "\n";
}
}
void enable_debug(bool debug)
{
print_debug_ = debug;
}
private:
bool print_debug_ = false;
};
RpcLinkLayerLogger logger;
} // namespace
RpcLinkLayer::RpcLinkLayer(boost::asio::io_context& io, boost::asio::ip::tcp::socket socket) :
io_(io),
event_port_(io),
event_loop_(event_port_),
asio_stream_(std::move(socket)),
wait_scope_(event_loop_),
client_(event_port_.getTimer(), asio_stream_, &logger)
{
auto id = client_.identify().then([](const vanetza::rpc::LinkLayerClient::Identity& identity) -> kj::Promise<void> {
std::cout << "Connected to RPC server id=" << identity.id << " version=" << identity.version << "\n";
if (!identity.info.empty()) {
std::cout << "RPC server's info: " << identity.info << "\n";
}
return kj::READY_NOW;
});
client_.add_task(kj::mv(id));
}
RpcLinkLayer::~RpcLinkLayer() noexcept
{
}
void RpcLinkLayer::set_source_address(const vanetza::MacAddress& addr)
{
client_.set_source_address(addr);
}
void RpcLinkLayer::request(const vanetza::access::DataRequest& request, std::unique_ptr<vanetza::ChunkPacket> packet)
{
client_.request(request, std::move(packet));
}
void RpcLinkLayer::indicate(IndicationCallback callback)
{
if (callback) {
auto wrapper = [callback, this](vanetza::rpc::LinkLayerClient::Indication&& indication) {
boost::asio::post(io_, [callback, indication]() mutable {
vanetza::EthernetHeader eth_hdr;
eth_hdr.destination = indication.destination;
eth_hdr.source = indication.source;
eth_hdr.type = vanetza::access::ethertype::GeoNetworking;
callback(std::move(indication.packet), eth_hdr);
});
};
client_.indicate(std::move(wrapper));
} else {
client_.indicate(nullptr);
}
}
void RpcLinkLayer::radio_technology(const std::string& technology)
{
if (technology == "ITS-G5") {
client_.configure(vanetza::rpc::LinkLayerClient::Technology::ITS_G5);
} else if (technology == "LTE-V2X" || technology == "C-V2X") {
client_.configure(vanetza::rpc::LinkLayerClient::Technology::LTE_V2X);
} else if (!technology.empty()) {
std::cerr << "Unknown radio technology '" << technology << "'. RPC link layer omits radio-specific fields.\n";
}
}
void RpcLinkLayer::enable_debug(bool debug)
{
logger.enable_debug(debug);
}
void RpcLinkLayer::add_options(boost::program_options::options_description& options)
{
namespace po = boost::program_options;
options.add_options()
("rpc-host", po::value<std::string>()->default_value("localhost"), "RPC host address")
("rpc-port", po::value<unsigned>()->default_value(23057), "RPC port number")
("rpc-radio-technology", po::value<std::string>()->default_value(""), "radio technology of RPC link layer (ITS-G5 | LTE-V2X)")
("rpc-debug", po::bool_switch()->default_value(false), "RPC debug output")
;
}
@@ -0,0 +1,41 @@
#pragma once
#include "link_layer.hpp"
#include <vanetza/access/interface.hpp>
#include <vanetza/rpc/asio_event_loop.hpp>
#include <vanetza/rpc/asio_event_port.hpp>
#include <vanetza/rpc/asio_stream.hpp>
#include <vanetza/rpc/link_layer_client.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/program_options/options_description.hpp>
#include <kj/async.h>
class RpcLinkLayer : public LinkLayer
{
public:
/**
* Create RPC link layer
* \param io ASIO context
* \param socket TCP socket connected to RPC server
*/
RpcLinkLayer(boost::asio::io_context& io, boost::asio::ip::tcp::socket socket);
~RpcLinkLayer() noexcept;
void request(const vanetza::access::DataRequest&, std::unique_ptr<vanetza::ChunkPacket>) override;
void indicate(IndicationCallback) override;
void set_source_address(const vanetza::MacAddress&) override;
void radio_technology(const std::string&);
void enable_debug(bool);
static void add_options(boost::program_options::options_description&);
private:
boost::asio::io_context& io_;
vanetza::rpc::AsioEventPort event_port_;
vanetza::rpc::AsioEventLoop event_loop_;
vanetza::rpc::AsioStream asio_stream_;
kj::WaitScope wait_scope_;
vanetza::rpc::LinkLayerClient client_;
IndicationCallback callback_;
};
@@ -0,0 +1,283 @@
#include "security.hpp"
#include "certificate_validation_v3.hpp"
#include <vanetza/geodesy/country_database.hpp>
#include <vanetza/security/delegating_security_entity.hpp>
#include <vanetza/security/persistence.hpp>
#include <vanetza/security/straight_verify_service.hpp>
#include <vanetza/security/v2/certificate_cache.hpp>
#include "vanetza/security/v2/certificate_provider.hpp"
#include <vanetza/security/v2/default_certificate_validator.hpp>
#include <vanetza/security/v2/naive_certificate_provider.hpp>
#include <vanetza/security/v2/persistence.hpp>
#include <vanetza/security/v2/sign_service.hpp>
#include <vanetza/security/v2/static_certificate_provider.hpp>
#include <vanetza/security/v2/trust_store.hpp>
#include <vanetza/security/v3/certificate_cache.hpp>
#include <vanetza/security/v3/certificate_validator.hpp>
#include <vanetza/security/v3/naive_certificate_provider.hpp>
#include <vanetza/security/v3/persistence.hpp>
#include <vanetza/security/v3/sign_header_policy.hpp>
#include <vanetza/security/v3/sign_service.hpp>
#include <vanetza/security/v3/static_certificate_provider.hpp>
#include <stdexcept>
using namespace vanetza;
namespace po = boost::program_options;
class SecurityContextV2 : public security::SecurityEntity
{
public:
SecurityContextV2(const Runtime& runtime, PositionProvider& positioning, const std::string& backend_name) :
runtime(runtime), positioning(positioning),
backend(security::create_backend(backend_name)),
sign_header_policy(runtime, positioning),
cert_cache(runtime),
cert_validator(*backend, cert_cache, trust_store)
{
}
security::EncapConfirm encapsulate_packet(security::EncapRequest&& request) override
{
if (!entity) {
throw std::runtime_error("security entity is not ready");
}
return entity->encapsulate_packet(std::move(request));
}
security::DecapConfirm decapsulate_packet(security::DecapRequest&& request) override
{
if (!entity) {
throw std::runtime_error("security entity is not ready");
}
return entity->decapsulate_packet(std::move(request));
}
void build_entity()
{
if (!cert_provider) {
throw std::runtime_error("certificate provider is missing");
}
std::unique_ptr<security::SignService> sign_service { new
security::v2::StraightSignService(*cert_provider, *backend, sign_header_policy) };
std::unique_ptr<security::StraightVerifyService> verify_service { new
security::StraightVerifyService(runtime, *backend, positioning) };
verify_service->use_certificate_provider(cert_provider.get());
verify_service->use_certificate_cache(&cert_cache);
verify_service->use_certificate_validator(&cert_validator);
verify_service->use_sign_header_policy(&sign_header_policy);
entity.reset(new security::DelegatingSecurityEntity { std::move(sign_service), std::move(verify_service) });
}
const Runtime& runtime;
PositionProvider& positioning;
std::unique_ptr<security::Backend> backend;
std::unique_ptr<security::SecurityEntity> entity;
std::unique_ptr<security::v2::CertificateProvider> cert_provider;
security::v2::DefaultSignHeaderPolicy sign_header_policy;
security::v2::TrustStore trust_store;
security::v2::CertificateCache cert_cache;
security::v2::DefaultCertificateValidator cert_validator;
};
class SecurityContextV3 : public security::SecurityEntity
{
public:
SecurityContextV3(const po::variables_map& options, const Runtime& runtime,
PositionProvider& positioning, const std::string& backend_name,
bool permissive_identified_region = false) :
runtime(runtime), positioning(positioning),
backend(security::create_backend(backend_name)),
country_database(geodesy::CountryDatabase::embedded())
{
location_checker.set_permissive_identified_region(permissive_identified_region);
location_checker.use_country_database(&country_database);
certificate_validation = create_certificate_validation_v3(
options, runtime, positioning, location_checker, *backend);
}
security::EncapConfirm encapsulate_packet(security::EncapRequest&& request) override
{
if (!entity) {
throw std::runtime_error("security entity is not ready");
}
return entity->encapsulate_packet(std::move(request));
}
security::DecapConfirm decapsulate_packet(security::DecapRequest&& request) override
{
if (!entity) {
throw std::runtime_error("security entity is not ready");
}
return entity->decapsulate_packet(std::move(request));
}
void build_entity()
{
if (!cert_provider) {
throw std::runtime_error("certificate provider is missing");
}
auto& cert_validator = certificate_validation->validator();
sign_header_policy.reset(new security::v3::DefaultSignHeaderPolicy(runtime, positioning, *cert_provider));
std::unique_ptr<security::SignService> sign_service { new
security::v3::StraightSignService(*cert_provider, *backend, *sign_header_policy, cert_validator) };
std::unique_ptr<security::StraightVerifyService> verify_service { new
security::StraightVerifyService(runtime, *backend, positioning) };
verify_service->use_certificate_provider(cert_provider.get());
verify_service->use_certificate_validator(&cert_validator);
verify_service->use_sign_header_policy(sign_header_policy.get());
entity.reset(new security::DelegatingSecurityEntity { std::move(sign_service), std::move(verify_service) });
}
const Runtime& runtime;
PositionProvider& positioning;
std::unique_ptr<security::Backend> backend;
geodesy::CountryDatabase country_database;
security::v3::DefaultLocationChecker location_checker;
std::unique_ptr<security::v3::CertificateProvider> cert_provider;
std::unique_ptr<security::v3::DefaultSignHeaderPolicy> sign_header_policy;
std::unique_ptr<CertificateValidationV3> certificate_validation;
std::unique_ptr<security::SecurityEntity> entity;
};
std::unique_ptr<security::SecurityEntity>
create_dummy_v2_security_entity(const Runtime& runtime)
{
std::unique_ptr<security::SignService> sign_service { new security::v2::DummySignService { runtime, nullptr } };
std::unique_ptr<security::VerifyService> verify_service { new security::DummyVerifyService {
security::VerificationReport::Success, security::CertificateValidity::valid() } };
return std::make_unique<security::DelegatingSecurityEntity>(std::move(sign_service), std::move(verify_service));
}
std::unique_ptr<security::SecurityEntity>
create_dummy_v3_security_entity(const Runtime& runtime)
{
std::unique_ptr<security::SignService> sign_service { new security::v3::DummySignService { runtime } };
std::unique_ptr<security::VerifyService> verify_service { new security::DummyVerifyService {
security::VerificationReport::Success, security::CertificateValidity::valid() } };
return std::make_unique<security::DelegatingSecurityEntity>(std::move(sign_service), std::move(verify_service));
}
std::unique_ptr<security::v2::CertificateProvider>
load_v2_certificates(const std::string& cert_path, const std::string& cert_key_path, const std::vector<std::string> cert_chain_path, security::v2::CertificateCache& cert_cache)
{
auto authorization_ticket = security::v2::load_certificate_from_file(cert_path);
auto authorization_ticket_key = security::v2::load_private_key_from_file(cert_key_path);
std::list<security::v2::Certificate> chain;
for (auto& chain_path : cert_chain_path) {
auto chain_certificate = security::v2::load_certificate_from_file(chain_path);
chain.push_back(chain_certificate);
cert_cache.insert(chain_certificate);
}
return std::make_unique<security::v2::StaticCertificateProvider>(authorization_ticket, authorization_ticket_key.private_key, chain);
}
std::unique_ptr<security::v3::CertificateProvider>
load_v3_certificates(const std::string& cert_path, const std::string& cert_key_path,
const std::vector<std::string> cert_chain_path, CertificateValidationV3& validation)
{
auto authorization_ticket = security::v3::load_certificate_from_file(cert_path);
auto authorization_ticket_key = validation.load_authorization_ticket_key(cert_key_path);
auto provider = std::make_unique<security::v3::StaticCertificateProvider>(authorization_ticket, authorization_ticket_key);
for (auto& chain_path : cert_chain_path) {
auto chain_certificate = security::v3::load_certificate_from_file(chain_path);
provider->cache().store(chain_certificate);
validation.add_chain_certificate(chain_certificate);
}
return provider;
}
std::unique_ptr<security::SecurityEntity>
create_security_entity(const po::variables_map& vm, const Runtime& runtime, PositionProvider& positioning)
{
std::unique_ptr<security::SecurityEntity> security;
const std::string name = vm["security"].as<std::string>();
const std::string backend_name = vm["crypto-backend"].as<std::string>();
if (name.empty() || name == "none") {
// no operation
} else if (name == "dummy" || name == "dummy-v3") {
security = create_dummy_v3_security_entity(runtime);
} else if (name == "dummy-v2") {
security = create_dummy_v2_security_entity(runtime);
} else if (name == "certs" || name == "certs-v3" || name == "certs-v2") {
const unsigned version = name == "certs-v2" ? 2 : 3;
if (vm.count("certificate") ^ vm.count("certificate-key")) {
throw std::runtime_error("Either --certificate and --certificate-key must be present or none.");
}
if (vm.count("certificate") && vm.count("certificate-key")) {
const std::string& cert_path = vm["certificate"].as<std::string>();
const std::string& cert_key_path = vm["certificate-key"].as<std::string>();
std::vector<std::string> chain_paths;
if (vm.count("certificate-chain")) {
chain_paths = vm["certificate-chain"].as<std::vector<std::string>>();
}
if (version == 3) {
bool permissive_ir = vm.count("security.permissive-identified-region") &&
vm["security.permissive-identified-region"].as<bool>();
auto context = std::make_unique<SecurityContextV3>(vm, runtime, positioning, backend_name, permissive_ir);
context->cert_provider = load_v3_certificates(
cert_path, cert_key_path, chain_paths, *context->certificate_validation);
context->build_entity();
security = std::move(context);
} else {
auto context = std::make_unique<SecurityContextV2>(runtime, positioning, backend_name);
context->cert_provider = load_v2_certificates(cert_path, cert_key_path, chain_paths, context->cert_cache);
if (vm.count("trusted-certificate")) {
for (auto& cert_path : vm["trusted-certificate"].as<std::vector<std::string> >()) {
auto trusted_certificate = security::v2::load_certificate_from_file(cert_path);
context->trust_store.insert(trusted_certificate);
}
}
context->build_entity();
security = std::move(context);
}
} else {
if (version == 3) {
bool permissive_ir = vm.count("security.permissive-identified-region") &&
vm["security.permissive-identified-region"].as<bool>();
auto context = std::make_unique<SecurityContextV3>(vm, runtime, positioning, backend_name, permissive_ir);
auto provider = std::make_unique<security::v3::NaiveCertificateProvider>(runtime);
context->certificate_validation->add_chain_certificate(provider->aa_certificate());
context->certificate_validation->add_chain_certificate(provider->root_certificate());
context->cert_provider = std::move(provider);
context->build_entity();
security = std::move(context);
} else {
auto context = std::make_unique<SecurityContextV2>(runtime, positioning, backend_name);
context->cert_provider = std::make_unique<security::v2::NaiveCertificateProvider>(runtime);
context->build_entity();
security = std::move(context);
}
}
if (!security) {
throw std::runtime_error("internal failure setting up security entity");
}
} else {
throw std::runtime_error("Unknown security entity requested");
}
return security;
}
void add_security_options(po::options_description& options)
{
options.add_options()
("security", po::value<std::string>()->default_value("dummy"), "Security entity [none,dummy,certs] (with optional -v2 or -v3 suffix)")
("crypto-backend", po::value<std::string>()->default_value("default"), "Crypto backend [default,OpenSSL,CryptoPP,Null]")
("certificate", po::value<std::string>(), "Certificate to use for secured messages.")
("certificate-key", po::value<std::string>(), "Certificate key to use for secured messages.")
("certificate-chain", po::value<std::vector<std::string> >()->multitoken(), "Certificate chain to use, use as often as needed.")
("trusted-certificate", po::value<std::vector<std::string> >()->multitoken(), "Trusted certificate, use as often as needed.")
("security.permissive-identified-region", po::bool_switch()->default_value(false),
"Accept IdentifiedRegion certificate constraints without verification (opt-in fallback; see issue #262). Default: reject (OutsideRegion).")
;
add_certificate_validation_v3_options(options);
}
@@ -0,0 +1,17 @@
#ifndef SECURITY_HPP_FV13ZIYA
#define SECURITY_HPP_FV13ZIYA
#include <vanetza/common/position_provider.hpp>
#include <vanetza/common/runtime.hpp>
#include <vanetza/security/security_entity.hpp>
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp>
#include <memory>
std::unique_ptr<vanetza::security::SecurityEntity>
create_security_entity(const boost::program_options::variables_map&, const vanetza::Runtime&, vanetza::PositionProvider&);
void add_security_options(boost::program_options::options_description&);
#endif /* SECURITY_HPP_FV13ZIYA */
@@ -0,0 +1,190 @@
#include "tcp_link.hpp"
#include <vanetza/access/data_request.hpp>
#include <vanetza/net/ethernet_header.hpp>
#include <boost/asio/write.hpp>
#include <boost/bind/bind.hpp>
#include <boost/bind/placeholders.hpp>
#include <iostream>
#include <utility>
namespace ip = boost::asio::ip;
using namespace vanetza;
TcpLink::TcpLink(boost::asio::io_context& io_context) :
io_context_(&io_context)
{
}
void TcpLink::indicate(IndicationCallback cb)
{
callback_ = cb;
for (auto& ep : waiting_endpoints_) {
connect(ep);
}
}
void TcpLink::request(const access::DataRequest& request, std::unique_ptr<ChunkPacket> packet)
{
// create ethernet header
vanetza::ByteBuffer eth = create_ethernet_header(request.destination_addr, request.source_addr, request.ether_type);
packet->layer(OsiLayer::Link) = std::move(eth);
// insert packet size as frame delimiter
uint16_t packet_size = packet->size(OsiLayer::Link, OsiLayer::Application);
vanetza::ByteBuffer frame_delimiter { uint8_t(packet_size >> 8), uint8_t(packet_size) };
packet->layer(OsiLayer::Physical) = std::move(frame_delimiter);
std::array<boost::asio::const_buffer, layers_> const_buffers;
for (auto& layer : osi_layer_range<OsiLayer::Physical, OsiLayer::Application>()) {
const auto index = distance(OsiLayer::Physical, layer);
packet->layer(layer).convert(tx_buffers_[index]);
const_buffers[index] = boost::asio::buffer(tx_buffers_[index]);
}
for (auto it = sockets_.begin(); it != sockets_.end();) {
if (it->status() == TcpSocket::CONNECTED) {
it->request(const_buffers);
++it;
} else if (it->status() == TcpSocket::ERROR) {
it = sockets_.erase(it);
std::cerr << "Socket removed" << std::endl;
} else {
++it;
}
}
}
void TcpLink::connect(ip::tcp::endpoint ep)
{
if (callback_) {
sockets_.emplace_back(*io_context_, &callback_);
auto& sock = sockets_.back();
sock.connect(ep);
} else {
waiting_endpoints_.push_back(ep);
}
}
void TcpLink::accept(ip::tcp::endpoint ep)
{
if (acceptors_.count(ep) == 0) {
acceptors_.insert(std::make_pair(ep, ip::tcp::acceptor(*io_context_, ep)));
}
sockets_.emplace_back(*io_context_, &callback_);
auto& sock = sockets_.back();
sock.status(TcpSocket::ACCEPTING);
boost::system::error_code ec;
std::cout << "Accept connetions at " << ep.address().to_string() << ":" << ep.port() << std::endl;
acceptors_.find(ep)->second.async_accept(
sock.socket(),
boost::bind(
&TcpLink::accept_handler,
this,
ec,
ep,
&sock
)
);
}
void TcpLink::accept_handler(boost::system::error_code& ec, ip::tcp::endpoint ep, TcpSocket* sock)
{
if (!ec) {
sock->status(TcpSocket::CONNECTED);
sock->do_receive();
accept(ep);
}
}
/**
* TcpSocket
*/
TcpSocket::TcpSocket(boost::asio::io_context& io_context, IndicationCallback* cb) :
socket_(io_context),
rx_buffer_(2560, 0x00),
callback_(cb)
{
}
void TcpSocket::connect(ip::tcp::endpoint ep)
{
boost::system::error_code ec;
socket_.connect(ep, ec);
if (!ec) {
status_ = CONNECTED;
do_receive();
}
}
void TcpSocket::request(std::array<boost::asio::const_buffer, layers_> const_buffers)
{
boost::system::error_code ec;
boost::asio::write(socket_, const_buffers, ec);
if (ec) {
status_ = ERROR;
}
}
void TcpSocket::do_receive()
{
socket_.async_read_some(
boost::asio::buffer(rx_buffer_),
boost::bind(
&TcpSocket::receive_handler,
this,
boost::placeholders::_1,
boost::placeholders::_2
)
);
}
void TcpSocket::receive_handler(boost::system::error_code ec, std::size_t length) {
if (!ec) {
status_ = CONNECTED;
rx_store_.insert(rx_store_.end(), rx_buffer_.begin(), rx_buffer_.begin() + length);
// While we have enough bytes stored to construct a packet, pass it up
while (rx_store_.size() >= get_next_packet_size() + 2)
{
auto packet_length = get_next_packet_size() + 2;
ByteBuffer packet_buffer(rx_store_.begin() + 2, rx_store_.begin() + packet_length);
pass_up(std::move(packet_buffer));
rx_store_.erase(rx_store_.begin(), rx_store_.begin() + packet_length);
}
do_receive();
} else {
status_ = ERROR;
}
}
void TcpSocket::pass_up(ByteBuffer&& packet_buffer)
{
CohesivePacket packet(std::move(packet_buffer), OsiLayer::Link);
if (packet.size(OsiLayer::Link) < EthernetHeader::length_bytes) {
std::cerr << "Dropped TCP packet too short to contain Ethernet header\n";
} else {
packet.set_boundary(OsiLayer::Link, EthernetHeader::length_bytes);
auto link_range = packet[OsiLayer::Link];
EthernetHeader eth = decode_ethernet_header(link_range.begin(), link_range.end());
if (*callback_) {
(*callback_)(std::move(packet), eth);
}
}
}
std::size_t TcpSocket::get_next_packet_size()
{
if (rx_store_.size() >= 2) {
return (rx_store_[0] << 8) + rx_store_[1];
} else {
return 0;
}
}
@@ -0,0 +1,75 @@
#ifndef TCP_LINK_HPP_A16QFBX3
#define TCP_LINK_HPP_A16QFBX3
#include "link_layer.hpp"
#include <vanetza/common/byte_buffer.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <array>
#include <list>
static constexpr std::size_t layers_ = num_osi_layers(vanetza::OsiLayer::Physical, vanetza::OsiLayer::Application);
class TcpSocket
{
public:
enum Status
{
UNDEFINED = 0,
CONNECTED = 1,
ACCEPTING = 2,
ERROR = 3
};
using IndicationCallback = std::function<void(vanetza::CohesivePacket&&, const vanetza::EthernetHeader&)>;
TcpSocket(boost::asio::io_context&, IndicationCallback*);
void connect(boost::asio::ip::tcp::endpoint);
void request(std::array<boost::asio::const_buffer, layers_>);
void do_receive();
void receive_handler(boost::system::error_code, std::size_t);
std::size_t get_next_packet_size();
void pass_up(vanetza::ByteBuffer&&);
boost::asio::ip::tcp::socket& socket() { return socket_; }
Status status() { return status_; }
void status(Status s) { status_ = s; }
private:
Status status_ = UNDEFINED;
boost::asio::io_context* io_context_;
boost::asio::ip::tcp::endpoint endpoint_;
boost::asio::ip::tcp::socket socket_;
vanetza::ByteBuffer rx_buffer_;
vanetza::ByteBuffer rx_store_;
IndicationCallback* callback_;
};
class TcpLink : public LinkLayer
{
public:
TcpLink(boost::asio::io_context&);
void indicate(IndicationCallback) override;
void request(const vanetza::access::DataRequest&, std::unique_ptr<vanetza::ChunkPacket>) override;
void connect(boost::asio::ip::tcp::endpoint);
void accept(boost::asio::ip::tcp::endpoint);
void accept_handler(boost::system::error_code& ec, boost::asio::ip::tcp::endpoint ep, TcpSocket* sock);
private:
std::list<TcpSocket> sockets_;
std::map<boost::asio::ip::tcp::endpoint, boost::asio::ip::tcp::acceptor> acceptors_;
IndicationCallback callback_;
boost::asio::io_context* io_context_;
std::array<vanetza::ByteBuffer, layers_> tx_buffers_;
std::list<boost::asio::ip::tcp::endpoint> waiting_endpoints_;
};
#endif /* TCP_LINK_HPP_A16QFBX3 */
@@ -0,0 +1,46 @@
#include "time_trigger.hpp"
#include <boost/date_time/posix_time/posix_time.hpp>
#include <iostream>
#include <functional>
namespace asio = boost::asio;
namespace posix_time = boost::posix_time;
using namespace vanetza;
TimeTrigger::TimeTrigger(asio::io_context& io_context) :
io_context_(io_context), timer_(io_context), runtime_(Clock::at(now()))
{
std::cout << "Starting runtime at " << now() <<"\n";
schedule();
}
posix_time::ptime TimeTrigger::now() const
{
return posix_time::microsec_clock::universal_time();
}
void TimeTrigger::schedule()
{
update_runtime();
auto next = runtime_.next();
if (next < Clock::time_point::max()) {
auto timeout = next - runtime_.now();
timer_.expires_after(timeout);
timer_.async_wait(std::bind(&TimeTrigger::on_timeout, this, std::placeholders::_1));
} else {
timer_.cancel();
}
}
void TimeTrigger::on_timeout(const boost::system::error_code& ec)
{
if (asio::error::operation_aborted != ec) {
schedule();
}
}
void TimeTrigger::update_runtime()
{
auto current_time = now();
runtime_.trigger(Clock::at(current_time));
}
@@ -0,0 +1,27 @@
#ifndef TIME_TRIGGER_HPP_XRPGDYXO
#define TIME_TRIGGER_HPP_XRPGDYXO
#include <vanetza/common/manual_runtime.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
class TimeTrigger
{
public:
TimeTrigger(boost::asio::io_context&);
vanetza::Runtime& runtime() { return runtime_; }
void schedule();
private:
boost::posix_time::ptime now() const;
void on_timeout(const boost::system::error_code&);
void update_runtime();
boost::asio::io_context& io_context_;
boost::asio::steady_timer timer_;
vanetza::ManualRuntime runtime_;
};
#endif /* TIME_TRIGGER_HPP_XRPGDYXO */
@@ -0,0 +1,75 @@
#include "udp_link.hpp"
#include <vanetza/access/data_request.hpp>
#include <vanetza/net/ethernet_header.hpp>
#include <boost/asio/ip/multicast.hpp>
#include <iostream>
namespace ip = boost::asio::ip;
using namespace vanetza;
UdpLink::UdpLink(boost::asio::io_context& io_context, const ip::udp::endpoint& endpoint, const EthernetDevice& device) :
multicast_endpoint_(endpoint),
tx_socket_(io_context), rx_socket_(io_context),
rx_buffer_(2560, 0x00)
{
auto ip = device.ip();
tx_socket_.open(multicast_endpoint_.protocol());
if (!ip.is_unspecified()) {
boost::asio::ip::multicast::outbound_interface option(ip);
tx_socket_.set_option(option);
}
rx_socket_.open(multicast_endpoint_.protocol());
rx_socket_.set_option(ip::udp::socket::reuse_address(true));
rx_socket_.bind(multicast_endpoint_);
rx_socket_.set_option(ip::multicast::enable_loopback(false));
if(!ip.is_unspecified() && multicast_endpoint_.address().is_v4()) {
rx_socket_.set_option(ip::multicast::join_group(multicast_endpoint_.address().to_v4(), ip));
} else {
rx_socket_.set_option(ip::multicast::join_group(multicast_endpoint_.address()));
}
do_receive();
}
void UdpLink::indicate(IndicationCallback cb)
{
callback_ = cb;
}
void UdpLink::do_receive()
{
rx_socket_.async_receive_from(boost::asio::buffer(rx_buffer_), rx_endpoint_,
[this](boost::system::error_code ec, std::size_t length) {
if (!ec) {
ByteBuffer buffer(rx_buffer_.begin(), rx_buffer_.begin() + length);
CohesivePacket packet(std::move(buffer), OsiLayer::Link);
if (packet.size(OsiLayer::Link) < EthernetHeader::length_bytes) {
std::cerr << "Dropped UDP packet too short to contain Ethernet header\n";
} else {
packet.set_boundary(OsiLayer::Link, EthernetHeader::length_bytes);
auto link_range = packet[OsiLayer::Link];
EthernetHeader eth = decode_ethernet_header(link_range.begin(), link_range.end());
if (callback_) {
callback_(std::move(packet), eth);
}
}
do_receive();
}
});
}
void UdpLink::request(const access::DataRequest& request, std::unique_ptr<ChunkPacket> packet)
{
packet->layer(OsiLayer::Link) = create_ethernet_header(request.destination_addr, request.source_addr, request.ether_type);
std::array<boost::asio::const_buffer, layers_> const_buffers;
for (auto& layer : osi_layer_range<OsiLayer::Link, OsiLayer::Application>()) {
const auto index = distance(OsiLayer::Link, layer);
packet->layer(layer).convert(tx_buffers_[index]);
const_buffers[index] = boost::asio::buffer(tx_buffers_[index]);
}
tx_socket_.send_to(const_buffers, multicast_endpoint_);
}
@@ -0,0 +1,35 @@
#ifndef UDP_LINK_HPP_A16QFBX3
#define UDP_LINK_HPP_A16QFBX3
#include "link_layer.hpp"
#include <vanetza/common/byte_buffer.hpp>
#include <boost/asio/ip/udp.hpp>
#include <array>
// forward declaration
class EthernetDevice;
class UdpLink : public LinkLayer
{
public:
UdpLink(boost::asio::io_context&, const boost::asio::ip::udp::endpoint&, const EthernetDevice&);
void indicate(IndicationCallback) override;
void request(const vanetza::access::DataRequest&, std::unique_ptr<vanetza::ChunkPacket>) override;
private:
void do_receive();
static constexpr std::size_t layers_ = num_osi_layers(vanetza::OsiLayer::Link, vanetza::OsiLayer::Application);
boost::asio::ip::udp::endpoint multicast_endpoint_;
boost::asio::ip::udp::socket tx_socket_;
boost::asio::ip::udp::socket rx_socket_;
std::array<vanetza::ByteBuffer, layers_> tx_buffers_;
vanetza::ByteBuffer rx_buffer_;
boost::asio::ip::udp::endpoint rx_endpoint_;
IndicationCallback callback_;
};
#endif /* UDP_LINK_HPP_A16QFBX3 */