]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-70145: Add support for channels in Bluetooth HCI protocol (GH-132481)
authorSerhiy Storchaka <storchaka@gmail.com>
Mon, 14 Apr 2025 17:09:16 +0000 (20:09 +0300)
committerGitHub <noreply@github.com>
Mon, 14 Apr 2025 17:09:16 +0000 (20:09 +0300)
Doc/library/socket.rst
Lib/test/test_socket.py
Misc/NEWS.d/next/Library/2025-04-13-19-17-14.gh-issue-70145.nJ2MKg.rst [new file with mode: 0644]
Modules/socketmodule.c

index 11f7f06c59e87391ed5a8a557114540247ddecaf..79b14a72258d4be539d2dcd932f38234c9b0890e 100644 (file)
@@ -156,8 +156,10 @@ created.  Socket addresses are represented as follows:
 
   - :const:`BTPROTO_HCI` accepts a format that depends on your OS.
 
-    - On Linux it accepts a tuple ``(device_id,)`` where ``device_id``
-      is an integer specifying the number of the Bluetooth device.
+    - On Linux it accepts a tuple ``(device_id, [channel])`` where ``device_id``
+      is an integer specifying the number of the Bluetooth device,
+      and ``channel`` is an optional integer specifying the HCI channel
+      (:const:`HCI_CHANNEL_RAW` by default).
     - On FreeBSD, NetBSD and DragonFly BSD it accepts ``bdaddr``
       where ``bdaddr`` is the Bluetooth address as a string.
 
@@ -167,6 +169,9 @@ created.  Socket addresses are represented as follows:
     .. versionchanged:: 3.13.3
        FreeBSD support added.
 
+    .. versionchanged:: next
+       Added ``channel`` field.
+
   - :const:`BTPROTO_SCO` accepts ``bdaddr`` where ``bdaddr`` is
     the Bluetooth address as a string or a :class:`bytes` object.
     (ex. ``'12:23:34:45:56:67'`` or ``b'12:23:34:45:56:67'``)
@@ -677,6 +682,18 @@ Constants
    available on Linux and FreeBSD. :const:`!HCI_TIME_STAMP` and
    :const:`!HCI_DATA_DIR` are only available on Linux.
 
+.. data:: HCI_CHANNEL_RAW
+          HCI_CHANNEL_USER
+          HCI_CHANNEL_MONITOR
+          HCI_CHANNEL_CONTROL
+          HCI_CHANNEL_LOGGING
+
+   Possible values for ``channel`` field in the :const:`BTPROTO_HCI` address.
+
+   .. availability:: Linux
+
+   .. versionadded:: next
+
 .. data:: AF_QIPCRTR
 
    Constant for Qualcomm's IPC router protocol, used to communicate with
index 93dbcc981591cadcd4d31d003af34e0982a33f62..23642217a51b270e9d9e0d021a530ab79361621a 100644 (file)
@@ -2622,6 +2622,13 @@ class BasicBluetoothTest(unittest.TestCase):
             socket.BTPROTO_L2CAP
             socket.BTPROTO_SCO
 
+        if sys.platform == "linux":
+            socket.HCI_CHANNEL_RAW
+            socket.HCI_CHANNEL_USER
+            socket.HCI_CHANNEL_MONITOR
+            socket.HCI_CHANNEL_CONTROL
+            socket.HCI_CHANNEL_LOGGING
+
     def testCreateRfcommSocket(self):
         with socket.socket(socket.AF_BLUETOOTH, socket.SOCK_STREAM, socket.BTPROTO_RFCOMM) as s:
             pass
@@ -2721,13 +2728,14 @@ class BasicBluetoothTest(unittest.TestCase):
 
     @unittest.skipUnless(hasattr(socket, 'BTPROTO_HCI'), 'Bluetooth HCI sockets required for this test')
     def testBindHciSocket(self):
-        with socket.socket(socket.AF_BLUETOOTH, socket.SOCK_RAW, socket.BTPROTO_HCI) as s:
-            if sys.platform.startswith(('netbsd', 'dragonfly', 'freebsd')):
+        if sys.platform.startswith(('netbsd', 'dragonfly', 'freebsd')):
+            with socket.socket(socket.AF_BLUETOOTH, socket.SOCK_RAW, socket.BTPROTO_HCI) as s:
                 s.bind(socket.BDADDR_ANY)
                 addr = s.getsockname()
                 self.assertEqual(addr, socket.BDADDR_ANY)
-            else:
-                dev = 0
+        else:
+            dev = 0
+            with socket.socket(socket.AF_BLUETOOTH, socket.SOCK_RAW, socket.BTPROTO_HCI) as s:
                 try:
                     s.bind((dev,))
                 except OSError as err:
@@ -2737,6 +2745,26 @@ class BasicBluetoothTest(unittest.TestCase):
                 addr = s.getsockname()
                 self.assertEqual(addr, dev)
 
