SCO(7)

πŸ¦‹ SCO(7) πŸ¦‹

okay so this is sco β€” the SCO transport, straight out of the Linux System Administration section of the manual. buckle up bestie, we're going into the Bluetooth audio-adjacent trenches πŸ’–


🌸 NAME 🌸

sco β€” SCO transport

✨ SYNOPSIS ✨

#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/sco.h>

sco_socket = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO);

πŸ’– DESCRIPTION πŸ’–

so the SCO logical transport is this symmetric, point-to-point transport between the Central and a specific Peripheral. it reserves slots, which is why we can lowkey think of it as a circuit-switched connection between the Central and the Peripheral. very "we booked this lane in advance" of it πŸ’…

and because reserved slots weren't enough of a flex β€” when eSCO is supported, a retransmission window follows immediately after them. together, the reserved slots and the retransmission window form the complete eSCO window. periodt. πŸ”₯

πŸŽ€ SOCKET ADDRESS πŸŽ€

struct sockaddr_sco {
    sa_family_t     sco_family;
    bdaddr_t        sco_bdaddr;
};

example time ✨

struct sockaddr_sco addr;

memset(&addr, 0, sizeof(addr));
addr.sco_family = AF_BLUETOOTH;
bacpy(&addr.sco_bdaddr, bdaddr);

πŸ’… SOCKET OPTIONS (SOL_BLUETOOTH) πŸ’…

the socket options listed below can all be set with setsockopt(2) and read with getsockopt(2), with the socket level set to SOL_BLUETOOTH. set the level, be a good citizen, we love to see it 🫢


πŸ” BT_SECURITY (since Linux 2.6.30)

channel security level. possible values are giving "pick your fighter":

  • BT_SECURITY_SDP β€” security level 0 (SDP Only); link key type: None; encryption: Not required
  • BT_SECURITY_LOW β€” security level 1 (Low); link key type: Unauthenticated; encryption: Not required
  • BT_SECURITY_MEDIUM β€” security level 2 (Medium - default); link key type: Unauthenticated; encryption: Desired
  • BT_SECURITY_HIGH β€” security level 3 (High); link key type: Authenticated; encryption: Required
  • BT_SECURITY_FIPS (since Linux 3.15) β€” security level 4 (Secure Only); link key type: Authenticated (P-256 based Secure Simple Pairing and Secure Authentication); encryption: Required

example:

int level = BT_SECURITY_HIGH;
int err = setsockopt(sco_socket, SOL_BLUETOOTH, BT_SECURITY, &level,
                     sizeof(level));
if (err == -1) {
    perror("setsockopt");
    return 1;
}

πŸ›‘οΈ BT_DEFER_SETUP (since Linux 2.6.30)

channel defer connection setup. this one controls whether the connection procedure needs to be authorized by userspace before responding β€” which is what allows authorization at the profile level. honestly kind of a big deal πŸ‘‘

possible values:

  • 0 β€” Disable (default); authorization: Not required
  • 1 β€” Enable; authorization: Required

example:

int defer_setup = 1;
int err = setsockopt(sco_socket, SOL_BLUETOOTH, BT_DEFER_SETUP,
                     &defer_setup, sizeof(defer_setup));
if (err == -1) {
    perror("setsockopt");
    return err;
}

err = listen(sco_socket, 5);
if (err) {
    perror("listen");
    return err;
}

struct sockaddr_sco remote_addr = {0};
socklen_t addr_len = sizeof(remote_addr);
int new_socket = accept(sco_socket, (struct sockaddr*)&remote_addr,
                        &addr_len);
if (new_socket < 0) {
    perror("accept");
    return new_socket;
}

/* To complete the connection setup of new_socket read 1 byte */
char c;
struct pollfd pfd;

memset(&pfd, 0, sizeof(pfd));
pfd.fd = new_socket;
pfd.events = POLLOUT;

err = poll(&pfd, 1, 0);
if (err) {
    perror("poll");
    return err;
}

if (!(pfd.revents & POLLOUT)) {
    err = read(sk, &c, 1);
    if (err < 0) {
        perror("read");
        return err;
    }
}

πŸŽ™οΈ BT_VOICE (since Linux 3.11)

transport voice settings. here's the little struct first, because we're classy:

struct bt_voice {
    uint16_t setting;
};

