Skip to content

Commit

Permalink
Feature/l2cap layer (#79)
Browse files Browse the repository at this point in the history
- fix LESC Pairing will fail if negotiated ATT MTU is less than 65 bytes #67 the maximum l2cap PDU size is required over all l2cap channels which then also capture the SM implementation
- factoring out some portions of the l2cap layer simplifies the link_layer
- Factoring out l2cap will allow the link layer and the l2cap layer to run in different thread context in the future
- Interface change: server<> will be instantiate with the binding / link_layer
  • Loading branch information
TorstenRobitzki authored Apr 5, 2022
1 parent 0309475 commit dcf9b53
Show file tree
Hide file tree
Showing 57 changed files with 1,705 additions and 1,150 deletions.
13 changes: 4 additions & 9 deletions bluetoe/bindings/nordic/nrf51/include/bluetoe/nrf51.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -285,24 +285,19 @@ namespace bluetoe
}

private:
static constexpr std::size_t att_mtu = bluetoe::details::find_by_meta_type<
bluetoe::link_layer::details::mtu_size_meta_type,
Options...,
bluetoe::link_layer::max_mtu_size< bluetoe::details::default_att_mtu_size >
>::type::mtu;

static constexpr std::size_t l2cap_mtu = att_mtu + 4;
// the value MAXPACKETSIZE from the documentation seems to be the maximum value, the size field can store,
// and is independent from the MTU size (https://devzone.nordicsemi.com/f/nordic-q-a/13123/what-is-actual-size-required-for-scratch-area-for-ccm-on-nrf52/50031#50031)
static constexpr std::size_t scratch_size = 267;
static constexpr std::size_t enrypted_size = l2cap_mtu + 3 + 4;

struct alignas( 4 ) scratch_area_t {
std::uint8_t data[ scratch_size ];
} scratch_area_;

// TODO should be calculated with more accuracy base on the configuration of:
// - l2cap MAX MTU
// - implementation of Data Length Update procedure
struct alignas( 4 ) encrypted_message_t {
std::uint8_t data[ enrypted_size ];
std::uint8_t data[ 260 ];
} encrypted_message_;
};

Expand Down
14 changes: 4 additions & 10 deletions bluetoe/bindings/nordic/nrf52/include/bluetoe/nrf52.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -772,17 +772,11 @@ namespace bluetoe
}

private:
static constexpr std::size_t att_mtu = bluetoe::details::find_by_meta_type<
bluetoe::link_layer::details::mtu_size_meta_type,
RadioOptions...,
bluetoe::link_layer::max_mtu_size< bluetoe::details::default_att_mtu_size >
>::type::mtu;

static constexpr std::size_t l2cap_mtu = att_mtu + 4;
static constexpr std::size_t enrypted_size = l2cap_mtu + 3 + 4;

// TODO should be calculated with more accuracy base on the configuration of:
// - l2cap MAX MTU
// - implementation of Data Length Update procedure
struct alignas( 4 ) encrypted_message_t {
std::uint8_t data[ enrypted_size ];
std::uint8_t data[ 260 ];
} encrypted_message_;
};

Expand Down
35 changes: 35 additions & 0 deletions bluetoe/gatt_options.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#ifndef BLUETOE_GATT_OPTIONS_HPP
#define BLUETOE_GATT_OPTIONS_HPP

#include <bluetoe/meta_types.hpp>

#include <cstdint>
#include <cstddef>

namespace bluetoe {

namespace details {
struct mtu_size_meta_type {};
}

/**
* @brief define the maximum GATT MTU size to be used
*
* The default is the minimum of 23.
*
* @sa server
*/
template < std::uint16_t MaxMTU >
struct max_mtu_size {
/** @cond HIDDEN_SYMBOLS */
struct meta_type :
details::mtu_size_meta_type,
details::valid_server_option_meta_type {};

static constexpr std::size_t mtu = MaxMTU;
/** @endcond */
};

}

#endif
289 changes: 289 additions & 0 deletions bluetoe/l2cap.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
#ifndef BLUETOE_L2CAP_HPP
#define BLUETOE_L2CAP_HPP

#include <cstdint>
#include <cstddef>
#include <cassert>

#include <bluetoe/codes.hpp>
#include <bluetoe/meta_tools.hpp>
#include <bluetoe/bits.hpp>

