Skip to content

Conversation

haukepetersen
Copy link
Member

In the current code, receiving L2CAP COC frames is broken for a number of specific packet sizes (n * BLE_L2CAP_COC_MPS) - 1, e.g. for an L2CAP MPS value of 250, all packets with size 249, 499, 749, ... will not be received correctly.

Reason (as I analyzed it so far): the call to ble_hs_mbuf_pullup_base() in the beginning of ble_l2cap_coc_rx_fn() expects the incoming frame to have at least 2 bytes of payload. However, continuation frames might have a payload of 1 byte, leading to the ble_hs_mbuf_pullup_base() call failing and thereby to error-exit the whole receive function. This way, the actually correct l2cap payload is never appended to the receive buffer and so the packet in the reassembly buffer is never completed...

This PR proposes a crude fix to this problem, by only calling ble_hs_mbuf_pullup_base() in case the received payload is >= 2. I verified the fix in my raw-l2cap-flooding setup -> with the fix I can successfully transfer those payloads that lead to frames with size 1.

However, I don't quite comprehend what ble_hs_mbuf_pullup_base() is actually good for, so I don't now if this proposed fix has any bad side effects. So please review careful and let me know about better approaches to fix this :-)

return rc;
/* Continuation LE frames might be of size 1, so we skip the pullup step
* for these */
if (om_total >= 2) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good but I'd use BLE_L2CAP_SDU_SIZE here (so that it match pullup call)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

of course, my bad -> fixed.

@haukepetersen
Copy link
Member Author

btw, could you enlighten me what the pullup() call does in particular? As far as I understood, it makes sure that all data in a mbuf chain is moved so that it resides continuously at the beginning of the chain and thereby using the optimal number of mbufs, right?!

But looking at the way the data is received in the l2cap layer, it seems to me that the this is always the case already when entering the ble_l2cap_coc_rx_fn() function. So would it maybe make sense to remove the pullup() call all together?

/* Continuation LE frames might be of size 1, so we skip the pullup step
* for these */
if (om_total >= BLE_L2CAP_SDU_SIZE) {
rc = ble_hs_mbuf_pullup_base(om, BLE_L2CAP_SDU_SIZE);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you noticed that should be done only for the very first packet.
Therefore instead of that if please move that code below the

   /* First LE frame */
    if (OS_MBUF_PKTLEN(rx->sdu) == 0) {

Btw this pullup might look paranoid but we want to do it before accessing om_data directly.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes good sense to me, will asap.

Copy link
Contributor

@rymanluk rymanluk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@haukepetersen Looks good. Just please make sure to squash it before merging :) Thanks!

@haukepetersen
Copy link
Member Author

Moved the pullup call into the handling for the first frame, looks much neater now.

Doing this, I was wondering if something like the following might also be a (slightly) more efficient variant:

uint8_t tmp[2];
os_mbuf_copydata(om, 0, 2, tmp);
sdu_len = get_le16(tmp);

if I understand correctly, the os_mbuf_copdata() takes care of reading the data independent on how its fragmented over mbuf boundaries?!

L2CAP payloads might be split into multiple frames, of which only
the first frame contains the 2-byte SDU length field and has
thereby a guaranteed length > 2. Any subsequent frame may be of
size 1 and are discarded without this fix. This commit fixes the
ble_l2cap_coc_rx_fn() so that it only calls
ble_hs_mbuf_pullup_base() if the incoming payload is >= 2 bytes.
@haukepetersen
Copy link
Member Author

Anyhow, the optimization, if valid, might as well be applied in a follow up PR...

Squashed, so we can proceed on this fix as is for now.

@sjanc
Copy link
Contributor

sjanc commented Nov 21, 2019

@haukepetersen looks good, could you try merging this yourself (use 'rebase and merge' policy) ?:)

@rymanluk
Copy link
Contributor

Moved the pullup call into the handling for the first frame, looks much neater now.

Doing this, I was wondering if something like the following might also be a (slightly) more efficient variant:

uint8_t tmp[2];
os_mbuf_copydata(om, 0, 2, tmp);
sdu_len = get_le16(tmp);

if I understand correctly, the os_mbuf_copdata() takes care of reading the data independent on how its fragmented over mbuf boundaries?!

I'm not sure if we should bother as in most of cases ble_hs_mbuf_pullup_base() will do 2 checks eventually:

    if (OS_MBUF_PKTLEN(*om) < base_len) {
        return BLE_HS_EBADDATA;
    }

and in os_mbuf_pullup() it will endup here.

if (om->om_len >= len) {
       return (om);
   }

@haukepetersen
Copy link
Member Author

@sjanc I am not allowed to merge, seems like my write access has not yet been properly configured...

@rymanluk I agree, lets leave it as is, so never mind - still learning to get a feeling for the mynewt APIs :-)

@rymanluk rymanluk merged commit 440cca7 into apache:master Nov 21, 2019
@haukepetersen haukepetersen deleted the fix_l2cap_framesize1 branch November 21, 2019 15:52
@haukepetersen
Copy link
Member Author

thanks @rymanluk ;-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants