// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2013 BMW Car IT GmbH. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include "lib/bluetooth.h" #include "lib/sdp.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 is_allowed; bool initiator; }; 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; 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); } if (state == BTD_SERVICE_STATE_DISCONNECTED) service->initiator = false; } 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->state = BTD_SERVICE_STATE_UNAVAILABLE; service->is_allowed = true; 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, bool initiator) { 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) return -ENOSYS; if (!service->is_allowed) { info("service %s is not allowed", service->profile->remote_uuid); return -ECONNABORTED; } service->initiator = initiator; 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: if (service->state == BTD_SERVICE_STATE_DISCONNECTED) change_state(service, BTD_SERVICE_STATE_CONNECTING, 0); return 0; } int service_set_connecting(struct btd_service *service) { 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; } 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; if (!btd_adapter_get_powered(device_get_adapter(service->device))) return -ENETDOWN; switch (service->state) { case BTD_SERVICE_STATE_UNAVAILABLE: return -EINVAL; case BTD_SERVICE_STATE_DISCONNECTED: break; case BTD_SERVICE_STATE_CONNECTING: return 0; case BTD_SERVICE_STATE_CONNECTED: return -EALREADY; case BTD_SERVICE_STATE_DISCONNECTING: return -EBUSY; } if (!service->is_allowed) { info("service %s is not allowed", service->profile->remote_uuid); return -ECONNABORTED; } err = profile->connect(service); if (err == 0) { service->initiator = true; 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: return -EALREADY; case BTD_SERVICE_STATE_DISCONNECTING: return 0; 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) { 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 service->state; } int btd_service_get_error(const struct btd_service *service) { return service->err; } bool btd_service_is_initiator(const struct btd_service *service) { return service->initiator; } 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_set_allowed(struct btd_service *service, bool allowed) { if (allowed == service->is_allowed) return; service->is_allowed = allowed; if (!allowed && (service->state == BTD_SERVICE_STATE_CONNECTING || service->state == BTD_SERVICE_STATE_CONNECTED)) { btd_service_disconnect(service); return; } } bool btd_service_is_allowed(struct btd_service *service) { return service->is_allowed; } void btd_service_connecting_complete(struct btd_service *service, int err) { if (service->state != BTD_SERVICE_STATE_DISCONNECTED && 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); }