+            with (self.subTest('channel=HCI_CHANNEL_RAW'),
+                  socket.socket(socket.AF_BLUETOOTH, socket.SOCK_RAW, socket.BTPROTO_HCI) as s):
+                channel = socket.HCI_CHANNEL_RAW
+                s.bind((dev, channel))
+                addr = s.getsockname()
+                self.assertEqual(addr, dev)
+
+            with (self.subTest('channel=HCI_CHANNEL_USER'),
+                  socket.socket(socket.AF_BLUETOOTH, socket.SOCK_RAW, socket.BTPROTO_HCI) as s):
+                channel = socket.HCI_CHANNEL_USER
+                try:
+                    s.bind((dev, channel))
+                except OSError as err:
+                    # Needs special permissions.
+                    if err.errno in (errno.EPERM, errno.EBUSY, errno.ERFKILL):
+                        self.skipTest(str(err))
+                    raise
+                addr = s.getsockname()
+                self.assertEqual(addr, (dev, channel))
+
     @unittest.skipUnless(hasattr(socket, 'BTPROTO_HCI'), 'Bluetooth HCI sockets required for this test')
     def testBadHciAddr(self):
         with socket.socket(socket.AF_BLUETOOTH, socket.SOCK_RAW, socket.BTPROTO_HCI) as s:
@@ -2760,7 +2788,7 @@ class BasicBluetoothTest(unittest.TestCase):
                 with self.assertRaises(OSError):
                     s.bind(())
                 with self.assertRaises(OSError):
-                    s.bind((dev, 0))
+                    s.bind((dev, socket.HCI_CHANNEL_RAW, 0, 0))
                 with self.assertRaises(OSError):
                     s.bind(dev)
                 with self.assertRaises(OSError):
diff --git a/Misc/NEWS.d/next/Library/2025-04-13-19-17-14.gh-issue-70145.nJ2MKg.rst b/Misc/NEWS.d/next/Library/2025-04-13-19-17-14.gh-issue-70145.nJ2MKg.rst
new file mode 100644 (file)
index 0000000..6983398
--- /dev/null
@@ -0,0 +1,2 @@
+Add support for channels in Bluetooth HCI protocol
+(:const:`~socket.BTPROTO_HCI`).
index 86c920aed9cf26f488f1e1e95a49f5dfd7e47270..1ee9d52b7968c6a6952444af3a2577011fc8a5ac 100644 (file)
@@ -1541,7 +1541,14 @@ makesockaddr(SOCKET_T sockfd, struct sockaddr *addr, size_t addrlen, int proto)
             struct sockaddr_hci *a = (struct sockaddr_hci *) addr;
 #if defined(HAVE_BLUETOOTH_BLUETOOTH_H)
             PyObject *ret = NULL;
-            ret = Py_BuildValue("i", _BT_HCI_MEMB(a, dev));
+            if (_BT_HCI_MEMB(a, channel) == HCI_CHANNEL_RAW) {
+                return Py_BuildValue("i", _BT_HCI_MEMB(a, dev));
+            }
+            else {
+                return Py_BuildValue("ii",
+                                     _BT_HCI_MEMB(a, dev),
+                                     _BT_HCI_MEMB(a, channel));
+            }
             return ret;
 #elif defined(__FreeBSD__)
             const char *node = _BT_HCI_MEMB(a, node);
@@ -2138,13 +2145,15 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args,
             memset(addr, 0, sizeof(struct sockaddr_hci));
             _BT_HCI_MEMB(addr, family) = AF_BLUETOOTH;
 #if defined(HAVE_BLUETOOTH_BLUETOOTH_H)
-            unsigned short dev = _BT_HCI_MEMB(addr, dev);
-            if (!PyArg_ParseTuple(args, "H", &dev)) {
+            unsigned short dev;
+            unsigned short channel = HCI_CHANNEL_RAW;
+            if (!PyArg_ParseTuple(args, "H|H", &dev, &channel)) {
                 PyErr_Format(PyExc_OSError,
                              "%s(): wrong format", caller);
                 return 0;
             }
             _BT_HCI_MEMB(addr, dev) = dev;
+            _BT_HCI_MEMB(addr, channel) = channel;
 #else
             const char *straddr;
             if (!PyArg_Parse(args, "s", &straddr)) {
@@ -7874,6 +7883,13 @@ socket_exec(PyObject *m)
 #ifdef BTPROTO_HCI
     ADD_INT_MACRO(m, BTPROTO_HCI);
     ADD_INT_MACRO(m, SOL_HCI);
+#if defined(HCI_CHANNEL_RAW)
+    ADD_INT_MACRO(m, HCI_CHANNEL_RAW);
+    ADD_INT_MACRO(m, HCI_CHANNEL_USER);
+    ADD_INT_MACRO(m, HCI_CHANNEL_MONITOR);
+    ADD_INT_MACRO(m, HCI_CHANNEL_CONTROL);
+    ADD_INT_MACRO(m, HCI_CHANNEL_LOGGING);
+#endif
 #if defined(HCI_FILTER)
     ADD_INT_MACRO(m, HCI_FILTER);
 #endif