and the possible values:

  • BT_VOICE_TRANSPARENT β€” value 0x0003; Transparent output
  • BT_VOICE_CVSD_16BIT β€” value 0x0060; C-VSD output PCM 16-bit input
  • BT_VOICE_TRANSPARENT_16BIT β€” value 0x0063; Transparent output PCM 16-bit input

example:

struct bt_voice voice;

memset(&voice, 0, sizeof(voice));
voice.setting = BT_VOICE_TRANSPARENT;
int err = setsockopt(sco_socket, SOL_BLUETOOTH, BT_VOICE, &voice,
                     sizeof(voice));
if (err == -1) {
    perror("setsockopt");
    return 1;
}

πŸ“¦ BT_PKT_STATUS (since Linux 5.9)

this one enables reporting packet status via BT_SCM_PKT_STATUS CMSG on received packets. possible values:

  • 0 β€” Disable (default)
  • 1 β€” Enable

🏷️ BT_SCM_PKT_STATUS

level SOL_BLUETOOTH CMSG with data:

uint8_t pkt_status;

the values are equal to the "Packet_Status_Flag" defined in Core Specification v6.0 Sec. 5.4.3 pp. 1877, which is a whole mood. so:

  • 0x0 β€” Correctly received data
  • 0x1 β€” Possibly invalid data
  • 0x2 β€” No data received
  • 0x3 β€” Data partially lost

and that's the end of part one bestie, don't go anywhere πŸ¦‹βœ¨

BT_PHY (since Linux 5.10) πŸ“‘

Which transport PHY(s) this socket supports β€” read-only, so like… there's no setsockopt support for this one bestie, you can look but you cannot touch. The possible values are:

  • BT_PHY_BR_1M_1SLOT β€” BIT 0, BR 1Mbps 1SLOT
  • BT_PHY_BR_1M_3SLOT β€” BIT 1, BR 1Mbps 3SLOT
  • BT_PHY_BR_2M_1SLOT β€” BIT 3, EDR 2Mbps 1SLOT
  • BT_PHY_BR_2M_3SLOT β€” BIT 4, EDR 2Mbps 3SLOT
  • BT_PHY_BR_3M_1SLOT β€” BIT 6, EDR 3Mbps 1SLOT
  • BT_PHY_BR_3M_3SLOT β€” BIT 7, EDR 3Mbps 3SLOT

(yeah, BIT 2 and BIT 5 just… aren't a thing here, that's not me being shady, that's literally the list 🫢)


BT_CODEC (since Linux 5.14) 🎧

Transport codec offload, and omg the possible values are these two structs:

struct bt_codec {
    uint8_t id;
    uint16_t cid;
    uint16_t vid;
    uint8_t data_path_id;
    uint8_t num_caps;
    struct codec_caps {
        uint8_t len;
        uint8_t data[];
    } caps[];
} __attribute__((packed));

struct bt_codecs {
    uint8_t num_codecs;
    struct bt_codec codecs[];
} __attribute__((packed));

Bestie, here's an example, no cap πŸ“:

char buffer[sizeof(struct bt_codecs) + sizeof(struct bt_codec)];
struct bt_codec *codecs = (void *)buffer;

memset(codecs, 0, sizeof(codecs));
codec->num_codecs = 1;
codecs->codecs[0].id = 0x05;
codecs->codecs[0].data_path_id = 1;

int err = setsockopt(sco_socket, SOL_BLUETOOTH, BT_CODEC, codecs,
            sizeof(buffer));
if (err == -1) {
    perror("setsockopt");
    return 1;
}

(yes, that's exactly how it's written, I'm not fixing it, it's iconic as-is πŸ’…)


✨ SOCKET OPTIONS (SOL_SOCKET) ✨

The SOL_SOCKET level socket options that tweak generic socket features (SO_SNDBUF, SO_RCVBUF, etc.) keep their usual meaning, okay? Go check socket(7) for the lore.

The SOL_SOCKET level SCO socket options that have Bluetooth-specific handling in the kernel are the ones listed right below. πŸ‘‡

SO_TIMESTAMPING, SO_TIMESTAMP, SO_TIMESTAMPNS ⏰

See https://docs.kernel.org/networking/timestamping.html

For SCO sockets, software RX timestamps are supported. Software TX timestamps (SOF_TIMESTAMPING_TX_SOFTWARE) are supported since Linux 6.15. πŸŽ€

The software RX timestamp is the time when the kernel received the packet from the controller driver.

The SCM_TSTAMP_SND timestamp is emitted when packet is sent to the controller driver.

The SCM_TSTAMP_COMPLETION timestamp is emitted when controller reports the packet completed. Heads up bestie: completion timestamps are only supported on controllers that have SCO flow control. Other TX timestamp types are not supported. 😭

You can use SIOCETHTOOL to query supported flags.

The timestamps are in CLOCK_REALTIME time.

Example (Enable RX timestamping):

int flags = SOF_TIMESTAMPING_SOFTWARE |
    SOF_TIMESTAMPING_RX_SOFTWARE;
setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags));

