From 2e145ef7e0ce3a817b35dcf5797760ef16131c2d Mon Sep 17 00:00:00 2001 From: Torsten Robitzki Date: Fri, 17 Nov 2023 09:29:19 +0100 Subject: [PATCH] store data for multiple connection callbacks (#135) --- .../include/bluetoe/connection_callbacks.hpp | 214 ++++++++++-------- bluetoe/utility/include/bluetoe/ring.hpp | 86 +++++++ tests/CMakeLists.txt | 1 + .../link_layer/connection_callbacks_tests.cpp | 25 ++ tests/ring_tests.cpp | 60 +++++ 5 files changed, 286 insertions(+), 100 deletions(-) create mode 100644 bluetoe/utility/include/bluetoe/ring.hpp create mode 100644 tests/ring_tests.cpp diff --git a/bluetoe/link_layer/include/bluetoe/connection_callbacks.hpp b/bluetoe/link_layer/include/bluetoe/connection_callbacks.hpp index d8f6206a..6cc902d5 100644 --- a/bluetoe/link_layer/include/bluetoe/connection_callbacks.hpp +++ b/bluetoe/link_layer/include/bluetoe/connection_callbacks.hpp @@ -2,6 +2,7 @@ #define BLUETOE_LINK_LAYER_CONNECTION_CALLBACKS_HPP #include +#include namespace bluetoe { namespace link_layer { @@ -73,12 +74,6 @@ namespace link_layer { details::connection_callbacks_meta_type, details::valid_link_layer_option_meta_type {}; - connection_callbacks() - : event_type_( none ) - , connection_( nullptr ) - { - } - void connection_request( const connection_addresses& addresses ) { addresses_ = addresses; @@ -90,9 +85,8 @@ namespace link_layer { Connection& connection, Radio& r ) { - event_type_ = requested; - connection_ = &connection; - details_ = details; + const event_data data( requested, &connection, details ); + events_.try_push( data ); r.wake_up(); } @@ -105,9 +99,8 @@ namespace link_layer { Connection& connection, Radio& r ) { - event_type_ = established; - connection_ = &connection; - details_ = details; + const event_data data( established, &connection, details ); + events_.try_push( data ); r.wake_up(); } @@ -115,8 +108,8 @@ namespace link_layer { template < class Connection, class Radio > void connection_attempt_timeout( Connection& connection, Radio& r ) { - event_type_ = attempt_timeout; - connection_ = &connection; + const event_data data( attempt_timeout, &connection ); + events_.try_push( data ); r.wake_up(); } @@ -124,9 +117,8 @@ namespace link_layer { template < class Connection, class Radio > void connection_changed( const bluetoe::link_layer::connection_details& details, Connection& connection, Radio& r ) { - event_type_ = changed; - connection_ = &connection; - details_ = details; + const event_data data = { changed, &connection, details }; + events_.try_push( data ); r.wake_up(); } @@ -134,9 +126,9 @@ namespace link_layer { template < class Connection, class Radio > void connection_closed( std::uint8_t reason, Connection& connection, Radio& r ) { - event_type_ = closed; - connection_ = &connection; - raw_details_[ 0 ] = reason; + event_data data = { closed, &connection }; + data.raw_details_[ 0 ] = reason; + events_.try_push( data ); r.wake_up(); } @@ -144,9 +136,9 @@ namespace link_layer { template < class Connection, class Radio > void procedure_rejected( std::uint8_t error_code, Connection& connection, Radio& r ) { - event_type_ = rejected; - connection_ = &connection; - raw_details_[ 0 ] = error_code; + event_data data = { rejected, &connection }; + data.raw_details_[ 0 ] = error_code; + events_.try_push( data ); r.wake_up(); } @@ -154,9 +146,9 @@ namespace link_layer { template < class Connection, class Radio > void procedure_unknown( std::uint8_t error_code, Connection& connection, Radio& r ) { - event_type_ = unknown; - connection_ = &connection; - raw_details_[ 0 ] = error_code; + event_data data = { unknown, &connection }; + data.raw_details_[ 0 ] = error_code; + events_.try_push( data ); r.wake_up(); } @@ -164,9 +156,9 @@ namespace link_layer { template < class Connection, class Radio > void version_indication_received( const std::uint8_t* details, Connection& connection, Radio& r ) { - event_type_ = version; - connection_ = &connection; - std::copy( details, details + version_ind_size, &raw_details_[ 0 ] ); + event_data data = { version, &connection }; + std::copy( details, details + version_ind_size, &data.raw_details_[ 0 ] ); + events_.try_push( data ); r.wake_up(); } @@ -174,9 +166,9 @@ namespace link_layer { template < class Connection, class Radio > void remote_features_received( const std::uint8_t rf[ 8 ], Connection& connection, Radio& r ) { - event_type_ = remote_features; - connection_ = &connection; - std::copy( rf, rf + feature_field_size, &raw_details_[ 0 ] ); + event_data data = { remote_features, &connection }; + std::copy( rf, rf + feature_field_size, &data.raw_details_[ 0 ] ); + events_.try_push( data ); r.wake_up(); } @@ -184,63 +176,71 @@ namespace link_layer { template < class Connection, class Radio > void phy_update( std::uint8_t phy_c_to_p, std::uint8_t phy_p_to_c, Connection& connection, Radio& r ) { - event_type_ = update_phy; - connection_ = &connection; - raw_details_[ 0 ] = phy_c_to_p; - raw_details_[ 1 ] = phy_p_to_c; + event_data data = { update_phy, &connection }; + data.raw_details_[ 0 ] = phy_c_to_p; + data.raw_details_[ 1 ] = phy_p_to_c; + events_.try_push( data ); r.wake_up(); } template < class LinkLayer > void handle_connection_events() { - if ( event_type_ == requested ) - { - call_ll_connection_requested< T >( Obj, details_, addresses_, connection_data< LinkLayer >() ); - } - else if ( event_type_ == attempt_timeout ) - { - call_ll_connection_attempt_timeout< T >( Obj, connection_data< LinkLayer >() ); - } - else if ( event_type_ == established ) - { - call_ll_connection_established< T >( Obj, details_, addresses_, connection_data< LinkLayer >() ); - } - else if ( event_type_ == changed ) - { - call_ll_connection_changed< T >( Obj, details_, connection_data< LinkLayer >() ); - } - else if ( event_type_ == closed ) - { - call_ll_connection_closed< T >( Obj, raw_details_[ 0 ], connection_data< LinkLayer >() ); - } - else if ( event_type_ == version ) - { - call_ll_version< T >( Obj, connection_data< LinkLayer >() ); - } - else if ( event_type_ == rejected ) - { - call_ll_rejected< T >( Obj, connection_data< LinkLayer >() ); - } - else if ( event_type_ == unknown ) - { - call_ll_unknown< T >( Obj, connection_data< LinkLayer >() ); - } - else if ( event_type_ == remote_features ) - { - call_ll_remote_features< T >( Obj, connection_data< LinkLayer >() ); - } - else if ( event_type_ == update_phy ) + event_data data; + + while ( events_.try_pop( data ) ) { - call_ll_phy_updated< T >( Obj, connection_data< LinkLayer >() ); + if ( data.event_type_ == requested ) + { + call_ll_connection_requested< T >( Obj, data.details_, addresses_, connection_data< LinkLayer >( data ) ); + } + else if ( data.event_type_ == attempt_timeout ) + { + call_ll_connection_attempt_timeout< T >( Obj, connection_data< LinkLayer >( data ) ); + } + else if ( data.event_type_ == established ) + { + call_ll_connection_established< T >( Obj, data.details_, addresses_, connection_data< LinkLayer >( data ) ); + } + else if ( data.event_type_ == changed ) + { + call_ll_connection_changed< T >( Obj, data.details_, connection_data< LinkLayer >( data ) ); + } + else if ( data.event_type_ == closed ) + { + call_ll_connection_closed< T >( Obj, data.raw_details_[ 0 ], connection_data< LinkLayer >( data ) ); + } + else if ( data.event_type_ == version ) + { + call_ll_version< T >( Obj, + connection_data< LinkLayer >( data ), + data.raw_details_[ 0 ], + bluetoe::details::read_16bit( &data.raw_details_[ 1 ] ), + bluetoe::details::read_16bit( &data.raw_details_[ 3 ] ) ); + } + else if ( data.event_type_ == rejected ) + { + call_ll_rejected< T >( Obj, connection_data< LinkLayer >( data ), data.raw_details_[ 0 ] ); + } + else if ( data.event_type_ == unknown ) + { + call_ll_unknown< T >( Obj, connection_data< LinkLayer >( data ), data.raw_details_[ 0 ] ); + } + else if ( data.event_type_ == remote_features ) + { + call_ll_remote_features< T >( Obj, connection_data< LinkLayer >( data ), data.raw_details_ ); + } + else if ( data.event_type_ == update_phy ) + { + call_ll_phy_updated< T >( Obj, connection_data< LinkLayer >( data ), + static_cast< bluetoe::link_layer::phy_ll_encoding::phy_ll_encoding_t >( data.raw_details_[ 0 ] ), + static_cast< bluetoe::link_layer::phy_ll_encoding::phy_ll_encoding_t >( data.raw_details_[ 1 ] ) ); + } } - - event_type_ = none; - connection_ = nullptr; } private: - enum { + enum event_type_t { none, requested, attempt_timeout, @@ -252,22 +252,34 @@ namespace link_layer { unknown, remote_features, update_phy - } event_type_; - - void* connection_; - connection_details details_; - connection_addresses addresses_; + }; static constexpr std::size_t version_ind_size = 5u; static constexpr std::size_t feature_field_size = 8u; + static constexpr std::size_t max_events = 4u; + + struct event_data { + event_type_t event_type_; + void* connection_; + connection_details details_; + std::uint8_t raw_details_[ feature_field_size ]; + + event_data( event_type_t ev, void* con, connection_details d = connection_details() ) + : event_type_( ev ) + , connection_( con ) + , details_( d ) + {} + + event_data() {} + }; - std::uint8_t raw_details_[ feature_field_size ]; + connection_addresses addresses_; + bluetoe::details::ring< max_events, event_data > events_; template < typename LinkLayer > - typename LinkLayer::connection_data_t& connection_data() + typename LinkLayer::connection_data_t& connection_data( const event_data& d ) { - assert( connection_ ); - return *static_cast< typename LinkLayer::connection_data_t* >( connection_ ); + return *static_cast< typename LinkLayer::connection_data_t* >( d.connection_ ); } template < typename TT, typename Connection > @@ -324,58 +336,60 @@ namespace link_layer { } template < typename TT, typename Connection > - auto call_ll_version( TT& obj, Connection& connection ) + auto call_ll_version( TT& obj, Connection& connection, std::uint8_t version, std::uint16_t company, std::uint16_t subversion ) -> decltype(&TT::template ll_version< Connection >) { obj.ll_version( - raw_details_[ 0 ], - bluetoe::details::read_16bit( &raw_details_[ 1 ] ), - bluetoe::details::read_16bit( &raw_details_[ 3 ] ), + version, + company, + subversion, connection ); return nullptr; } template < typename TT, typename Connection > - auto call_ll_rejected( TT& obj, Connection& connection ) + auto call_ll_rejected( TT& obj, Connection& connection, std::uint8_t error_code ) -> decltype(&TT::template ll_rejected< Connection >) { obj.ll_rejected( - raw_details_[ 0 ], + error_code, connection ); return nullptr; } template < typename TT, typename Connection > - auto call_ll_unknown( TT& obj, Connection& connection ) + auto call_ll_unknown( TT& obj, Connection& connection, std::uint8_t error_code ) -> decltype(&TT::template ll_unknown< Connection >) { obj.ll_unknown( - raw_details_[ 0 ], + error_code, connection ); return nullptr; } template < typename TT, typename Connection > - auto call_ll_remote_features( TT& obj, Connection& connection ) + auto call_ll_remote_features( TT& obj, Connection& connection, std::uint8_t remote_features[ 8 ] ) -> decltype(&TT::template ll_remote_features< Connection >) { obj.ll_remote_features( - &raw_details_[ 0 ], + remote_features, connection ); return nullptr; } template < typename TT, typename Connection > - auto call_ll_phy_updated( TT& obj, Connection& connection ) + auto call_ll_phy_updated( TT& obj, Connection& connection, + bluetoe::link_layer::phy_ll_encoding::phy_ll_encoding_t transmit_encoding, + bluetoe::link_layer::phy_ll_encoding::phy_ll_encoding_t receive_encoding ) -> decltype(&TT::template ll_phy_updated< Connection >) { obj.ll_phy_updated( - static_cast< bluetoe::link_layer::phy_ll_encoding::phy_ll_encoding_t >( raw_details_[ 0 ] ), - static_cast< bluetoe::link_layer::phy_ll_encoding::phy_ll_encoding_t >( raw_details_[ 1 ] ), + transmit_encoding, + receive_encoding, connection ); return nullptr; diff --git a/bluetoe/utility/include/bluetoe/ring.hpp b/bluetoe/utility/include/bluetoe/ring.hpp new file mode 100644 index 00000000..54d1dbc0 --- /dev/null +++ b/bluetoe/utility/include/bluetoe/ring.hpp @@ -0,0 +1,86 @@ +#ifndef BLUETOE_UTILITY_RING_HPP +#define BLUETOE_UTILITY_RING_HPP + +#include +#include + +namespace bluetoe { +namespace details { + + /** + * @brief an atomic ring buffer + * + * Ring buffer that supports a single consumer, single producer which + * do not have to run in the same CPU context. + */ + template < std::size_t S, typename T > + class ring + { + public: + /** + * @brief contructs an empty ring + */ + ring(); + + bool try_push( const T& ); + + bool try_pop( T& ); + + private: + // queue is empty, if both point to the very same element + // if read_ptr_ != write_ptr_, the ring is not empty and data_[ read_ptr_ ] + // contains the next element to read from. + std::atomic_int read_ptr_; + std::atomic_int write_ptr_; + + static constexpr std::size_t length = S + 1; + + T data_[ length ]; + }; + + // implementation + template < std::size_t S, typename T > + ring< S, T >::ring() + : read_ptr_( 0 ) + , write_ptr_( 0 ) + { + static_assert( ATOMIC_INT_LOCK_FREE, "atomic_int is expected to be lock free" ); + } + + template < std::size_t S, typename T > + bool ring< S, T >::try_push( const T& in ) + { + const int read = read_ptr_.load(); + const int write = write_ptr_.load(); + const int next = ( write + 1 ) % length; + + if ( next == read ) + return false; + + data_[ write ] = in; + write_ptr_.store( next ); + + return true; + } + + template < std::size_t S, typename T > + bool ring< S, T >::try_pop( T& out ) + { + const int read = read_ptr_.load(); + const int write = write_ptr_.load(); + + if ( read == write ) + return false; + + const int next = ( read + 1 ) % length; + + out = data_[ read ]; + read_ptr_.store( next ); + + return true; + } + +} +} + +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e03ef5e2..c8494120 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -45,6 +45,7 @@ add_and_register_test(attribute_handle_tests) add_and_register_test(l2cap_tests) add_and_register_test(notification_queue_tests) add_and_register_test(bits_tests) +add_and_register_test(ring_tests) add_subdirectory(att) add_subdirectory(link_layer) diff --git a/tests/link_layer/connection_callbacks_tests.cpp b/tests/link_layer/connection_callbacks_tests.cpp index 04703394..b4779d48 100644 --- a/tests/link_layer/connection_callbacks_tests.cpp +++ b/tests/link_layer/connection_callbacks_tests.cpp @@ -740,3 +740,28 @@ BOOST_FIXTURE_TEST_CASE( phy_update_test_III, link_layer_only_phy_updated_callba BOOST_CHECK_EQUAL( only_phy_updated_callback.transmit_encoding, bluetoe::link_layer::phy_ll_encoding::le_unchanged_coding ); BOOST_CHECK_EQUAL( only_phy_updated_callback.receive_encoding, bluetoe::link_layer::phy_ll_encoding::le_unchanged_coding ); } + +BOOST_FIXTURE_TEST_CASE( multiple_events, link_layer_only_connect_callback ) +{ + respond_to( 37, valid_connection_request_pdu ); + ll_control_pdu( + { + 0x0c, 0x08, 0x22, 0x33, 0xbb, 0xaa + } + ); + ll_empty_pdus( 1 ); + + run( 4 ); + + const auto reported_details = only_connect_callback.reported_details; + + static const std::uint8_t map_data[] = { 0xff, 0xff, 0xff, 0xff, 0xff }; + bluetoe::link_layer::channel_map channels; + channels.reset( &map_data[ 0 ], 10 ); + + BOOST_CHECK( equal( reported_details.channels(), channels ) ); + BOOST_CHECK_EQUAL( reported_details.interval(), 0x18 ); + BOOST_CHECK_EQUAL( reported_details.latency(), 0 ); + BOOST_CHECK_EQUAL( reported_details.timeout(), 72 ); + BOOST_CHECK_EQUAL( reported_details.cumulated_sleep_clock_accuracy_ppm(), unsigned{ 50 + 100 } ); +} diff --git a/tests/ring_tests.cpp b/tests/ring_tests.cpp new file mode 100644 index 00000000..18e36512 --- /dev/null +++ b/tests/ring_tests.cpp @@ -0,0 +1,60 @@ +#include + +#define BOOST_TEST_MODULE +#include + +using ring_t = bluetoe::details::ring< 3u, std::string >; + +BOOST_FIXTURE_TEST_CASE( default_empty, ring_t ) +{ + std::string d; + BOOST_CHECK( !try_pop( d ) ); +} + +BOOST_FIXTURE_TEST_CASE( default_capacity, ring_t ) +{ + std::string d; + BOOST_CHECK( try_push( d ) ); +} + +BOOST_FIXTURE_TEST_CASE( fill, ring_t ) +{ + std::string d1, d2, d3, d4; + BOOST_CHECK( try_push( d1 ) ); + BOOST_CHECK( try_push( d2 ) ); + BOOST_CHECK( try_push( d3 ) ); + BOOST_CHECK( !try_push( d4 ) ); +} + +BOOST_FIXTURE_TEST_CASE( push_and_pop, ring_t ) +{ + std::string in = "Hallo"; + BOOST_CHECK( try_push( in ) ); + + std::string out; + BOOST_CHECK( try_pop( out ) ); + + BOOST_CHECK_EQUAL( in, out ); + BOOST_CHECK( !try_pop( out ) ); +} + +BOOST_FIXTURE_TEST_CASE( around, ring_t ) +{ + std::string d1 = "1", d2 = "2", d3 = "3", d4 = "4"; + std::string out1, out2, out3, out4, out5; + + BOOST_CHECK( try_push( d1 ) ); + BOOST_CHECK( try_push( d2 ) ); + BOOST_CHECK( try_push( d3 ) ); + BOOST_CHECK( try_pop( out1 ) ); + BOOST_CHECK( try_push( d4 ) ); + BOOST_CHECK( try_pop( out2 ) ); + BOOST_CHECK( try_pop( out3 ) ); + BOOST_CHECK( try_pop( out4 ) ); + BOOST_CHECK( !try_pop( out5 ) ); + + BOOST_CHECK_EQUAL( d1, out1 ); + BOOST_CHECK_EQUAL( d2, out2 ); + BOOST_CHECK_EQUAL( d3, out3 ); + BOOST_CHECK_EQUAL( d4, out4 ); +}