|  | /* | 
|  | * | 
|  | *  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"); | 
|  | } |