1181 lines
26 KiB
C
1181 lines
26 KiB
C
/*
|
|
*
|
|
* BlueZ - Bluetooth protocol stack for Linux
|
|
*
|
|
* Copyright (C) 2013-2014 Intel Corporation. All rights reserved.
|
|
*
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; 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 <glib.h>
|
|
#include <stdbool.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
|
|
#include "lib/bluetooth.h"
|
|
#include "btio/btio.h"
|
|
#include "lib/sdp.h"
|
|
#include "lib/sdp_lib.h"
|
|
#include "src/sdp-client.h"
|
|
#include "src/sdpd.h"
|
|
#include "src/log.h"
|
|
|
|
#include "hal-msg.h"
|
|
#include "ipc-common.h"
|
|
#include "ipc.h"
|
|
#include "utils.h"
|
|
#include "bluetooth.h"
|
|
#include "socket.h"
|
|
|
|
#define RFCOMM_CHANNEL_MAX 30
|
|
|
|
#define OPP_DEFAULT_CHANNEL 9
|
|
#define HSP_AG_DEFAULT_CHANNEL 12
|
|
#define HFP_AG_DEFAULT_CHANNEL 13
|
|
#define PBAP_DEFAULT_CHANNEL 15
|
|
#define MAP_MAS_DEFAULT_CHANNEL 16
|
|
|
|
#define SVC_HINT_OBEX 0x10
|
|
|
|
/* Hardcoded MAP stuff needed for MAS SMS Instance.*/
|
|
#define DEFAULT_MAS_INSTANCE 0x00
|
|
|
|
#define MAP_MSG_TYPE_SMS_GSM 0x02
|
|
#define MAP_MSG_TYPE_SMS_CDMA 0x04
|
|
#define DEFAULT_MAS_MSG_TYPE (MAP_MSG_TYPE_SMS_GSM | MAP_MSG_TYPE_SMS_CDMA)
|
|
|
|
static struct ipc *hal_ipc = NULL;
|
|
struct rfcomm_sock {
|
|
int channel; /* RFCOMM channel */
|
|
BtIOSecLevel sec_level;
|
|
|
|
/* for socket to BT */
|
|
int bt_sock;
|
|
guint bt_watch;
|
|
|
|
/* for socket to HAL */
|
|
int jv_sock;
|
|
guint jv_watch;
|
|
|
|
bdaddr_t dst;
|
|
uint32_t service_handle;
|
|
|
|
uint8_t *buf;
|
|
int buf_size;
|
|
};
|
|
|
|
struct rfcomm_channel {
|
|
bool reserved;
|
|
struct rfcomm_sock *rfsock;
|
|
};
|
|
|
|
static bdaddr_t adapter_addr;
|
|
|
|
static const uint8_t zero_uuid[16] = { 0 };
|
|
|
|
/* Simple list of RFCOMM connected sockets */
|
|
static GList *connections = NULL;
|
|
|
|
static struct rfcomm_channel servers[RFCOMM_CHANNEL_MAX + 1];
|
|
|
|
static int rfsock_set_buffer(struct rfcomm_sock *rfsock)
|
|
{
|
|
socklen_t len = sizeof(int);
|
|
int rcv, snd, size, err;
|
|
|
|
err = getsockopt(rfsock->bt_sock, SOL_SOCKET, SO_RCVBUF, &rcv, &len);
|
|
if (err < 0) {
|
|
int err = -errno;
|
|
error("getsockopt(SO_RCVBUF): %s", strerror(-err));
|
|
return err;
|
|
}
|
|
|
|
err = getsockopt(rfsock->bt_sock, SOL_SOCKET, SO_SNDBUF, &snd, &len);
|
|
if (err < 0) {
|
|
int err = -errno;
|
|
error("getsockopt(SO_SNDBUF): %s", strerror(-err));
|
|
return err;
|
|
}
|
|
|
|
size = MAX(rcv, snd);
|
|
|
|
DBG("Set buffer size %d", size);
|
|
|
|
rfsock->buf = g_malloc(size);
|
|
rfsock->buf_size = size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void cleanup_rfsock(gpointer data)
|
|
{
|
|
struct rfcomm_sock *rfsock = data;
|
|
|
|
DBG("rfsock %p bt_sock %d jv_sock %d", rfsock, rfsock->bt_sock,
|
|
rfsock->jv_sock);
|
|
|
|
if (rfsock->jv_sock >= 0)
|
|
if (close(rfsock->jv_sock) < 0)
|
|
error("close() fd %d failed: %s", rfsock->jv_sock,
|
|
strerror(errno));
|
|
|
|
if (rfsock->bt_sock >= 0)
|
|
if (close(rfsock->bt_sock) < 0)
|
|
error("close() fd %d: failed: %s", rfsock->bt_sock,
|
|
strerror(errno));
|
|
|
|
if (rfsock->bt_watch > 0)
|
|
if (!g_source_remove(rfsock->bt_watch))
|
|
error("bt_watch source was not found");
|
|
|
|
if (rfsock->jv_watch > 0)
|
|
if (!g_source_remove(rfsock->jv_watch))
|
|
error("stack_watch source was not found");
|
|
|
|
if (rfsock->service_handle)
|
|
bt_adapter_remove_record(rfsock->service_handle);
|
|
|
|
if (rfsock->buf)
|
|
g_free(rfsock->buf);
|
|
|
|
g_free(rfsock);
|
|
}
|
|
|
|
static struct rfcomm_sock *create_rfsock(int bt_sock, int *hal_sock)
|
|
{
|
|
int fds[2] = {-1, -1};
|
|
struct rfcomm_sock *rfsock;
|
|
|
|
if (socketpair(AF_LOCAL, SOCK_STREAM, 0, fds) < 0) {
|
|
error("socketpair(): %s", strerror(errno));
|
|
*hal_sock = -1;
|
|
return NULL;
|
|
}
|
|
|
|
rfsock = g_new0(struct rfcomm_sock, 1);
|
|
rfsock->jv_sock = fds[0];
|
|
*hal_sock = fds[1];
|
|
rfsock->bt_sock = bt_sock;
|
|
|
|
DBG("rfsock %p", rfsock);
|
|
|
|
if (bt_sock < 0)
|
|
return rfsock;
|
|
|
|
if (rfsock_set_buffer(rfsock) < 0) {
|
|
cleanup_rfsock(rfsock);
|
|
return NULL;
|
|
}
|
|
|
|
return rfsock;
|
|
}
|
|
|
|
static sdp_record_t *create_rfcomm_record(uint8_t chan, uuid_t *uuid,
|
|
const char *svc_name,
|
|
bool has_obex)
|
|
{
|
|
sdp_list_t *svclass_id;
|
|
sdp_list_t *seq, *proto_seq, *pbg_seq;
|
|
sdp_list_t *proto[3];
|
|
uuid_t l2cap_uuid, rfcomm_uuid, obex_uuid, pbg_uuid;
|
|
sdp_data_t *channel;
|
|
sdp_record_t *record;
|
|
|
|
record = sdp_record_alloc();
|
|
if (!record)
|
|
return NULL;
|
|
|
|
record->handle = sdp_next_handle();
|
|
|
|
svclass_id = sdp_list_append(NULL, uuid);
|
|
sdp_set_service_classes(record, svclass_id);
|
|
|
|
sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
|
|
proto[0] = sdp_list_append(NULL, &l2cap_uuid);
|
|
seq = sdp_list_append(NULL, proto[0]);
|
|
|
|
sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
|
|
proto[1] = sdp_list_append(NULL, &rfcomm_uuid);
|
|
channel = sdp_data_alloc(SDP_UINT8, &chan);
|
|
proto[1] = sdp_list_append(proto[1], channel);
|
|
seq = sdp_list_append(seq, proto[1]);
|
|
|
|
if (has_obex) {
|
|
sdp_uuid16_create(&obex_uuid, OBEX_UUID);
|
|
proto[2] = sdp_list_append(NULL, &obex_uuid);
|
|
seq = sdp_list_append(seq, proto[2]);
|
|
}
|
|
|
|
proto_seq = sdp_list_append(NULL, seq);
|
|
sdp_set_access_protos(record, proto_seq);
|
|
|
|
sdp_uuid16_create(&pbg_uuid, PUBLIC_BROWSE_GROUP);
|
|
pbg_seq = sdp_list_append(NULL, &pbg_uuid);
|
|
sdp_set_browse_groups(record, pbg_seq);
|
|
|
|
if (svc_name)
|
|
sdp_set_info_attr(record, svc_name, NULL, NULL);
|
|
|
|
sdp_data_free(channel);
|
|
sdp_list_free(proto[0], NULL);
|
|
sdp_list_free(proto[1], NULL);
|
|
if (has_obex)
|
|
sdp_list_free(proto[2], NULL);
|
|
sdp_list_free(seq, NULL);
|
|
sdp_list_free(proto_seq, NULL);
|
|
sdp_list_free(pbg_seq, NULL);
|
|
sdp_list_free(svclass_id, NULL);
|
|
|
|
return record;
|
|
}
|
|
|
|
static sdp_record_t *create_opp_record(uint8_t chan, const char *svc_name)
|
|
{
|
|
uint8_t formats[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0xff };
|
|
uint8_t dtd = SDP_UINT8;
|
|
uuid_t uuid;
|
|
sdp_list_t *seq;
|
|
sdp_profile_desc_t profile[1];
|
|
void *dtds[sizeof(formats)], *values[sizeof(formats)];
|
|
sdp_data_t *formats_list;
|
|
sdp_record_t *record;
|
|
size_t i;
|
|
|
|
sdp_uuid16_create(&uuid, OBEX_OBJPUSH_SVCLASS_ID);
|
|
|
|
record = create_rfcomm_record(chan, &uuid, svc_name, true);
|
|
if (!record)
|
|
return NULL;
|
|
|
|
sdp_uuid16_create(&profile[0].uuid, OBEX_OBJPUSH_PROFILE_ID);
|
|
profile[0].version = 0x0100;
|
|
seq = sdp_list_append(NULL, profile);
|
|
sdp_set_profile_descs(record, seq);
|
|
|
|
for (i = 0; i < sizeof(formats); i++) {
|
|
dtds[i] = &dtd;
|
|
values[i] = &formats[i];
|
|
}
|
|
formats_list = sdp_seq_alloc(dtds, values, sizeof(formats));
|
|
sdp_attr_add(record, SDP_ATTR_SUPPORTED_FORMATS_LIST, formats_list);
|
|
|
|
sdp_list_free(seq, NULL);
|
|
|
|
return record;
|
|
}
|
|
|
|
static sdp_record_t *create_pbap_record(uint8_t chan, const char *svc_name)
|
|
{
|
|
sdp_list_t *seq;
|
|
sdp_profile_desc_t profile[1];
|
|
uint8_t formats = 0x01;
|
|
sdp_record_t *record;
|
|
uuid_t uuid;
|
|
|
|
sdp_uuid16_create(&uuid, PBAP_PSE_SVCLASS_ID);
|
|
|
|
record = create_rfcomm_record(chan, &uuid, svc_name, true);
|
|
if (!record)
|
|
return NULL;
|
|
|
|
sdp_uuid16_create(&profile[0].uuid, PBAP_PROFILE_ID);
|
|
profile[0].version = 0x0101;
|
|
seq = sdp_list_append(NULL, profile);
|
|
sdp_set_profile_descs(record, seq);
|
|
|
|
sdp_attr_add_new(record, SDP_ATTR_SUPPORTED_REPOSITORIES, SDP_UINT8,
|
|
&formats);
|
|
|
|
sdp_list_free(seq, NULL);
|
|
|
|
return record;
|
|
}
|
|
|
|
static sdp_record_t *create_mas_record(uint8_t chan, const char *svc_name)
|
|
{
|
|
sdp_list_t *seq;
|
|
sdp_profile_desc_t profile[1];
|
|
uint8_t minst = DEFAULT_MAS_INSTANCE;
|
|
uint8_t mtype = DEFAULT_MAS_MSG_TYPE;
|
|
sdp_record_t *record;
|
|
uuid_t uuid;
|
|
|
|
sdp_uuid16_create(&uuid, MAP_MSE_SVCLASS_ID);
|
|
|
|
record = create_rfcomm_record(chan, &uuid, svc_name, true);
|
|
if (!record)
|
|
return NULL;
|
|
|
|
sdp_uuid16_create(&profile[0].uuid, MAP_PROFILE_ID);
|
|
profile[0].version = 0x0101;
|
|
seq = sdp_list_append(NULL, profile);
|
|
sdp_set_profile_descs(record, seq);
|
|
|
|
sdp_attr_add_new(record, SDP_ATTR_MAS_INSTANCE_ID, SDP_UINT8, &minst);
|
|
sdp_attr_add_new(record, SDP_ATTR_SUPPORTED_MESSAGE_TYPES, SDP_UINT8,
|
|
&mtype);
|
|
|
|
sdp_list_free(seq, NULL);
|
|
|
|
return record;
|
|
}
|
|
|
|
static sdp_record_t *create_spp_record(uint8_t chan, const char *svc_name)
|
|
{
|
|
sdp_record_t *record;
|
|
uuid_t uuid;
|
|
|
|
sdp_uuid16_create(&uuid, SERIAL_PORT_SVCLASS_ID);
|
|
|
|
record = create_rfcomm_record(chan, &uuid, svc_name, false);
|
|
if (!record)
|
|
return NULL;
|
|
|
|
return record;
|
|
}
|
|
|
|
static sdp_record_t *create_app_record(uint8_t chan,
|
|
const uint8_t *app_uuid,
|
|
const char *svc_name)
|
|
{
|
|
sdp_record_t *record;
|
|
uuid_t uuid;
|
|
|
|
sdp_uuid128_create(&uuid, app_uuid);
|
|
|
|
record = create_rfcomm_record(chan, &uuid, svc_name, false);
|
|
if (!record)
|
|
return NULL;
|
|
|
|
return record;
|
|
}
|
|
|
|
static const struct profile_info {
|
|
uint8_t uuid[16];
|
|
uint8_t channel;
|
|
uint8_t svc_hint;
|
|
BtIOSecLevel sec_level;
|
|
sdp_record_t * (*create_record)(uint8_t chan, const char *svc_name);
|
|
} profiles[] = {
|
|
{
|
|
.uuid = {
|
|
0x00, 0x00, 0x11, 0x08, 0x00, 0x00, 0x10, 0x00,
|
|
0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB
|
|
},
|
|
.channel = HSP_AG_DEFAULT_CHANNEL,
|
|
.svc_hint = 0,
|
|
.sec_level = BT_IO_SEC_MEDIUM,
|
|
.create_record = NULL
|
|
}, {
|
|
.uuid = {
|
|
0x00, 0x00, 0x11, 0x1F, 0x00, 0x00, 0x10, 0x00,
|
|
0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB
|
|
},
|
|
.channel = HFP_AG_DEFAULT_CHANNEL,
|
|
.svc_hint = 0,
|
|
.sec_level = BT_IO_SEC_MEDIUM,
|
|
.create_record = NULL
|
|
}, {
|
|
.uuid = {
|
|
0x00, 0x00, 0x11, 0x2F, 0x00, 0x00, 0x10, 0x00,
|
|
0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB
|
|
},
|
|
.channel = PBAP_DEFAULT_CHANNEL,
|
|
.svc_hint = SVC_HINT_OBEX,
|
|
.sec_level = BT_IO_SEC_MEDIUM,
|
|
.create_record = create_pbap_record
|
|
}, {
|
|
.uuid = {
|
|
0x00, 0x00, 0x11, 0x05, 0x00, 0x00, 0x10, 0x00,
|
|
0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB
|
|
},
|
|
.channel = OPP_DEFAULT_CHANNEL,
|
|
.svc_hint = SVC_HINT_OBEX,
|
|
.sec_level = BT_IO_SEC_LOW,
|
|
.create_record = create_opp_record
|
|
}, {
|
|
.uuid = {
|
|
0x00, 0x00, 0x11, 0x32, 0x00, 0x00, 0x10, 0x00,
|
|
0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB
|
|
},
|
|
.channel = MAP_MAS_DEFAULT_CHANNEL,
|
|
.svc_hint = SVC_HINT_OBEX,
|
|
.sec_level = BT_IO_SEC_MEDIUM,
|
|
.create_record = create_mas_record
|
|
}, {
|
|
.uuid = {
|
|
0x00, 0x00, 0x11, 0x01, 0x00, 0x00, 0x10, 0x00,
|
|
0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB
|
|
},
|
|
.channel = 0,
|
|
.svc_hint = 0,
|
|
.sec_level = BT_IO_SEC_MEDIUM,
|
|
.create_record = create_spp_record
|
|
},
|
|
};
|
|
|
|
static uint32_t sdp_service_register(uint8_t channel, const uint8_t *uuid,
|
|
const struct profile_info *profile,
|
|
const void *svc_name)
|
|
{
|
|
sdp_record_t *record = NULL;
|
|
uint8_t svc_hint = 0;
|
|
|
|
if (profile && profile->create_record) {
|
|
record = profile->create_record(channel, svc_name);
|
|
svc_hint = profile->svc_hint;
|
|
} else if (uuid) {
|
|
record = create_app_record(channel, uuid, svc_name);
|
|
}
|
|
|
|
if (!record)
|
|
return 0;
|
|
|
|
if (bt_adapter_add_record(record, svc_hint) < 0) {
|
|
error("Failed to register on SDP record");
|
|
sdp_record_free(record);
|
|
return 0;
|
|
}
|
|
|
|
return record->handle;
|
|
}
|
|
|
|
static int bt_sock_send_fd(int sock_fd, const void *buf, int len, int send_fd)
|
|
{
|
|
ssize_t ret;
|
|
struct msghdr msg;
|
|
struct cmsghdr *cmsg;
|
|
struct iovec iv;
|
|
char cmsgbuf[CMSG_SPACE(sizeof(int))];
|
|
|
|
DBG("len %d sock_fd %d send_fd %d", len, sock_fd, send_fd);
|
|
|
|
if (sock_fd == -1 || send_fd == -1)
|
|
return -1;
|
|
|
|
memset(&msg, 0, sizeof(msg));
|
|
memset(cmsgbuf, 0, sizeof(cmsgbuf));
|
|
|
|
msg.msg_control = cmsgbuf;
|
|
msg.msg_controllen = sizeof(cmsgbuf);
|
|
|
|
cmsg = CMSG_FIRSTHDR(&msg);
|
|
cmsg->cmsg_level = SOL_SOCKET;
|
|
cmsg->cmsg_type = SCM_RIGHTS;
|
|
cmsg->cmsg_len = CMSG_LEN(sizeof(send_fd));
|
|
|
|
memcpy(CMSG_DATA(cmsg), &send_fd, sizeof(send_fd));
|
|
|
|
iv.iov_base = (unsigned char *) buf;
|
|
iv.iov_len = len;
|
|
|
|
msg.msg_iov = &iv;
|
|
msg.msg_iovlen = 1;
|
|
|
|
ret = sendmsg(sock_fd, &msg, MSG_NOSIGNAL);
|
|
if (ret < 0) {
|
|
error("sendmsg(): sock_fd %d send_fd %d: %s",
|
|
sock_fd, send_fd, strerror(errno));
|
|
return ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct profile_info *get_profile_by_uuid(const uint8_t *uuid)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < G_N_ELEMENTS(profiles); i++) {
|
|
if (!memcmp(profiles[i].uuid, uuid, 16))
|
|
return &profiles[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int try_write_all(int fd, unsigned char *buf, int len)
|
|
{
|
|
int sent = 0;
|
|
|
|
while (len > 0) {
|
|
int written;
|
|
|
|
written = write(fd, buf, len);
|
|
if (written < 0) {
|
|
if (errno == EINTR || errno == EAGAIN)
|
|
continue;
|
|
return -1;
|
|
}
|
|
|
|
if (!written)
|
|
return 0;
|
|
|
|
len -= written; buf += written; sent += written;
|
|
}
|
|
|
|
return sent;
|
|
}
|
|
|
|
static gboolean jv_sock_client_event_cb(GIOChannel *io, GIOCondition cond,
|
|
gpointer data)
|
|
{
|
|
struct rfcomm_sock *rfsock = data;
|
|
int len, sent;
|
|
|
|
if (cond & G_IO_HUP) {
|
|
DBG("Socket %d hang up", g_io_channel_unix_get_fd(io));
|
|
goto fail;
|
|
}
|
|
|
|
if (cond & (G_IO_ERR | G_IO_NVAL)) {
|
|
error("Socket %d error", g_io_channel_unix_get_fd(io));
|
|
goto fail;
|
|
}
|
|
|
|
len = read(rfsock->jv_sock, rfsock->buf, rfsock->buf_size);
|
|
if (len <= 0) {
|
|
error("read(): %s", strerror(errno));
|
|
/* Read again */
|
|
return TRUE;
|
|
}
|
|
|
|
sent = try_write_all(rfsock->bt_sock, rfsock->buf, len);
|
|
if (sent < 0) {
|
|
error("write(): %s", strerror(errno));
|
|
goto fail;
|
|
}
|
|
|
|
return TRUE;
|
|
fail:
|
|
DBG("rfsock %p jv_sock %d cond %d", rfsock, rfsock->jv_sock, cond);
|
|
|
|
connections = g_list_remove(connections, rfsock);
|
|
cleanup_rfsock(rfsock);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean bt_sock_event_cb(GIOChannel *io, GIOCondition cond,
|
|
gpointer data)
|
|
{
|
|
struct rfcomm_sock *rfsock = data;
|
|
int len, sent;
|
|
|
|
if (cond & G_IO_HUP) {
|
|
DBG("Socket %d hang up", g_io_channel_unix_get_fd(io));
|
|
goto fail;
|
|
}
|
|
|
|
if (cond & (G_IO_ERR | G_IO_NVAL)) {
|
|
error("Socket %d error", g_io_channel_unix_get_fd(io));
|
|
goto fail;
|
|
}
|
|
|
|
len = read(rfsock->bt_sock, rfsock->buf, rfsock->buf_size);
|
|
if (len <= 0) {
|
|
error("read(): %s", strerror(errno));
|
|
/* Read again */
|
|
return TRUE;
|
|
}
|
|
|
|
sent = try_write_all(rfsock->jv_sock, rfsock->buf, len);
|
|
if (sent < 0) {
|
|
error("write(): %s", strerror(errno));
|
|
goto fail;
|
|
}
|
|
|
|
return TRUE;
|
|
fail:
|
|
DBG("rfsock %p bt_sock %d cond %d", rfsock, rfsock->bt_sock, cond);
|
|
|
|
connections = g_list_remove(connections, rfsock);
|
|
cleanup_rfsock(rfsock);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static bool sock_send_accept(struct rfcomm_sock *rfsock, bdaddr_t *bdaddr,
|
|
int fd_accepted)
|
|
{
|
|
struct hal_sock_connect_signal cmd;
|
|
int len;
|
|
|
|
DBG("");
|
|
|
|
cmd.size = sizeof(cmd);
|
|
bdaddr2android(bdaddr, cmd.bdaddr);
|
|
cmd.channel = rfsock->channel;
|
|
cmd.status = 0;
|
|
|
|
len = bt_sock_send_fd(rfsock->jv_sock, &cmd, sizeof(cmd), fd_accepted);
|
|
if (len != sizeof(cmd)) {
|
|
error("Error sending accept signal");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static gboolean jv_sock_server_event_cb(GIOChannel *io, GIOCondition cond,
|
|
gpointer data)
|
|
{
|
|
struct rfcomm_sock *rfsock = data;
|
|
|
|
DBG("rfsock %p jv_sock %d cond %d", rfsock, rfsock->jv_sock, cond);
|
|
|
|
if (cond & G_IO_NVAL)
|
|
return FALSE;
|
|
|
|
if (cond & (G_IO_ERR | G_IO_HUP)) {
|
|
servers[rfsock->channel].rfsock = NULL;
|
|
cleanup_rfsock(rfsock);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void accept_cb(GIOChannel *io, GError *err, gpointer user_data)
|
|
{
|
|
struct rfcomm_sock *rfsock = user_data;
|
|
struct rfcomm_sock *new_rfsock;
|
|
GIOChannel *jv_io;
|
|
GError *gerr = NULL;
|
|
bdaddr_t dst;
|
|
char address[18];
|
|
int new_sock;
|
|
int hal_sock;
|
|
guint id;
|
|
GIOCondition cond;
|
|
|
|
if (err) {
|
|
error("%s", err->message);
|
|
return;
|
|
}
|
|
|
|
bt_io_get(io, &gerr,
|
|
BT_IO_OPT_DEST_BDADDR, &dst,
|
|
BT_IO_OPT_INVALID);
|
|
if (gerr) {
|
|
error("%s", gerr->message);
|
|
g_error_free(gerr);
|
|
g_io_channel_shutdown(io, TRUE, NULL);
|
|
return;
|
|
}
|
|
|
|
ba2str(&dst, address);
|
|
DBG("Incoming connection from %s on channel %d (rfsock %p)", address,
|
|
rfsock->channel, rfsock);
|
|
|
|
new_sock = g_io_channel_unix_get_fd(io);
|
|
new_rfsock = create_rfsock(new_sock, &hal_sock);
|
|
if (!new_rfsock) {
|
|
g_io_channel_shutdown(io, TRUE, NULL);
|
|
return;
|
|
}
|
|
|
|
DBG("new rfsock %p bt_sock %d jv_sock %d hal_sock %d", new_rfsock,
|
|
new_rfsock->bt_sock, new_rfsock->jv_sock, hal_sock);
|
|
|
|
if (!sock_send_accept(rfsock, &dst, hal_sock)) {
|
|
cleanup_rfsock(new_rfsock);
|
|
return;
|
|
}
|
|
|
|
connections = g_list_append(connections, new_rfsock);
|
|
|
|
/* Handle events from Android */
|
|
cond = G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL;
|
|
jv_io = g_io_channel_unix_new(new_rfsock->jv_sock);
|
|
id = g_io_add_watch(jv_io, cond, jv_sock_client_event_cb, new_rfsock);
|
|
g_io_channel_unref(jv_io);
|
|
|
|
new_rfsock->jv_watch = id;
|
|
|
|
/* Handle rfcomm events */
|
|
cond = G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL;
|
|
id = g_io_add_watch(io, cond, bt_sock_event_cb, new_rfsock);
|
|
g_io_channel_set_close_on_unref(io, FALSE);
|
|
|
|
new_rfsock->bt_watch = id;
|
|
}
|
|
|
|
static int find_free_channel(void)
|
|
{
|
|
int ch;
|
|
|
|
/* channel 0 is reserver so we don't use it */
|
|
for (ch = 1; ch <= RFCOMM_CHANNEL_MAX; ch++) {
|
|
struct rfcomm_channel *srv = &servers[ch];
|
|
|
|
if (!srv->reserved && srv->rfsock == NULL)
|
|
return ch;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static BtIOSecLevel get_sec_level(uint8_t flags)
|
|
{
|
|
/*
|
|
* HAL_SOCK_FLAG_AUTH should require MITM but in our case setting
|
|
* security to BT_IO_SEC_HIGH would also require 16-digits PIN code
|
|
* for pre-2.1 devices which is not what Android expects. For this
|
|
* reason we ignore this flag to not break apps which use "secure"
|
|
* sockets (have both auth and encrypt flags set, there is no public
|
|
* API in Android which should provide proper high security socket).
|
|
*/
|
|
return flags & HAL_SOCK_FLAG_ENCRYPT ? BT_IO_SEC_MEDIUM :
|
|
BT_IO_SEC_LOW;
|
|
}
|
|
|
|
static uint8_t rfcomm_listen(int chan, const uint8_t *name, const uint8_t *uuid,
|
|
uint8_t flags, int *hal_sock)
|
|
{
|
|
const struct profile_info *profile;
|
|
struct rfcomm_sock *rfsock = NULL;
|
|
BtIOSecLevel sec_level;
|
|
GIOChannel *io, *jv_io;
|
|
GIOCondition cond;
|
|
GError *err = NULL;
|
|
guint id;
|
|
uuid_t uu;
|
|
char uuid_str[32];
|
|
|
|
sdp_uuid128_create(&uu, uuid);
|
|
sdp_uuid2strn(&uu, uuid_str, sizeof(uuid_str));
|
|
|
|
DBG("chan %d flags 0x%02x uuid %s name %s", chan, flags, uuid_str,
|
|
name);
|
|
|
|
if ((!memcmp(uuid, zero_uuid, sizeof(zero_uuid)) && chan <= 0) ||
|
|
(chan > RFCOMM_CHANNEL_MAX)) {
|
|
error("Invalid rfcomm listen params");
|
|
return HAL_STATUS_INVALID;
|
|
}
|
|
|
|
profile = get_profile_by_uuid(uuid);
|
|
if (!profile) {
|
|
sec_level = get_sec_level(flags);
|
|
} else {
|
|
if (!profile->create_record)
|
|
return HAL_STATUS_INVALID;
|
|
|
|
chan = profile->channel;
|
|
sec_level = profile->sec_level;
|
|
}
|
|
|
|
if (chan <= 0)
|
|
chan = find_free_channel();
|
|
|
|
if (!chan) {
|
|
error("No free channels");
|
|
return HAL_STATUS_BUSY;
|
|
}
|
|
|
|
if (servers[chan].rfsock != NULL) {
|
|
error("Channel already registered (%d)", chan);
|
|
return HAL_STATUS_BUSY;
|
|
}
|
|
|
|
DBG("chan %d sec_level %d", chan, sec_level);
|
|
|
|
rfsock = create_rfsock(-1, hal_sock);
|
|
if (!rfsock)
|
|
return HAL_STATUS_FAILED;
|
|
|
|
rfsock->channel = chan;
|
|
|
|
io = bt_io_listen(accept_cb, NULL, rfsock, NULL, &err,
|
|
BT_IO_OPT_SOURCE_BDADDR, &adapter_addr,
|
|
BT_IO_OPT_CHANNEL, chan,
|
|
BT_IO_OPT_SEC_LEVEL, sec_level,
|
|
BT_IO_OPT_INVALID);
|
|
if (!io) {
|
|
error("Failed listen: %s", err->message);
|
|
g_error_free(err);
|
|
goto failed;
|
|
}
|
|
|
|
rfsock->bt_sock = g_io_channel_unix_get_fd(io);
|
|
|
|
g_io_channel_set_close_on_unref(io, FALSE);
|
|
g_io_channel_unref(io);
|
|
|
|
/* Handle events from Android */
|
|
cond = G_IO_HUP | G_IO_ERR | G_IO_NVAL;
|
|
jv_io = g_io_channel_unix_new(rfsock->jv_sock);
|
|
id = g_io_add_watch_full(jv_io, G_PRIORITY_HIGH, cond,
|
|
jv_sock_server_event_cb, rfsock,
|
|
NULL);
|
|
g_io_channel_unref(jv_io);
|
|
|
|
rfsock->jv_watch = id;
|
|
|
|
DBG("rfsock %p bt_sock %d jv_sock %d hal_sock %d", rfsock,
|
|
rfsock->bt_sock,
|
|
rfsock->jv_sock,
|
|
*hal_sock);
|
|
|
|
if (write(rfsock->jv_sock, &chan, sizeof(chan)) != sizeof(chan)) {
|
|
error("Error sending RFCOMM channel");
|
|
goto failed;
|
|
}
|
|
|
|
rfsock->service_handle = sdp_service_register(chan, uuid, profile,
|
|
name);
|
|
|
|
servers[chan].rfsock = rfsock;
|
|
|
|
return HAL_STATUS_SUCCESS;
|
|
|
|
failed:
|
|
|
|
cleanup_rfsock(rfsock);
|
|
close(*hal_sock);
|
|
return HAL_STATUS_FAILED;
|
|
}
|
|
|
|
static void handle_listen(const void *buf, uint16_t len)
|
|
{
|
|
const struct hal_cmd_socket_listen *cmd = buf;
|
|
uint8_t status;
|
|
int hal_sock;
|
|
|
|
switch (cmd->type) {
|
|
case HAL_SOCK_RFCOMM:
|
|
status = rfcomm_listen(cmd->channel, cmd->name, cmd->uuid,
|
|
cmd->flags, &hal_sock);
|
|
break;
|
|
case HAL_SOCK_SCO:
|
|
case HAL_SOCK_L2CAP:
|
|
status = HAL_STATUS_UNSUPPORTED;
|
|
break;
|
|
default:
|
|
status = HAL_STATUS_INVALID;
|
|
break;
|
|
}
|
|
|
|
if (status != HAL_STATUS_SUCCESS)
|
|
goto failed;
|
|
|
|
ipc_send_rsp_full(hal_ipc, HAL_SERVICE_ID_SOCKET, HAL_OP_SOCKET_LISTEN,
|
|
0, NULL, hal_sock);
|
|
close(hal_sock);
|
|
return;
|
|
|
|
failed:
|
|
ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_SOCKET, HAL_OP_SOCKET_LISTEN,
|
|
status);
|
|
}
|
|
|
|
static bool sock_send_connect(struct rfcomm_sock *rfsock, bdaddr_t *bdaddr)
|
|
{
|
|
struct hal_sock_connect_signal cmd;
|
|
int len;
|
|
|
|
DBG("");
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
cmd.size = sizeof(cmd);
|
|
bdaddr2android(bdaddr, cmd.bdaddr);
|
|
cmd.channel = rfsock->channel;
|
|
cmd.status = 0;
|
|
|
|
len = write(rfsock->jv_sock, &cmd, sizeof(cmd));
|
|
if (len < 0) {
|
|
error("%s", strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
if (len != sizeof(cmd)) {
|
|
error("Error sending connect signal");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void connect_cb(GIOChannel *io, GError *err, gpointer user_data)
|
|
{
|
|
struct rfcomm_sock *rfsock = user_data;
|
|
bdaddr_t *dst = &rfsock->dst;
|
|
GIOChannel *jv_io;
|
|
char address[18];
|
|
guint id;
|
|
GIOCondition cond;
|
|
|
|
if (err) {
|
|
error("%s", err->message);
|
|
goto fail;
|
|
}
|
|
|
|
ba2str(dst, address);
|
|
DBG("Connected to %s on channel %d (rfsock %p)", address,
|
|
rfsock->channel, rfsock);
|
|
|
|
if (!sock_send_connect(rfsock, dst))
|
|
goto fail;
|
|
|
|
/* Handle events from Android */
|
|
cond = G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL;
|
|
jv_io = g_io_channel_unix_new(rfsock->jv_sock);
|
|
id = g_io_add_watch(jv_io, cond, jv_sock_client_event_cb, rfsock);
|
|
g_io_channel_unref(jv_io);
|
|
|
|
rfsock->jv_watch = id;
|
|
|
|
/* Handle rfcomm events */
|
|
cond = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL;
|
|
id = g_io_add_watch(io, cond, bt_sock_event_cb, rfsock);
|
|
g_io_channel_set_close_on_unref(io, FALSE);
|
|
|
|
rfsock->bt_watch = id;
|
|
|
|
return;
|
|
fail:
|
|
connections = g_list_remove(connections, rfsock);
|
|
cleanup_rfsock(rfsock);
|
|
}
|
|
|
|
static bool do_rfcomm_connect(struct rfcomm_sock *rfsock, int chan)
|
|
{
|
|
GIOChannel *io;
|
|
GError *gerr = NULL;
|
|
|
|
DBG("rfsock %p sec_level %d chan %d", rfsock, rfsock->sec_level, chan);
|
|
|
|
io = bt_io_connect(connect_cb, rfsock, NULL, &gerr,
|
|
BT_IO_OPT_SOURCE_BDADDR, &adapter_addr,
|
|
BT_IO_OPT_DEST_BDADDR, &rfsock->dst,
|
|
BT_IO_OPT_CHANNEL, chan,
|
|
BT_IO_OPT_SEC_LEVEL, rfsock->sec_level,
|
|
BT_IO_OPT_INVALID);
|
|
if (!io) {
|
|
error("Failed connect: %s", gerr->message);
|
|
g_error_free(gerr);
|
|
return false;
|
|
}
|
|
|
|
g_io_channel_set_close_on_unref(io, FALSE);
|
|
g_io_channel_unref(io);
|
|
|
|
if (write(rfsock->jv_sock, &chan, sizeof(chan)) != sizeof(chan)) {
|
|
error("Error sending RFCOMM channel");
|
|
return false;
|
|
}
|
|
|
|
rfsock->bt_sock = g_io_channel_unix_get_fd(io);
|
|
rfsock_set_buffer(rfsock);
|
|
rfsock->channel = chan;
|
|
connections = g_list_append(connections, rfsock);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void sdp_search_cb(sdp_list_t *recs, int err, gpointer data)
|
|
{
|
|
struct rfcomm_sock *rfsock = data;
|
|
sdp_list_t *list;
|
|
int chan;
|
|
|
|
DBG("");
|
|
|
|
if (err < 0) {
|
|
error("Unable to get SDP record: %s", strerror(-err));
|
|
goto fail;
|
|
}
|
|
|
|
if (!recs || !recs->data) {
|
|
error("No SDP records found");
|
|
goto fail;
|
|
}
|
|
|
|
for (list = recs; list != NULL; list = list->next) {
|
|
sdp_record_t *rec = list->data;
|
|
sdp_list_t *protos;
|
|
|
|
if (sdp_get_access_protos(rec, &protos) < 0) {
|
|
error("Unable to get proto list");
|
|
goto fail;
|
|
}
|
|
|
|
chan = sdp_get_proto_port(protos, RFCOMM_UUID);
|
|
|
|
sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free,
|
|
NULL);
|
|
sdp_list_free(protos, NULL);
|
|
|
|
if (chan)
|
|
break;
|
|
}
|
|
|
|
if (chan <= 0) {
|
|
error("Could not get RFCOMM channel %d", chan);
|
|
goto fail;
|
|
}
|
|
|
|
DBG("Got RFCOMM channel %d", chan);
|
|
|
|
if (do_rfcomm_connect(rfsock, chan))
|
|
return;
|
|
fail:
|
|
cleanup_rfsock(rfsock);
|
|
}
|
|
|
|
static uint8_t connect_rfcomm(const bdaddr_t *addr, int chan,
|
|
const uint8_t *uuid, uint8_t flags,
|
|
int *hal_sock)
|
|
{
|
|
struct rfcomm_sock *rfsock;
|
|
char address[18];
|
|
uuid_t uu;
|
|
char uuid_str[32];
|
|
|
|
sdp_uuid128_create(&uu, uuid);
|
|
sdp_uuid2strn(&uu, uuid_str, sizeof(uuid_str));
|
|
ba2str(addr, address);
|
|
|
|
DBG("addr %s chan %d flags 0x%02x uuid %s", address, chan, flags,
|
|
uuid_str);
|
|
|
|
if ((!memcmp(uuid, zero_uuid, sizeof(zero_uuid)) && chan <= 0) ||
|
|
!bacmp(addr, BDADDR_ANY)) {
|
|
error("Invalid rfcomm connect params");
|
|
return HAL_STATUS_INVALID;
|
|
}
|
|
|
|
rfsock = create_rfsock(-1, hal_sock);
|
|
if (!rfsock)
|
|
return HAL_STATUS_FAILED;
|
|
|
|
DBG("rfsock %p jv_sock %d hal_sock %d", rfsock, rfsock->jv_sock,
|
|
*hal_sock);
|
|
|
|
rfsock->sec_level = get_sec_level(flags);
|
|
|
|
bacpy(&rfsock->dst, addr);
|
|
|
|
if (!memcmp(uuid, zero_uuid, sizeof(zero_uuid))) {
|
|
if (!do_rfcomm_connect(rfsock, chan))
|
|
goto failed;
|
|
} else {
|
|
|
|
if (bt_search_service(&adapter_addr, &rfsock->dst, &uu,
|
|
sdp_search_cb, rfsock, NULL, 0) < 0) {
|
|
error("Failed to search SDP records");
|
|
goto failed;
|
|
}
|
|
}
|
|
|
|
return HAL_STATUS_SUCCESS;
|
|
|
|
failed:
|
|
cleanup_rfsock(rfsock);
|
|
close(*hal_sock);
|
|
return HAL_STATUS_FAILED;
|
|
}
|
|
|
|
static void handle_connect(const void *buf, uint16_t len)
|
|
{
|
|
const struct hal_cmd_socket_connect *cmd = buf;
|
|
bdaddr_t bdaddr;
|
|
uint8_t status;
|
|
int hal_sock;
|
|
|
|
DBG("");
|
|
|
|
android2bdaddr(cmd->bdaddr, &bdaddr);
|
|
|
|
switch (cmd->type) {
|
|
case HAL_SOCK_RFCOMM:
|
|
status = connect_rfcomm(&bdaddr, cmd->channel, cmd->uuid,
|
|
cmd->flags, &hal_sock);
|
|
break;
|
|
case HAL_SOCK_SCO:
|
|
case HAL_SOCK_L2CAP:
|
|
status = HAL_STATUS_UNSUPPORTED;
|
|
break;
|
|
default:
|
|
status = HAL_STATUS_INVALID;
|
|
break;
|
|
}
|
|
|
|
if (status != HAL_STATUS_SUCCESS)
|
|
goto failed;
|
|
|
|
ipc_send_rsp_full(hal_ipc, HAL_SERVICE_ID_SOCKET, HAL_OP_SOCKET_CONNECT,
|
|
0, NULL, hal_sock);
|
|
close(hal_sock);
|
|
return;
|
|
|
|
failed:
|
|
ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_SOCKET, HAL_OP_SOCKET_CONNECT,
|
|
status);
|
|
|
|
}
|
|
|
|
static const struct ipc_handler cmd_handlers[] = {
|
|
/* HAL_OP_SOCKET_LISTEN */
|
|
{ handle_listen, false, sizeof(struct hal_cmd_socket_listen) },
|
|
/* HAL_OP_SOCKET_CONNECT */
|
|
{ handle_connect, false, sizeof(struct hal_cmd_socket_connect) },
|
|
};
|
|
|
|
void bt_socket_register(struct ipc *ipc, const bdaddr_t *addr, uint8_t mode)
|
|
{
|
|
size_t i;
|
|
|
|
DBG("");
|
|
|
|
/*
|
|
* make sure channels assigned for profiles are reserved and not used
|
|
* for app services
|
|
*/
|
|
for (i = 0; i < G_N_ELEMENTS(profiles); i++)
|
|
if (profiles[i].channel)
|
|
servers[profiles[i].channel].reserved = true;
|
|
|
|
bacpy(&adapter_addr, addr);
|
|
|
|
hal_ipc = ipc;
|
|
ipc_register(hal_ipc, HAL_SERVICE_ID_SOCKET, cmd_handlers,
|
|
G_N_ELEMENTS(cmd_handlers));
|
|
}
|
|
|
|
void bt_socket_unregister(void)
|
|
{
|
|
int ch;
|
|
|
|
DBG("");
|
|
|
|
g_list_free_full(connections, cleanup_rfsock);
|
|
|
|
for (ch = 0; ch <= RFCOMM_CHANNEL_MAX; ch++)
|
|
if (servers[ch].rfsock)
|
|
cleanup_rfsock(servers[ch].rfsock);
|
|
|
|
memset(servers, 0, sizeof(servers));
|
|
|
|
ipc_unregister(hal_ipc, HAL_SERVICE_ID_SOCKET);
|
|
hal_ipc = NULL;
|
|
}
|