Я пытался установить 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;
}
Теперь эту тему можно закрыть, поскольку мне удалось решить проблему.