4

I have been trying to establish a BLE connection between and send data between two NRF52-52840 USB dongles via sockets. Moreover, I am interested in sending L2CAP data over the HCI layer.

I am able to establish a LE connection between the two devices from the client side, for which I also get the hci_handle to transmit data with, but I am not sure how I should obtain a handle on the server side.

I have tried opening both HCI and L2CAP sockets on the server. With the former approach, I get errors for listen() and accept() functions, because these operations are not supported on the HCI socket. For the latter approach, the execution stalls at accept().

Am I doing something conceptually wrong or is it due to wrong PSM/CID values (which I have tried a bunch of with the help of BT spec)? I Hope you can point out an obvious mistake.

Client with HCI socket:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/l2cap.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <errno.h>
#include <unistd.h>

#define BUFFER_SIZE 4

int main() {
    bdaddr_t dst_addr;
    str2ba("E2:4A:46:17:F6:F6", &dst_addr);

    // Get HCI Socket
    printf("\nCreating HCI socket...\n");
    int hci_device_id = hci_get_route(NULL);
    int hci_socket = hci_open_dev(hci_device_id);

    // HCI Connect
    printf("\nCreating a HCI BLE connection...\n");
    uint16_t hci_handle;
    int result = hci_le_create_conn(hci_socket, //Socket
        htobs(0x0060),                          //interval
        htobs(0x0060),                          //window
        0,                                      //initiation_filter
        LE_RANDOM_ADDRESS,                      //peer_bdaddr_type
        dst_addr,                               //peer_bdaddr
        LE_PUBLIC_ADDRESS,                      //own_bdaddr_type
        htobs(0x0018),                          //min_interval
        htobs(0x0028),                          //max_interval
        htobs(0),                               //latency
        htobs(0x002a),                          //supervision_timeout
        htobs(0x0000),                          //min_ce_length
        htobs(0x0000),                          //max_ce_length
        &hci_handle,                            //handle
        25000);                                 //timeout

    // Send data
    uint16_t buffer[BUFFER_SIZE];
    buffer[0] = htobs(0x0004);
    buffer[1] = htobs(0x0004);
    buffer[2] = htobs(0x0002);
    buffer[3] = htobs(0x0102);
    while(1){
        printf("\nSending...\n");
        int bytes_sent = write(hci_handle, buffer, sizeof buffer);
        printf("Sent %d\n", bytes_sent);
        sleep(1);
    }

    // Close Socket
    printf("\nClosing HCI socket...\n");
    close(hci_socket);
    return 0;
}

Server with L2CAP socket:

//Same headers

#define PSM 0x001F
#define CID 0x0004

int main(int argc, char **argv)
{
    //Get L2CAP socket
    printf("\nGetting L22CAP Socket...\n");
    int sock = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);

    //Bind
    printf("\nBinding...\n");
    struct sockaddr_l2 loc_addr = { 0 };
    memset(&loc_addr, 0, sizeof(loc_addr));
    loc_addr.l2_family = AF_BLUETOOTH;
    //loc_addr.l2_psm = htobs(PSM);
    //loc_addr.l2_cid = htobs(CID);
    bacpy(&loc_addr.l2_bdaddr, BDADDR_ANY);
    int bind_sock = bind(sock, (struct sockaddr *)&loc_addr, 
        sizeof(loc_addr));

    //Listening
    printf("\nListening...\n");
    int listen_sock = listen(sock, 1);

    //Accepting
    printf("\nAccepting...\n");
    struct sockaddr_l2 addr = { 0 };
    memset(&addr, 0, sizeof(addr));
    socklen_t len = sizeof(addr);
    int accept_sock = accept(sock, (struct sockaddr *)&addr, &len);

    // close connection
    close(sock);
    return 0;
}

Connection is established correctly, as I can read the services and characteristics of the remote device via bluetoothctl, but the handle is not returned.

Edit #1

As it turns out, you cannot write directly to socket like this, and this is why none of the output was caught with btmon either. I have implemented functions for encapsulating data with the L2CAP headers and sending it through the HCI socket. The following example implements the "ATT: Exchange MTU request".

#define BUFFER_SIZE 4
...

