Repository navigation
fix(l2cap): propagate send failures without replaying partial SDUs #448
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
mickeyl
wants to merge
2
commits into
h2zero:master
Choose a base branch
from
mickeyl:upstream/l2cap-send-errors
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| name: L2CAP native tests | ||
| on: | ||
| workflow_dispatch: | ||
| pull_request: | ||
| push: | ||
| branches: [master] | ||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| native: | ||
| runs-on: ubuntu-latest | ||
| strategy: | ||
| matrix: | ||
| cxx_flags: ["", "-fsanitize=address,undefined -fno-omit-frame-pointer"] | ||
| steps: | ||
| - uses: actions/checkout@v5 | ||
| with: | ||
| persist-credentials: false | ||
| - run: cmake -S tests/l2cap -B build/l2cap-tests -DCMAKE_CXX_FLAGS="${{ matrix.cxx_flags }}" | ||
| - run: cmake --build build/l2cap-tests | ||
| - run: ctest --test-dir build/l2cap-tests --output-on-failure | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| cmake_minimum_required(VERSION 3.20) | ||
| project(L2CAPTests LANGUAGES CXX) | ||
| enable_testing() | ||
| add_executable(l2cap_send_tests SendErrorsTests.cpp ../../src/NimBLEL2CAPChannel.cpp) | ||
| target_compile_features(l2cap_send_tests PRIVATE cxx_std_17) | ||
| target_include_directories(l2cap_send_tests PRIVATE stubs ../../src) | ||
| target_compile_options(l2cap_send_tests PRIVATE -UNDEBUG -include ${CMAKE_CURRENT_SOURCE_DIR}/stubs/Host.h) | ||
| foreach(case errors partial ownership append success stalled) | ||
| add_test(NAME l2cap_send_${case} COMMAND l2cap_send_tests ${case}) | ||
| endforeach() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| # L2CAP send regression tests | ||
|
|
||
| The tests compile the production `NimBLEL2CAPChannel.cpp` with minimal native | ||
| host replacements. They check positive native error propagation, abort after a | ||
| partly submitted SDU without replay, caller/native mbuf ownership, append | ||
| failure cleanup, successful fragmentation and accepted credit-stalled sends. | ||
| They do not simulate RF, controller buffers or concurrency. | ||
|
|
||
| ```sh | ||
| cmake -S tests/l2cap -B build/l2cap-tests | ||
| cmake --build build/l2cap-tests | ||
| ctest --test-dir build/l2cap-tests --output-on-failure | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| #include "NimBLEL2CAPChannel.h" | ||
| #include <iostream> | ||
| #include <stdexcept> | ||
|
|
||
| static void require(bool value,const char* message) { if (!value) throw std::runtime_error(message); } | ||
| struct Channel : NimBLEL2CAPChannel { | ||
| ble_l2cap_chan native; | ||
| Channel() : NimBLEL2CAPChannel(129,64,new NimBLEL2CAPChannelCallbacks) { | ||
| ble_l2cap_event e{};e.connect.chan=&native;handleConnectionEvent(&e); | ||
| } | ||
| }; | ||
| int main(int argc,char** argv) { | ||
| try { | ||
| const std::string test=argc>1?argv[1]:"errors"; | ||
| int sends=0; | ||
| if (test=="errors" || test=="ownership") { | ||
| const std::vector<int> codes=test=="errors" ? | ||
| std::vector<int>{BLE_HS_ENOMEM,BLE_HS_EAGAIN,BLE_HS_ENOTCONN,BLE_HS_EINVAL} : | ||
| std::vector<int>{BLE_HS_EBADDATA,BLE_HS_EBUSY}; | ||
| for (int error:codes) { | ||
| Channel c;sends=0;hostDisconnects=0; | ||
| hostSend=[&](os_mbuf* b) { | ||
| ++sends; | ||
| if (error!=BLE_HS_EBADDATA && error!=BLE_HS_EBUSY) os_mbuf_free_chain(b); | ||
| return error; | ||
| }; | ||
| require(!c.write(std::vector<uint8_t>(20,0x55)),"positive error reported success"); | ||
| require(sends==1,"failed SDU was retried or later fragment submitted"); | ||
| require(hostDisconnects==1,"failed stream was not disconnected"); | ||
| require(liveMbufs==0,"send buffer leaked or freed twice"); | ||
| } | ||
| } else if (test=="partial") { | ||
| Channel c;std::vector<uint8_t> delivered; | ||
| hostSend=[&](os_mbuf* b) { | ||
| ++sends; | ||
| const size_t length=sends==1?b->bytes.size():2; | ||
| delivered.insert(delivered.end(),b->bytes.begin(),b->bytes.begin()+length); | ||
| os_mbuf_free_chain(b);return sends==1?0:BLE_HS_ENOMEM; | ||
| }; | ||
| require(!c.write(std::vector<uint8_t>(24,0x55)),"partially sent stream reported success"); | ||
| require(sends==2 && delivered.size()==10,"partial SDU prefix replayed or remaining fragment sent"); | ||
| require(hostDisconnects==1 && liveMbufs==0,"partial-send abort/ownership failed"); | ||
| } else if (test=="append") { | ||
| Channel c;appendError=BLE_HS_ENOMEM; | ||
| hostSend=[&](os_mbuf*) { ++sends;return 0; }; | ||
| require(!c.write({1,2,3}),"append failure reported success"); | ||
| require(sends==0 && liveMbufs==0 && hostDisconnects==1,"append error leaked buffer or submitted data"); | ||
| } else if (test=="success") { | ||
| Channel c;std::vector<uint8_t> received;std::vector<uint8_t> input; | ||
| for (int i=0;i<20;++i) input.push_back(uint8_t(i)); | ||
| hostSend=[&](os_mbuf* b) {++sends;received.insert(received.end(),b->bytes.begin(),b->bytes.end());return os_mbuf_free_chain(b);}; | ||
| require(c.write(input) && sends==3 && received==input && liveMbufs==0 && hostDisconnects==0,"valid fragmented write failed"); | ||
| } else if (test=="stalled") { | ||
| Channel c; | ||
| hostSend=[&](os_mbuf* b) {++sends;os_mbuf_free_chain(b);return BLE_HS_ESTALLED;}; | ||
| require(c.write({1,2,3}) && sends==1 && liveMbufs==0 && hostDisconnects==0,"accepted stalled SDU incorrectly treated as error"); | ||
| } else { throw std::runtime_error("unknown test"); } | ||
| std::cout<<test<<" passed\n";return 0; | ||
| } catch (const std::exception& e) {std::cerr<<e.what()<<'\n';return 1;} | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| #pragma once | ||
| // Minimal host replacements; tests compile the production channel source. | ||
| #define NIMBLE_CPP_CLIENT_H_ | ||
| #define NIMBLE_CPP_LOG_H_ | ||
| #define NIMBLE_CPP_UTILS_H_ | ||
| #define NIMBLE_LOGI(...) ((void)0) | ||
| #define NIMBLE_LOGD(...) ((void)0) | ||
| #define NIMBLE_LOGW(...) ((void)0) | ||
| #define NIMBLE_LOGE(...) ((void)0) | ||
| #define CONFIG_BT_NIMBLE_ENABLED 1 | ||
| #define MYNEWT_VAL(x) MYNEWT_VAL_##x | ||
| #define MYNEWT_VAL_BLE_L2CAP_COC_MAX_NUM 1 | ||
| #define MYNEWT_VAL_BLE_ROLE_CENTRAL 0 | ||
| #include <atomic> | ||
| #include <cassert> | ||
| #include <cerrno> | ||
| #include <cstdint> | ||
| #include <cstdlib> | ||
| #include <cstring> | ||
| #include <functional> | ||
| #include <vector> | ||
| struct os_mempool {}; | ||
| struct os_mbuf_pool {}; | ||
| using os_membuf_t = uint8_t; | ||
| struct os_mbuf { std::vector<uint8_t> bytes; }; | ||
| #define OS_MEMPOOL_SIZE(n,s) ((n)*(s)) | ||
| #define OS_MBUF_PKTLEN(m) ((m)->bytes.size()) | ||
| inline int liveMbufs=0, appendError=0; | ||
| inline int os_mempool_init(os_mempool*,size_t,size_t,void*,const char*) { return 0; } | ||
| inline int os_mbuf_pool_init(os_mbuf_pool*,os_mempool*,size_t,size_t) { return 0; } | ||
| inline os_mbuf* os_mbuf_get_pkthdr(os_mbuf_pool*,int) { ++liveMbufs; return new os_mbuf; } | ||
| inline int os_mbuf_append(os_mbuf* b,const void* p,size_t n) { | ||
| if (appendError) return appendError; | ||
| auto a=static_cast<const uint8_t*>(p); b->bytes.insert(b->bytes.end(),a,a+n); return 0; | ||
| } | ||
| inline int os_mbuf_free_chain(os_mbuf* b) { --liveMbufs; delete b; return 0; } | ||
| inline int os_mbuf_copydata(os_mbuf* b,size_t off,size_t n,void* p) { memcpy(p,b->bytes.data()+off,n); return 0; } | ||
| constexpr int BLE_HS_EAGAIN=1, BLE_HS_ENOMEM=6, BLE_HS_ENOTCONN=7, BLE_HS_EINVAL=3, | ||
| BLE_HS_EALREADY=2, BLE_HS_EBADDATA=10, BLE_HS_EBUSY=15, BLE_HS_EREJECT=14, | ||
| BLE_HS_ESTALLED=31, BLE_HS_EUNKNOWN=17; | ||
| constexpr uint16_t BLE_HS_CONN_HANDLE_NONE=0xffff; | ||
| constexpr uint32_t BLE_NPL_TIME_FOREVER=UINT32_MAX; | ||
| inline uint32_t ble_npl_time_ms_to_ticks32(uint32_t ms) { return ms; } | ||
| inline void ble_npl_time_delay(uint32_t) {} // Retry count, not elapsed radio time, is tested. | ||
| struct ble_l2cap_chan {}; | ||
| struct ble_l2cap_chan_info { uint16_t peer_coc_mtu=8,our_coc_mtu=8,peer_l2cap_mtu=8,our_l2cap_mtu=8; }; | ||
| constexpr int BLE_L2CAP_EVENT_COC_CONNECTED=1,BLE_L2CAP_EVENT_COC_DISCONNECTED=2, | ||
| BLE_L2CAP_EVENT_COC_ACCEPT=3,BLE_L2CAP_EVENT_COC_DATA_RECEIVED=4,BLE_L2CAP_EVENT_COC_TX_UNSTALLED=5; | ||
| struct ble_l2cap_event { | ||
| int type=0; | ||
| struct { ble_l2cap_chan* chan; } connect{},accept{},disconnect{}; | ||
| struct { os_mbuf* sdu_rx; } receive{}; | ||
| struct { int status; ble_l2cap_chan* chan; } tx_unstalled{}; | ||
| }; | ||
| inline std::function<int(os_mbuf*)> hostSend; | ||
| inline int hostDisconnects=0; | ||
| inline int ble_l2cap_get_chan_info(ble_l2cap_chan*,ble_l2cap_chan_info* i) { *i={}; return 0; } | ||
| inline uint16_t ble_l2cap_get_conn_handle(ble_l2cap_chan*) { return 1; } | ||
| inline int ble_l2cap_send(ble_l2cap_chan*,os_mbuf* b) { return hostSend(b); } | ||
| inline int ble_l2cap_disconnect(ble_l2cap_chan*) { ++hostDisconnects; return 0; } | ||
| inline int ble_l2cap_recv_ready(ble_l2cap_chan*,os_mbuf* b) { return os_mbuf_free_chain(b); } | ||
| class NimBLEUtils { | ||
| public: | ||
| struct TaskData { mutable int m_flags=0; }; | ||
| static bool taskWait(const TaskData&,uint32_t) { assert(false && "credit wait not exercised by send error tests"); return false; } | ||
| static void taskRelease(const TaskData&,int=0) {} | ||
| static const char* returnCodeToString(int) { return "stub"; } | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| #include "Host.h" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| #include "Host.h" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| #include "Host.h" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| #include "Host.h" |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.