Krolyxon
Commit 0155f71

Branches

Refs →

budctldocsprotocoltransport.md

140 lines · 6.7 KB

Transport: Linux Bluetooth sockets, SDP and BlueZ

This file covers the parts of the link that are the same for every device: finding paired devices, finding the RFCOMM channel and opening sockets. Device protocols are in <device>.md.

All of this is public, standard material:

1. Paired devices (BlueZ over D-Bus)

2. Linux Bluetooth sockets

Constants (from <sys/socket.h> and the kernel Bluetooth headers):

Name Value
AF_BLUETOOTH 31
BTPROTO_L2CAP 0 (used with SOCK_SEQPACKET, for SDP)
BTPROTO_RFCOMM 3 (used with SOCK_STREAM, for the control link)

Address byte order. A bdaddr_t holds the address reversed: AA:BB:CC:DD:EE:FF is stored as bytes FF EE DD CC BB AA.

struct sockaddr_rc is 10 bytes:

Offset Field Type
0 rc_family u16, AF_BLUETOOTH
2 rc_bdaddr 6 bytes, reversed address
8 rc_channel u8
9 padding —

struct sockaddr_l2 is 14 bytes:

Offset Field Type
0 l2_family u16
2 l2_psm u16, little endian
4 l2_bdaddr 6 bytes, reversed address
10 l2_cid u16, little endian, 0
12 l2_bdaddr_type u8, 0 = BR/EDR
13 padding —

The SDP server always listens on PSM 1.

Connect timeout.

What budctl makes of errno. This mapping is our own choice. It decides the exit code.

errno Meaning for budctl Exit
EBUSY, EADDRINUSE, ECONNREFUSED on the RFCOMM connect another client holds the control channel 3
EHOSTDOWN, EHOSTUNREACH, ENOTCONN, ECONNRESET, ECONNABORTED, EPIPE, ENODEV, ENETDOWN buds out of range, adapter off, or link dropped (for example, the case lid closed) 2
ETIMEDOUT, EINPROGRESS, EAGAIN no answer in time 4
anything else OS error, reported with its text 2

Reads use poll(POLLIN) with the time left until the deadline. A read() that returns 0 bytes means the buds closed the link.

Writes use send(..., MSG_NOSIGNAL), so a dead link gives EPIPE instead of killing the process with SIGPIPE.

3. Finding the RFCOMM channel with SDP

The buds publish their control service in their SDP record. budctl asks for that record over L2CAP PSM 1 and reads the RFCOMM channel number out of it. It never uses a fixed channel.

PDU header (Core Spec Vol 3 Part B §4.2). All multi-byte fields are big endian.

Field Size
PDU id 1
transaction id 2
parameter length 2
parameters n
PDU id Name
0x01 ErrorResponse; parameters: error code (u16)
0x06 ServiceSearchAttributeRequest
0x07 ServiceSearchAttributeResponse

ServiceSearchAttributeRequest parameters (§4.7.1):

  1. ServiceSearchPattern: a data-element sequence of UUIDs. budctl sends one 128-bit UUID: 35 11 1C <16 UUID bytes>.
  2. MaximumAttributeByteCount (u16): FF FF. The server still splits a long answer with continuation state.
  3. AttributeIDList: a data-element sequence of attribute ids. budctl asks only for 0x0004, the ProtocolDescriptorList: 35 03 09 00 04.
  4. ContinuationState: 1 length byte followed by that many bytes. It is 00 on the first request, and on each later request it is copied from the previous response.

ServiceSearchAttributeResponse parameters (§4.7.2):

  1. AttributeListsByteCount (u16).
  2. AttributeLists: that many bytes. This is one part of a data element. Joined across all responses, it forms the complete data element.
  3. ContinuationState. If its length byte is not 0, send the request again with this state. budctl gives up after 16 rounds.

Data elements (§3).

Reading the channel.