Missing calls to L2CAP Stream Delegate

I have a C++/Objective-C command line application, running on MacOs (15.1.1 (24B91)), that communicates with a Bluetooth LE peripheral. The application is build with Apple clang 16.0.0 and CMake as build system using Boost.Asio.

I'm able to establish a L2CAP channel and after the channel is established, the peripheral sends a first (quite small) SDU on that channel to the application. The PSM is 0x80 and was chosen by the peripherals BLE stack. The application receives the PSM via GATT notification.

I can see the SDU being send in a single LL PDU with Wireshark. I can also see the SDU being received in Apples PacketLogger. But I miss the corresponding call to a stream event handler. For all other GATT related events, the corresponding delegates / callbacks are called.

The code that creates a dispatch queue and passes it to the CBCentralManager looks like this:

dispatch_queue  = dispatch_queue_create("de.torrox.ble_event_queue", NULL);
manager         = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_queue options:nil];

When the L2CAP channel is established, the didOpenL2CAPChannel callback gets called from a thread within the dispatch_queue (has been verified with lldb):

- (void)peripheral:(CBPeripheral *)peripheral
didOpenL2CAPChannel:(CBL2CAPChannel *)channel
             error:(NSError *)error
{
    [channel inputStream].delegate = self;
    [channel outputStream].delegate = self;
    [[channel inputStream] scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [[channel outputStream] scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [[channel inputStream] open];
    [[channel outputStream] open];

    ...
    // a reference to the channel is stored in the outside channel object
    [channel retain];
    ...
}

Yet, not a single stream event is generated:

- (void)stream:(NSStream *)stream
   handleEvent:(NSStreamEvent)event_code
{
    Log( @"stream:handleEvent %@, %lu", stream, event_code );
    ...
}

When I add a functionality, to poll the input stream, the stream will report the expected L2CAP input. But no event is generated.

The main thread of execution is usually blocking on a boost::asio::io_context::run() call. The design is, to have the stream callback stream:handleEvent to post call back invocations on that io_context, and thus to wake up the main thread and get that callbacks being invoked on the main thread.

All asynchronous GATT delegate calls are working as expected. The only missing events, are the events from the L2CAP streams. The same code worked in an older project on an older version of MacOs and an older version of Boost.

How can I find out, why the stream delegates are not called?

Missing calls to L2CAP Stream Delegate
 
 
Q