Skip to content

Commit 45a24f5

Browse files
pythongh-129288: Add optional l2_cid and l2_bdaddr_type in BTPROTO_L2CAP socket address tuple (python#129293)
Add two optional, traling elements in the AF_BLUETOOTH socket address tuple: - l2_cid, to allow e.g raw LE ATT connections - l2_bdaddr_type. To be able to connect L2CAP sockets to Bluetooth LE devices, the l2_bdaddr_type must be set to BDADDR_LE_PUBLIC or BDADDR_LE_RANDOM.
1 parent a083633 commit 45a24f5

File tree

4 files changed

+93
-7
lines changed

4 files changed

+93
-7
lines changed

Doc/library/socket.rst

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,19 @@ created. Socket addresses are represented as follows:
137137
- :const:`AF_BLUETOOTH` supports the following protocols and address
138138
formats:
139139

140-
- :const:`BTPROTO_L2CAP` accepts ``(bdaddr, psm)`` where ``bdaddr`` is
141-
the Bluetooth address as a string and ``psm`` is an integer.
140+
- :const:`BTPROTO_L2CAP` accepts a tuple
141+
``(bdaddr, psm[, cid[, bdaddr_type]])`` where:
142+
143+
- ``bdaddr`` is a string specifying the Bluetooth address.
144+
- ``psm`` is an integer specifying the Protocol/Service Multiplexer.
145+
- ``cid`` is an optional integer specifying the Channel Identifier.
146+
If not given, defaults to zero.
147+
- ``bdaddr_type`` is an optional integer specifying the address type;
148+
one of :const:`BDADDR_BREDR` (default), :const:`BDADDR_LE_PUBLIC`,
149+
:const:`BDADDR_LE_RANDOM`.
150+
151+
.. versionchanged:: next
152+
Added ``cid`` and ``bdaddr_type`` fields.
142153

143154
- :const:`BTPROTO_RFCOMM` accepts ``(bdaddr, channel)`` where ``bdaddr``
144155
is the Bluetooth address as a string and ``channel`` is an integer.
@@ -626,6 +637,14 @@ Constants
626637
This constant contains a boolean value which indicates if IPv6 is supported on
627638
this platform.
628639

640+
.. data:: AF_BLUETOOTH
641+
BTPROTO_L2CAP
642+
BTPROTO_RFCOMM
643+
BTPROTO_HCI
644+
BTPROTO_SCO
645+
646+
Integer constants for use with Bluetooth addresses.
647+
629648
.. data:: BDADDR_ANY
630649
BDADDR_LOCAL
631650

@@ -634,6 +653,15 @@ Constants
634653
any address when specifying the binding socket with
635654
:const:`BTPROTO_RFCOMM`.
636655

656+
.. data:: BDADDR_BREDR
657+
BDADDR_LE_PUBLIC
658+
BDADDR_LE_RANDOM
659+
660+
These constants describe the Bluetooth address type when binding or
661+
connecting a :const:`BTPROTO_L2CAP` socket.
662+
663+
.. versionadded:: next
664+
637665
.. data:: HCI_FILTER
638666
HCI_TIME_STAMP
639667
HCI_DATA_DIR

Lib/test/test_socket.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,17 @@ def _have_socket_bluetooth():
177177
return True
178178

179179

180+
def _have_socket_bluetooth_l2cap():
181+
"""Check whether BTPROTO_L2CAP sockets are supported on this host."""
182+
try:
183+
s = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP)
184+
except (AttributeError, OSError):
185+
return False
186+
else:
187+
s.close()
188+
return True
189+
190+
180191
def _have_socket_hyperv():
181192
"""Check whether AF_HYPERV sockets are supported on this host."""
182193
try:
@@ -219,6 +230,8 @@ def socket_setdefaulttimeout(timeout):
219230

220231
HAVE_SOCKET_BLUETOOTH = _have_socket_bluetooth()
221232

233+
HAVE_SOCKET_BLUETOOTH_L2CAP = _have_socket_bluetooth_l2cap()
234+
222235
HAVE_SOCKET_HYPERV = _have_socket_hyperv()
223236

224237
# Size in bytes of the int type
@@ -2608,6 +2621,33 @@ def testCreateScoSocket(self):
26082621
with socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_SCO) as s:
26092622
pass
26102623

