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.