| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2012 Intel Corporation. All rights reserved. |
| * |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdbool.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| |
| #include <glib.h> |
| #include <dbus/dbus.h> |
| |
| #include "lib/bluetooth.h" |
| #include "lib/sdp.h" |
| #include "lib/sdp_lib.h" |
| #include "lib/uuid.h" |
| |
| #include "gdbus/gdbus.h" |
| |
| #include "btio/btio.h" |
| #include "sdpd.h" |
| #include "log.h" |
| #include "error.h" |
| #include "uuid-helper.h" |
| #include "dbus-common.h" |
| #include "sdp-client.h" |
| #include "sdp-xml.h" |
| #include "adapter.h" |
| #include "device.h" |
| #include "profile.h" |
| #include "service.h" |
| |
| #define DUN_DEFAULT_CHANNEL 1 |
| #define SPP_DEFAULT_CHANNEL 3 |
| #define HFP_HF_DEFAULT_CHANNEL 7 |
| #define OPP_DEFAULT_CHANNEL 9 |
| #define FTP_DEFAULT_CHANNEL 10 |
| #define BIP_DEFAULT_CHANNEL 11 |
| #define HSP_AG_DEFAULT_CHANNEL 12 |
| #define HFP_AG_DEFAULT_CHANNEL 13 |
| #define SYNC_DEFAULT_CHANNEL 14 |
| #define PBAP_DEFAULT_CHANNEL 15 |
| #define MAS_DEFAULT_CHANNEL 16 |
| #define MNS_DEFAULT_CHANNEL 17 |
| |
| #define BTD_PROFILE_PSM_AUTO -1 |
| #define BTD_PROFILE_CHAN_AUTO -1 |
| |
| #define HFP_HF_RECORD \ |
| "<?xml version=\"1.0\" encoding=\"UTF-8\" ?> \ |
| <record> \ |
| <attribute id=\"0x0001\"> \ |
| <sequence> \ |
| <uuid value=\"0x111e\" /> \ |
| <uuid value=\"0x1203\" /> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0004\"> \ |
| <sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0100\" /> \ |
| </sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0003\" /> \ |
| <uint8 value=\"0x%02x\" /> \ |
| </sequence> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0005\"> \ |
| <sequence> \ |
| <uuid value=\"0x1002\" /> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0009\"> \ |
| <sequence> \ |
| <sequence> \ |
| <uuid value=\"0x111e\" /> \ |
| <uint16 value=\"0x%04x\" /> \ |
| </sequence> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0100\"> \ |
| <text value=\"%s\" /> \ |
| </attribute> \ |
| <attribute id=\"0x0311\"> \ |
| <uint16 value=\"0x%04x\" /> \ |
| </attribute> \ |
| </record>" |
| |
| #define HFP_AG_RECORD \ |
| "<?xml version=\"1.0\" encoding=\"UTF-8\" ?> \ |
| <record> \ |
| <attribute id=\"0x0001\"> \ |
| <sequence> \ |
| <uuid value=\"0x111f\" /> \ |
| <uuid value=\"0x1203\" /> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0004\"> \ |
| <sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0100\" /> \ |
| </sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0003\" /> \ |
| <uint8 value=\"0x%02x\" /> \ |
| </sequence> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0005\"> \ |
| <sequence> \ |
| <uuid value=\"0x1002\" /> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0009\"> \ |
| <sequence> \ |
| <sequence> \ |
| <uuid value=\"0x111e\" /> \ |
| <uint16 value=\"0x%04x\" /> \ |
| </sequence> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0100\"> \ |
| <text value=\"%s\" /> \ |
| </attribute> \ |
| <attribute id=\"0x0311\"> \ |
| <uint16 value=\"0x%04x\" /> \ |
| </attribute> \ |
| <attribute id=\"0x0301\" > \ |
| <uint8 value=\"0x01\" /> \ |
| </attribute> \ |
| </record>" |
| |
| #define HSP_AG_RECORD \ |
| "<?xml version=\"1.0\" encoding=\"UTF-8\" ?> \ |
| <record> \ |
| <attribute id=\"0x0001\"> \ |
| <sequence> \ |
| <uuid value=\"0x1112\" /> \ |
| <uuid value=\"0x1203\" /> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0004\"> \ |
| <sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0100\" /> \ |
| </sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0003\" /> \ |
| <uint8 value=\"0x%02x\" /> \ |
| </sequence> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0005\"> \ |
| <sequence> \ |
| <uuid value=\"0x1002\" /> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0009\"> \ |
| <sequence> \ |
| <sequence> \ |
| <uuid value=\"0x1108\" /> \ |
| <uint16 value=\"0x%04x\" /> \ |
| </sequence> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0100\"> \ |
| <text value=\"%s\" /> \ |
| </attribute> \ |
| </record>" |
| |
| #define SPP_RECORD \ |
| "<?xml version=\"1.0\" encoding=\"UTF-8\" ?> \ |
| <record> \ |
| <attribute id=\"0x0001\"> \ |
| <sequence> \ |
| <uuid value=\"0x1101\" /> \ |
| %s \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0004\"> \ |
| <sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0100\" /> \ |
| </sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0003\" /> \ |
| <uint8 value=\"0x%02x\" /> \ |
| </sequence> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0005\"> \ |
| <sequence> \ |
| <uuid value=\"0x1002\" /> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0009\"> \ |
| <sequence> \ |
| <sequence> \ |
| <uuid value=\"0x1101\" /> \ |
| <uint16 value=\"0x%04x\" /> \ |
| </sequence> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0100\"> \ |
| <text value=\"%s\" /> \ |
| </attribute> \ |
| </record>" |
| |
| #define DUN_RECORD \ |
| "<?xml version=\"1.0\" encoding=\"UTF-8\" ?> \ |
| <record> \ |
| <attribute id=\"0x0001\"> \ |
| <sequence> \ |
| <uuid value=\"0x1103\" /> \ |
| <uuid value=\"0x1201\" /> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0004\"> \ |
| <sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0100\" /> \ |
| </sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0003\" /> \ |
| <uint8 value=\"0x%02x\" /> \ |
| </sequence> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0005\"> \ |
| <sequence> \ |
| <uuid value=\"0x1002\" /> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0009\"> \ |
| <sequence> \ |
| <sequence> \ |
| <uuid value=\"0x1103\" /> \ |
| <uint16 value=\"0x%04x\" /> \ |
| </sequence> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0100\"> \ |
| <text value=\"%s\" /> \ |
| </attribute> \ |
| </record>" |
| |
| #define OPP_RECORD \ |
| "<?xml version=\"1.0\" encoding=\"UTF-8\" ?> \ |
| <record> \ |
| <attribute id=\"0x0001\"> \ |
| <sequence> \ |
| <uuid value=\"0x1105\" /> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0004\"> \ |
| <sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0100\" /> \ |
| </sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0003\" /> \ |
| <uint8 value=\"0x%02x\" /> \ |
| </sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0008\"/> \ |
| </sequence> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0005\"> \ |
| <sequence> \ |
| <uuid value=\"0x1002\" /> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0009\"> \ |
| <sequence> \ |
| <sequence> \ |
| <uuid value=\"0x1105\" /> \ |
| <uint16 value=\"0x%04x\" /> \ |
| </sequence> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0303\"> \ |
| <sequence> \ |
| <uint8 value=\"0x01\"/> \ |
| <uint8 value=\"0x02\"/> \ |
| <uint8 value=\"0x03\"/> \ |
| <uint8 value=\"0x04\"/> \ |
| <uint8 value=\"0x05\"/> \ |
| <uint8 value=\"0x06\"/> \ |
| <uint8 value=\"0xff\"/> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0200\"> \ |
| <uint16 value=\"%u\" name=\"psm\"/> \ |
| </attribute> \ |
| <attribute id=\"0x0100\"> \ |
| <text value=\"%s\" /> \ |
| </attribute> \ |
| </record>" |
| |
| #define FTP_RECORD \ |
| "<?xml version=\"1.0\" encoding=\"UTF-8\" ?> \ |
| <record> \ |
| <attribute id=\"0x0001\"> \ |
| <sequence> \ |
| <uuid value=\"0x1106\" /> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0004\"> \ |
| <sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0100\" /> \ |
| </sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0003\" /> \ |
| <uint8 value=\"0x%02x\" /> \ |
| </sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0008\"/> \ |
| </sequence> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0005\"> \ |
| <sequence> \ |
| <uuid value=\"0x1002\" /> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0009\"> \ |
| <sequence> \ |
| <sequence> \ |
| <uuid value=\"0x1106\" /> \ |
| <uint16 value=\"0x%04x\" /> \ |
| </sequence> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0200\"> \ |
| <uint16 value=\"%u\" name=\"psm\"/> \ |
| </attribute> \ |
| <attribute id=\"0x0100\"> \ |
| <text value=\"%s\" /> \ |
| </attribute> \ |
| </record>" |
| |
| #define PCE_RECORD \ |
| "<?xml version=\"1.0\" encoding=\"UTF-8\" ?> \ |
| <record> \ |
| <attribute id=\"0x0001\"> \ |
| <sequence> \ |
| <uuid value=\"0x112e\" /> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0005\"> \ |
| <sequence> \ |
| <uuid value=\"0x1002\" /> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0009\"> \ |
| <sequence> \ |
| <sequence> \ |
| <uuid value=\"0x1130\" /> \ |
| <uint16 value=\"0x%04x\" /> \ |
| </sequence> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0100\"> \ |
| <text value=\"%s\" /> \ |
| </attribute> \ |
| </record>" |
| |
| #define PSE_RECORD \ |
| "<?xml version=\"1.0\" encoding=\"UTF-8\" ?> \ |
| <record> \ |
| <attribute id=\"0x0001\"> \ |
| <sequence> \ |
| <uuid value=\"0x112f\" /> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0004\"> \ |
| <sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0100\" /> \ |
| </sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0003\" /> \ |
| <uint8 value=\"0x%02x\" /> \ |
| </sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0008\"/> \ |
| </sequence> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0005\"> \ |
| <sequence> \ |
| <uuid value=\"0x1002\" /> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0009\"> \ |
| <sequence> \ |
| <sequence> \ |
| <uuid value=\"0x1130\" /> \ |
| <uint16 value=\"0x%04x\" /> \ |
| </sequence> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0100\"> \ |
| <text value=\"%s\" /> \ |
| </attribute> \ |
| <attribute id=\"0x0314\"> \ |
| <uint8 value=\"0x01\"/> \ |
| </attribute> \ |
| <attribute id=\"0x0317\"> \ |
| <uint32 value=\"0x00000003\"/> \ |
| </attribute> \ |
| <attribute id=\"0x0200\"> \ |
| <uint16 value=\"%u\" name=\"psm\"/> \ |
| </attribute> \ |
| </record>" |
| |
| #define MAS_RECORD \ |
| "<?xml version=\"1.0\" encoding=\"UTF-8\" ?> \ |
| <record> \ |
| <attribute id=\"0x0001\"> \ |
| <sequence> \ |
| <uuid value=\"0x1132\"/> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0004\"> \ |
| <sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0100\"/> \ |
| </sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0003\"/> \ |
| <uint8 value=\"0x%02x\"/> \ |
| </sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0008\"/> \ |
| </sequence> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0005\"> \ |
| <sequence> \ |
| <uuid value=\"0x1002\" /> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0009\"> \ |
| <sequence> \ |
| <sequence> \ |
| <uuid value=\"0x1134\"/> \ |
| <uint16 value=\"0x%04x\" /> \ |
| </sequence> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0100\"> \ |
| <text value=\"%s\"/> \ |
| </attribute> \ |
| <attribute id=\"0x0315\"> \ |
| <uint8 value=\"0x00\"/> \ |
| </attribute> \ |
| <attribute id=\"0x0316\"> \ |
| <uint8 value=\"0x0F\"/> \ |
| </attribute> \ |
| <attribute id=\"0x0317\"> \ |
| <uint32 value=\"0x0000007f\"/> \ |
| </attribute> \ |
| <attribute id=\"0x0200\"> \ |
| <uint16 value=\"%u\" name=\"psm\"/> \ |
| </attribute> \ |
| </record>" |
| |
| #define MNS_RECORD \ |
| "<?xml version=\"1.0\" encoding=\"UTF-8\" ?> \ |
| <record> \ |
| <attribute id=\"0x0001\"> \ |
| <sequence> \ |
| <uuid value=\"0x1133\"/> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0004\"> \ |
| <sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0100\"/> \ |
| </sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0003\"/> \ |
| <uint8 value=\"0x%02x\"/> \ |
| </sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0008\"/> \ |
| </sequence> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0005\"> \ |
| <sequence> \ |
| <uuid value=\"0x1002\" /> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0009\"> \ |
| <sequence> \ |
| <sequence> \ |
| <uuid value=\"0x1134\"/> \ |
| <uint16 value=\"0x%04x\"/> \ |
| </sequence> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0100\"> \ |
| <text value=\"%s\"/> \ |
| </attribute> \ |
| <attribute id=\"0x0317\"> \ |
| <uint32 value=\"0x0000007f\"/> \ |
| </attribute> \ |
| <attribute id=\"0x0200\"> \ |
| <uint16 value=\"%u\" name=\"psm\"/> \ |
| </attribute> \ |
| </record>" |
| |
| #define SYNC_RECORD \ |
| "<?xml version=\"1.0\" encoding=\"UTF-8\" ?> \ |
| <record> \ |
| <attribute id=\"0x0001\"> \ |
| <sequence> \ |
| <uuid value=\"0x1104\"/> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0004\"> \ |
| <sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0100\"/> \ |
| </sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0003\"/> \ |
| <uint8 value=\"0x%02x\"/> \ |
| </sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0008\"/> \ |
| </sequence> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0005\"> \ |
| <sequence> \ |
| <uuid value=\"0x1002\" /> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0009\"> \ |
| <sequence> \ |
| <sequence> \ |
| <uuid value=\"0x1104\"/> \ |
| <uint16 value=\"0x%04x\" /> \ |
| </sequence> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0100\"> \ |
| <text value=\"%s\"/> \ |
| </attribute> \ |
| <attribute id=\"0x0301\"> \ |
| <sequence> \ |
| <uint8 value=\"0x01\"/> \ |
| </sequence> \ |
| </attribute> \ |
| </record>" |
| |
| #define GENERIC_RECORD \ |
| "<?xml version=\"1.0\" encoding=\"UTF-8\" ?> \ |
| <record> \ |
| <attribute id=\"0x0001\"> \ |
| <sequence> \ |
| <uuid value=\"%s\" /> \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0004\"> \ |
| <sequence> \ |
| <sequence> \ |
| <uuid value=\"0x0100\" /> \ |
| %s \ |
| </sequence> \ |
| %s \ |
| </sequence> \ |
| </attribute> \ |
| <attribute id=\"0x0005\"> \ |
| <sequence> \ |
| <uuid value=\"0x1002\" /> \ |
| </sequence> \ |
| </attribute> \ |
| %s \ |
| <attribute id=\"0x0100\"> \ |
| <text value=\"%s\" /> \ |
| </attribute> \ |
| </record>" |
| |
| struct ext_io; |
| |
| struct ext_profile { |
| struct btd_profile p; |
| |
| char *name; |
| char *owner; |
| char *path; |
| char *uuid; |
| char *service; |
| char *role; |
| |
| char *record; |
| char *(*get_record)(struct ext_profile *ext, struct ext_io *l2cap, |
| struct ext_io *rfcomm); |
| |
| char *remote_uuid; |
| |
| guint id; |
| |
| BtIOMode mode; |
| BtIOSecLevel sec_level; |
| bool authorize; |
| |
| bool enable_client; |
| bool enable_server; |
| |
| int local_psm; |
| int local_chan; |
| |
| uint16_t remote_psm; |
| uint8_t remote_chan; |
| |
| uint16_t version; |
| uint16_t features; |
| |
| GSList *records; |
| GSList *servers; |
| GSList *conns; |
| |
| GSList *connects; |
| }; |
| |
| struct ext_io { |
| struct ext_profile *ext; |
| int proto; |
| GIOChannel *io; |
| guint io_id; |
| struct btd_adapter *adapter; |
| struct btd_device *device; |
| struct btd_service *service; |
| |
| bool resolving; |
| bool connected; |
| |
| uint16_t version; |
| uint16_t features; |
| |
| uint16_t psm; |
| uint8_t chan; |
| |
| guint auth_id; |
| DBusPendingCall *pending; |
| }; |
| |
| struct ext_record { |
| struct btd_adapter *adapter; |
| uint32_t handle; |
| }; |
| |
| struct btd_profile_custom_property { |
| char *uuid; |
| char *type; |
| char *name; |
| btd_profile_prop_exists exists; |
| btd_profile_prop_get get; |
| void *user_data; |
| }; |
| |
| static GSList *custom_props = NULL; |
| |
| static GSList *profiles = NULL; |
| static GSList *ext_profiles = NULL; |
| |
| void btd_profile_foreach(void (*func)(struct btd_profile *p, void *data), |
| void *data) |
| { |
| GSList *l, *next; |
| |
| for (l = profiles; l != NULL; l = next) { |
| struct btd_profile *profile = l->data; |
| |
| next = g_slist_next(l); |
| |
| func(profile, data); |
| } |
| |
| for (l = ext_profiles; l != NULL; l = next) { |
| struct ext_profile *profile = l->data; |
| |
| next = g_slist_next(l); |
| |
| func(&profile->p, data); |
| } |
| } |
| |
| int btd_profile_register(struct btd_profile *profile) |
| { |
| profiles = g_slist_append(profiles, profile); |
| return 0; |
| } |
| |
| void btd_profile_unregister(struct btd_profile *profile) |
| { |
| profiles = g_slist_remove(profiles, profile); |
| } |
| |
| static struct ext_profile *find_ext_profile(const char *owner, |
| const char *path) |
| { |
| GSList *l; |
| |
| for (l = ext_profiles; l != NULL; l = g_slist_next(l)) { |
| struct ext_profile *ext = l->data; |
| |
| if (g_strcmp0(ext->owner, owner)) |
| continue; |
| |
| if (!g_strcmp0(ext->path, path)) |
| return ext; |
| } |
| |
| return NULL; |
| } |
| |
| static void ext_io_destroy(gpointer p) |
| { |
| struct ext_io *ext_io = p; |
| |
| if (ext_io->io_id > 0) |
| g_source_remove(ext_io->io_id); |
| |
| if (ext_io->io) { |
| g_io_channel_shutdown(ext_io->io, FALSE, NULL); |
| g_io_channel_unref(ext_io->io); |
| } |
| |
| if (ext_io->auth_id != 0) |
| btd_cancel_authorization(ext_io->auth_id); |
| |
| if (ext_io->pending) { |
| dbus_pending_call_cancel(ext_io->pending); |
| dbus_pending_call_unref(ext_io->pending); |
| } |
| |
| if (ext_io->resolving) |
| bt_cancel_discovery(btd_adapter_get_address(ext_io->adapter), |
| device_get_address(ext_io->device)); |
| |
| if (ext_io->adapter) |
| btd_adapter_unref(ext_io->adapter); |
| |
| if (ext_io->device) |
| btd_device_unref(ext_io->device); |
| |
| if (ext_io->service) |
| btd_service_unref(ext_io->service); |
| |
| g_free(ext_io); |
| } |
| |
| static gboolean ext_io_disconnected(GIOChannel *io, GIOCondition cond, |
| gpointer user_data) |
| { |
| struct ext_io *conn = user_data; |
| struct ext_profile *ext = conn->ext; |
| GError *gerr = NULL; |
| char addr[18]; |
| |
| if (cond & G_IO_NVAL) |
| return FALSE; |
| |
| bt_io_get(io, &gerr, BT_IO_OPT_DEST, addr, BT_IO_OPT_INVALID); |
| if (gerr != NULL) { |
| error("Unable to get io data for %s: %s", |
| ext->name, gerr->message); |
| g_error_free(gerr); |
| goto drop; |
| } |
| |
| DBG("%s disconnected from %s", ext->name, addr); |
| drop: |
| if (conn->service) { |
| if (btd_service_get_state(conn->service) == |
| BTD_SERVICE_STATE_CONNECTING) |
| btd_service_connecting_complete(conn->service, -EIO); |
| else |
| btd_service_disconnecting_complete(conn->service, 0); |
| } |
| |
| ext->conns = g_slist_remove(ext->conns, conn); |
| ext_io_destroy(conn); |
| return FALSE; |
| } |
| |
| static void new_conn_reply(DBusPendingCall *call, void *user_data) |
| { |
| struct ext_io *conn = user_data; |
| struct ext_profile *ext = conn->ext; |
| DBusMessage *reply = dbus_pending_call_steal_reply(call); |
| DBusError err; |
| |
| dbus_error_init(&err); |
| dbus_set_error_from_message(&err, reply); |
| |
| dbus_message_unref(reply); |
| |
| dbus_pending_call_unref(conn->pending); |
| conn->pending = NULL; |
| |
| if (!dbus_error_is_set(&err)) { |
| if (conn->service) |
| btd_service_connecting_complete(conn->service, 0); |
| |
| conn->connected = true; |
| return; |
| } |
| |
| error("%s replied with an error: %s, %s", ext->name, |
| err.name, err.message); |
| |
| if (conn->service) |
| btd_service_connecting_complete(conn->service, -ECONNREFUSED); |
| |
| dbus_error_free(&err); |
| |
| ext->conns = g_slist_remove(ext->conns, conn); |
| ext_io_destroy(conn); |
| } |
| |
| static void disconn_reply(DBusPendingCall *call, void *user_data) |
| { |
| struct ext_io *conn = user_data; |
| struct ext_profile *ext = conn->ext; |
| DBusMessage *reply = dbus_pending_call_steal_reply(call); |
| DBusError err; |
| |
| dbus_error_init(&err); |
| dbus_set_error_from_message(&err, reply); |
| |
| dbus_message_unref(reply); |
| |
| dbus_pending_call_unref(conn->pending); |
| conn->pending = NULL; |
| |
| if (!dbus_error_is_set(&err)) { |
| if (conn->service) |
| btd_service_disconnecting_complete(conn->service, 0); |
| |
| goto disconnect; |
| } |
| |
| error("%s replied with an error: %s, %s", ext->name, |
| err.name, err.message); |
| |
| if (conn->service) |
| btd_service_disconnecting_complete(conn->service, |
| -ECONNREFUSED); |
| |
| dbus_error_free(&err); |
| |
| disconnect: |
| ext->conns = g_slist_remove(ext->conns, conn); |
| ext_io_destroy(conn); |
| } |
| |
| struct prop_append_data { |
| DBusMessageIter *dict; |
| struct ext_io *io; |
| }; |
| |
| static void append_prop(gpointer a, gpointer b) |
| { |
| struct btd_profile_custom_property *p = a; |
| struct prop_append_data *data = b; |
| DBusMessageIter entry, value, *dict = data->dict; |
| struct btd_device *dev = data->io->device; |
| struct ext_profile *ext = data->io->ext; |
| const char *uuid = ext->service ? ext->service : ext->uuid; |
| |
| if (strcasecmp(p->uuid, uuid) != 0) |
| return; |
| |
| if (p->exists && !p->exists(p->uuid, dev, p->user_data)) |
| return; |
| |
| dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, NULL, |
| &entry); |
| dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &p->name); |
| dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, p->type, |
| &value); |
| |
| p->get(p->uuid, dev, &value, p->user_data); |
| |
| dbus_message_iter_close_container(&entry, &value); |
| dbus_message_iter_close_container(dict, &entry); |
| } |
| |
| static uint16_t get_supported_features(const sdp_record_t *rec) |
| { |
| sdp_data_t *data; |
| |
| data = sdp_data_get(rec, SDP_ATTR_SUPPORTED_FEATURES); |
| if (!data || data->dtd != SDP_UINT16) |
| return 0; |
| |
| return data->val.uint16; |
| } |
| |
| static uint16_t get_profile_version(const sdp_record_t *rec) |
| { |
| sdp_list_t *descs; |
| uint16_t version; |
| |
| if (sdp_get_profile_descs(rec, &descs) < 0) |
| return 0; |
| |
| if (descs && descs->data) { |
| sdp_profile_desc_t *desc = descs->data; |
| version = desc->version; |
| } else { |
| version = 0; |
| } |
| |
| sdp_list_free(descs, free); |
| |
| return version; |
| } |
| |
| static bool send_new_connection(struct ext_profile *ext, struct ext_io *conn) |
| { |
| DBusMessage *msg; |
| DBusMessageIter iter, dict; |
| struct prop_append_data data = { &dict, conn }; |
| const char *remote_uuid = ext->remote_uuid; |
| const sdp_record_t *rec; |
| const char *path; |
| int fd; |
| |
| msg = dbus_message_new_method_call(ext->owner, ext->path, |
| "org.bluez.Profile1", |
| "NewConnection"); |
| if (!msg) { |
| error("Unable to create NewConnection call for %s", ext->name); |
| return false; |
| } |
| |
| if (remote_uuid) { |
| rec = btd_device_get_record(conn->device, remote_uuid); |
| if (rec) { |
| conn->features = get_supported_features(rec); |
| conn->version = get_profile_version(rec); |
| } |
| } |
| |
| dbus_message_iter_init_append(msg, &iter); |
| |
| path = device_get_path(conn->device); |
| dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path); |
| |
| fd = g_io_channel_unix_get_fd(conn->io); |
| dbus_message_iter_append_basic(&iter, DBUS_TYPE_UNIX_FD, &fd); |
| |
| dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &dict); |
| |
| if (conn->version) |
| dict_append_entry(&dict, "Version", DBUS_TYPE_UINT16, |
| &conn->version); |
| |
| if (conn->features) |
| dict_append_entry(&dict, "Features", DBUS_TYPE_UINT16, |
| &conn->features); |
| |
| g_slist_foreach(custom_props, append_prop, &data); |
| |
| dbus_message_iter_close_container(&iter, &dict); |
| |
| if (!g_dbus_send_message_with_reply(btd_get_dbus_connection(), |
| msg, &conn->pending, -1)) { |
| error("%s: sending NewConnection failed", ext->name); |
| dbus_message_unref(msg); |
| return false; |
| } |
| |
| dbus_message_unref(msg); |
| |
| dbus_pending_call_set_notify(conn->pending, new_conn_reply, conn, NULL); |
| |
| return true; |
| } |
| |
| static void ext_connect(GIOChannel *io, GError *err, gpointer user_data) |
| { |
| struct ext_io *conn = user_data; |
| struct ext_profile *ext = conn->ext; |
| GError *io_err = NULL; |
| char addr[18]; |
| |
| if (!bt_io_get(io, &io_err, |
| BT_IO_OPT_DEST, addr, |
| BT_IO_OPT_INVALID)) { |
| error("Unable to get connect data for %s: %s", ext->name, |
| io_err->message); |
| if (err) { |
| g_error_free(io_err); |
| io_err = NULL; |
| } else { |
| err = io_err; |
| } |
| goto drop; |
| } |
| |
| if (err != NULL) { |
| error("%s failed to connect to %s: %s", ext->name, addr, |
| err->message); |
| goto drop; |
| } |
| |
| DBG("%s connected to %s", ext->name, addr); |
| |
| if (conn->io_id == 0) { |
| GIOCondition cond = G_IO_HUP | G_IO_ERR | G_IO_NVAL; |
| conn->io_id = g_io_add_watch(io, cond, ext_io_disconnected, |
| conn); |
| } |
| |
| if (conn->service && service_set_connecting(conn->service) < 0) |
| goto drop; |
| |
| if (send_new_connection(ext, conn)) |
| return; |
| |
| drop: |
| if (conn->service) |
| btd_service_connecting_complete(conn->service, |
| err ? -err->code : -EIO); |
| |
| if (io_err) |
| g_error_free(io_err); |
| |
| ext->conns = g_slist_remove(ext->conns, conn); |
| ext_io_destroy(conn); |
| } |
| |
| static void ext_auth(DBusError *err, void *user_data) |
| { |
| struct ext_io *conn = user_data; |
| struct ext_profile *ext = conn->ext; |
| GError *gerr = NULL; |
| char addr[18]; |
| |
| conn->auth_id = 0; |
| |
| bt_io_get(conn->io, &gerr, BT_IO_OPT_DEST, addr, BT_IO_OPT_INVALID); |
| if (gerr != NULL) { |
| error("Unable to get connect data for %s: %s", |
| ext->name, gerr->message); |
| g_error_free(gerr); |
| goto drop; |
| } |
| |
| if (err && dbus_error_is_set(err)) { |
| error("%s rejected %s: %s", ext->name, addr, err->message); |
| goto drop; |
| } |
| |
| if (!bt_io_accept(conn->io, ext_connect, conn, NULL, &gerr)) { |
| error("bt_io_accept: %s", gerr->message); |
| g_error_free(gerr); |
| goto drop; |
| } |
| |
| DBG("%s authorized to connect to %s", addr, ext->name); |
| |
| return; |
| |
| drop: |
| ext->conns = g_slist_remove(ext->conns, conn); |
| ext_io_destroy(conn); |
| } |
| |
| static struct ext_io *create_conn(struct ext_io *server, GIOChannel *io, |
| bdaddr_t *src, bdaddr_t *dst) |
| { |
| struct btd_device *device; |
| struct btd_service *service; |
| struct ext_io *conn; |
| GIOCondition cond; |
| char addr[18]; |
| |
| device = btd_adapter_find_device(server->adapter, dst, BDADDR_BREDR); |
| if (device == NULL) { |
| ba2str(dst, addr); |
| error("%s device %s not found", server->ext->name, addr); |
| return NULL; |
| } |
| |
| /* Do not add UUID if client role is not enabled */ |
| if (!server->ext->enable_client) { |
| service = NULL; |
| goto done; |
| } |
| |
| btd_device_add_uuid(device, server->ext->remote_uuid); |
| service = btd_device_get_service(device, server->ext->remote_uuid); |
| if (service == NULL) { |
| ba2str(dst, addr); |
| error("%s service not found for device %s", server->ext->name, |
| addr); |
| return NULL; |
| } |
| |
| done: |
| conn = g_new0(struct ext_io, 1); |
| conn->io = g_io_channel_ref(io); |
| conn->proto = server->proto; |
| conn->ext = server->ext; |
| conn->adapter = btd_adapter_ref(server->adapter); |
| conn->device = btd_device_ref(device); |
| |
| if (service) |
| conn->service = btd_service_ref(service); |
| |
| cond = G_IO_HUP | G_IO_ERR | G_IO_NVAL; |
| conn->io_id = g_io_add_watch(io, cond, ext_io_disconnected, conn); |
| |
| return conn; |
| } |
| |
| static void ext_confirm(GIOChannel *io, gpointer user_data) |
| { |
| struct ext_io *server = user_data; |
| struct ext_profile *ext = server->ext; |
| const char *uuid = ext->service ? ext->service : ext->uuid; |
| struct ext_io *conn; |
| GError *gerr = NULL; |
| bdaddr_t src, dst; |
| char addr[18]; |
| |
| bt_io_get(io, &gerr, |
| BT_IO_OPT_SOURCE_BDADDR, &src, |
| BT_IO_OPT_DEST_BDADDR, &dst, |
| BT_IO_OPT_DEST, addr, |
| BT_IO_OPT_INVALID); |
| if (gerr != NULL) { |
| error("%s failed to get connect data: %s", ext->name, |
| gerr->message); |
| g_error_free(gerr); |
| return; |
| } |
| |
| DBG("incoming connect from %s", addr); |
| |
| conn = create_conn(server, io, &src, &dst); |
| if (conn == NULL) |
| return; |
| |
| conn->auth_id = btd_request_authorization(&src, &dst, uuid, ext_auth, |
| conn); |
| if (conn->auth_id == 0) { |
| error("%s authorization failure", ext->name); |
| ext_io_destroy(conn); |
| return; |
| } |
| |
| ext->conns = g_slist_append(ext->conns, conn); |
| |
| DBG("%s authorizing connection from %s", ext->name, addr); |
| } |
| |
| static void ext_direct_connect(GIOChannel *io, GError *err, gpointer user_data) |
| { |
| struct ext_io *server = user_data; |
| struct ext_profile *ext = server->ext; |
| GError *gerr = NULL; |
| struct ext_io *conn; |
| bdaddr_t src, dst; |
| |
| bt_io_get(io, &gerr, |
| BT_IO_OPT_SOURCE_BDADDR, &src, |
| BT_IO_OPT_DEST_BDADDR, &dst, |
| BT_IO_OPT_INVALID); |
| if (gerr != NULL) { |
| error("%s failed to get connect data: %s", ext->name, |
| gerr->message); |
| g_error_free(gerr); |
| return; |
| } |
| |
| conn = create_conn(server, io, &src, &dst); |
| if (conn == NULL) |
| return; |
| |
| ext->conns = g_slist_append(ext->conns, conn); |
| |
| ext_connect(io, err, conn); |
| } |
| |
| static uint32_t ext_register_record(struct ext_profile *ext, |
| struct ext_io *l2cap, |
| struct ext_io *rfcomm, |
| struct btd_adapter *a) |
| { |
| sdp_record_t *rec; |
| char *dyn_record = NULL; |
| const char *record = ext->record; |
| |
| if (!record && ext->get_record) { |
| dyn_record = ext->get_record(ext, l2cap, rfcomm); |
| record = dyn_record; |
| } |
| |
| if (!record) |
| return 0; |
| |
| rec = sdp_xml_parse_record(record, strlen(record)); |
| |
| g_free(dyn_record); |
| |
| if (!rec) { |
| error("Unable to parse record for %s", ext->name); |
| return 0; |
| } |
| |
| if (adapter_service_add(a, rec) < 0) { |
| error("Failed to register service record"); |
| sdp_record_free(rec); |
| return 0; |
| } |
| |
| return rec->handle; |
| } |
| |
| static uint32_t ext_start_servers(struct ext_profile *ext, |
| struct btd_adapter *adapter) |
| { |
| struct ext_io *l2cap = NULL; |
| struct ext_io *rfcomm = NULL; |
| BtIOConfirm confirm; |
| BtIOConnect connect; |
| GError *err = NULL; |
| GIOChannel *io; |
| |
| if (ext->authorize) { |
| confirm = ext_confirm; |
| connect = NULL; |
| } else { |
| confirm = NULL; |
| connect = ext_direct_connect; |
| } |
| |
| if (ext->local_psm) { |
| uint16_t psm; |
| |
| if (ext->local_psm > 0) |
| psm = ext->local_psm; |
| else |
| psm = 0; |
| |
| l2cap = g_new0(struct ext_io, 1); |
| l2cap->ext = ext; |
| |
| io = bt_io_listen(connect, confirm, l2cap, NULL, &err, |
| BT_IO_OPT_SOURCE_BDADDR, |
| btd_adapter_get_address(adapter), |
| BT_IO_OPT_MODE, ext->mode, |
| BT_IO_OPT_PSM, psm, |
| BT_IO_OPT_SEC_LEVEL, ext->sec_level, |
| BT_IO_OPT_INVALID); |
| if (err != NULL) { |
| error("L2CAP server failed for %s: %s", |
| ext->name, err->message); |
| g_free(l2cap); |
| l2cap = NULL; |
| g_clear_error(&err); |
| goto failed; |
| } else { |
| if (psm == 0) |
| bt_io_get(io, NULL, BT_IO_OPT_PSM, &psm, |
| BT_IO_OPT_INVALID); |
| l2cap->io = io; |
| l2cap->proto = BTPROTO_L2CAP; |
| l2cap->psm = psm; |
| l2cap->adapter = btd_adapter_ref(adapter); |
| ext->servers = g_slist_append(ext->servers, l2cap); |
| DBG("%s listening on PSM %u", ext->name, psm); |
| } |
| } |
| |
| if (ext->local_chan) { |
| uint8_t chan; |
| |
| if (ext->local_chan > 0) |
| chan = ext->local_chan; |
| else |
| chan = 0; |
| |
| rfcomm = g_new0(struct ext_io, 1); |
| rfcomm->ext = ext; |
| |
| io = bt_io_listen(connect, confirm, rfcomm, NULL, &err, |
| BT_IO_OPT_SOURCE_BDADDR, |
| btd_adapter_get_address(adapter), |
| BT_IO_OPT_CHANNEL, chan, |
| BT_IO_OPT_SEC_LEVEL, ext->sec_level, |
| BT_IO_OPT_INVALID); |
| if (err != NULL) { |
| error("RFCOMM server failed for %s: %s", |
| ext->name, err->message); |
| g_free(rfcomm); |
| g_clear_error(&err); |
| goto failed; |
| } else { |
| if (chan == 0) |
| bt_io_get(io, NULL, BT_IO_OPT_CHANNEL, &chan, |
| BT_IO_OPT_INVALID); |
| rfcomm->io = io; |
| rfcomm->proto = BTPROTO_RFCOMM; |
| rfcomm->chan = chan; |
| rfcomm->adapter = btd_adapter_ref(adapter); |
| ext->servers = g_slist_append(ext->servers, rfcomm); |
| DBG("%s listening on chan %u", ext->name, chan); |
| } |
| } |
| |
| return ext_register_record(ext, l2cap, rfcomm, adapter); |
| |
| failed: |
| if (l2cap) { |
| ext->servers = g_slist_remove(ext->servers, l2cap); |
| ext_io_destroy(l2cap); |
| } |
| |
| return 0; |
| } |
| |
| static struct ext_profile *find_ext(struct btd_profile *p) |
| { |
| GSList *l; |
| |
| l = g_slist_find(ext_profiles, p); |
| if (!l) |
| return NULL; |
| |
| return l->data; |
| } |
| |
| static int ext_adapter_probe(struct btd_profile *p, |
| struct btd_adapter *adapter) |
| { |
| struct ext_profile *ext; |
| struct ext_record *rec; |
| uint32_t handle; |
| |
| ext = find_ext(p); |
| if (!ext) |
| return -ENOENT; |
| |
| DBG("\"%s\" probed", ext->name); |
| |
| handle = ext_start_servers(ext, adapter); |
| if (!handle) |
| return 0; |
| |
| rec = g_new0(struct ext_record, 1); |
| rec->adapter = btd_adapter_ref(adapter); |
| rec->handle = handle; |
| |
| ext->records = g_slist_append(ext->records, rec); |
| |
| return 0; |
| } |
| |
| static void ext_remove_records(struct ext_profile *ext, |
| struct btd_adapter *adapter) |
| { |
| GSList *l, *next; |
| |
| for (l = ext->records; l != NULL; l = next) { |
| struct ext_record *r = l->data; |
| |
| next = g_slist_next(l); |
| |
| if (adapter && r->adapter != adapter) |
| continue; |
| |
| ext->records = g_slist_remove(ext->records, r); |
| |
| adapter_service_remove(adapter, r->handle); |
| btd_adapter_unref(r->adapter); |
| g_free(r); |
| } |
| } |
| |
| static void ext_adapter_remove(struct btd_profile *p, |
| struct btd_adapter *adapter) |
| { |
| struct ext_profile *ext; |
| GSList *l, *next; |
| |
| ext = find_ext(p); |
| if (!ext) |
| return; |
| |
| DBG("\"%s\" removed", ext->name); |
| |
| ext_remove_records(ext, adapter); |
| |
| for (l = ext->servers; l != NULL; l = next) { |
| struct ext_io *server = l->data; |
| |
| next = g_slist_next(l); |
| |
| if (server->adapter != adapter) |
| continue; |
| |
| ext->servers = g_slist_remove(ext->servers, server); |
| ext_io_destroy(server); |
| } |
| } |
| |
| static int ext_device_probe(struct btd_service *service) |
| { |
| struct btd_profile *p = btd_service_get_profile(service); |
| struct ext_profile *ext; |
| |
| ext = find_ext(p); |
| if (!ext) |
| return -ENOENT; |
| |
| DBG("%s probed with UUID %s", ext->name, p->remote_uuid); |
| |
| return 0; |
| } |
| |
| static struct ext_io *find_connection(struct ext_profile *ext, |
| struct btd_device *dev) |
| { |
| GSList *l; |
| |
| for (l = ext->conns; l != NULL; l = g_slist_next(l)) { |
| struct ext_io *conn = l->data; |
| |
| if (conn->device == dev) |
| return conn; |
| } |
| |
| return NULL; |
| } |
| |
| static void ext_device_remove(struct btd_service *service) |
| { |
| struct btd_profile *p = btd_service_get_profile(service); |
| struct btd_device *dev = btd_service_get_device(service); |
| struct ext_profile *ext; |
| struct ext_io *conn; |
| |
| ext = find_ext(p); |
| if (!ext) |
| return; |
| |
| DBG("%s", ext->name); |
| |
| conn = find_connection(ext, dev); |
| if (conn) { |
| ext->conns = g_slist_remove(ext->conns, conn); |
| ext_io_destroy(conn); |
| } |
| } |
| |
| static int connect_io(struct ext_io *conn, const bdaddr_t *src, |
| const bdaddr_t *dst) |
| { |
| struct ext_profile *ext = conn->ext; |
| GError *gerr = NULL; |
| GIOChannel *io; |
| |
| if (conn->psm) { |
| conn->proto = BTPROTO_L2CAP; |
| io = bt_io_connect(ext_connect, conn, NULL, &gerr, |
| BT_IO_OPT_SOURCE_BDADDR, src, |
| BT_IO_OPT_DEST_BDADDR, dst, |
| BT_IO_OPT_SEC_LEVEL, ext->sec_level, |
| BT_IO_OPT_PSM, conn->psm, |
| BT_IO_OPT_INVALID); |
| } else { |
| conn->proto = BTPROTO_RFCOMM; |
| io = bt_io_connect(ext_connect, conn, NULL, &gerr, |
| BT_IO_OPT_SOURCE_BDADDR, src, |
| BT_IO_OPT_DEST_BDADDR, dst, |
| BT_IO_OPT_SEC_LEVEL, ext->sec_level, |
| BT_IO_OPT_CHANNEL, conn->chan, |
| BT_IO_OPT_INVALID); |
| } |
| |
| if (gerr != NULL) { |
| error("Unable to connect %s: %s", ext->name, gerr->message); |
| g_error_free(gerr); |
| return -EIO; |
| } |
| |
| conn->io = io; |
| |
| return 0; |
| } |
| |
| static uint16_t get_goep_l2cap_psm(sdp_record_t *rec) |
| { |
| sdp_data_t *data; |
| |
| data = sdp_data_get(rec, SDP_ATTR_GOEP_L2CAP_PSM); |
| if (!data) |
| return 0; |
| |
| if (data->dtd != SDP_UINT16) |
| return 0; |
| |
| /* PSM must be odd and lsb of upper byte must be 0 */ |
| if ((data->val.uint16 & 0x0101) != 0x0001) |
| return 0; |
| |
| return data->val.uint16; |
| } |
| |
| static void record_cb(sdp_list_t *recs, int err, gpointer user_data) |
| { |
| struct ext_io *conn = user_data; |
| struct ext_profile *ext = conn->ext; |
| sdp_list_t *r; |
| |
| conn->resolving = false; |
| |
| if (err < 0) { |
| error("Unable to get %s SDP record: %s", ext->name, |
| strerror(-err)); |
| goto failed; |
| } |
| |
| if (!recs || !recs->data) { |
| error("No SDP records found for %s", ext->name); |
| err = -ENOTSUP; |
| goto failed; |
| } |
| |
| for (r = recs; r != NULL; r = r->next) { |
| sdp_record_t *rec = r->data; |
| sdp_list_t *protos; |
| int port; |
| |
| if (sdp_get_access_protos(rec, &protos) < 0) { |
| error("Unable to get proto list from %s record", |
| ext->name); |
| err = -ENOTSUP; |
| goto failed; |
| } |
| |
| port = sdp_get_proto_port(protos, L2CAP_UUID); |
| if (port > 0) |
| conn->psm = port; |
| |
| port = sdp_get_proto_port(protos, RFCOMM_UUID); |
| if (port > 0) |
| conn->chan = port; |
| |
| if (conn->psm == 0 && sdp_get_proto_desc(protos, OBEX_UUID)) |
| conn->psm = get_goep_l2cap_psm(rec); |
| |
| conn->features = get_supported_features(rec); |
| conn->version = get_profile_version(rec); |
| |
| sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, |
| NULL); |
| sdp_list_free(protos, NULL); |
| |
| if (conn->chan || conn->psm) |
| break; |
| } |
| |
| if (!conn->chan && !conn->psm) { |
| error("Failed to find L2CAP PSM or RFCOMM channel for %s", |
| ext->name); |
| err = -ENOTSUP; |
| goto failed; |
| } |
| |
| err = connect_io(conn, btd_adapter_get_address(conn->adapter), |
| device_get_address(conn->device)); |
| if (err < 0) { |
| error("Connecting %s failed: %s", ext->name, strerror(-err)); |
| goto failed; |
| } |
| |
| return; |
| |
| failed: |
| if (conn->service) |
| btd_service_connecting_complete(conn->service, err); |
| |
| ext->conns = g_slist_remove(ext->conns, conn); |
| ext_io_destroy(conn); |
| } |
| |
| static int resolve_service(struct ext_io *conn, const bdaddr_t *src, |
| const bdaddr_t *dst) |
| { |
| struct ext_profile *ext = conn->ext; |
| uuid_t uuid; |
| int err; |
| |
| bt_string2uuid(&uuid, ext->remote_uuid); |
| sdp_uuid128_to_uuid(&uuid); |
| |
| err = bt_search_service(src, dst, &uuid, record_cb, conn, NULL, 0); |
| if (err == 0) |
| conn->resolving = true; |
| |
| return err; |
| } |
| |
| static int ext_connect_dev(struct btd_service *service) |
| { |
| struct btd_device *dev = btd_service_get_device(service); |
| struct btd_profile *profile = btd_service_get_profile(service); |
| struct btd_adapter *adapter; |
| struct ext_io *conn; |
| struct ext_profile *ext; |
| int err; |
| |
| ext = find_ext(profile); |
| if (!ext) |
| return -ENOENT; |
| |
| conn = find_connection(ext, dev); |
| if (conn) |
| return -EALREADY; |
| |
| adapter = device_get_adapter(dev); |
| |
| conn = g_new0(struct ext_io, 1); |
| conn->ext = ext; |
| |
| if (ext->remote_psm || ext->remote_chan) { |
| conn->psm = ext->remote_psm; |
| conn->chan = ext->remote_chan; |
| err = connect_io(conn, btd_adapter_get_address(adapter), |
| device_get_address(dev)); |
| } else { |
| err = resolve_service(conn, btd_adapter_get_address(adapter), |
| device_get_address(dev)); |
| } |
| |
| if (err < 0) |
| goto failed; |
| |
| conn->adapter = btd_adapter_ref(adapter); |
| conn->device = btd_device_ref(dev); |
| conn->service = btd_service_ref(service); |
| |
| ext->conns = g_slist_append(ext->conns, conn); |
| |
| return 0; |
| |
| failed: |
| g_free(conn); |
| return err; |
| } |
| |
| static int send_disconn_req(struct ext_profile *ext, struct ext_io *conn) |
| { |
| DBusMessage *msg; |
| const char *path; |
| |
| msg = dbus_message_new_method_call(ext->owner, ext->path, |
| "org.bluez.Profile1", |
| "RequestDisconnection"); |
| if (!msg) { |
| error("Unable to create RequestDisconnection call for %s", |
| ext->name); |
| return -ENOMEM; |
| } |
| |
| path = device_get_path(conn->device); |
| dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &path, |
| DBUS_TYPE_INVALID); |
| |
| if (!g_dbus_send_message_with_reply(btd_get_dbus_connection(), |
| msg, &conn->pending, -1)) { |
| error("%s: sending RequestDisconnection failed", ext->name); |
| dbus_message_unref(msg); |
| return -EIO; |
| } |
| |
| dbus_message_unref(msg); |
| |
| dbus_pending_call_set_notify(conn->pending, disconn_reply, conn, NULL); |
| |
| return 0; |
| } |
| |
| static int ext_disconnect_dev(struct btd_service *service) |
| { |
| struct btd_device *dev = btd_service_get_device(service); |
| struct btd_profile *profile = btd_service_get_profile(service); |
| struct ext_profile *ext; |
| struct ext_io *conn; |
| int err; |
| |
| ext = find_ext(profile); |
| if (!ext) |
| return -ENOENT; |
| |
| conn = find_connection(ext, dev); |
| if (!conn || !conn->connected) |
| return -ENOTCONN; |
| |
| if (conn->pending) |
| return -EBUSY; |
| |
| err = send_disconn_req(ext, conn); |
| if (err < 0) |
| return err; |
| |
| return 0; |
| } |
| |
| static char *get_hfp_hf_record(struct ext_profile *ext, struct ext_io *l2cap, |
| struct ext_io *rfcomm) |
| { |
| return g_strdup_printf(HFP_HF_RECORD, rfcomm->chan, ext->version, |
| ext->name, ext->features); |
| } |
| |
| static char *get_hfp_ag_record(struct ext_profile *ext, struct ext_io *l2cap, |
| struct ext_io *rfcomm) |
| { |
| return g_strdup_printf(HFP_AG_RECORD, rfcomm->chan, ext->version, |
| ext->name, ext->features); |
| } |
| |
| static char *get_hsp_ag_record(struct ext_profile *ext, struct ext_io *l2cap, |
| struct ext_io *rfcomm) |
| { |
| return g_strdup_printf(HSP_AG_RECORD, rfcomm->chan, ext->version, |
| ext->name); |
| } |
| |
| static char *get_spp_record(struct ext_profile *ext, struct ext_io *l2cap, |
| struct ext_io *rfcomm) |
| { |
| char *svc, *rec; |
| |
| if (ext->service) |
| svc = g_strdup_printf("<uuid value=\"%s\" />", ext->service); |
| else |
| svc = g_strdup(""); |
| |
| rec = g_strdup_printf(SPP_RECORD, svc, rfcomm->chan, ext->version, |
| ext->name); |
| g_free(svc); |
| return rec; |
| } |
| |
| static char *get_dun_record(struct ext_profile *ext, struct ext_io *l2cap, |
| struct ext_io *rfcomm) |
| { |
| return g_strdup_printf(DUN_RECORD, rfcomm->chan, ext->version, |
| ext->name); |
| } |
| |
| static char *get_pce_record(struct ext_profile *ext, struct ext_io *l2cap, |
| struct ext_io *rfcomm) |
| { |
| return g_strdup_printf(PCE_RECORD, ext->version, ext->name); |
| } |
| |
| static char *get_pse_record(struct ext_profile *ext, struct ext_io *l2cap, |
| struct ext_io *rfcomm) |
| { |
| uint16_t psm = 0; |
| uint8_t chan = 0; |
| |
| if (l2cap) |
| psm = l2cap->psm; |
| if (rfcomm) |
| chan = rfcomm->chan; |
| |
| return g_strdup_printf(PSE_RECORD, chan, ext->version, ext->name, psm); |
| } |
| |
| static char *get_mas_record(struct ext_profile *ext, struct ext_io *l2cap, |
| struct ext_io *rfcomm) |
| { |
| uint16_t psm = 0; |
| uint8_t chan = 0; |
| |
| if (l2cap) |
| psm = l2cap->psm; |
| if (rfcomm) |
| chan = rfcomm->chan; |
| |
| return g_strdup_printf(MAS_RECORD, chan, ext->version, ext->name, psm); |
| } |
| |
| static char *get_mns_record(struct ext_profile *ext, struct ext_io *l2cap, |
| struct ext_io *rfcomm) |
| { |
| uint16_t psm = 0; |
| uint8_t chan = 0; |
| |
| if (l2cap) |
| psm = l2cap->psm; |
| if (rfcomm) |
| chan = rfcomm->chan; |
| |
| return g_strdup_printf(MNS_RECORD, chan, ext->version, ext->name, psm); |
| } |
| |
| static char *get_sync_record(struct ext_profile *ext, struct ext_io *l2cap, |
| struct ext_io *rfcomm) |
| { |
| return g_strdup_printf(SYNC_RECORD, rfcomm->chan, ext->version, |
| ext->name); |
| } |
| |
| static char *get_opp_record(struct ext_profile *ext, struct ext_io *l2cap, |
| struct ext_io *rfcomm) |
| { |
| uint16_t psm = 0; |
| uint8_t chan = 0; |
| |
| if (l2cap) |
| psm = l2cap->psm; |
| if (rfcomm) |
| chan = rfcomm->chan; |
| |
| return g_strdup_printf(OPP_RECORD, chan, ext->version, psm, ext->name); |
| } |
| |
| static char *get_ftp_record(struct ext_profile *ext, struct ext_io *l2cap, |
| struct ext_io *rfcomm) |
| { |
| uint16_t psm = 0; |
| uint8_t chan = 0; |
| |
| if (l2cap) |
| psm = l2cap->psm; |
| if (rfcomm) |
| chan = rfcomm->chan; |
| |
| return g_strdup_printf(FTP_RECORD, chan, ext->version, psm, ext->name); |
| } |
| |
| #define RFCOMM_SEQ "<sequence> \ |
| <uuid value=\"0x0003\" /> \ |
| <uint8 value=\"0x%02x\" /> \ |
| </sequence>" |
| |
| #define VERSION_ATTR \ |
| "<attribute id=\"0x0009\"> \ |
| <sequence> \ |
| <sequence> \ |
| <uuid value=\"%s\" /> \ |
| <uint16 value=\"0x%04x\" /> \ |
| </sequence> \ |
| </sequence> \ |
| </attribute>" |
| |
| static char *get_generic_record(struct ext_profile *ext, struct ext_io *l2cap, |
| struct ext_io *rfcomm) |
| { |
| char uuid_str[MAX_LEN_UUID_STR], svc_str[MAX_LEN_UUID_STR], psm[30]; |
| char *rf_seq, *ver_attr, *rec; |
| uuid_t uuid; |
| |
| bt_string2uuid(&uuid, ext->uuid); |
| sdp_uuid2strn(&uuid, uuid_str, sizeof(uuid_str)); |
| |
| if (ext->service) { |
| bt_string2uuid(&uuid, ext->service); |
| sdp_uuid2strn(&uuid, svc_str, sizeof(svc_str)); |
| } else { |
| strncpy(svc_str, uuid_str, sizeof(svc_str)); |
| } |
| |
| if (l2cap) |
| snprintf(psm, sizeof(psm), "<uint16 value=\"0x%04x\" />", |
| l2cap->psm); |
| else |
| psm[0] = '\0'; |
| |
| if (rfcomm) |
| rf_seq = g_strdup_printf(RFCOMM_SEQ, rfcomm->chan); |
| else |
| rf_seq = g_strdup(""); |
| |
| if (ext->version) |
| ver_attr = g_strdup_printf(VERSION_ATTR, uuid_str, |
| ext->version); |
| else |
| ver_attr = g_strdup(""); |
| |
| rec = g_strdup_printf(GENERIC_RECORD, svc_str, psm, rf_seq, ver_attr, |
| ext->name); |
| |
| g_free(rf_seq); |
| g_free(ver_attr); |
| |
| return rec; |
| } |
| |
| static struct default_settings { |
| const char *uuid; |
| const char *name; |
| int priority; |
| const char *remote_uuid; |
| int channel; |
| int psm; |
| BtIOMode mode; |
| BtIOSecLevel sec_level; |
| bool authorize; |
| bool auto_connect; |
| char * (*get_record)(struct ext_profile *ext, |
| struct ext_io *l2cap, |
| struct ext_io *rfcomm); |
| uint16_t version; |
| uint16_t features; |
| } defaults[] = { |
| { |
| .uuid = SPP_UUID, |
| .name = "Serial Port", |
| .channel = SPP_DEFAULT_CHANNEL, |
| .authorize = true, |
| .get_record = get_spp_record, |
| .version = 0x0102, |
| }, { |
| .uuid = DUN_GW_UUID, |
| .name = "Dial-Up Networking", |
| .channel = DUN_DEFAULT_CHANNEL, |
| .authorize = true, |
| .get_record = get_dun_record, |
| .version = 0x0102, |
| }, { |
| .uuid = HFP_HS_UUID, |
| .name = "Hands-Free unit", |
| .priority = BTD_PROFILE_PRIORITY_HIGH, |
| .remote_uuid = HFP_AG_UUID, |
| .channel = HFP_HF_DEFAULT_CHANNEL, |
| .authorize = true, |
| .auto_connect = true, |
| .get_record = get_hfp_hf_record, |
| .version = 0x0105, |
| }, { |
| .uuid = HFP_AG_UUID, |
| .name = "Hands-Free Voice gateway", |
| .priority = BTD_PROFILE_PRIORITY_HIGH, |
| .remote_uuid = HFP_HS_UUID, |
| .channel = HFP_AG_DEFAULT_CHANNEL, |
| .authorize = true, |
| .auto_connect = true, |
| .get_record = get_hfp_ag_record, |
| .version = 0x0105, |
| }, { |
| .uuid = HSP_AG_UUID, |
| .name = "Headset Voice gateway", |
| .priority = BTD_PROFILE_PRIORITY_HIGH, |
| .remote_uuid = HSP_HS_UUID, |
| .channel = HSP_AG_DEFAULT_CHANNEL, |
| .authorize = true, |
| .auto_connect = true, |
| .get_record = get_hsp_ag_record, |
| .version = 0x0102, |
| }, { |
| .uuid = OBEX_OPP_UUID, |
| .name = "Object Push", |
| .channel = OPP_DEFAULT_CHANNEL, |
| .psm = BTD_PROFILE_PSM_AUTO, |
| .mode = BT_IO_MODE_ERTM, |
| .sec_level = BT_IO_SEC_LOW, |
| .authorize = false, |
| .get_record = get_opp_record, |
| .version = 0x0102, |
| }, { |
| .uuid = OBEX_FTP_UUID, |
| .name = "File Transfer", |
| .channel = FTP_DEFAULT_CHANNEL, |
| .psm = BTD_PROFILE_PSM_AUTO, |
| .mode = BT_IO_MODE_ERTM, |
| .authorize = true, |
| .get_record = get_ftp_record, |
| .version = 0x0102, |
| }, { |
| .uuid = OBEX_SYNC_UUID, |
| .name = "Synchronization", |
| .channel = SYNC_DEFAULT_CHANNEL, |
| .authorize = true, |
| .get_record = get_sync_record, |
| .version = 0x0100, |
| }, { |
| .uuid = OBEX_PSE_UUID, |
| .name = "Phone Book Access", |
| .channel = PBAP_DEFAULT_CHANNEL, |
| .psm = BTD_PROFILE_PSM_AUTO, |
| .mode = BT_IO_MODE_ERTM, |
| .authorize = true, |
| .get_record = get_pse_record, |
| .version = 0x0101, |
| }, { |
| .uuid = OBEX_PCE_UUID, |
| .name = "Phone Book Access Client", |
| .remote_uuid = OBEX_PSE_UUID, |
| .authorize = true, |
| .get_record = get_pce_record, |
| .version = 0x0102, |
| }, { |
| .uuid = OBEX_MAS_UUID, |
| .name = "Message Access", |
| .channel = MAS_DEFAULT_CHANNEL, |
| .psm = BTD_PROFILE_PSM_AUTO, |
| .mode = BT_IO_MODE_ERTM, |
| .authorize = true, |
| .get_record = get_mas_record, |
| .version = 0x0100 |
| }, { |
| .uuid = OBEX_MNS_UUID, |
| .name = "Message Notification", |
| .channel = MNS_DEFAULT_CHANNEL, |
| .psm = BTD_PROFILE_PSM_AUTO, |
| .mode = BT_IO_MODE_ERTM, |
| .authorize = true, |
| .get_record = get_mns_record, |
| .version = 0x0102 |
| }, |
| }; |
| |
| static void ext_set_defaults(struct ext_profile *ext) |
| { |
| unsigned int i; |
| |
| ext->mode = BT_IO_MODE_BASIC; |
| ext->sec_level = BT_IO_SEC_MEDIUM; |
| ext->authorize = true; |
| ext->enable_client = true; |
| ext->enable_server = true; |
| ext->remote_uuid = NULL; |
| |
| for (i = 0; i < G_N_ELEMENTS(defaults); i++) { |
| struct default_settings *settings = &defaults[i]; |
| const char *remote_uuid; |
| |
| if (strcasecmp(ext->uuid, settings->uuid) != 0) |
| continue; |
| |
| if (settings->remote_uuid) |
| remote_uuid = settings->remote_uuid; |
| else |
| remote_uuid = ext->uuid; |
| |
| ext->remote_uuid = g_strdup(remote_uuid); |
| |
| if (settings->channel) |
| ext->local_chan = settings->channel; |
| |
| if (settings->psm) |
| ext->local_psm = settings->psm; |
| |
| if (settings->sec_level) |
| ext->sec_level = settings->sec_level; |
| |
| if (settings->mode) |
| ext->mode = settings->mode; |
| |
| ext->authorize = settings->authorize; |
| |
| if (settings->auto_connect) |
| ext->p.auto_connect = true; |
| |
| if (settings->priority) |
| ext->p.priority = settings->priority; |
| |
| if (settings->get_record) |
| ext->get_record = settings->get_record; |
| |
| if (settings->version) |
| ext->version = settings->version; |
| |
| if (settings->features) |
| ext->features = settings->features; |
| |
| if (settings->name) |
| ext->name = g_strdup(settings->name); |
| } |
| } |
| |
| static int parse_ext_opt(struct ext_profile *ext, const char *key, |
| DBusMessageIter *value) |
| { |
| int type = dbus_message_iter_get_arg_type(value); |
| const char *str; |
| uint16_t u16; |
| dbus_bool_t b; |
| |
| if (strcasecmp(key, "Name") == 0) { |
| if (type != DBUS_TYPE_STRING) |
| return -EINVAL; |
| dbus_message_iter_get_basic(value, &str); |
| g_free(ext->name); |
| ext->name = g_strdup(str); |
| } else if (strcasecmp(key, "AutoConnect") == 0) { |
| if (type != DBUS_TYPE_BOOLEAN) |
| return -EINVAL; |
| dbus_message_iter_get_basic(value, &b); |
| ext->p.auto_connect = b; |
| } else if (strcasecmp(key, "PSM") == 0) { |
| if (type != DBUS_TYPE_UINT16) |
| return -EINVAL; |
| dbus_message_iter_get_basic(value, &u16); |
| ext->local_psm = u16 ? u16 : BTD_PROFILE_PSM_AUTO; |
| } else if (strcasecmp(key, "Channel") == 0) { |
| if (type != DBUS_TYPE_UINT16) |
| return -EINVAL; |
| |
| dbus_message_iter_get_basic(value, &u16); |
| if (u16 > 31) |
| return -EINVAL; |
| ext->local_chan = u16 ? u16 : BTD_PROFILE_CHAN_AUTO; |
| } else if (strcasecmp(key, "RequireAuthentication") == 0) { |
| if (type != DBUS_TYPE_BOOLEAN) |
| return -EINVAL; |
| |
| dbus_message_iter_get_basic(value, &b); |
| if (b) |
| ext->sec_level = BT_IO_SEC_MEDIUM; |
| else |
| ext->sec_level = BT_IO_SEC_LOW; |
| } else if (strcasecmp(key, "RequireAuthorization") == 0) { |
| if (type != DBUS_TYPE_BOOLEAN) |
| return -EINVAL; |
| dbus_message_iter_get_basic(value, &b); |
| ext->authorize = b; |
| } else if (strcasecmp(key, "Role") == 0) { |
| if (type != DBUS_TYPE_STRING) |
| return -EINVAL; |
| dbus_message_iter_get_basic(value, &str); |
| g_free(ext->role); |
| ext->role = g_strdup(str); |
| |
| if (g_str_equal(ext->role, "client")) { |
| ext->enable_server = false; |
| ext->enable_client = true; |
| } else if (g_str_equal(ext->role, "server")) { |
| ext->enable_server = true; |
| ext->enable_client = false; |
| } |
| } else if (strcasecmp(key, "ServiceRecord") == 0) { |
| if (type != DBUS_TYPE_STRING) |
| return -EINVAL; |
| dbus_message_iter_get_basic(value, &str); |
| g_free(ext->record); |
| ext->record = g_strdup(str); |
| ext->enable_server = true; |
| } else if (strcasecmp(key, "Version") == 0) { |
| uint16_t ver; |
| |
| if (type != DBUS_TYPE_UINT16) |
| return -EINVAL; |
| |
| dbus_message_iter_get_basic(value, &ver); |
| ext->version = ver; |
| } else if (strcasecmp(key, "Features") == 0) { |
| uint16_t feat; |
| |
| if (type != DBUS_TYPE_UINT16) |
| return -EINVAL; |
| |
| dbus_message_iter_get_basic(value, &feat); |
| ext->features = feat; |
| } else if (strcasecmp(key, "Service") == 0) { |
| if (type != DBUS_TYPE_STRING) |
| return -EINVAL; |
| dbus_message_iter_get_basic(value, &str); |
| free(ext->service); |
| ext->service = bt_name2string(str); |
| } |
| |
| return 0; |
| } |
| |
| static void set_service(struct ext_profile *ext) |
| { |
| if (strcasecmp(ext->uuid, HSP_HS_UUID) == 0) { |
| ext->service = strdup(ext->uuid); |
| } else if (strcasecmp(ext->uuid, HSP_AG_UUID) == 0) { |
| ext->service = ext->uuid; |
| ext->uuid = strdup(HSP_HS_UUID); |
| } else if (strcasecmp(ext->uuid, HFP_HS_UUID) == 0) { |
| ext->service = strdup(ext->uuid); |
| } else if (strcasecmp(ext->uuid, HFP_AG_UUID) == 0) { |
| ext->service = ext->uuid; |
| ext->uuid = strdup(HFP_HS_UUID); |
| } else if (strcasecmp(ext->uuid, OBEX_SYNC_UUID) == 0 || |
| strcasecmp(ext->uuid, OBEX_OPP_UUID) == 0 || |
| strcasecmp(ext->uuid, OBEX_FTP_UUID) == 0) { |
| ext->service = strdup(ext->uuid); |
| } else if (strcasecmp(ext->uuid, OBEX_PSE_UUID) == 0 || |
| strcasecmp(ext->uuid, OBEX_PCE_UUID) == 0) { |
| ext->service = ext->uuid; |
| ext->uuid = strdup(OBEX_PBAP_UUID); |
| } else if (strcasecmp(ext->uuid, OBEX_MAS_UUID) == 0 || |
| strcasecmp(ext->uuid, OBEX_MNS_UUID) == 0) { |
| ext->service = ext->uuid; |
| ext->uuid = strdup(OBEX_MAP_UUID); |
| } |
| } |
| |
| static struct ext_profile *create_ext(const char *owner, const char *path, |
| const char *uuid, |
| DBusMessageIter *opts) |
| { |
| struct btd_profile *p; |
| struct ext_profile *ext; |
| |
| ext = g_new0(struct ext_profile, 1); |
| |
| ext->uuid = bt_name2string(uuid); |
| if (ext->uuid == NULL) { |
| g_free(ext); |
| return NULL; |
| } |
| |
| ext->owner = g_strdup(owner); |
| ext->path = g_strdup(path); |
| |
| ext_set_defaults(ext); |
| |
| while (dbus_message_iter_get_arg_type(opts) == DBUS_TYPE_DICT_ENTRY) { |
| DBusMessageIter value, entry; |
| const char *key; |
| |
| dbus_message_iter_recurse(opts, &entry); |
| dbus_message_iter_get_basic(&entry, &key); |
| |
| dbus_message_iter_next(&entry); |
| dbus_message_iter_recurse(&entry, &value); |
| |
| if (parse_ext_opt(ext, key, &value) < 0) |
| error("Invalid value for profile option %s", key); |
| |
| dbus_message_iter_next(opts); |
| } |
| |
| if (!ext->service) |
| set_service(ext); |
| |
| if (ext->enable_server && !(ext->record || ext->get_record)) |
| ext->get_record = get_generic_record; |
| |
| if (!ext->name) |
| ext->name = g_strdup_printf("%s%s/%s", owner, path, uuid); |
| |
| if (!ext->remote_uuid) { |
| if (ext->service) |
| ext->remote_uuid = g_strdup(ext->service); |
| else |
| ext->remote_uuid = g_strdup(ext->uuid); |
| } |
| |
| p = &ext->p; |
| |
| p->name = ext->name; |
| p->local_uuid = ext->service ? ext->service : ext->uuid; |
| p->remote_uuid = ext->remote_uuid; |
| p->external = true; |
| |
| if (ext->enable_server) { |
| p->adapter_probe = ext_adapter_probe; |
| p->adapter_remove = ext_adapter_remove; |
| } |
| |
| if (ext->enable_client) { |
| p->device_probe = ext_device_probe; |
| p->device_remove = ext_device_remove; |
| p->connect = ext_connect_dev; |
| p->disconnect = ext_disconnect_dev; |
| } |
| |
| DBG("Created \"%s\"", ext->name); |
| |
| ext_profiles = g_slist_append(ext_profiles, ext); |
| |
| adapter_foreach(adapter_add_profile, &ext->p); |
| |
| return ext; |
| } |
| |
| static void remove_ext(struct ext_profile *ext) |
| { |
| adapter_foreach(adapter_remove_profile, &ext->p); |
| |
| ext_profiles = g_slist_remove(ext_profiles, ext); |
| |
| DBG("Removed \"%s\"", ext->name); |
| |
| ext_remove_records(ext, NULL); |
| |
| g_slist_free_full(ext->servers, ext_io_destroy); |
| g_slist_free_full(ext->conns, ext_io_destroy); |
| |
| g_free(ext->remote_uuid); |
| g_free(ext->name); |
| g_free(ext->owner); |
| free(ext->uuid); |
| free(ext->service); |
| g_free(ext->role); |
| g_free(ext->path); |
| g_free(ext->record); |
| |
| g_free(ext); |
| } |
| |
| static void ext_exited(DBusConnection *conn, void *user_data) |
| { |
| struct ext_profile *ext = user_data; |
| |
| DBG("\"%s\" exited", ext->name); |
| |
| remove_ext(ext); |
| } |
| |
| static DBusMessage *register_profile(DBusConnection *conn, |
| DBusMessage *msg, void *user_data) |
| { |
| const char *path, *sender, *uuid; |
| DBusMessageIter args, opts; |
| struct ext_profile *ext; |
| |
| sender = dbus_message_get_sender(msg); |
| |
| DBG("sender %s", sender); |
| |
| dbus_message_iter_init(msg, &args); |
| |
| dbus_message_iter_get_basic(&args, &path); |
| dbus_message_iter_next(&args); |
| |
| ext = find_ext_profile(sender, path); |
| if (ext) |
| return btd_error_already_exists(msg); |
| |
| dbus_message_iter_get_basic(&args, &uuid); |
| dbus_message_iter_next(&args); |
| |
| if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_ARRAY) |
| return btd_error_invalid_args(msg); |
| |
| dbus_message_iter_recurse(&args, &opts); |
| |
| ext = create_ext(sender, path, uuid, &opts); |
| if (!ext) |
| return btd_error_invalid_args(msg); |
| |
| ext->id = g_dbus_add_disconnect_watch(conn, sender, ext_exited, ext, |
| NULL); |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static DBusMessage *unregister_profile(DBusConnection *conn, |
| DBusMessage *msg, void *user_data) |
| { |
| const char *path, *sender; |
| struct ext_profile *ext; |
| |
| sender = dbus_message_get_sender(msg); |
| |
| DBG("sender %s", sender); |
| |
| if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, |
| DBUS_TYPE_INVALID)) |
| return btd_error_invalid_args(msg); |
| |
| ext = find_ext_profile(sender, path); |
| if (!ext) |
| return btd_error_does_not_exist(msg); |
| |
| g_dbus_remove_watch(conn, ext->id); |
| remove_ext(ext); |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static const GDBusMethodTable methods[] = { |
| { GDBUS_METHOD("RegisterProfile", |
| GDBUS_ARGS({ "profile", "o"}, { "UUID", "s" }, |
| { "options", "a{sv}" }), |
| NULL, register_profile) }, |
| { GDBUS_METHOD("UnregisterProfile", GDBUS_ARGS({ "profile", "o" }), |
| NULL, unregister_profile) }, |
| { } |
| }; |
| |
| static struct btd_profile_custom_property *find_custom_prop(const char *uuid, |
| const char *name) |
| { |
| GSList *l; |
| |
| for (l = custom_props; l; l = l->next) { |
| struct btd_profile_custom_property *prop = l->data; |
| |
| if (strcasecmp(prop->uuid, uuid) != 0) |
| continue; |
| |
| if (g_strcmp0(prop->name, name) == 0) |
| return prop; |
| } |
| |
| return NULL; |
| } |
| |
| bool btd_profile_add_custom_prop(const char *uuid, const char *type, |
| const char *name, |
| btd_profile_prop_exists exists, |
| btd_profile_prop_get get, |
| void *user_data) |
| { |
| struct btd_profile_custom_property *prop; |
| |
| prop = find_custom_prop(uuid, name); |
| if (prop != NULL) |
| return false; |
| |
| prop = g_new0(struct btd_profile_custom_property, 1); |
| |
| prop->uuid = strdup(uuid); |
| prop->type = g_strdup(type); |
| prop->name = g_strdup(name); |
| prop->exists = exists; |
| prop->get = get; |
| prop->user_data = user_data; |
| |
| custom_props = g_slist_append(custom_props, prop); |
| |
| return true; |
| } |
| |
| static void free_property(gpointer data) |
| { |
| struct btd_profile_custom_property *p = data; |
| |
| g_free(p->uuid); |
| g_free(p->type); |
| g_free(p->name); |
| |
| g_free(p); |
| } |
| |
| bool btd_profile_remove_custom_prop(const char *uuid, const char *name) |
| { |
| struct btd_profile_custom_property *prop; |
| |
| prop = find_custom_prop(uuid, name); |
| if (prop == NULL) |
| return false; |
| |
| custom_props = g_slist_remove(custom_props, prop); |
| free_property(prop); |
| |
| return false; |
| } |
| |
| void btd_profile_init(void) |
| { |
| g_dbus_register_interface(btd_get_dbus_connection(), |
| "/org/bluez", "org.bluez.ProfileManager1", |
| methods, NULL, NULL, NULL, NULL); |
| } |
| |
| void btd_profile_cleanup(void) |
| { |
| while (ext_profiles) { |
| struct ext_profile *ext = ext_profiles->data; |
| DBusConnection *conn = btd_get_dbus_connection(); |
| DBusMessage *msg; |
| |
| DBG("Releasing \"%s\"", ext->name); |
| |
| g_slist_free_full(ext->conns, ext_io_destroy); |
| ext->conns = NULL; |
| |
| msg = dbus_message_new_method_call(ext->owner, ext->path, |
| "org.bluez.Profile1", |
| "Release"); |
| if (msg) |
| g_dbus_send_message(conn, msg); |
| |
| g_dbus_remove_watch(conn, ext->id); |
| remove_ext(ext); |
| |
| } |
| |
| g_slist_free_full(custom_props, free_property); |
| custom_props = NULL; |
| |
| g_dbus_unregister_interface(btd_get_dbus_connection(), |
| "/org/bluez", "org.bluez.ProfileManager1"); |
| } |