2529 lines
58 KiB
C
2529 lines
58 KiB
C
/*
|
|
*
|
|
* 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");
|
|
}
|