2624+
@unittest.skipUnless(HAVE_SOCKET_BLUETOOTH_L2CAP, 'Bluetooth L2CAP sockets required for this test')
2625+
def testBindLeAttL2capSocket(self):
2626+
with socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP) as f:
2627+
# ATT is the only CID allowed in userspace by the Linux kernel
2628+
CID_ATT = 4
2629+
f.bind((socket.BDADDR_ANY, 0, CID_ATT, socket.BDADDR_LE_PUBLIC))
2630+
addr = f.getsockname()
2631+
self.assertEqual(addr, (socket.BDADDR_ANY, 0, CID_ATT, socket.BDADDR_LE_PUBLIC))
2632+
2633+
@unittest.skipUnless(HAVE_SOCKET_BLUETOOTH_L2CAP, 'Bluetooth L2CAP sockets required for this test')
2634+
def testBindLePsmL2capSocket(self):
2635+
with socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP) as f:
2636+
# First user PSM in LE L2CAP
2637+
psm = 0x80
2638+
f.bind((socket.BDADDR_ANY, psm, 0, socket.BDADDR_LE_RANDOM))
2639+
addr = f.getsockname()
2640+
self.assertEqual(addr, (socket.BDADDR_ANY, psm, 0, socket.BDADDR_LE_RANDOM))
2641+
2642+
@unittest.skipUnless(HAVE_SOCKET_BLUETOOTH_L2CAP, 'Bluetooth L2CAP sockets required for this test')
2643+
def testBindBrEdrL2capSocket(self):
2644+
with socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP) as f:
2645+
# First user PSM in BR/EDR L2CAP
2646+
psm = 0x1001
2647+
f.bind((socket.BDADDR_ANY, psm))
2648+
addr = f.getsockname()
2649+
self.assertEqual(addr, (socket.BDADDR_ANY, psm))
2650+
26112651

26122652
@unittest.skipUnless(HAVE_SOCKET_HYPERV,
26132653
'Hyper-V sockets required for this test.')
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add optional ``l2_cid`` and ``l2_bdaddr_type`` fields to :mod:`socket` ``BTPROTO_L2CAP`` sockaddr tuple.

Modules/socketmodule.c

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1495,9 +1495,20 @@ makesockaddr(SOCKET_T sockfd, struct sockaddr *addr, size_t addrlen, int proto)
14951495
PyObject *addrobj = makebdaddr(&_BT_L2_MEMB(a, bdaddr));
14961496
PyObject *ret = NULL;
14971497
if (addrobj) {
1498-
ret = Py_BuildValue("Oi",
1499-
addrobj,
1500-
_BT_L2_MEMB(a, psm));
1498+
/* Retain old format for non-LE address.
1499+
(cid may be set for BR/EDR, but we're discarding it for now)
1500+
*/
1501+
if (_BT_L2_MEMB(a, bdaddr_type) == BDADDR_BREDR) {
1502+
ret = Py_BuildValue("Oi",
1503+
addrobj,
1504+
_BT_L2_MEMB(a, psm));
1505+
} else {
1506+
ret = Py_BuildValue("OiiB",
1507+
addrobj,
1508+
_BT_L2_MEMB(a, psm),
1509+
_BT_L2_MEMB(a, cid),
1510+
_BT_L2_MEMB(a, bdaddr_type));
1511+
}
15011512
Py_DECREF(addrobj);
15021513
}
15031514
return ret;
@@ -2047,8 +2058,11 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args,
20472058
struct sockaddr_l2 *addr = &addrbuf->bt_l2;
20482059
memset(addr, 0, sizeof(struct sockaddr_l2));
20492060
_BT_L2_MEMB(addr, family) = AF_BLUETOOTH;
2050-
if (!PyArg_ParseTuple(args, "si", &straddr,
2051-
&_BT_L2_MEMB(addr, psm))) {
2061+
_BT_L2_MEMB(addr, bdaddr_type) = BDADDR_BREDR;
2062+
if (!PyArg_ParseTuple(args, "si|iB", &straddr,
2063+
&_BT_L2_MEMB(addr, psm),
2064+
&_BT_L2_MEMB(addr, cid),
2065+
&_BT_L2_MEMB(addr, bdaddr_type))) {
20522066
PyErr_Format(PyExc_OSError,
20532067
"%s(): wrong format", caller);
20542068
return 0;
@@ -7782,6 +7796,9 @@ socket_exec(PyObject *m)
77827796
ADD_INT_MACRO(m, AF_BLUETOOTH);
77837797
#ifdef BTPROTO_L2CAP
77847798
ADD_INT_MACRO(m, BTPROTO_L2CAP);
7799+
ADD_INT_MACRO(m, BDADDR_BREDR);
7800+
ADD_INT_MACRO(m, BDADDR_LE_PUBLIC);
7801+
ADD_INT_MACRO(m, BDADDR_LE_RANDOM);
77857802
#endif /* BTPROTO_L2CAP */
77867803
#ifdef BTPROTO_HCI
77877804
ADD_INT_MACRO(m, BTPROTO_HCI);

0 commit comments

Comments
 (0)