Example (Read packet and its RX timestamp):

char data_buf[256];
union {
    char buf[CMSG_SPACE(sizeof(struct scm_timestamping))];
    struct cmsghdr align;
} control;
struct iovec data = {
    .iov_base = data_buf,
    .iov_len = sizeof(data_buf),
};
struct msghdr msg = {
    .msg_iov = &data,
    .msg_iovlen = 1,
    .msg_control = control.buf,
    .msg_controllen = sizeof(control.buf),
};
struct scm_timestamping tss;

res = recvmsg(fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT);
if (res < 0)
    goto error;

for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
    if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPING)
        memcpy(&tss, CMSG_DATA(cmsg), sizeof(tss));
}

tstamp_clock_realtime = tss.ts[0];

Example (Enable TX timestamping):

int flags = SOF_TIMESTAMPING_SOFTWARE |
    SOF_TIMESTAMPING_TX_SOFTWARE |
    SOF_TIMESTAMPING_OPT_ID;
setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags));

Example (Read TX timestamps):

union {
    char buf[CMSG_SPACE(sizeof(struct scm_timestamping))];
    struct cmsghdr align;
} control;
struct iovec data = {
    .iov_base = NULL,
    .iov_len = 0
};
struct msghdr msg = {
    .msg_iov = &data,
    .msg_iovlen = 1,
    .msg_control = control.buf,
    .msg_controllen = sizeof(control.buf),
};
struct cmsghdr *cmsg;
struct scm_timestamping tss;
struct sock_extended_err serr;
int res;

res = recvmsg(fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT);
if (res < 0)
    goto error;

for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
    if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPING)
        memcpy(&tss, CMSG_DATA(cmsg), sizeof(tss));
    else if (cmsg->cmsg_level == SOL_BLUETOOTH && cmsg->cmsg_type == BT_SCM_ERROR)
        memcpy(&serr, CMSG_DATA(cmsg), sizeof(serr));
}

tstamp_clock_realtime = tss.ts[0];
tstamp_type = serr->ee_info;      /* SCM_TSTAMP_SND or SCM_TSTAMP_COMPLETION */
tstamp_seqnum = serr->ee_data;

πŸ¦‹ IOCTLS πŸ¦‹

The following ioctls with operation specific for SCO sockets are available. Slay.

SIOCETHTOOL (since Linux 6.16-rc1) πŸ”

Supports only the command ETHTOOL_GET_TS_INFO, which may be used to query supported SOF_TIMESTAMPING_* flags. The SOF_TIMESTAMPING_OPT_* flags are always available as applicable. πŸ’–

Example:

#include <linux/ethtool.h>
#include <linux/sockios.h>
#include <net/if.h>
#include <sys/socket.h>
#include <sys/ioctl.h>

...

struct ifreq ifr = {};
struct ethtool_ts_info cmd = {};
int sk;

snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "hci0");
ifr.ifr_data = (void *)&cmd;
cmd.cmd = ETHTOOL_GET_TS_INFO;

sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO);
if (sk < 0)
    goto error;
if (ioctl(sk, SIOCETHTOOL, &ifr))
    goto error;

sof_available = cmd.so_timestamping;

🌈 RESOURCES 🌈

http://www.bluez.org


πŸ™ˆ REPORTING BUGS πŸ™ˆ

linux-bluetooth@vger.kernel.org β€” please do actually tell them, not just subtweet the kernel πŸ’€


⭐ SEE ALSO ⭐


Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). 🫢


and that's the whole page bestie!! this little gem is brought to you by BlueZ, dated March 2025, and it's SCO(7) β€” go forth and timestamp your heart out βœ¨πŸŽ€πŸ’œ

πŸ“¦ where this page comes from