Установление соединения L2CAP через HCI - PullRequest
3 голосов
/ 11 октября 2019

Я пытался установить BLE-соединение между двумя USB-ключами NRF52-52840 и передавать их через гнезда. Более того, я заинтересован в отправке данных L2CAP через уровень HCI.

Я могу установить соединение LE между двумя устройствами со стороны клиента, для чего я также получаю hci_handle для передачи данных, но я не уверен, как мне получить дескриптор на стороне сервера.

Я попытался открыть на сервере как сокеты HCI, так и L2CAP. При первом подходе я получаю ошибки для функций listen () и accept () , поскольку эти операции не поддерживаются в сокете HCI. При последнем подходе выполнение останавливается на accept () .

Я делаю что-то концептуально неправильно или это из-за неправильных значений PSM / CID (которые я пробовал кучу спомощь спецификаций BT)? Надеюсь, вы можете указать на очевидную ошибку.

Клиент с разъемом HCI:

#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;
}

Сервер с разъемом L2CAP:

//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;
}

Соединение установлено правильно, так какЯ могу прочитать службы и характеристики удаленного устройства через bluetoothctl , но дескриптор не возвращается.

Edit # 1

Как оказалось, вы не можете написатьпрямо в сокет, как это, и поэтому ни один из выходных данных не был пойман с btmon . Я реализовал функции для инкапсуляции данных с заголовками L2CAP и отправки их через сокет HCI. В следующем примере реализуется запрос «ATT: Exchange MTU».

#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;
}

Теперь он перехватывается btmon следующим образом:

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

и перехватывается hcidump --raw как таковой:

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

, и это также перехватывается btmon и hcidump в системе назначения.

Я продолжаю устранять неполадки, связанные с открытием дескриптора на стороне сервера. Любые идеи приветствуются.

Edit # 2

Так что мне удалось также получать сообщения на стороне сервера. Оказывается, вы должны сначала установить правильные фильтры для канала сокета HCI, прежде чем пытаться прочитать какую-либо информацию на стороне сервера. Здесь я создал демонстрационную функцию для всех, кто заинтересован. На стороне клиента выполняется код, аналогичный показанному в «Правке №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;
}

Теперь эту тему можно закрыть, поскольку мне удалось решить проблему.

...