int hci_send_l2cap(int hci_socket, uint16_t hci_handle){
    uint16_t buffer[BUFFER_SIZE];
    uint16_t data_length = (sizeof(uint16_t) * BUFFER_SIZE) - 1;
    uint16_t *data;
    buffer[0] = htobs(0x0003);  // Length
    buffer[1] = htobs(0x0004);  // CID
    buffer[2] = htobs(0x0502);  // Request
    buffer[3] = htobs(0x0002);  // Request
    data = &buffer;
    hci_send_acl(hci_socket, hci_handle, data, data_length);   
}

int hci_send_acl(int hci_socket, uint16_t hci_handle, uint16_t *data, uint16_t data_length){
    uint8_t type = HCI_ACLDATA_PKT;
    uint16_t BCflag = 0x0000;               // Broadcast flag
    uint16_t PBflag = 0x0002;               // Packet Boundary flag
    uint16_t flags = ((BCflag << 2) | PBflag) & 0x000F;       
    hci_acl_hdr hd;
    hd.handle = htobs(acl_handle_pack(hci_handle, flags));  
    hd.dlen = (data_length);
    struct iovec iv[3];
    int ivn = 3;

    iv[0].iov_base = &type;                 // Type of operation
    iv[0].iov_len = 1;                      // Size of ACL operation flag
    iv[1].iov_base = &hd;                   // Handle info + flags
    iv[1].iov_len = HCI_ACL_HDR_SIZE;       // L2CAP header length + data length
    iv[2].iov_base = data;                  // L2CAP header + data
    iv[2].iov_len = (data_length);          // L2CAP header length + data length

    while (writev(hci_socket, iv, ivn) < 0) {
        if (errno == EAGAIN || errno == EINTR)
            continue;
        return -1;
    }
    return 0;
}

This is now caught by btmon as such:

< ACL Data TX: Handle 0 flags 0x02 dlen 7
      ATT: Exchange MTU Request (0x02) len 2
        Client RX MTU: 517

and caught by hcidump --raw as such:

< 02 00 20 07 00 03 00 04 00 02 05 02 

and this is also caught by btmon and hcidump on the destination system.

I'm continuing troubleshooting on how to open a handle on the server side. Any ideas are welcome.

Edit #2

So I have managed to also receive the messages on the server side. Turns out you have to first set the proper filters for the HCI socket channel before trying to read any information on the server side. Here I have created a demonstration function for anyone interested. The client side is running similar code as demonstrated in Edit #1.

int hci_read_data(int hci_socket){
    unsigned char buf[HCI_MAX_EVENT_SIZE];
    struct hci_filter nf, of;
    socklen_t olen;
    int err, try, len, cnt = 0, to = TIMEOUT;

    olen = sizeof(of);
    if (getsockopt(hci_socket, SOL_HCI, HCI_FILTER, &of, &olen) < 0)
        return -1;
    hci_filter_clear(&nf);
    hci_filter_set_ptype(HCI_ACLDATA_PKT,  &nf);
    if (setsockopt(hci_socket, SOL_HCI, HCI_FILTER, &nf, sizeof(nf)) < 0)
        return -1;

    while (1) {
        if (to) {
            struct pollfd p;
            int n;

            p.fd = hci_socket; p.events = POLLIN;
            while ((n = poll(&p, 1, to)) < 0) {
                if (errno == EAGAIN || errno == EINTR)
                    continue;
                goto failed;
            }

            if (!n) {
                errno = ETIMEDOUT;
                goto failed;
            }

            to -= 10;
            if (to < 0)
                to = 0;
        }

        while ((len = read(hci_socket, buf, sizeof(buf))) < 0) {
            printf("here1\n");
            if (errno == EAGAIN || errno == EINTR)
                continue;
            goto failed;
        }
    }
    errno = ETIMEDOUT;

failed:
    if (DEBUG) printf("ACL data sending failed.\n");
    err = errno;
    setsockopt(hci_socket, SOL_HCI, HCI_FILTER, &of, sizeof(of));
    errno = err;
    return -1;
}

This topic can now be closed as I have managed to solve the issue.

kajax
  • 41
  • 3

0 Answers0