/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2013 BMW Car IT GmbH. 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 #endif #include #include #include #include #include #include #include #include #include #include "lib/bluetooth.h" #include "lib/sdp.h" #include "lib/sdp_lib.h" #include "log.h" #include "backtrace.h" #include "adapter.h" #include "device.h" #include "profile.h" #include "service.h" struct btd_service { int ref; struct btd_device *device; struct btd_profile *profile; void *user_data; btd_service_state_t state; int err; bool auto_connect; bool blocked; bool reconnecting; }; struct service_state_callback { btd_service_state_cb cb; void *user_data; unsigned int id; }; static GSList *state_callbacks = NULL; static const char *state2str(btd_service_state_t state) { switch (state) { case BTD_SERVICE_STATE_UNAVAILABLE: return "unavailable"; case BTD_SERVICE_STATE_DISCONNECTED: return "disconnected"; case BTD_SERVICE_STATE_CONNECTING: return "connecting"; case BTD_SERVICE_STATE_CONNECTED: return "connected"; case BTD_SERVICE_STATE_DISCONNECTING: return "disconnecting"; } return NULL; } static void change_state(struct btd_service *service, btd_service_state_t state, int err) { btd_service_state_t old = service->state; char addr[18]; GSList *l; if (state == old) return; btd_assert(service->device != NULL); btd_assert(service->profile != NULL); service->state = state; service->err = err; if (state == BTD_SERVICE_STATE_CONNECTED) service->reconnecting = false; ba2str(device_get_address(service->device), addr); DBG("%p: device %s profile %s state changed: %s -> %s (%d)", service, addr, service->profile->name, state2str(old), state2str(state), err); for (l = state_callbacks; l != NULL; l = g_slist_next(l)) { struct service_state_callback *cb = l->data; cb->cb(service, old, state, cb->user_data); } /* Change state back to connecting if reconnect flag is set */ if (service->reconnecting && state == BTD_SERVICE_STATE_DISCONNECTED) change_state(service, BTD_SERVICE_STATE_CONNECTING, err); } struct btd_service *btd_service_ref(struct btd_service *service) { service->ref++; DBG("%p: ref=%d", service, service->ref); return service; } void btd_service_unref(struct btd_service *service) { service->ref--; DBG("%p: ref=%d", service, service->ref); if (service->ref > 0) return; g_free(service); } struct btd_service *service_create(struct btd_device *device, struct btd_profile *profile) { struct btd_service *service; service = g_try_new0(struct btd_service, 1); if (!service) { error("service_create: failed to alloc memory"); return NULL; } service->ref = 1; service->device = device; /* Weak ref */ service->profile = profile; service->auto_connect = profile->auto_connect; service->state = BTD_SERVICE_STATE_UNAVAILABLE; return service; } int service_probe(struct btd_service *service) { char addr[18]; int err; btd_assert(service->state == BTD_SERVICE_STATE_UNAVAILABLE); err = service->profile->device_probe(service); if (err == 0) { change_state(service, BTD_SERVICE_STATE_DISCONNECTED, 0); return 0; } ba2str(device_get_address(service->device), addr); error("%s profile probe failed for %s", service->profile->name, addr); return err; } void service_remove(struct btd_service *service) { change_state(service, BTD_SERVICE_STATE_DISCONNECTED, -ECONNABORTED); change_state(service, BTD_SERVICE_STATE_UNAVAILABLE, 0); service->profile->device_remove(service); service->device = NULL; service->profile = NULL; btd_service_unref(service); } int service_accept(struct btd_service *service) { char addr[18]; int err; switch (service->state) { case BTD_SERVICE_STATE_UNAVAILABLE: return -EINVAL; case BTD_SERVICE_STATE_DISCONNECTED: break; case BTD_SERVICE_STATE_CONNECTING: case BTD_SERVICE_STATE_CONNECTED: return 0; case BTD_SERVICE_STATE_DISCONNECTING: return -EBUSY; } if (!service->profile->accept) goto done; err = service->profile->accept(service); if (!err) goto done; ba2str(device_get_address(service->device), addr); error("%s profile accept failed for %s", service->profile->name, addr); return err; done: change_state(service, BTD_SERVICE_STATE_CONNECTING, 0); return 0; } int btd_service_connect(struct btd_service *service) { struct btd_profile *profile = service->profile; char addr[18]; int err; if (!profile->connect) return -ENOTSUP; switch (service->state) { case BTD_SERVICE_STATE_UNAVAILABLE: return -EINVAL; case BTD_SERVICE_STATE_DISCONNECTED: break; case BTD_SERVICE_STATE_CONNECTING: if (service->reconnecting) break; return 0; case BTD_SERVICE_STATE_CONNECTED: return -EALREADY; case BTD_SERVICE_STATE_DISCONNECTING: return -EBUSY; } err = profile->connect(service); if (err == 0) { change_state(service, BTD_SERVICE_STATE_CONNECTING, 0); return 0; } ba2str(device_get_address(service->device), addr); error("%s profile connect failed for %s: %s", profile->name, addr, strerror(-err)); return err; } int btd_service_disconnect(struct btd_service *service) { struct btd_profile *profile = service->profile; char addr[18]; int err; if (!profile->disconnect) return -ENOTSUP; switch (service->state) { case BTD_SERVICE_STATE_UNAVAILABLE: return -EINVAL; case BTD_SERVICE_STATE_DISCONNECTED: case BTD_SERVICE_STATE_DISCONNECTING: return -EALREADY; case BTD_SERVICE_STATE_CONNECTING: case BTD_SERVICE_STATE_CONNECTED: break; } change_state(service, BTD_SERVICE_STATE_DISCONNECTING, 0); err = profile->disconnect(service); if (err == 0) return 0; if (err == -ENOTCONN) { btd_service_disconnecting_complete(service, 0); return 0; } ba2str(device_get_address(service->device), addr); error("%s profile disconnect failed for %s: %s", profile->name, addr, strerror(-err)); btd_service_disconnecting_complete(service, err); return err; } struct btd_device *btd_service_get_device(const struct btd_service *service) { return service->device; } struct btd_profile *btd_service_get_profile(const struct btd_service *service) { return service->profile; } void btd_service_set_user_data(struct btd_service *service, void *user_data) { btd_assert(service->state == BTD_SERVICE_STATE_UNAVAILABLE); service->user_data = user_data; } void *btd_service_get_user_data(const struct btd_service *service) { return service->user_data; } btd_service_state_t btd_service_get_state(const struct btd_service *service) { /* Return connecting if reconnection policy is active */ if (service->state == BTD_SERVICE_STATE_DISCONNECTED && service->reconnecting) return BTD_SERVICE_STATE_CONNECTING; return service->state; } int btd_service_get_error(const struct btd_service *service) { return service->err; } uint16_t btd_service_get_version(const struct btd_service *service) { const sdp_record_t *rec; sdp_list_t *list; sdp_profile_desc_t *desc; uint16_t version; if (!service->profile->version) return 0; rec = btd_device_get_record(service->device, service->profile->remote_uuid); if (rec == NULL) return 0; if (sdp_get_profile_descs(rec, &list) < 0) return 0; desc = list->data; version = desc->version; sdp_list_free(list, free); return MIN(version, service->profile->version); } void btd_service_set_auto_connect(struct btd_service *service, bool value) { service->auto_connect = value; } bool btd_service_get_auto_connect(const struct btd_service *service) { return service->auto_connect; } void btd_service_set_blocked(struct btd_service *service, bool value) { service->blocked = value; } bool btd_service_is_blocked(const struct btd_service *service) { return service->blocked; } unsigned int btd_service_add_state_cb(btd_service_state_cb cb, void *user_data) { struct service_state_callback *state_cb; static unsigned int id = 0; state_cb = g_new0(struct service_state_callback, 1); state_cb->cb = cb; state_cb->user_data = user_data; state_cb->id = ++id; state_callbacks = g_slist_append(state_callbacks, state_cb); return state_cb->id; } bool btd_service_remove_state_cb(unsigned int id) { GSList *l; for (l = state_callbacks; l != NULL; l = g_slist_next(l)) { struct service_state_callback *cb = l->data; if (cb && cb->id == id) { state_callbacks = g_slist_remove(state_callbacks, cb); g_free(cb); return true; } } return false; } void btd_service_connecting_complete(struct btd_service *service, int err) { if (service->state != BTD_SERVICE_STATE_CONNECTING) return; if (err == 0) change_state(service, BTD_SERVICE_STATE_CONNECTED, 0); else change_state(service, BTD_SERVICE_STATE_DISCONNECTED, err); } void btd_service_disconnecting_complete(struct btd_service *service, int err) { if (service->state != BTD_SERVICE_STATE_CONNECTED && service->state != BTD_SERVICE_STATE_DISCONNECTING) return; if (err == 0) change_state(service, BTD_SERVICE_STATE_DISCONNECTED, 0); else /* If disconnect fails, we assume it remains connected */ change_state(service, BTD_SERVICE_STATE_CONNECTED, err); } void btd_service_reconnect(struct btd_service *service, bool value) { service->reconnecting = value; /* Don't change state if already connected */ if (service->state == BTD_SERVICE_STATE_CONNECTED) return; change_state(service, value ? BTD_SERVICE_STATE_CONNECTING : BTD_SERVICE_STATE_DISCONNECTED, value ? 0 : -EHOSTDOWN); } bool btd_service_is_reconnecting(struct btd_service *service) { return service->reconnecting; }