/*
* Design decisions:
* - Link layer should not know nothing about l2cap fragmentation / channels and so on
* - L2CAP layer should not know anything about LL PDU layouts
* - link_layer::read_buffer / link_layer::read_buffer should be assigned to LL PDUs and their
* layout and thus, not be used in the l2cap layer.
* - For now, the fragmentation, defragmentation is still done in the ll_l2cap_sdu_buffer
* of the link_layer, but should be moved into the l2cap<> class.
*/
namespace bluetoe {

namespace details {

/**
* @brief this class documents the requirements of a single l2cap channel to satisfy
* the requirements of the l2cap<> class.
*/
class l2cap_channel
{
public:
static constexpr std::uint16_t channel_id = 42;
static constexpr std::size_t minimum_channel_mtu_size = bluetoe::details::default_att_mtu_size;
static constexpr std::size_t maximum_channel_mtu_size = bluetoe::details::default_att_mtu_size;

template < typename ConnectionData >
void l2cap_input( const std::uint8_t* input, std::size_t in_size, std::uint8_t* output, std::size_t& out_size, ConnectionData& );

template < typename ConnectionData >
void l2cap_output( std::uint8_t* output, std::size_t& out_size, ConnectionData& );

template < class PreviousData >
using channel_data_t = PreviousData;
};

template < typename CurrentMaximum, typename Channel >
struct maximum_min_channel_mtu_size
{
using type =
typename select_type<
( CurrentMaximum::value > Channel::minimum_channel_mtu_size ),
CurrentMaximum,
std::integral_constant< std::size_t, Channel::minimum_channel_mtu_size >
>::type;
};

template < typename CurrentMaximum, typename Channel >
struct maximum_max_channel_mtu_size
{
using type =
typename select_type<
( CurrentMaximum::value > Channel::maximum_channel_mtu_size ),
CurrentMaximum,
std::integral_constant< std::size_t, Channel::maximum_channel_mtu_size >
>::type;
};

template < typename ComposedData, typename Channel >
struct add_channel_data
{
using type = typename Channel::template channel_data_t< ComposedData >;
};

static constexpr std::size_t l2cap_layer_header_size = 4u;

/**
* @brief l2cap layer, as list of l2cap channels
*
* The interface between link_layer and l2cap is designed, so that
* both parts could be run in different CPU contexts. This basically means,
* that the reponse to an incomming PDU must be able to be defered. That's
* why the functions do not directly return the result of the L2CAP request,
* but use the output parameters to allocate buffers and commit that buffers.
*
* LinkLayer derives from l2cap<> and provides following functions:
*
* - std::pair< std::size_t, std::uint8_t* > allocate_l2cap_output_buffer( std::size_t )
*
* This function is used to allocate outgoing capacity. If the function can not provide
* the requested size, it has to provide { 0, nullptr }.
*
* - void commit_l2cap_output_buffer( std::pair< std::size_t, std::uint8_t* > )
*
* This function is used to commit the allocated output buffer.
*/
template < class LinkLayer, class ChannelData, class ... Channels >
class l2cap : public derive_from< std::tuple< Channels... > >
{
public:
/**
* @ret true, if the passed input was consumed
*/
template < class ConnectionDetails >
bool handle_l2cap_input( const std::uint8_t* input, std::size_t in_size, ConnectionDetails& connection );

/**
* @brief function to be called one every connection event, from the link layer
* to collect outstanding responses.
*/
template < class ConnectionDetails >
void transmit_pending_l2cap_output( ConnectionDetails& connection );

/**
* @brief the minimum MTU size, that is required by all L2CAP channels
*
* This is the size without the 4 byte L2CAP header.
*/
static constexpr std::size_t minimum_mtu_size = fold<
std::tuple< Channels... >,
maximum_min_channel_mtu_size,
std::integral_constant< std::size_t, 0 >
>::type::value;

static constexpr std::size_t maximum_mtu_size = fold<
std::tuple< Channels... >,
maximum_max_channel_mtu_size,
std::integral_constant< std::size_t, 0 >
>::type::value;

static_assert( maximum_mtu_size >= minimum_mtu_size, "maximum_mtu_size have to be greater than minimum_mtu_size" );

using connection_data_t = typename fold<
std::tuple< Channels... >,
add_channel_data,
ChannelData
>::type;

private:
template < class ConnectionDetails >
bool transmit_single_pending_l2cap_output( ConnectionDetails& connection );

template < class ConnectionDetails >
struct l2cap_input_handler
{
l2cap_input_handler( l2cap* t, std::uint16_t ci, const std::uint8_t* i, std::size_t is, std::uint8_t* o, std::size_t os, ConnectionDetails& c )
: that( t )
, channel_id( ci )
, input( i )
, in_size( is )
, output( o )
, out_size( os )
, connection( c )
, handled( false )
{
}

template< typename Channel >
void each()
{
if ( channel_id == Channel::channel_id )
{
static_cast< Channel& >( *that ).l2cap_input( input, in_size, output, out_size, connection );
handled = true;
}
}

l2cap* that;
std::uint16_t channel_id;
const std::uint8_t* input;
std::size_t in_size;
std::uint8_t* output;
std::size_t out_size;
ConnectionDetails& connection;
bool handled;
};

template < typename ConnectionDetails >
class l2cap_output_handler
{
public:
l2cap_output_handler( l2cap* t, std::uint8_t* out, std::size_t s, ConnectionDetails& c )
: that( t )
, output( out )
, size( s )
, out_size( 0 )
, connection( c )
{
}

template< typename Channel >
void each()
{
if ( out_size == 0 )
{
out_size = size;
static_cast< Channel& >( *that ).l2cap_output( output, out_size, connection );
channel_id = Channel::channel_id;
}
}

l2cap* that;
std::uint8_t* output;
std::size_t size;
std::size_t out_size;
std::uint16_t channel_id;
ConnectionDetails& connection;
};

LinkLayer& link_layer()
{
return static_cast< LinkLayer&>( *this );
}
};


// implemenation
template < class LinkLayer, class ChannelData, class ... Channels >
template < class ConnectionDetails >
bool l2cap< LinkLayer, ChannelData, Channels... >::handle_l2cap_input( const std::uint8_t* input, std::size_t in_size, ConnectionDetails& connection )
{
// just swallow input, if not resonable
if ( in_size < l2cap_layer_header_size )
return true;

const std::uint16_t size = read_16bit( input );
const std::uint16_t channel_id = read_16bit( input + 2 );

if ( in_size != size + l2cap_layer_header_size )
return true;

auto output = link_layer().allocate_l2cap_output_buffer( maximum_mtu_size );
if ( output.first == 0 )
return false;

assert( output.second );

l2cap_input_handler< ConnectionDetails > handler(
this, channel_id, input + l2cap_layer_header_size, in_size - l2cap_layer_header_size,
output.second + l2cap_layer_header_size, maximum_mtu_size, connection );

for_< Channels... >::template each< l2cap_input_handler< ConnectionDetails >& >( handler );

if ( handler.handled && handler.out_size )
{
write_16bit( output.second, handler.out_size );
write_16bit( output.second + 2, channel_id );
link_layer().commit_l2cap_output_buffer( { handler.out_size + l2cap_layer_header_size, output.second } );
}

return true;
}

template < class LinkLayer, class ChannelData, class ... Channels >
template < class ConnectionDetails >
void l2cap< LinkLayer, ChannelData, Channels... >::transmit_pending_l2cap_output( ConnectionDetails& connection )
{
for ( bool cont = transmit_single_pending_l2cap_output( connection ); cont;
cont = transmit_single_pending_l2cap_output( connection ) )
;
}

template < class LinkLayer, class ChannelData, class ... Channels >
template < class ConnectionDetails >
bool l2cap< LinkLayer, ChannelData, Channels... >::transmit_single_pending_l2cap_output( ConnectionDetails& connection )
{
auto output = link_layer().allocate_l2cap_output_buffer( maximum_mtu_size );
if ( output.first == 0 )
return false;

assert( output.second );

l2cap_output_handler< ConnectionDetails > handler(
this, output.second + l2cap_layer_header_size, output.first - l2cap_layer_header_size, connection );

for_< Channels... >::template each< l2cap_output_handler< ConnectionDetails >& >( handler );

if ( handler.out_size )
{
write_16bit( output.second, handler.out_size );
write_16bit( output.second + 2, handler.channel_id );
link_layer().commit_l2cap_output_buffer( { handler.out_size + l2cap_layer_header_size, output.second } );
}

return handler.out_size;
}
}
}

#endif
18 changes: 18 additions & 0 deletions bluetoe/l2cap_channels.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#ifndef BLUETOE_L2CAP_CHANNELS_HPP
#define BLUETOE_L2CAP_CHANNELS_HPP

namespace bluetoe {

namespace l2cap_channel_ids {
/**
* @brief currently supported LE L2CAP channels
*/
enum l2cap_channel_ids : std::uint16_t {
att = 4,
signaling = 5,
sm = 6
};
}
}

#endif
Loading

0 comments on commit dcf9b53

Please sign in to comment.