1# Copyright 2024 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""Client class to access the Floss socket manager interface.""" 15 16import logging 17 18from floss.pandora.floss import floss_enums 19from floss.pandora.floss import observer_base 20from floss.pandora.floss import utils 21from gi.repository import GLib 22 23 24class SocketManagerCallbacks: 25 """Callbacks for the socket manager interface. 26 27 Implement this to observe these callbacks when exporting callbacks via register_callback. 28 """ 29 30 def on_incoming_socket_ready(self, socket, status): 31 """Called when incoming socket is ready. 32 33 Args: 34 socket: BluetoothServerSocket. 35 status: BtStatus. 36 """ 37 pass 38 39 def on_incoming_socket_closed(self, listener_id, reason): 40 """Called when incoming socket is closed. 41 42 Args: 43 listener_id: SocketId. 44 reason: BtStatus. 45 """ 46 pass 47 48 def on_handle_incoming_connection( 49 self, 50 listener_id, 51 connection, 52 *, # Keyword only after bare asterisk 53 dbus_unix_fd_list=None): 54 """Called when incoming connection is handled. 55 56 Args: 57 listener_id: SocketId. 58 connection: BluetoothSocket. 59 dbus_unix_fd_list: List of fds. Use the handle inside connection to look up the target fd. If it would be 60 kept, dup it with os.dup first. CrOS specific pydbus feature. 61 """ 62 pass 63 64 def on_outgoing_connection_result( 65 self, 66 connecting_id, 67 result, 68 socket, 69 *, # Keyword only after bare asterisk 70 dbus_unix_fd_list=None): 71 """Called when outgoing connection is handled. 72 73 Args: 74 connecting_id: SocketId. 75 result: BtStatus. 76 socket: BluetoothSocket. 77 dbus_unix_fd_list: List of fds. Use the handle inside socket to look up the target fd. If it would be kept, 78 dup it with os.dup first. CrOS specific pydbus feature. 79 """ 80 pass 81 82 83class FlossSocketManagerClient(SocketManagerCallbacks): 84 """Handles method calls and callbacks from the socket manager interface.""" 85 86 ADAPTER_SERVICE = 'org.chromium.bluetooth' 87 SOCKET_MANAGER_INTERFACE = 'org.chromium.bluetooth.SocketManager' 88 ADAPTER_OBJECT_PATTERN = '/org/chromium/bluetooth/hci{}/adapter' 89 SOCKET_CB_OBJ_NAME = 'test_socket_client' 90 CB_EXPORTED_INTF = 'org.chromium.bluetooth.SocketManagerCallback' 91 FLOSS_RESPONSE_LATENCY_SECS = 3 92 93 class ExportedSocketManagerCallbacks(observer_base.ObserverBase): 94 """ 95 <node> 96 <interface name="org.chromium.bluetooth.SocketManagerCallback"> 97 <method name="OnIncomingSocketReady"> 98 <arg type="a{sv}" name="socket" direction="in" /> 99 <arg type="u" name="status" direction="in" /> 100 </method> 101 <method name="OnIncomingSocketClosed"> 102 <arg type="t" name="listener_id" direction="in" /> 103 <arg type="u" name="reason" direction="in" /> 104 </method> 105 <method name="OnHandleIncomingConnection"> 106 <arg type="t" name="listener_id" direction="in" /> 107 <arg type="a{sv}" name="connection" direction="in" /> 108 </method> 109 <method name="OnOutgoingConnectionResult"> 110 <arg type="t" name="connecting_id" direction="in" /> 111 <arg type="u" name="result" direction="in" /> 112 <arg type="a{sv}" name="socket" direction="in" /> 113 </method> 114 </interface> 115 </node> 116 """ 117 118 def __init__(self): 119 """Constructs exported callbacks object.""" 120 observer_base.ObserverBase.__init__(self) 121 122 def OnIncomingSocketReady(self, socket, status): 123 """Handles incoming socket ready callback.""" 124 for observer in self.observers.values(): 125 observer.on_incoming_socket_ready(socket, status) 126 127 def OnIncomingSocketClosed(self, listener_id, reason): 128 """Handles incoming socket closed callback.""" 129 for observer in self.observers.values(): 130 observer.on_incoming_socket_closed(listener_id, reason) 131 132 def OnHandleIncomingConnection(self, listener_id, connection, *, dbus_unix_fd_list=None): 133 """Handles incoming socket connection callback.""" 134 for observer in self.observers.values(): 135 observer.on_handle_incoming_connection(listener_id, connection, dbus_unix_fd_list=dbus_unix_fd_list) 136 137 def OnOutgoingConnectionResult(self, connecting_id, result, socket, *, dbus_unix_fd_list=None): 138 """Handles outgoing socket connection callback.""" 139 for observer in self.observers.values(): 140 observer.on_outgoing_connection_result(connecting_id, 141 result, 142 socket, 143 dbus_unix_fd_list=dbus_unix_fd_list) 144 145 def __init__(self, bus, hci): 146 self.bus = bus 147 self.hci = hci 148 self.callbacks = None 149 self.callback_id = None 150 self.objpath = self.ADAPTER_OBJECT_PATTERN.format(hci) 151 152 def __del__(self): 153 """Destructor.""" 154 del self.callbacks 155 156 @utils.glib_callback() 157 def on_incoming_socket_ready(self, socket, status): 158 """Handles incoming socket ready callback.""" 159 logging.debug('on_incoming_socket_ready: socket: %s, status: %s', socket, status) 160 161 @utils.glib_callback() 162 def on_incoming_socket_closed(self, listener_id, reason): 163 """Handles incoming socket closed callback.""" 164 logging.debug('on_incoming_socket_closed: listener_id: %s, reason: %s', listener_id, reason) 165 166 @utils.glib_callback() 167 def on_handle_incoming_connection(self, listener_id, connection, *, dbus_unix_fd_list=None): 168 """Handles incoming socket connection callback.""" 169 logging.debug('on_handle_incoming_connection: listener_id: %s, connection: %s', listener_id, connection) 170 171 @utils.glib_callback() 172 def on_outgoing_connection_result(self, connecting_id, result, socket, *, dbus_unix_fd_list=None): 173 """Handles outgoing socket connection callback.""" 174 logging.debug('on_outgoing_connection_result: connecting_id: %s, result: %s, socket: %s', connecting_id, result, 175 socket) 176 177 def make_dbus_device(self, address, name): 178 return {'address': GLib.Variant('s', address), 'name': GLib.Variant('s', name)} 179 180 def _make_dbus_timeout(self, timeout): 181 return utils.dbus_optional_value('u', timeout) 182 183 @utils.glib_call(False) 184 def has_proxy(self): 185 """Checks whether manager proxy can be acquired.""" 186 return bool(self.proxy()) 187 188 def proxy(self): 189 """Gets proxy object to socket manager interface for method calls.""" 190 return self.bus.get(self.ADAPTER_SERVICE, self.objpath)[self.SOCKET_MANAGER_INTERFACE] 191 192 @utils.glib_call(False) 193 def register_callbacks(self): 194 """Registers socket manager callbacks if one doesn't already exist. 195 196 Returns: 197 True on success, False otherwise. 198 """ 199 # Callbacks already registered 200 if self.callbacks: 201 return True 202 203 # Create and publish callbacks 204 self.callbacks = self.ExportedSocketManagerCallbacks() 205 self.callbacks.add_observer('socket_client', self) 206 objpath = utils.generate_dbus_cb_objpath(self.SOCKET_CB_OBJ_NAME, self.hci) 207 self.bus.register_object(objpath, self.callbacks, None) 208 209 # Register published callbacks with adapter daemon 210 self.callback_id = self.proxy().RegisterCallback(objpath) 211 return True 212 213 def register_callback_observer(self, name, observer): 214 """Add an observer for all callbacks. 215 216 Args: 217 name: Name of the observer. 218 observer: Observer that implements all callback classes. 219 """ 220 if isinstance(observer, SocketManagerCallbacks): 221 self.callbacks.add_observer(name, observer) 222 223 def unregister_callback_observer(self, name, observer): 224 """Remove an observer for all callbacks. 225 226 Args: 227 name: Name of the observer. 228 observer: Observer that implements all callback classes. 229 """ 230 if isinstance(observer, SocketManagerCallbacks): 231 self.callbacks.remove_observer(name, observer) 232 233 @utils.glib_call(None) 234 def listen_using_l2cap_channel(self): 235 """Listens using L2CAP channel. 236 237 Returns: 238 SocketResult as {status:BtStatus, id:int} on success, None otherwise. 239 """ 240 return self.proxy().ListenUsingL2capChannel(self.callback_id) 241 242 @utils.glib_call(None) 243 def listen_using_insecure_l2cap_channel(self): 244 """Listens using insecure L2CAP channel. 245 246 Returns: 247 SocketResult as {status:BtStatus, id:int} on success, None otherwise. 248 """ 249 250 return self.proxy().ListenUsingInsecureL2capChannel(self.callback_id) 251 252 @utils.glib_call(None) 253 def listen_using_insecure_rfcomm_with_service_record(self, name, uuid): 254 """Listens using insecure RFCOMM channel with service record. 255 256 Args: 257 name: Service name. 258 uuid: 128-bit service UUID. 259 260 Returns: 261 SocketResult as {status:BtStatus, id:int} on success, None otherwise. 262 """ 263 return self.proxy().ListenUsingInsecureRfcommWithServiceRecord(self.callback_id, name, uuid) 264 265 @utils.glib_call(None) 266 def listen_using_rfcomm_with_service_record(self, name, uuid): 267 """Listens using RFCOMM channel with service record. 268 269 Args: 270 name: Service name. 271 uuid: 128-bit service UUID. 272 273 Returns: 274 SocketResult as {status:BtStatus, id:int} on success, None otherwise. 275 """ 276 return self.proxy().ListenUsingRfcommWithServiceRecord(self.callback_id, name, uuid) 277 278 @utils.glib_call(None) 279 def create_insecure_l2cap_channel(self, device, psm): 280 """Creates insecure L2CAP channel. 281 282 Args: 283 device: D-bus device. 284 psm: Protocol Service Multiplexor. 285 286 Returns: 287 SocketResult as {status:BtStatus, id:int} on success, None otherwise. 288 """ 289 return self.proxy().CreateInsecureL2capChannel(self.callback_id, device, psm) 290 291 @utils.glib_call(None) 292 def create_l2cap_channel(self, device, psm): 293 """Creates L2CAP channel. 294 295 Args: 296 device: D-bus device. 297 psm: Protocol Service Multiplexor. 298 299 Returns: 300 SocketResult as {status:BtStatus, id:int} on success, None otherwise. 301 """ 302 return self.proxy().CreateL2capChannel(self.callback_id, device, psm) 303 304 @utils.glib_call(None) 305 def create_insecure_rfcomm_socket_to_service_record(self, device, uuid): 306 """Creates insecure RFCOMM socket to service record. 307 308 Args: 309 device: New D-bus device. 310 uuid: 128-bit service UUID. 311 312 Returns: 313 SocketResult as {status:BtStatus, id:int} on success, None otherwise. 314 """ 315 return self.proxy().CreateInsecureRfcommSocketToServiceRecord(self.callback_id, device, uuid) 316 317 @utils.glib_call(None) 318 def create_rfcomm_socket_to_service_record(self, device, uuid): 319 """Creates RFCOMM socket to service record. 320 321 Args: 322 device: D-bus device. 323 uuid: 128-bit service UUID. 324 325 Returns: 326 SocketResult as {status:BtStatus, id:int} on success, None otherwise. 327 """ 328 return self.proxy().CreateRfcommSocketToServiceRecord(self.callback_id, device, uuid) 329 330 @utils.glib_call(None) 331 def accept(self, socket_id, timeout_ms=None): 332 """Accepts socket connection. 333 334 Args: 335 socket_id: New address of the adapter. 336 timeout_ms: Timeout in ms. 337 338 Returns: 339 BtStatus as int on success, None otherwise. 340 """ 341 timeout_ms = self._make_dbus_timeout(timeout_ms) 342 return self.proxy().Accept(self.callback_id, socket_id, timeout_ms) 343 344 @utils.glib_call(False) 345 def close(self, socket_id): 346 """Closes socket connection. 347 348 Args: 349 socket_id: Socket id to be closed. 350 351 Returns: 352 True on success, False otherwise. 353 """ 354 status = self.proxy().Close(self.callback_id, socket_id) 355 if floss_enums.BtStatus(status) != floss_enums.BtStatus.SUCCESS: 356 logging.error('Failed to close socket with id: %s, status = %s', socket_id, status) 357 return floss_enums.BtStatus(status) == floss_enums.BtStatus.SUCCESS 358