diff --git a/.travis.yml b/.travis.yml index 2824121f5..af233a560 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,18 @@ language: cpp + sudo: false + compiler: -- gcc + - gcc + os: -- linux + - linux + cache: apt: true directories: - - $HOME/deps + - $HOME/deps + addons: apt: sources: @@ -15,67 +20,43 @@ addons: packages: - gcc-4.8 - g++-4.8 + - expect + - libssl-dev + - libxml2-dev + - libncurses5-dev + - flex + - bison + - xutils-dev + - ant + - xsltproc + install: -- if [ "$CXX" = "g++" ]; then export CXX="g++-4.8" CC="gcc-4.8"; fi -- mkdir -p "$HOME/deps" -- |- - # Boost dependency - pushd "$HOME/deps" - export BOOST_ROOT="$PWD/boost_1_58_0" - if [ -d ${BOOST_ROOT} ]; then - echo "using Boost from Travis cache" - else - wget -nc 'https://sourceforge.net/projects/boost/files/boost/1.58.0/boost_1_58_0.tar.bz2' - tar xjf boost_1_58_0.tar.bz2 - pushd ${BOOST_ROOT} - ./bootstrap.sh - ./b2 --with-date_time --with-serialization --with-system --with-program_options - popd - fi - popd -- |- - # GeographicLib dependency - pushd "$HOME/deps" - GEOGRAPHICLIB_ROOT="$PWD/GeographicLib-1.37" - if [ -d ${GEOGRAPHICLIB_ROOT} ]; then - echo "using GeographicLib from Travis cache" - else - wget -nc 'https://sourceforge.net/projects/geographiclib/files/distrib/archive/GeographicLib-1.37.tar.gz' - tar xzf GeographicLib-1.37.tar.gz - pushd ${GEOGRAPHICLIB_ROOT} - ./configure - make - popd - fi - export CMAKE_GEOGRAPHICLIB_FLAGS="-DGeographicLib_INCLUDE_DIR=${GEOGRAPHICLIB_ROOT}/include -DGeographicLib_LIBRARY_RELEASE=${GEOGRAPHICLIB_ROOT}/src/.libs/libGeographic.so" - popd -- |- - # Crypto++ dependency - pushd "$HOME/deps" - CRYPTOPP_ROOT="$PWD/cryptopp-5.6.2" - if [ -d ${CRYPTOPP_ROOT} ]; then - echo "using Crypto++ from Travis cache" - else - wget -nc 'https://sourceforge.net/projects/cryptopp/files/cryptopp/5.6.2/cryptopp562.zip' - unzip cryptopp562.zip -d ${CRYPTOPP_ROOT} - pushd ${CRYPTOPP_ROOT} - make dynamic install CXXFLAGS="-fPIC -DNDEBUG -march=native -pipe" PREFIX=./install - popd - fi - export CMAKE_CRYPTOPP_FLAGS="-DCryptoPP_INCLUDE_DIR=${CRYPTOPP_ROOT}/install/include -DCryptoPP_LIBRARY=${CRYPTOPP_ROOT}/install/lib/libcryptopp.so" - popd -- |- - # CMake 3.0+ - pushd "$HOME/deps" - CMAKE_VERSION="3.1.3-Linux-x86_64" - wget -nc --no-check-certificate "https://cmake.org/files/v3.1/cmake-${CMAKE_VERSION}.tar.gz" - tar xzf cmake-${CMAKE_VERSION}.tar.gz - export PATH="$PWD/cmake-${CMAKE_VERSION}/bin:$PATH" - popd + - mkdir -p "$HOME/deps" + + - if [ "$CXX" = "g++" ]; then export CXX="g++-4.8" CC="gcc-4.8"; fi + - export BOOST_ROOT="$HOME/deps/boost_1_58_0" + - export GEOGRAPHICLIB_ROOT="$HOME/deps/GeographicLib-1.37" + - export CMAKE_GEOGRAPHICLIB_FLAGS="-DGeographicLib_INCLUDE_DIR=${GEOGRAPHICLIB_ROOT}/include -DGeographicLib_LIBRARY_RELEASE=${GEOGRAPHICLIB_ROOT}/src/.libs/libGeographic.so" + - export CRYPTOPP_ROOT="$PWD/cryptopp-5.6.2" + - export CMAKE_CRYPTOPP_FLAGS="-DCryptoPP_INCLUDE_DIR=${CRYPTOPP_ROOT}/install/include -DCryptoPP_LIBRARY=${CRYPTOPP_ROOT}/install/lib/libcryptopp.so" + - export CMAKE_VERSION="3.1.3-Linux-x86_64" + - export TTCN3_DIR=$HOME/titan.core/Install + - export LD_LIBRARY_PATH=$HOME/titan.core/Install/lib:${LD_LIBRARY_PATH} + - export PATH="$HOME/deps/cmake-${CMAKE_VERSION}/bin:$HOME/titan.core/Install/bin:$PATH" + + - travis/boost.sh + - travis/cmake.sh + - travis/cryptopp.sh + - travis/geographiclib.sh + - travis/titan.sh + script: -- mkdir -p "${HOME}/build" -- pushd "${HOME}/build" -- cmake -DBUILD_TESTS=ON ${CMAKE_GEOGRAPHICLIB_FLAGS} ${CMAKE_CRYPTOPP_FLAGS} ${TRAVIS_BUILD_DIR} -- cmake --build . -- ctest -- popd + - mkdir -p "${HOME}/build" + - pushd "${HOME}/build" + - cmake -DBUILD_TESTS=ON -DBUILD_UPPERTESTER=ON ${CMAKE_GEOGRAPHICLIB_FLAGS} ${CMAKE_CRYPTOPP_FLAGS} ${TRAVIS_BUILD_DIR} + - cmake --build . + - ctest + - popd + - pushd ${TRAVIS_BUILD_DIR}/tools/uppertester + - BUILD_DIR=$HOME/build ./run.sh + - popd diff --git a/CMakeLists.txt b/CMakeLists.txt index 3307af772..ebe3a2e94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,6 +75,11 @@ if(BUILD_CERTIFY) add_subdirectory(tools/certify) endif() +option(BUILD_UPPERTESTER "Build uppertester for TTCN-3 tests" OFF) +if(BUILD_UPPERTESTER) + add_subdirectory(tools/uppertester) +endif() + # interface library for convenience get_property(_components GLOBAL PROPERTY VANETZA_COMPONENTS) add_library(vanetza INTERFACE) diff --git a/tools/socktap/application.hpp b/tools/socktap/application.hpp index a80f465e0..1e6c8f0e4 100644 --- a/tools/socktap/application.hpp +++ b/tools/socktap/application.hpp @@ -35,4 +35,3 @@ class Application : public vanetza::btp::IndicationInterface }; #endif /* APPLICATION_HPP_PSIGPUTG */ - diff --git a/tools/socktap/router_context.cpp b/tools/socktap/router_context.cpp index 1ae94a995..88d55d4ae 100644 --- a/tools/socktap/router_context.cpp +++ b/tools/socktap/router_context.cpp @@ -129,4 +129,3 @@ void RouterContext::update_packet_flow(const geonet::LongPositionVector& lpv) request_interface_->allow_packet_flow(true); } } - diff --git a/tools/uppertester/CMakeLists.txt b/tools/uppertester/CMakeLists.txt new file mode 100644 index 000000000..73d5af3f5 --- /dev/null +++ b/tools/uppertester/CMakeLists.txt @@ -0,0 +1,39 @@ +if(NOT TARGET Boost::system) + message(STATUS "Skip build of uppertester because of missing Boost::system dependency") + return() +endif() + +if(NOT TARGET Boost::program_options) + message(STATUS "Skip build of uppertester because of missing Boost::program_options dependency") + return() +endif() + +find_package(Threads REQUIRED) + +add_executable(uppertester + application.cpp + ethernet_device.cpp + main.cpp + passthrough.cpp + socket.cpp + time_trigger.cpp + uppertester.cpp + + trigger/btp.cpp + trigger/btp_a.cpp + trigger/btp_b.cpp + trigger/common_change_position.cpp + trigger/common_change_pseudonym.cpp + trigger/common_ut_initialize.cpp + trigger/common_ut_result.cpp + trigger/gn.cpp + trigger/gn_geo_anycast.cpp + trigger/gn_geo_broadcast.cpp + trigger/gn_geo_unicast.cpp + trigger/gn_shb.cpp + trigger/gn_tsb.cpp + trigger/trigger.cpp +) + +set_target_properties(uppertester PROPERTIES INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(uppertester Boost::program_options Boost::system Threads::Threads vanetza) diff --git a/tools/uppertester/README.md b/tools/uppertester/README.md new file mode 100644 index 000000000..1ee211940 --- /dev/null +++ b/tools/uppertester/README.md @@ -0,0 +1,40 @@ +# UpperTester + +This directory contains an upper tester implementation for ETSI's TTCN-3 test suite. A TTCN-3 compiler is required to execute these tests. + +## Running + +A script is provided that works on Ubuntu, which does the following steps automatically. +You're encouraged to read through the script and the following instructions. +If you use the script, you have to setup TITAN manually, everything else is covered by the script. + +```bash +# With $PWD being the current directory +./run.sh +``` + +This guide will provide instructions how to run these tests manually using the open source TTCN-3 compiler [TITAN](https://github.com/eclipse/titan.core). + +The TITAN TRI Mapper requires a network interface with a MAC address where it injects and captures packets. The easiest way to have a local virtual interface is to create two `peer` virtual interfaces. + +``` +sudo ip link add veth0 type veth peer name veth1 +sudo ip link set dev veth0 up +sudo ip link set dev veth1 up +``` + +This will create the two interface `veth0` and `veth1` that are connected. If a packet is sent on `veth0` it will be available at `veth1` and the other way around. + +The MAC address can be queried using `sudo ip link` and looking for the correct interface. + +You need to setup TITAN as instructed in their documentation. + +We make use of a [fork of the official test suite](https://github.com/elnrnag/ITSG5_TS_Titanized_TA), as the official test suite isn't compatible with TITAN (yet). Please clone the linked repository and install it as instructed in its README. You might need to add `jnetpcap` to your Java installation. + +Switch to the `titan_tri_mapper` directory of the clone and copy `jnetpcap.jar` there. Adjust the `taproperties.cfg` to contain the correct MAC address for the virtual interface. Use the MAC address of the `veth1@veth0` interface. Then launch `sudo java -Djava.library.path=/path/to/jnetpcap -jar TitanTriMapper.jar -l info` where `/path/to/jnetpcap` is the path to the directory containing `libjnetpcap.so`. + +Next, launch the upper tester using `bin/uppertester -i veth0` from the build directory of Vanetza. + +Last, start the test suite by running `ttcn3_start ./ITS_Exec cfg.cfg ItsBtp_TestControl`. You might want to append ` | grep -v TRI_encode | grep -v TRI_decode` to suppress the verbose encoding and decoding output. + +You might need to `up` and `down` one network interface after each test run to repeat the tests, because the TITAN TRI Mapper only seems to capture the frames, but not consume them. You can do so using `sudo ip link set dev veth0 down && sudo ip link set dev veth0 up`. diff --git a/tools/uppertester/application.cpp b/tools/uppertester/application.cpp new file mode 100644 index 000000000..a6bdb8997 --- /dev/null +++ b/tools/uppertester/application.cpp @@ -0,0 +1,56 @@ +#include "application.hpp" +#include +#include +#include + +using namespace vanetza; + +Application::DataConfirm Application::request(const btp::DataRequestA& request, DownPacketPtr packet) +{ + DataConfirm confirm(DataConfirm::ResultCode::REJECTED_UNSPECIFIED); + + if (router && packet) { + btp::HeaderA btp_header; + btp_header.destination_port = request.destination_port; + btp_header.source_port = request.source_port; + packet->layer(OsiLayer::Transport) = btp_header; + + switch (request.gn.transport_type) { + case geonet::TransportType::SHB: { + geonet::ShbDataRequest shb(router->get_mib()); + copy_request_parameters(request, shb); + confirm = router->request(shb, std::move(packet)); } + break; + default: + // TODO remaining transport types are not implemented + break; + } + } + + return confirm; +} + +Application::DataConfirm Application::request(const btp::DataRequestB& request, DownPacketPtr packet) +{ + DataConfirm confirm(DataConfirm::ResultCode::REJECTED_UNSPECIFIED); + + if (router && packet) { + btp::HeaderB btp_header; + btp_header.destination_port = request.destination_port; + btp_header.destination_port_info = request.destination_port_info; + packet->layer(OsiLayer::Transport) = btp_header; + + switch (request.gn.transport_type) { + case geonet::TransportType::SHB: { + geonet::ShbDataRequest shb(router->get_mib()); + copy_request_parameters(request, shb); + confirm = router->request(shb, std::move(packet)); } + break; + default: + // TODO remaining transport types are not implemented + break; + } + } + + return confirm; +} diff --git a/tools/uppertester/application.hpp b/tools/uppertester/application.hpp new file mode 100644 index 000000000..4efe88b7e --- /dev/null +++ b/tools/uppertester/application.hpp @@ -0,0 +1,32 @@ +#ifndef UPPERTESTER_APPLICATION_HPP +#define UPPERTESTER_APPLICATION_HPP + +#include +#include +#include +#include +#include + +class Application : public vanetza::btp::PortDispatcher::PromiscuousHook +{ +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 UpPacketPtr = vanetza::geonet::Router::UpPacketPtr; + + Application() = default; + Application(const Application&) = delete; + Application& operator=(const Application&) = delete; + virtual ~Application() = default; + + DataConfirm request(const vanetza::btp::DataRequestA& request, DownPacketPtr packet); + DataConfirm request(const vanetza::btp::DataRequestB& request, DownPacketPtr packet); + +protected: + vanetza::geonet::Router* router; +}; + +#endif /* UPPERTESTER_APPLICATION_HPP */ diff --git a/tools/uppertester/ethernet_device.cpp b/tools/uppertester/ethernet_device.cpp new file mode 100644 index 000000000..8f30df3cf --- /dev/null +++ b/tools/uppertester/ethernet_device.cpp @@ -0,0 +1,56 @@ +#include "ethernet_device.hpp" +#include +#include +#include +#include +#include +#include + +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) : + local_socket_(::socket(AF_LOCAL, SOCK_DGRAM, 0)), + interface_name_(devname) +{ + if (!local_socket_) { + throw std::system_error(errno, std::system_category()); + } +} + +EthernetDevice::~EthernetDevice() +{ + if (local_socket_ >= 0) + ::close(local_socket_); +} + +EthernetDevice::protocol::endpoint EthernetDevice::endpoint(int family) const +{ + sockaddr_ll socket_address = {0}; + socket_address.sll_family = family; + 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(local_socket_, SIOCGIFINDEX, &data); + return data.ifr_ifindex; +} + +vanetza::MacAddress EthernetDevice::address() const +{ + ifreq data; + initialize(data, interface_name_.c_str()); + ::ioctl(local_socket_, SIOCGIFHWADDR, &data); + + vanetza::MacAddress addr; + std::copy_n(data.ifr_hwaddr.sa_data, addr.octets.size(), addr.octets.data()); + return addr; +} diff --git a/tools/uppertester/ethernet_device.hpp b/tools/uppertester/ethernet_device.hpp new file mode 100644 index 000000000..4ce6549ed --- /dev/null +++ b/tools/uppertester/ethernet_device.hpp @@ -0,0 +1,28 @@ +#ifndef UPPERTESTER_ETHERNET_DEVICE_HPP +#define UPPERTESTER_ETHERNET_DEVICE_HPP + +#include +#include +#include + +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; + +private: + int index() const; + + int local_socket_; + std::string interface_name_; +}; + +#endif /* UPPERTESTER_ETHERNET_DEVICE_HPP */ diff --git a/tools/uppertester/main.cpp b/tools/uppertester/main.cpp new file mode 100644 index 000000000..421759bdd --- /dev/null +++ b/tools/uppertester/main.cpp @@ -0,0 +1,75 @@ +#include "ethernet_device.hpp" +#include "socket.hpp" +#include "uppertester.hpp" +#include +#include +#include +#include +#include +#include + +namespace po = boost::program_options; +namespace asio = boost::asio; +using namespace vanetza; + +int main(int argc, const char** argv) +{ + po::options_description options("options"); + options.add_options() + ("port", po::value()->default_value(5000), "UDP port to receive messages on.") + ("interface,i", po::value()->default_value("lo"), "Network interface to use.") + ; + + po::variables_map vm; + + po::parsed_options parsed = po::command_line_parser(argc, argv) + .options(options) + .run(); + + po::store(parsed, vm); + po::notify(vm); + + uint16_t port = vm["port"].as(); + + std::cerr << "Listening on port " << port << " for UDP packets." << std::endl; + + try { + asio::io_service io_service; + + const char* device_name = vm["interface"].as().c_str(); + EthernetDevice device(device_name); + + geonet::MIB mib; + mib.itsGnLocalGnAddr.mid(device.address()); + mib.itsGnLocalGnAddr.is_manually_configured(true); + mib.itsGnLocalAddrConfMethod = geonet::AddrConfMethod::MANAGED; + + asio::generic::raw_protocol raw_protocol(AF_PACKET, geonet::ether_type.net()); + asio::generic::raw_protocol::socket raw_socket(io_service, raw_protocol); + asio::socket_base::do_not_route do_not_route; + raw_socket.set_option(do_not_route); + raw_socket.bind(device.endpoint(AF_PACKET)); + + TimeTrigger trigger(io_service); + UpperTester tester(raw_socket, trigger, mib); + Socket socket(tester, io_service, port); + + auto signal_handler = [&io_service](const boost::system::error_code& ec, int signal_number) { + if (!ec) { + std::cout << "Termination requested." << std::endl; + io_service.stop(); + } + }; + + asio::signal_set signals(io_service, SIGINT, SIGTERM); + signals.async_wait(signal_handler); + + io_service.run(); + } catch (std::exception& e) { + std::cerr << "Exception: " << e.what() << std::endl; + + return 1; + } + + return 0; +} diff --git a/tools/uppertester/passthrough.cpp b/tools/uppertester/passthrough.cpp new file mode 100644 index 000000000..b6f408b7d --- /dev/null +++ b/tools/uppertester/passthrough.cpp @@ -0,0 +1,32 @@ +#include "passthrough.hpp" +#include "time_trigger.hpp" +#include +#include +#include +#include +#include +#include + +namespace asio = boost::asio; +using boost::asio::generic::raw_protocol; +using namespace vanetza; + +Passthrough::Passthrough(raw_protocol::socket& socket) : socket(socket) { } + +void Passthrough::request(const dcc::DataRequest& request, std::unique_ptr packet) +{ + buffers[1] = create_ethernet_header(request.destination, request.source, request.ether_type); + for (auto& layer : osi_layer_range()) { + const auto index = distance(OsiLayer::Physical, layer); + packet->layer(layer).convert(buffers[index]); + } + + std::array const_buffers; + for (unsigned i = 0; i < const_buffers.size(); ++i) { + const_buffers[i] = asio::buffer(buffers[i]); + } + + std::cout << "Sending packet to " << request.destination << std::endl; + auto bytes_sent = socket.send(const_buffers); + std::cout << "Sent packet to " << request.destination << " (" << bytes_sent << " bytes)" << std::endl; +} diff --git a/tools/uppertester/passthrough.hpp b/tools/uppertester/passthrough.hpp new file mode 100644 index 000000000..b07535a66 --- /dev/null +++ b/tools/uppertester/passthrough.hpp @@ -0,0 +1,24 @@ +#ifndef UPPERTESTER_PASSTHROUGH_HPP +#define UPPERTESTER_PASSTHROUGH_HPP + +#include +#include +#include +#include +#include +#include + +class Passthrough : public vanetza::dcc::RequestInterface +{ +public: + Passthrough(boost::asio::generic::raw_protocol::socket& socket); + + void request(const vanetza::dcc::DataRequest& request, std::unique_ptr packet); + +private: + static constexpr std::size_t layers_ = num_osi_layers(vanetza::OsiLayer::Physical, vanetza::OsiLayer::Application); + boost::asio::generic::raw_protocol::socket& socket; + std::array buffers; +}; + +#endif /* UPPERTESTER_PASSTHROUGH_HPP */ diff --git a/tools/uppertester/run.sh b/tools/uppertester/run.sh new file mode 100755 index 000000000..1618799c1 --- /dev/null +++ b/tools/uppertester/run.sh @@ -0,0 +1,123 @@ +#!/usr/bin/env bash + +BUILD_DIR=${BUILD_DIR:-$PWD/../../build} +ITS_G5_TS_DIR=$BUILD_DIR/its-g5-ts + +echo "Using build directory '$BUILD_DIR'" + +set -e +set -x + +if [ ! -d $BUILD_DIR ]; then + echo "Build directory '$BUILD_DIR' does not exist." + exit 1 +fi + +needs_update=0 + +if [ ! -d $ITS_G5_TS_DIR ]; then + git clone https://github.com/elnrnag/ITSG5_TS_Titanized_TA $ITS_G5_TS_DIR + + wget https://netcologne.dl.sourceforge.net/project/jnetpcap/jnetpcap/1.3/jnetpcap-1.3.0-1.ubuntu.x86_64.tgz -O $BUILD_DIR/jnetpcap.tgz + pushd $BUILD_DIR + tar -xvzf jnetpcap.tgz + popd + + needs_update=1 +else + pushd $ITS_G5_TS_DIR + + if git checkout master && git fetch origin master && [ `git rev-list HEAD...origin/master --count` != 0 ] && git merge origin/master; then + needs_update=1 + fi + + popd +fi + +if [ $needs_update = 1 ]; then + pushd $ITS_G5_TS_DIR + + mkdir build || true + pushd build + ../src/install.script + make + popd + + popd +fi + +if ip a show vanet0; then + echo "Interface vanet0 already exists, aborting." + exit 1 +fi + +if ip a show vanet1; then + echo "Interface vanet1 already exists, aborting." + exit 1 +fi + +echo "Creating network interfaces..." +sudo ip link add vanet0 type veth peer name vanet1 +sudo ip link set dev vanet0 up +sudo ip link set dev vanet1 up + +function clean_up_interfaces { + # Deleting one interface deletes both, because they're peered + echo "Cleaning up network interfaces..." + sudo ip link delete vanet0 +} + +trap clean_up_interfaces EXIT + +if [ ! -f $BUILD_DIR/bin/uppertester ]; then + echo "Missing $BUILD_DIR/bin/uppertester, did you compile it?" + exit 1 +fi + +# Starting UpperTester in the background +sudo $BUILD_DIR/bin/uppertester -i vanet0 & +UPPERTESTER_PID=$! + +# Allow observing output on failures +sleep 3 + +function clean_up_uppertester { + echo "Shutting down UpperTester..." + sudo pkill -TERM -P $UPPERTESTER_PID && wait $UPPERTESTER_PID || true + + clean_up_interfaces +} + +trap clean_up_uppertester EXIT + +# Starting TRI mapper +pushd $ITS_G5_TS_DIR/titan_tri_mapper + +# Set correct MAC address and UpperTester address in test adapter configuration +VANET_MAC=$(ip a show vanet1 | grep "link/ether" | awk '{print $2}' | sed 's/://g') +sed -i -E "s/\"LocalEthernetMAC\": \"([a-f0-9]+)\"/\"LocalEthernetMAC\": \"$VANET_MAC\"/" taproperties.cfg +sed -i -E 's/"UpperTesterSettings": "192.168.1.100:12345"/"UpperTesterSettings": "127.0.0.1:5000"/' taproperties.cfg + +sudo java -Djava.library.path=$BUILD_DIR/jnetpcap-1.3.0 -jar TitanTriMapper.jar -l info & +TITAN_TRI_MAPPER_PID=$! + +function clean_up_titan_tri_mapper { + echo "Shutting down TITAN TRI Mapper..." + sudo pkill -TERM -P $TITAN_TRI_MAPPER_PID && wait $TITAN_TRI_MAPPER_PID || true + + clean_up_uppertester +} + +trap clean_up_titan_tri_mapper EXIT + +popd + +# Wait until mapper is up +sleep 3 + +cp ./ttcn3.config $ITS_G5_TS_DIR/build/cfg.cfg +pushd $ITS_G5_TS_DIR/build + +ttcn3_start ./ITS_Exec cfg.cfg | tee ttcn3.log | grep -v TRI_encode | grep -v TRI_decode + +popd diff --git a/tools/uppertester/serialization.hpp b/tools/uppertester/serialization.hpp new file mode 100644 index 000000000..c868c2053 --- /dev/null +++ b/tools/uppertester/serialization.hpp @@ -0,0 +1,16 @@ +#ifndef UPPERTESTER_SERIALIZATION_HPP +#define UPPERTESTER_SERIALIZATION_HPP + +#include + +using vanetza::InputArchive; +using vanetza::OutputArchive; + +template +void serialize(OutputArchive& oa, T&& t) +{ + using vanetza::serialize; + serialize(oa, std::forward(t)); +} + +#endif /* UPPERTESTER_SERIALIZATION_HPP */ diff --git a/tools/uppertester/socket.cpp b/tools/uppertester/socket.cpp new file mode 100644 index 000000000..da844e2a9 --- /dev/null +++ b/tools/uppertester/socket.cpp @@ -0,0 +1,51 @@ +#include "socket.hpp" +#include "uppertester.hpp" +#include +#include +#include + +using namespace vanetza; +using namespace boost::asio::ip; + +Socket::Socket(UpperTester& tester, boost::asio::io_service& io_service, uint16_t port) + : tester(tester), io_service(io_service), socket(io_service, udp::endpoint(udp::v4(), port)) +{ + tester.socket = this; + + socket.async_receive_from( + boost::asio::buffer(buffer, MAX_LENGTH), + endpoint, + boost::bind(&Socket::handle_receive_from, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred) + ); +} + +void Socket::handle_receive_from(const boost::system::error_code& error, std::size_t bytes_recvd) +{ + if (error) { + return; + } + + std::cout << "Received a packet with " << bytes_recvd << " bytes." << std::endl; + + ByteBuffer packet(buffer, buffer + bytes_recvd); + tester.process_udp_trigger(packet); + + socket.async_receive_from( + boost::asio::buffer(buffer, MAX_LENGTH), + endpoint, + boost::bind(&Socket::handle_receive_from, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred) + ); +} + +void Socket::send(const ByteBuffer& payload) +{ + char buffer[payload.size()]; + std::copy(payload.begin(), payload.end(), buffer); + + std::cout << "Sending packet: "; + boost::algorithm::hex(payload.begin(), payload.end(), std::ostream_iterator(std::cout)); + std::cout << " (" << payload.size() << " bytes)" << std::endl; + + // Don't bother with sending things async in this application, no need for the additional complexity + socket.send_to(boost::asio::buffer(buffer, payload.size()), endpoint); +} diff --git a/tools/uppertester/socket.hpp b/tools/uppertester/socket.hpp new file mode 100644 index 000000000..4ae2376ee --- /dev/null +++ b/tools/uppertester/socket.hpp @@ -0,0 +1,39 @@ +#ifndef UPPERTESTER_SOCKET_HPP +#define UPPERTESTER_SOCKET_HPP + +#include +#include +#include +#include + +class UpperTester; + +class Socket +{ + const static uint MAX_LENGTH = 65535; + +public: + Socket(UpperTester& app, boost::asio::io_service& io_service, uint16_t port); + + void handle_receive_from(const boost::system::error_code& error, std::size_t bytes_recvd); + + template + void send(const T& t) + { + vanetza::ByteBuffer buffer; + vanetza::serialize_into_buffer(t, buffer); + send(buffer); + } + + void send(const vanetza::ByteBuffer& payload); + +private: + UpperTester& tester; + vanetza::btp::PortDispatcher dispatcher; + boost::asio::io_service& io_service; + boost::asio::ip::udp::socket socket; + boost::asio::ip::udp::endpoint endpoint; + char buffer[MAX_LENGTH]; +}; + +#endif /* UPPERTESTER_SOCKET_HPP */ diff --git a/tools/uppertester/time_trigger.cpp b/tools/uppertester/time_trigger.cpp new file mode 100644 index 000000000..f45eabbfb --- /dev/null +++ b/tools/uppertester/time_trigger.cpp @@ -0,0 +1,45 @@ +#include "time_trigger.hpp" +#include +#include +#include + +namespace asio = boost::asio; +namespace posix_time = boost::posix_time; +using namespace vanetza; + +TimeTrigger::TimeTrigger(asio::io_service& io_service) : + m_io_service(io_service), m_timer(io_service), m_runtime(Clock::at(now())) +{ + std::cout << "Starting runtime at " << now() << std::endl; + schedule(); +} + +posix_time::ptime TimeTrigger::now() const +{ + return posix_time::microsec_clock::universal_time(); +} + +void TimeTrigger::schedule() +{ + update_runtime(); + auto next = m_runtime.next(); + if (next < Clock::time_point::max()) { + m_timer.expires_at(Clock::at(next)); + m_timer.async_wait(std::bind(&TimeTrigger::on_timeout, this, std::placeholders::_1)); + } else { + m_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(); + m_runtime.trigger(Clock::at(current_time)); +} diff --git a/tools/uppertester/time_trigger.hpp b/tools/uppertester/time_trigger.hpp new file mode 100644 index 000000000..a71956f5a --- /dev/null +++ b/tools/uppertester/time_trigger.hpp @@ -0,0 +1,26 @@ +#ifndef UPPERTESTER_TIME_TRIGGER_HPP +#define UPPERTESTER_TIME_TRIGGER_HPP + +#include +#include +#include +#include + +class TimeTrigger +{ +public: + TimeTrigger(boost::asio::io_service&); + vanetza::Runtime& runtime() { return m_runtime; } + void schedule(); + +private: + boost::posix_time::ptime now() const; + void on_timeout(const boost::system::error_code&); + void update_runtime(); + + boost::asio::io_service& m_io_service; + boost::asio::deadline_timer m_timer; + vanetza::Runtime m_runtime; +}; + +#endif /* UPPERTESTER_TIME_TRIGGER_HPP */ diff --git a/tools/uppertester/trigger/btp.cpp b/tools/uppertester/trigger/btp.cpp new file mode 100644 index 000000000..c8dd5567e --- /dev/null +++ b/tools/uppertester/trigger/btp.cpp @@ -0,0 +1,22 @@ +#include "btp.hpp" +#include + +using namespace vanetza; + +void serialize(OutputArchive& ar, const BtpTriggerResult& result) +{ + ar << result.message_type; + ar << result.result; +} + +void serialize(OutputArchive& ar, const BtpEventIndication& indication) +{ + ar << indication.message_type; + + uint16_t size = indication.packet.size(); + serialize(ar, host_cast(size)); + + for (auto byte : indication.packet) { + ar << byte; + } +} diff --git a/tools/uppertester/trigger/btp.hpp b/tools/uppertester/trigger/btp.hpp new file mode 100644 index 000000000..e3069fd6a --- /dev/null +++ b/tools/uppertester/trigger/btp.hpp @@ -0,0 +1,26 @@ +#ifndef UPPERTESTER_TRIGGER_BTP_HPP +#define UPPERTESTER_TRIGGER_BTP_HPP + +#include "serialization.hpp" +#include +#include + +// C.7.1 + C.7.2 in TR 103 099 V1.3.1 +struct BtpTriggerResult +{ + const uint8_t message_type = 0x61; + uint8_t result; +}; + +void serialize(OutputArchive& ar, const BtpTriggerResult& result); + +// C.7.3 in TR 103 099 V1.3.1 +struct BtpEventIndication +{ + const uint8_t message_type = 0x63; + vanetza::ByteBuffer packet; +}; + +void serialize(OutputArchive& ar, const BtpEventIndication& indication); + +#endif /* UPPERTESTER_TRIGGER_BTP_HPP */ diff --git a/tools/uppertester/trigger/btp_a.cpp b/tools/uppertester/trigger/btp_a.cpp new file mode 100644 index 000000000..b03dba5ce --- /dev/null +++ b/tools/uppertester/trigger/btp_a.cpp @@ -0,0 +1,32 @@ +#include "btp.hpp" +#include "btp_a.hpp" +#include "uppertester.hpp" + +using namespace vanetza; + +void BtpATrigger::process(UpperTester& tester, Socket& socket) +{ + BtpTriggerResult result; + + Application::DownPacketPtr packet { new DownPacket() }; + packet->layer(OsiLayer::Application) = ByteBuffer { }; + + Application::DataRequest gn_request; + gn_request.transport_type = geonet::TransportType::SHB; + gn_request.communication_profile = geonet::CommunicationProfile::ITS_G5; + + btp::DataRequestA btp_request; + btp_request.destination_port = host_cast(destination_port); + btp_request.source_port = host_cast(source_port); + btp_request.gn = gn_request; + + auto confirm = tester.request(btp_request, std::move(packet)); + + if (confirm.accepted()) { + result.result = 1; + } else { + result.result = 0; + } + + socket.send(result); +} diff --git a/tools/uppertester/trigger/btp_a.hpp b/tools/uppertester/trigger/btp_a.hpp new file mode 100644 index 000000000..ce01dc12e --- /dev/null +++ b/tools/uppertester/trigger/btp_a.hpp @@ -0,0 +1,29 @@ +#ifndef UPPERTESTER_TRIGGER_BTP_A_HPP +#define UPPERTESTER_TRIGGER_BTP_A_HPP + +#include "trigger.hpp" +#include + +// C.7.1 in TR 103 099 V1.3.1 +struct BtpATrigger : Trigger +{ + const uint8_t message_type = 0x70; + uint16_t destination_port; + uint16_t source_port; + + bool deserialize(const vanetza::ByteBuffer& buffer) override + { + if (buffer.size() != 5) { + return false; + } + + destination_port = deserialize_uint16_t(buffer, 1); + source_port = deserialize_uint16_t(buffer, 3); + + return true; + } + + void process(UpperTester& tester, Socket& socket) override; +}; + +#endif /* UPPERTESTER_TRIGGER_BTP_A_HPP */ diff --git a/tools/uppertester/trigger/btp_b.cpp b/tools/uppertester/trigger/btp_b.cpp new file mode 100644 index 000000000..40b895741 --- /dev/null +++ b/tools/uppertester/trigger/btp_b.cpp @@ -0,0 +1,32 @@ +#include "btp.hpp" +#include "btp_b.hpp" +#include "uppertester.hpp" + +using namespace vanetza; + +void BtpBTrigger::process(UpperTester& tester, Socket& socket) +{ + BtpTriggerResult result; + + Application::DownPacketPtr packet { new DownPacket() }; + packet->layer(OsiLayer::Application) = ByteBuffer { }; + + Application::DataRequest gn_request; + gn_request.transport_type = geonet::TransportType::SHB; + gn_request.communication_profile = geonet::CommunicationProfile::ITS_G5; + + btp::DataRequestB btp_request; + btp_request.destination_port = host_cast(destination_port); + btp_request.destination_port_info = host_cast(destination_port_info); + btp_request.gn = gn_request; + + auto confirm = tester.request(btp_request, std::move(packet)); + + if (confirm.accepted()) { + result.result = 1; + } else { + result.result = 0; + } + + socket.send(result); +} diff --git a/tools/uppertester/trigger/btp_b.hpp b/tools/uppertester/trigger/btp_b.hpp new file mode 100644 index 000000000..c01309f93 --- /dev/null +++ b/tools/uppertester/trigger/btp_b.hpp @@ -0,0 +1,29 @@ +#ifndef UPPERTESTER_TRIGGER_BTP_B_HPP +#define UPPERTESTER_TRIGGER_BTP_B_HPP + +#include "trigger.hpp" +#include + +// C.7.2 in TR 103 099 V1.3.1 +struct BtpBTrigger : Trigger +{ + const uint8_t message_type = 0x71; + uint16_t destination_port; + uint16_t destination_port_info; + + bool deserialize(const vanetza::ByteBuffer& buffer) override + { + if (buffer.size() != 5) { + return false; + } + + destination_port = deserialize_uint16_t(buffer, 1); + destination_port_info = deserialize_uint16_t(buffer, 3); + + return true; + } + + void process(UpperTester& tester, Socket& socket) override; +}; + +#endif /* UPPERTESTER_TRIGGER_BTP_B_HPP */ diff --git a/tools/uppertester/trigger/common_change_position.cpp b/tools/uppertester/trigger/common_change_position.cpp new file mode 100644 index 000000000..f7616b0d4 --- /dev/null +++ b/tools/uppertester/trigger/common_change_position.cpp @@ -0,0 +1,12 @@ +#include "common_change_position.hpp" + +void ChangePositionTrigger::process(UpperTester& tester, Socket& socket) +{ + // TODO: Implement +} + +void serialize(OutputArchive& ar, const ChangePositionResult& result) +{ + ar << result.message_type; + ar << result.result; +} diff --git a/tools/uppertester/trigger/common_change_position.hpp b/tools/uppertester/trigger/common_change_position.hpp new file mode 100644 index 000000000..8143dde47 --- /dev/null +++ b/tools/uppertester/trigger/common_change_position.hpp @@ -0,0 +1,40 @@ +#ifndef UPPERTESTER_TRIGGER_COMMON_CHANGE_POSITON_HPP +#define UPPERTESTER_TRIGGER_COMMON_CHANGE_POSITON_HPP + +#include "serialization.hpp" +#include "trigger.hpp" +#include + +// C.2.2 in TR 103 099 V1.3.1 +struct ChangePositionTrigger : Trigger +{ + const static uint8_t message_type = 0x02; + int32_t delta_latitude; + int32_t delta_longitude; + uint8_t delta_elevation; + + bool deserialize(const vanetza::ByteBuffer& buffer) override + { + if (buffer.size() != 10) { + return false; + } + + delta_latitude = deserialize_uint32_t(buffer, 1); + delta_longitude = deserialize_uint32_t(buffer, 5); + delta_elevation = buffer.at(9); + + return true; + } + + void process(UpperTester& tester, Socket& socket) override; +}; + +struct ChangePositionResult +{ + const uint8_t message_type = 0x03; + uint8_t result; +}; + +void serialize(OutputArchive& ar, const ChangePositionResult& result); + +#endif /* UPPERTESTER_TRIGGER_COMMON_CHANGE_POSITON_HPP */ diff --git a/tools/uppertester/trigger/common_change_pseudonym.cpp b/tools/uppertester/trigger/common_change_pseudonym.cpp new file mode 100644 index 000000000..9b13c0f84 --- /dev/null +++ b/tools/uppertester/trigger/common_change_pseudonym.cpp @@ -0,0 +1,12 @@ +#include "common_change_pseudonym.hpp" + +void ChangePseudonymTrigger::process(UpperTester& tester, Socket& socket) +{ + // TODO: Implement +} + +void serialize(OutputArchive& ar, const ChangePseudonymResult& result) +{ + ar << result.message_type; + ar << result.result; +} diff --git a/tools/uppertester/trigger/common_change_pseudonym.hpp b/tools/uppertester/trigger/common_change_pseudonym.hpp new file mode 100644 index 000000000..38ca145b3 --- /dev/null +++ b/tools/uppertester/trigger/common_change_pseudonym.hpp @@ -0,0 +1,29 @@ +#ifndef UPPERTESTER_TRIGGER_COMMON_CHANGE_PSEUDONYM_HPP +#define UPPERTESTER_TRIGGER_COMMON_CHANGE_PSEUDONYM_HPP + +#include "serialization.hpp" +#include "trigger.hpp" +#include + +// C.2.3 in TR 103 099 V1.3.1 +struct ChangePseudonymTrigger : Trigger +{ + const static uint8_t message_type = 0x04; + + bool deserialize(const vanetza::ByteBuffer& buffer) override + { + return buffer.size() == 1; + } + + void process(UpperTester& tester, Socket& socket) override; +}; + +struct ChangePseudonymResult +{ + const uint8_t message_type = 0x05; + uint8_t result; +}; + +void serialize(OutputArchive& ar, const ChangePseudonymResult& result); + +#endif /* UPPERTESTER_TRIGGER_COMMON_CHANGE_PSEUDONYM_HPP */ diff --git a/tools/uppertester/trigger/common_ut_initialize.cpp b/tools/uppertester/trigger/common_ut_initialize.cpp new file mode 100644 index 000000000..110c554d6 --- /dev/null +++ b/tools/uppertester/trigger/common_ut_initialize.cpp @@ -0,0 +1,20 @@ +#include "common_ut_initialize.hpp" +#include "uppertester.hpp" + +void UtInitializeTrigger::process(UpperTester& tester, Socket& socket) +{ + std::cout << "Processing initialize trigger..." << std::endl; + + tester.reset(); + + UtInitializeResult result; + result.result = 1; + + socket.send(result); +} + +void serialize(OutputArchive& ar, const UtInitializeResult& result) +{ + ar << result.message_type; + ar << result.result; +} diff --git a/tools/uppertester/trigger/common_ut_initialize.hpp b/tools/uppertester/trigger/common_ut_initialize.hpp new file mode 100644 index 000000000..b1d9c11a0 --- /dev/null +++ b/tools/uppertester/trigger/common_ut_initialize.hpp @@ -0,0 +1,37 @@ +#ifndef UPPERTESTER_TRIGGER_COMMON_UT_INITIALIZE_HPP +#define UPPERTESTER_TRIGGER_COMMON_UT_INITIALIZE_HPP + +#include "serialization.hpp" +#include "trigger.hpp" +#include +#include + +// C.2.1 in TR 103 099 V1.3.1 +struct UtInitializeTrigger : Trigger +{ + static const uint8_t message_type = 0x00; + vanetza::security::HashedId8 certificate; + + bool deserialize(const vanetza::ByteBuffer& buffer) override + { + if (buffer.size() != 9) { + return false; + } + + // TODO assign HashedId8 + + return true; + } + + void process(UpperTester& tester, Socket& socket) override; +}; + +struct UtInitializeResult +{ + const uint8_t message_type = 0x01; + uint8_t result; +}; + +void serialize(OutputArchive& ar, const UtInitializeResult& result); + +#endif /* UPPERTESTER_TRIGGER_COMMON_UT_INITIALIZE_HPP */ diff --git a/tools/uppertester/trigger/common_ut_result.cpp b/tools/uppertester/trigger/common_ut_result.cpp new file mode 100644 index 000000000..783e2772e --- /dev/null +++ b/tools/uppertester/trigger/common_ut_result.cpp @@ -0,0 +1,7 @@ +#include "common_ut_result.hpp" + +void serialize(OutputArchive& ar, const UtResult& result) +{ + ar << result.message_type; + ar << result.result; +} diff --git a/tools/uppertester/trigger/common_ut_result.hpp b/tools/uppertester/trigger/common_ut_result.hpp new file mode 100644 index 000000000..332fc2fae --- /dev/null +++ b/tools/uppertester/trigger/common_ut_result.hpp @@ -0,0 +1,16 @@ +#ifndef UPPERTESTER_TRIGGER_COMMON_UT_RESULT_HPP +#define UPPERTESTER_TRIGGER_COMMON_UT_RESULT_HPP + +#include "serialization.hpp" +#include + +// C.1 in TR 103 099 V1.3.1 +struct UtResult +{ + const uint8_t message_type = 0x01; + uint8_t result; +}; + +void serialize(OutputArchive& ar, const UtResult& result); + +#endif /* UPPERTESTER_TRIGGER_COMMON_UT_RESULT_HPP */ diff --git a/tools/uppertester/trigger/gn.cpp b/tools/uppertester/trigger/gn.cpp new file mode 100644 index 000000000..fed290880 --- /dev/null +++ b/tools/uppertester/trigger/gn.cpp @@ -0,0 +1,22 @@ +#include "gn.hpp" +#include + +using namespace vanetza; + +void serialize(OutputArchive& ar, const GnTriggerResult& result) +{ + ar << result.message_type; + ar << result.result; +} + +void serialize(OutputArchive& ar, const GnEventIndication& indication) +{ + ar << indication.message_type; + + uint16_t size = indication.packet.size(); + serialize(ar, host_cast(size)); + + for (auto byte : indication.packet) { + ar << byte; + } +} diff --git a/tools/uppertester/trigger/gn.hpp b/tools/uppertester/trigger/gn.hpp new file mode 100644 index 000000000..31f1b4b9e --- /dev/null +++ b/tools/uppertester/trigger/gn.hpp @@ -0,0 +1,26 @@ +#ifndef UPPERTESTER_TRIGGER_GN_HPP +#define UPPERTESTER_TRIGGER_GN_HPP + +#include "serialization.hpp" +#include +#include + +// C.5.1 + C.5.2 + C.5.3 + C.5.4 + C.5.5 in TR 103 099 V1.3.1 +struct GnTriggerResult +{ + const uint8_t message_type = 0x41; + uint8_t result; +}; + +void serialize(OutputArchive& ar, const GnTriggerResult& result); + +// C.4.6 in TR 103 099 V1.3.1 +struct GnEventIndication +{ + const uint8_t message_type = 0x63; + vanetza::ByteBuffer packet; +}; + +void serialize(OutputArchive& ar, const GnEventIndication& indication); + +#endif /* UPPERTESTER_TRIGGER_GN_HPP */ diff --git a/tools/uppertester/trigger/gn_geo_anycast.cpp b/tools/uppertester/trigger/gn_geo_anycast.cpp new file mode 100644 index 000000000..67e6c7579 --- /dev/null +++ b/tools/uppertester/trigger/gn_geo_anycast.cpp @@ -0,0 +1,15 @@ +#include "gn.hpp" +#include "gn_geo_anycast.hpp" +#include "uppertester.hpp" + +using namespace vanetza; + +void GeoAnycastTrigger::process(UpperTester& tester, Socket& socket) +{ + GnTriggerResult result; + result.result = 0; + + // TODO: Implementation + + socket.send(result); +} diff --git a/tools/uppertester/trigger/gn_geo_anycast.hpp b/tools/uppertester/trigger/gn_geo_anycast.hpp new file mode 100644 index 000000000..6f25f0136 --- /dev/null +++ b/tools/uppertester/trigger/gn_geo_anycast.hpp @@ -0,0 +1,23 @@ +#ifndef UPPERTESTER_TRIGGER_GN_GEO_ANYCAST_HPP +#define UPPERTESTER_TRIGGER_GN_GEO_ANYCAST_HPP + +#include "trigger.hpp" +#include + +// C.5.3 in TR 103 099 V1.3.1 +struct GeoAnycastTrigger : Trigger +{ + const uint8_t message_type = 0x52; + // TODO: Implementation + + bool deserialize(const vanetza::ByteBuffer& buffer) override + { + // TODO: Implementation + + return true; + } + + void process(UpperTester& tester, Socket& socket) override; +}; + +#endif /* UPPERTESTER_TRIGGER_GN_GEO_ANYCAST_HPP */ diff --git a/tools/uppertester/trigger/gn_geo_broadcast.cpp b/tools/uppertester/trigger/gn_geo_broadcast.cpp new file mode 100644 index 000000000..1c8fd6135 --- /dev/null +++ b/tools/uppertester/trigger/gn_geo_broadcast.cpp @@ -0,0 +1,15 @@ +#include "gn.hpp" +#include "gn_geo_broadcast.hpp" +#include "uppertester.hpp" + +using namespace vanetza; + +void GeoBroadcastTrigger::process(UpperTester& tester, Socket& socket) +{ + GnTriggerResult result; + result.result = 0; + + // TODO: Implementation + + socket.send(result); +} diff --git a/tools/uppertester/trigger/gn_geo_broadcast.hpp b/tools/uppertester/trigger/gn_geo_broadcast.hpp new file mode 100644 index 000000000..bb47fb507 --- /dev/null +++ b/tools/uppertester/trigger/gn_geo_broadcast.hpp @@ -0,0 +1,23 @@ +#ifndef UPPERTESTER_TRIGGER_GN_GEO_BROADCAST_HPP +#define UPPERTESTER_TRIGGER_GN_GEO_BROADCAST_HPP + +#include "trigger.hpp" +#include + +// C.5.2 in TR 103 099 V1.3.1 +struct GeoBroadcastTrigger : Trigger +{ + const uint8_t message_type = 0x51; + // TODO: Implementation + + bool deserialize(const vanetza::ByteBuffer& buffer) override + { + // TODO: Implementation + + return true; + } + + void process(UpperTester& tester, Socket& socket) override; +}; + +#endif /* UPPERTESTER_TRIGGER_GN_GEO_BROADCAST_HPP */ diff --git a/tools/uppertester/trigger/gn_geo_unicast.cpp b/tools/uppertester/trigger/gn_geo_unicast.cpp new file mode 100644 index 000000000..92f4f9e36 --- /dev/null +++ b/tools/uppertester/trigger/gn_geo_unicast.cpp @@ -0,0 +1,15 @@ +#include "gn.hpp" +#include "gn_geo_unicast.hpp" +#include "uppertester.hpp" + +using namespace vanetza; + +void GeoUnicastTrigger::process(UpperTester& tester, Socket& socket) +{ + GnTriggerResult result; + result.result = 0; + + // TODO: Implementation + + socket.send(result); +} diff --git a/tools/uppertester/trigger/gn_geo_unicast.hpp b/tools/uppertester/trigger/gn_geo_unicast.hpp new file mode 100644 index 000000000..bb0ab8de9 --- /dev/null +++ b/tools/uppertester/trigger/gn_geo_unicast.hpp @@ -0,0 +1,23 @@ +#ifndef UPPERTESTER_TRIGGER_GN_GEO_UNICAST_HPP +#define UPPERTESTER_TRIGGER_GN_GEO_UNICAST_HPP + +#include "trigger.hpp" +#include + +// C.5.1 in TR 103 099 V1.3.1 +struct GeoUnicastTrigger : Trigger +{ + const uint8_t message_type = 0x50; + // TODO: Implementation + + bool deserialize(const vanetza::ByteBuffer& buffer) override + { + // TODO: Implementation + + return true; + } + + void process(UpperTester& tester, Socket& socket) override; +}; + +#endif /* UPPERTESTER_TRIGGER_GN_GEO_UNICAST_HPP */ diff --git a/tools/uppertester/trigger/gn_shb.cpp b/tools/uppertester/trigger/gn_shb.cpp new file mode 100644 index 000000000..83042fbe9 --- /dev/null +++ b/tools/uppertester/trigger/gn_shb.cpp @@ -0,0 +1,15 @@ +#include "gn.hpp" +#include "gn_shb.hpp" +#include "uppertester.hpp" + +using namespace vanetza; + +void ShbTrigger::process(UpperTester& tester, Socket& socket) +{ + GnTriggerResult result; + result.result = 0; + + // TODO: Implementation + + socket.send(result); +} diff --git a/tools/uppertester/trigger/gn_shb.hpp b/tools/uppertester/trigger/gn_shb.hpp new file mode 100644 index 000000000..3b619fe7e --- /dev/null +++ b/tools/uppertester/trigger/gn_shb.hpp @@ -0,0 +1,23 @@ +#ifndef UPPERTESTER_TRIGGER_GN_SHB_HPP +#define UPPERTESTER_TRIGGER_GN_SHB_HPP + +#include "trigger.hpp" +#include + +// C.5.4 in TR 103 099 V1.3.1 +struct ShbTrigger : Trigger +{ + const uint8_t message_type = 0x53; + // TODO: Implementation + + bool deserialize(const vanetza::ByteBuffer& buffer) override + { + // TODO: Implementation + + return true; + } + + void process(UpperTester& tester, Socket& socket) override; +}; + +#endif /* UPPERTESTER_TRIGGER_GN_SHB_HPP */ diff --git a/tools/uppertester/trigger/gn_tsb.cpp b/tools/uppertester/trigger/gn_tsb.cpp new file mode 100644 index 000000000..ba9868171 --- /dev/null +++ b/tools/uppertester/trigger/gn_tsb.cpp @@ -0,0 +1,15 @@ +#include "gn.hpp" +#include "gn_tsb.hpp" +#include "uppertester.hpp" + +using namespace vanetza; + +void TsbTrigger::process(UpperTester& tester, Socket& socket) +{ + GnTriggerResult result; + result.result = 0; + + // TODO: Implementation + + socket.send(result); +} diff --git a/tools/uppertester/trigger/gn_tsb.hpp b/tools/uppertester/trigger/gn_tsb.hpp new file mode 100644 index 000000000..e7981243f --- /dev/null +++ b/tools/uppertester/trigger/gn_tsb.hpp @@ -0,0 +1,23 @@ +#ifndef UPPERTESTER_TRIGGER_GN_TSB_HPP +#define UPPERTESTER_TRIGGER_GN_TSB_HPP + +#include "trigger.hpp" +#include + +// C.5.5 in TR 103 099 V1.3.1 +struct TsbTrigger : Trigger +{ + const uint8_t message_type = 0x54; + // TODO: Implementation + + bool deserialize(const vanetza::ByteBuffer& buffer) override + { + // TODO: Implementation + + return true; + } + + void process(UpperTester& tester, Socket& socket) override; +}; + +#endif /* UPPERTESTER_TRIGGER_GN_TSB_HPP */ diff --git a/tools/uppertester/trigger/trigger.cpp b/tools/uppertester/trigger/trigger.cpp new file mode 100644 index 000000000..13d71c5ac --- /dev/null +++ b/tools/uppertester/trigger/trigger.cpp @@ -0,0 +1,11 @@ +#include "trigger.hpp" + +uint16_t deserialize_uint16_t(const vanetza::ByteBuffer& buffer, int position) +{ + return (buffer.at(position) << 8) + buffer.at(position + 1); +} + +uint32_t deserialize_uint32_t(const vanetza::ByteBuffer& buffer, int position) +{ + return (buffer.at(position) << 24) + (buffer.at(position + 1) << 16) + (buffer.at(position + 2) << 8) + buffer.at(position + 3); +} diff --git a/tools/uppertester/trigger/trigger.hpp b/tools/uppertester/trigger/trigger.hpp new file mode 100644 index 000000000..d6cc0755e --- /dev/null +++ b/tools/uppertester/trigger/trigger.hpp @@ -0,0 +1,22 @@ +#ifndef UPPERTESTER_TRIGGER_HPP +#define UPPERTESTER_TRIGGER_HPP + +#include + +class Socket; +class UpperTester; + +struct Trigger +{ + uint8_t message_type; + + virtual bool deserialize(const vanetza::ByteBuffer& buffer) = 0; + + virtual void process(UpperTester& tester, Socket& socket) = 0; +}; + +uint16_t deserialize_uint16_t(const vanetza::ByteBuffer& buffer, int position); + +uint32_t deserialize_uint32_t(const vanetza::ByteBuffer& buffer, int position); + +#endif /* UPPERTESTER_TRIGGER_HPP */ diff --git a/tools/uppertester/ttcn3.config b/tools/uppertester/ttcn3.config new file mode 100644 index 000000000..22553c5e3 --- /dev/null +++ b/tools/uppertester/ttcn3.config @@ -0,0 +1,35 @@ +[DEFINE] + +DESTPORT := 4000 +SOURCEPORT := 5000 + +[TESTPORT_PARAMETERS] +*.*.TRI_mapper_address := "127.0.0.1" +*.*.TRI_mapper_port := "2222" + +[MODULE_PARAMETERS] +LibCommon_Time.PX_TWAIT := 120.0 +LibCommon_Sync.PX_TSYNC_TIME_LIMIT := 120.0; +LibItsBtp_Pixits.PX_DESTINATION_PORT := ${DESTPORT,integer}; +LibItsBtp_Pixits.PX_SOURCE_PORT := ${SOURCEPORT,integer}; + +[LOGGING] +ConsoleMask := ERROR | WARNING | VERDICTOP | TESTCASE | USER | DEBUG | STATISTICS +FileMask := LOG_ALL | MATCHING | DEBUG | ERROR | WARNING | VERDICTOP | TESTCASE | USER | DEBUG | STATISTICS +TimeStampFormat := DateTime +LogEventTypes := Yes +SourceInfoFormat := Stack +LogSourceInfo := Yes +LogFile := "%e.%h-%r" + +[MAIN_CONTROLLER] +TCPPort := 9999 +# NumHCs := 1 +KillTimer := 3 + +[EXECUTE] +ItsBtp_TestControl.control +//ItsCam_TestControl.control +//ItsDenm_TestControl.control +//ItsIpv6OverGeoNetworking_TestControl.control +//ItsGeoNetworking_TestControl.control diff --git a/tools/uppertester/uppertester.cpp b/tools/uppertester/uppertester.cpp new file mode 100644 index 000000000..c6498b0f0 --- /dev/null +++ b/tools/uppertester/uppertester.cpp @@ -0,0 +1,156 @@ +#include "serialization.hpp" +#include "trigger/btp.hpp" +#include "trigger/btp_a.hpp" +#include "trigger/btp_b.hpp" +#include "trigger/common_ut_initialize.hpp" +#include "trigger/common_change_position.hpp" +#include "trigger/common_change_pseudonym.hpp" +#include "trigger/gn_geo_anycast.hpp" +#include "trigger/gn_geo_broadcast.hpp" +#include "trigger/gn_geo_unicast.hpp" +#include "trigger/gn_shb.hpp" +#include "trigger/gn_tsb.hpp" +#include "uppertester.hpp" +#include +#include +#include +#include +#include +#include + +using namespace vanetza; +namespace asio = boost::asio; +using boost::asio::generic::raw_protocol; + +UpperTester::UpperTester(boost::asio::generic::raw_protocol::socket& raw_socket, TimeTrigger& trigger, const vanetza::geonet::MIB& mib) + : raw_socket(raw_socket), m_trigger(trigger), mib(mib), request_interface(new Passthrough(raw_socket)), receive_buffer(2048, 0x00) +{ + dispatcher.add_promiscuous_hook(this); + + do_receive(); + trigger.schedule(); +} + +void UpperTester::reset() +{ + if (router) { + delete router; + } + + router = new geonet::Router(m_trigger.runtime(), mib); + router->set_address(mib.itsGnLocalGnAddr); + router->set_access_interface(request_interface.get()); + router->set_transport_handler(geonet::UpperProtocol::BTP_A, &dispatcher); + router->set_transport_handler(geonet::UpperProtocol::BTP_B, &dispatcher); + router->packet_dropped = std::bind(&UpperTester::log_packet_drop, this, std::placeholders::_1); +} + +void UpperTester::do_receive() +{ + raw_socket.async_receive_from( + asio::buffer(receive_buffer), receive_endpoint, + std::bind(&UpperTester::on_read, this, std::placeholders::_1, std::placeholders::_2)); +} + +void UpperTester::on_read(const boost::system::error_code& ec, std::size_t read_bytes) +{ + if (!ec) { + std::cout << ">>> Raw Ethernet Frame (" << read_bytes << " bytes)" << std::endl; + + ByteBuffer buffer(receive_buffer.begin(), receive_buffer.begin() + read_bytes); + pass_up(CohesivePacket(std::move(buffer), OsiLayer::Physical)); + do_receive(); + } +} + +void UpperTester::pass_up(CohesivePacket&& packet) +{ + if (!router) { + return; + } + + packet.set_boundary(OsiLayer::Physical, 0); + + if (packet.size(OsiLayer::Link) < EthernetHeader::length_bytes) { + std::cerr << "UpperTester dropped invalid packet (too short for Ethernet header)" << std::endl; + } else { + packet.set_boundary(OsiLayer::Link, EthernetHeader::length_bytes); + auto link_range = packet[OsiLayer::Link]; + EthernetHeader hdr = decode_ethernet_header(link_range.begin(), link_range.end()); + if (hdr.source != mib.itsGnLocalGnAddr.mid()) { + std::cout << "Received packet from " << hdr.source << " (" << packet.size() << " bytes)" << std::endl; + std::unique_ptr up { new PacketVariant(std::move(packet)) }; + m_trigger.schedule(); // ensure the clock is up-to-date for the security entity + router->indicate(std::move(up), hdr.source, hdr.destination); + m_trigger.schedule(); // schedule packet forwarding + } + } +} + +void UpperTester::log_packet_drop(geonet::Router::PacketDropReason reason) +{ + auto reason_string = stringify(reason); + std::cout << "Router dropped packet because of " << reason_string << " (" << static_cast(reason) << ")" << std::endl; +} + +void UpperTester::tap_packet(const DataIndication& indication, const UpPacket& packet) +{ + BtpEventIndication btp_event; + + auto byte_range = boost::create_byte_view(packet, OsiLayer::Session); + ByteBuffer payload_buffer(byte_range.begin(), byte_range.end()); + btp_event.packet = payload_buffer; + + std::cout << ">>> BTP received: "; + boost::algorithm::hex(payload_buffer.begin(), payload_buffer.end(), std::ostream_iterator(std::cout)); + std::cout << " (" << payload_buffer.size() << " bytes)" << std::endl; + + ByteBuffer buffer; + serialize_into_buffer(btp_event, buffer); + socket->send(buffer); +} + +void UpperTester::process_udp_trigger(ByteBuffer& packet) +{ + std::cout << "UpperTester received an UDP packet (" << packet.size() << " bytes)" << std::endl; + + if (packet.size() < 1) { + std::cerr << "Ignoring packet, because it is too short." << std::endl; + return; + } + + uint8_t type = packet.at(0); + Trigger *trigger; + + if (type == 0x00) { + trigger = new UtInitializeTrigger(); + } else if (type == 0x02) { + trigger = new ChangePositionTrigger(); + } else if (type == 0x04) { + trigger = new ChangePseudonymTrigger(); + } else if (type == 0x50) { + trigger = new GeoUnicastTrigger(); + } else if (type == 0x51) { + trigger = new GeoBroadcastTrigger(); + } else if (type == 0x52) { + trigger = new ShbTrigger(); + } else if (type == 0x53) { + trigger = new TsbTrigger(); + } else if (type == 0x54) { + trigger = new GeoUnicastTrigger(); + } else if (type == 0x70) { + trigger = new BtpATrigger(); + } else if (type == 0x71) { + trigger = new BtpBTrigger(); + } else { + std::cerr << "Unknown packet type: " << type << std::endl; + return; + } + + if (!trigger->deserialize(packet)) { + std::cerr << "Error during deserialization." << std::endl; + return; + } + + trigger->process(*this, *socket); +} diff --git a/tools/uppertester/uppertester.hpp b/tools/uppertester/uppertester.hpp new file mode 100644 index 000000000..bfe778588 --- /dev/null +++ b/tools/uppertester/uppertester.hpp @@ -0,0 +1,49 @@ +#ifndef UPPERTESTER_UPPERTESTER_HPP +#define UPPERTESTER_UPPERTESTER_HPP + +#include "application.hpp" +#include "passthrough.hpp" +#include "socket.hpp" +#include "time_trigger.hpp" +#include +#include +#include +#include +#include +#include +#include + +class UpperTester : public Application +{ +public: + UpperTester(boost::asio::generic::raw_protocol::socket& raw_socket, TimeTrigger& trigger, const vanetza::geonet::MIB& mib); + + PortType port(); + + void reset(); + + void tap_packet(const DataIndication&, const vanetza::UpPacket&) override; + + void process_udp_trigger(vanetza::ByteBuffer& packet); + +private: + void do_receive(); + void on_read(const boost::system::error_code&, std::size_t); + void pass_up(vanetza::CohesivePacket&&); + + void log_packet_drop(vanetza::geonet::Router::PacketDropReason); + + friend class Socket; + + TimeTrigger& m_trigger; + const vanetza::geonet::MIB& mib; + std::unique_ptr request_interface; + vanetza::btp::PortDispatcher dispatcher; + Socket* socket; + + boost::asio::generic::raw_protocol::socket& raw_socket; + boost::asio::generic::raw_protocol::endpoint receive_endpoint; + vanetza::ByteBuffer receive_buffer; +}; + +#endif /* UPPERTESTER_UPPERTESTER_HPP */ diff --git a/travis/boost.sh b/travis/boost.sh new file mode 100755 index 000000000..e91ff6562 --- /dev/null +++ b/travis/boost.sh @@ -0,0 +1,17 @@ +set -e + +pushd "$HOME/deps" + +if [ -d ${BOOST_ROOT} ]; then + echo "using Boost from Travis cache" +else + wget -nc 'https://sourceforge.net/projects/boost/files/boost/1.58.0/boost_1_58_0.tar.bz2' + tar xjf boost_1_58_0.tar.bz2 + + pushd ${BOOST_ROOT} + ./bootstrap.sh + ./b2 --with-date_time --with-serialization --with-system --with-program_options + popd +fi + +popd diff --git a/travis/cmake.sh b/travis/cmake.sh new file mode 100755 index 000000000..449d32bf1 --- /dev/null +++ b/travis/cmake.sh @@ -0,0 +1,8 @@ +set -e + +pushd "$HOME/deps" + +wget -nc --no-check-certificate "https://cmake.org/files/v3.1/cmake-${CMAKE_VERSION}.tar.gz" +tar xzf cmake-${CMAKE_VERSION}.tar.gz + +popd diff --git a/travis/cryptopp.sh b/travis/cryptopp.sh new file mode 100755 index 000000000..f7824cd30 --- /dev/null +++ b/travis/cryptopp.sh @@ -0,0 +1,16 @@ +set -e + +pushd "$HOME/deps" + +if [ -d ${CRYPTOPP_ROOT} ]; then + echo "using Crypto++ from Travis cache" +else + wget -nc 'https://sourceforge.net/projects/cryptopp/files/cryptopp/5.6.2/cryptopp562.zip' + unzip cryptopp562.zip -d ${CRYPTOPP_ROOT} + + pushd ${CRYPTOPP_ROOT} + make dynamic install CXXFLAGS="-fPIC -DNDEBUG -march=native -pipe" PREFIX=./install + popd +fi + +popd diff --git a/travis/geographiclib.sh b/travis/geographiclib.sh new file mode 100755 index 000000000..04aa583d9 --- /dev/null +++ b/travis/geographiclib.sh @@ -0,0 +1,15 @@ +set -e + +pushd "$HOME/deps" + +if [ -d ${GEOGRAPHICLIB_ROOT} ]; then + echo "using GeographicLib from Travis cache" +else + wget -nc 'https://sourceforge.net/projects/geographiclib/files/distrib/archive/GeographicLib-1.37.tar.gz' + tar xzf GeographicLib-1.37.tar.gz + + pushd ${GEOGRAPHICLIB_ROOT} + ./configure + make + popd +fi diff --git a/travis/titan-makefile b/travis/titan-makefile new file mode 100644 index 000000000..3e22bac59 --- /dev/null +++ b/travis/titan-makefile @@ -0,0 +1,6 @@ +TTCN3_DIR := /home/travis/titan.core/Install +OPENSSL_DIR := /usr +#JDKDIR := /usr/lib/jvm/java-7-openjdk-amd64 +XMLDIR := /usr +JNI := no +GEN_PDF := no diff --git a/travis/titan.sh b/travis/titan.sh new file mode 100755 index 000000000..ca4b2d6e7 --- /dev/null +++ b/travis/titan.sh @@ -0,0 +1,10 @@ +set -e + +git clone https://github.com/eclipse/titan.core.git $HOME/titan.core + +cp travis/titan-makefile $HOME/titan.core/Makefile.personal +cd $HOME/titan.core + +# Titan doesn't like concurrency. :-( +make +make install diff --git a/vanetza/common/byte_buffer.hpp b/vanetza/common/byte_buffer.hpp index 5d3ce1723..d0f4dbda7 100644 --- a/vanetza/common/byte_buffer.hpp +++ b/vanetza/common/byte_buffer.hpp @@ -51,4 +51,3 @@ ByteBuffer buffer_copy(const T& object) } // namespace vanetza #endif /* BYTE_BUFFER_HPP_7NOEQO4F */ - diff --git a/vanetza/geonet/data_request.cpp b/vanetza/geonet/data_request.cpp index 5f920ccaf..68ec38bb0 100644 --- a/vanetza/geonet/data_request.cpp +++ b/vanetza/geonet/data_request.cpp @@ -51,6 +51,21 @@ DataRequest& access_request(DataRequestVariant& variant) return boost::apply_visitor(visitor, variant); } +void copy_request_parameters(const btp::DataRequestA& btp, DataRequest& gn) +{ + gn.upper_protocol = geonet::UpperProtocol::BTP_A; + gn.communication_profile = btp.gn.communication_profile; + gn.security_profile = btp.gn.security_profile; + if (btp.gn.maximum_lifetime) { + gn.maximum_lifetime = *btp.gn.maximum_lifetime; + } + gn.repetition = btp.gn.repetition; + if (btp.gn.maximum_hop_limit) { + gn.max_hop_limit = *btp.gn.maximum_hop_limit; + } + gn.traffic_class = btp.gn.traffic_class; +} + void copy_request_parameters(const btp::DataRequestB& btp, DataRequest& gn) { gn.upper_protocol = geonet::UpperProtocol::BTP_B; diff --git a/vanetza/geonet/data_request.hpp b/vanetza/geonet/data_request.hpp index 36914116d..21930c595 100644 --- a/vanetza/geonet/data_request.hpp +++ b/vanetza/geonet/data_request.hpp @@ -16,7 +16,7 @@ namespace vanetza { // forward declaration -namespace btp { struct DataRequestB; } +namespace btp { struct DataRequestA; struct DataRequestB; } namespace geonet { @@ -122,6 +122,13 @@ using DataRequestVariant = */ DataRequest& access_request(DataRequestVariant&); +/** + * Copy request parameters from BTP-A request + * \param btp BTP-A data request + * \param gn GeoNet destination request + */ +void copy_request_parameters(const btp::DataRequestA& btp, DataRequest& gn); + /** * Copy request parameters from BTP-B request * \param btp BTP-B data request @@ -133,4 +140,3 @@ void copy_request_parameters(const btp::DataRequestB& btp, DataRequest& gn); } // namespace vanetza #endif /* DATA_REQUEST_HPP_3JYISVXB */ -